Tree Api+ClassScanner = 识别三方隐私权限调用 | Android Lint

3,889 阅读3分钟

背景

之前在做隐私权限的时候和大家介绍过关于ClassScanner.之前这块对我来说其实一直都是一个小盲区,因为lint相关的文档资料比较少,这次在参考另外一个ClassScanner之后还是写出来了。

秉承着独乐乐不如众乐乐,还是给大家装个逼吧。

项目地址 AndroidLint

正文

之前介绍过UastScanner是针对于java,kt语法树的,所以我们能在他们编译成产物的过程的时候校验他们的语法是否合理,如果不合理就能用Lint进行阻断,之前的文章也有介绍。

文章开头就说了,这次我们要盘的是ClassScanner。我们的主要扫描目标是第三方aar,jar还有class产物。这些其实都是.class文件,因为已经跳过了javac的过程,所以也就没有语法树了。

写吧

class PrivacyClassDetector : Detector(), Detector.ClassScanner {

    override fun checkClass(context: ClassContext, classNode: ClassNode) {
        super.checkClass(context, classNode)
    }

    override fun checkCall(
        context: ClassContext,
        classNode: ClassNode,
        method: MethodNode,
        call: MethodInsnNode
    ) {
        super.checkCall(context, classNode, method, call)
    }

    override fun getApplicableAsmNodeTypes(): IntArray? {
        return intArrayOf(AbstractInsnNode.METHOD_INSN)
    }


    override fun checkInstruction(
        context: ClassContext,
        classNode: ClassNode,
        method: MethodNode,
        instruction: AbstractInsnNode
    ) {
        super.checkInstruction(context, classNode, method, instruction)
        if (instruction is MethodInsnNode) {
            if (instruction.isPrivacy() != null) {
                print("checkInstruction AbstractInsnNode:${instruction.opcode} \r\n")
                context.report(
                    ISSUE, context.getLocation(instruction),
                    "外圈q+内圈"
                )

            }
        }

    }

    private fun MethodInsnNode.isPrivacy(): PrivacyAsmEntity? {
        val pair = PrivacyHelper.privacyList.firstOrNull {
            val first = it
            first.owner == owner && first.code == opcode && first.name == name && first.desc == desc
        }
        return pair

    }

    companion object {
        val ISSUE = Issue.create(
            "ClassSampleDetector",  //唯一 ID
            "咦 ",  //简单描述
            "我只是想让你报错而已",  //详细描述
            Category.CORRECTNESS,  //问题种类(正确性、安全性等)
            6, Severity.ERROR,  //问题严重程度(忽略、警告、错误)
            Implementation( //实现,包括处理实例和作用域
                PrivacyClassDetector::class.java,
                Scope.CLASS_FILE_SCOPE
            )
        )
    }
}

这部分其实和我写的隐私权限替换的asm很相似,因为已经编译成字节码了,而lint也是使用tree api,可以看到很多老熟人,比如ClassNode,MethodNode,还是一点点分析吧。

interface ClassScanner : FileScanner {

    // 需要检查的 node 类型 
    fun getApplicableAsmNodeTypes(): IntArray?

    // 函数调用
    fun checkInstruction(
        context: ClassContext,
        classNode: ClassNode,
        method: MethodNode,
        instruction: AbstractInsnNode
    )

    //   和其他lint api 提供的类似
    fun getApplicableCallNames(): List<String>?
    //   和其他lint api 提供的类似
    fun getApplicableCallOwners(): List<String>?

    // 和getApplicableCallNames匹配
    fun checkCall(
        context: ClassContext,
        classNode: ClassNode,
        method: MethodNode,
        call: MethodInsnNode
    )

    // 和getApplicableCallOwners匹配
    fun checkClass(context: ClassContext, classNode: ClassNode)
}

我们先从这个抽象接口看起,我在函数上面做了简单的方法描述。这次隐私api的判断因为我们要判断的栈帧方法比较多,比如INVOKEVIRTUAL,INVOKESTATIC这种都有,所以getApplicableAsmNodeTypes这个上面,我们获取了所有的函数调用。其中INVOKEVIRTUAL,INVOKESTATIC 都是MethodInsnNode,所以我们只要在getApplicableAsmNodeTypes方法中添加AbstractInsnNode.METHOD_INSN就行了。

然后我们需要做的也很简单,因为我们的输入类型只有MethodInsnNode,所以当checkInstruction就是栈帧调用方法被执行的时候,将call直接转化成MethodInsnNode,之后判断当前栈判断当前方法是不是操作符,描述符,所有者等都符合我们的隐私api的定义,如果是则调用lint repot就行了。

结尾

算是奇淫技巧补足,有兴趣的大佬可以学习下,这篇文章就这么短了。当然理解文章内容的前提是你要有一定的虚拟机机器码的认知,对于asm相关的理解就会融汇贯通,无论是transform还是ClassScanner就都是一样的。