Tree Api+ClassScanner = 識別三方隱私許可權呼叫 | Android Lint

語言: CN / TW / HK

theme: smartblue

背景

之前在做隱私許可權的時候和大家介紹過關於ClassScanner.之前這塊對我來說其實一直都是一個小盲區,因為lint相關的文件資料比較少,這次在參考另外一個ClassScanner之後還是寫出來了。

秉承著獨樂樂不如眾樂樂,還是給大家裝個逼吧。

專案地址 AndroidLint

正文

之前介紹過UastScanner是針對於java,kt語法樹的,所以我們能在他們編譯成產物的過程的時候校驗他們的語法是否合理,如果不合理就能用Lint進行阻斷,之前的文章也有介紹。

文章開頭就說了,這次我們要盤的是ClassScanner。我們的主要掃描目標是第三方aar,jar還有class產物。這些其實都是.class檔案,因為已經跳過了javac的過程,所以也就沒有語法樹了。

寫吧

```kotlin 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,還是一點點分析吧。

```kotlin 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就都是一樣的。