在看了京东零售技术
的公众号发的文章: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,然后在插件某个地方读取这些数据从而可以实现排查自己想要的隐私违规。
最后,我们也需要设计个开关,毕竟线上的包这些代码是不能带进去的。