在看了京東零售技術
的公衆號發的文章:ASM在隱私合規掃描中的應用實戰之後,想把這個插件整合進自己的一個ASM實現plugin合集中來,由於自己的工程是仿造booster框架實現的,也就是說得把原文中是採用MethodVisitor
的方式替換成ClassNode
的方式。
本文默認你已經熟悉了採用ASM實現gradle plugin 以及熟悉booster...
第一步:
先按原文的方式構造代碼,這裏簡單採用打印log的方式輸出 如下:
public class PrivateUtil {
public static void reportPrivateApi(String hookedClass, String hookedMethod, String invokedClass, String invokedMethod) {
Log.w("Jayuchou", "======hookedClass = " + hookedClass);
Log.w("Jayuchou", "======hookedMethod = " + hookedMethod);
Log.w("Jayuchou", "======invokedClass = " + invokedClass);
Log.w("Jayuchou", "======invokedMethod = " + invokedMethod);
}
}
構建個Activity調用它:
class MainActivity : AppCompatActivity() {
@SuppressLint("MissingPermission")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setSupportActionBar(toolbar)
// 這裏類似原文中涉及了隱私違規的api了
val tel: TelephonyManager = getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
val nt = tel.networkType
}
}
所以,按原文的邏輯,我們運行代碼後會把哪個類、哪個方法調用了隱私違規的方法進行上報給H5端展示(具體如何實現的思路後面講)。
第二步:
如何按原文的思路把我們需要的log打印出來?
這裏先看下如果直接調用如下的代碼對應的字節碼長啥樣,方便我們書寫插入代碼。
PrivateUtil.reportPrivateApi("hookedClass", "hookedMethod", "invokedClass", "invokedMethod");
對應的字節碼大致如下:
LDC "hookedClass"
LDC "hookedMethod"
LDC "invokedClass"
LDC "invokedMethod"
INVOKESTATIC PrivateUtil.reportPrivateApi(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
所以,我們只需要在對應的地方插入四個LdcInsnNode
以及一個MethodInsnNode
就可以實現在調用涉及隱私的方法末尾插入PrivateUtil.reportPrivateApi
從而實現成功打印出log。
按照booster
框架的設計規範,這裏直接給出對應的代碼;當然這裏只是一個demo版本的代碼,且僅僅查找TelephonyManager.getNetworkType
:
@AutoService(ClassTransformer::class)
class TestPrivateTransformer : ClassTransformer {
override fun transform(context: TransformContext, klass: ClassNode): ClassNode {
val hookedClassName = klass.name
var hookedMethodName = ""
val iterator = klass.methods.iterator()
while (iterator.hasNext()) {
val method = iterator.next()
method.instructions?.iterator()?.forEach {
// 查找TelephonyManager.getNetworkType
if (it.opcode == Opcodes.INVOKEVIRTUAL && it is MethodInsnNode) {
if (it.owner == "android/telephony/TelephonyManager" && it.name == "getNetworkType") {
hookedMethodName = method.name
println("====find private success : $hookedClassName / $hookedMethodName")
}
}
}
// 如果hookedMethodName不爲空那麼表示找到了,那麼就插入代碼
if (!TextUtils.isEmpty(hookedMethodName)) {
method?.instructions?.iterator()?.asIterable()?.filter {
it.opcode == Opcodes.RETURN
}?.forEach {
method.instructions?.apply {
insertBefore(it, LdcInsnNode(hookedClassName))
insertBefore(it, LdcInsnNode(hookedMethodName))
insertBefore(it, LdcInsnNode("android/telephony/TelephonyManager"))
insertBefore(it, LdcInsnNode("getNetworkType"))
insertBefore(
it,
MethodInsnNode(
Opcodes.INVOKESTATIC,
"com/remote/neacy/PrivateUtil",
"reportPrivateApi",
"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V",
false
)
)
}
}
}
}
return super.transform(context, klass)
}
}
運行下代碼,看看我們的成功插入後結代碼:
再看看下打印出來的log:
基本上到這就整個代碼就寫完了,接下來說說原文中的在H5界面展示該如何實現以及如果真要使用該插件應該如何進一步拓展。
首先,H5展示按原文的意思類似是調用了PrivateUtil.reportPrivateApi
時調用了對應的後端接口即可,這樣子H5就可以正常展示,這點其實不是很難,如果有需要實時在H5展示可以用socket把數據直接傳到H5,不過我們通常還是用請求接口的方式方便統計。
那麼如何拓展該插件從而實現商用?
我們可以先設計個配置文件,因爲我們不僅僅只有TelephonyManager
涉嫌違規,可以類似:
"android/telephony/TelephonyManager#getNetworkType#()I",
分別代表隱私的類 方法 以及方法的desc,然後在插件某個地方讀取這些數據從而可以實現排查自己想要的隱私違規。
最後,我們也需要設計個開關,畢竟線上的包這些代碼是不能帶進去的。