一:信息采集
1.1 Class信息采集
类的基本信息
kotlin
class XXClassCollectVisitor(
api: Int,
classVisitor: ClassVisitor) : ClassVisitor(api, classVisitor) {
override fun visit(
version: Int,
access: Int,
name: String?,
signature: String?,
superName: String?,
interfaces: Array<out String>?
) {
super.visit(version, access, name, signature, superName, interfaces)
}
}
-
access
:类的访问修饰符(如ACC_PUBLIC
、ACC_FINAL
、ACC_RECORD
等) -name
:类的内部名称(格式为包名/类名
,如java/lang/String
) -
signature
:类的泛型签名(非泛型类为null
) -
superName
:父类的内部名称(如java/lang/Object
,接口的父类为null
) -
interfaces
:实现的接口内部名称数组(如[java/io/Serializable]
)
类的注解信息
kotlin
class XXClassCollectVisitor( api: Int, classVisitor: ClassVisitor) : ClassVisitor(api, classVisitor) {
override fun visitAnnotation(descriptor: String?, visible: Boolean): AnnotationVisitor {
return super.visitAnnotation(descriptor, visible)
}
}
-
descriptor
:注解类型的描述符(如Lcom/xxx/MyAnnotation;
) -
visible
:是否为运行时可见注解(true
对应@Retention(RUNTIME)
)
类的方法信息
kotlin
class XXClassCollectVisitor( api: Int, classVisitor: ClassVisitor) : ClassVisitor(api, classVisitor) {
override fun visitMethod(
access: Int,
name: String?,
descriptor: String?,
signature: String?,
exceptions: Array<out String>?
): MethodVisitor {
var mv = super.visitMethod(access, name, descriptor, signature, exceptions)
return mv
}
}
-
access
:方法的访问修饰符(如ACC_PUBLIC
、ACC_STATIC
、ACC_ABSTRACT
等) -
name
:方法名(构造方法为<init>
,静态初始化方法为<clinit>
) -
descriptor
:方法的描述符(如(Ljava/lang/String;)I
表示参数为String
、返回int
) -
signature
:方法的泛型签名(非泛型为null
) -
exceptions
:方法抛出的异常类内部名称数组(如[java/lang/IOException]
)
返回值:MethodVisitor
实例,用于访问方法的指令、局部变量等
2.2 Method信息采集
方法的名称、签名等信息
由ClassVisitor#visitMethod
采集
方法的注解信息
同ClassVisitor#visitAnnotation
方法体指令信息
kotlin
class PrivacyMethodProxyVisitor(
...
) : AdviceAdapter(api, methodVisitor, access, methodName, descriptor) {
override fun visitMethodInsn(
opcode: Int,
owner: String?,
name: String?,
descriptor: String?,
isInterface: Boolean
) {
super.visitMethodInsn(opcode, owner, name, descriptor, isInterface)
}
}
opcode
:指令操作码(如INVOKEVIRTUAL
、INVOKESTATIC
、INVOKESPECIAL
等owner
:方法所属类的内部名称name
:方法名descriptor
:方法描述符isInterface
:是否为接口方法(ASM 5+ 新增参数)
3.1 Annotation信息采集
注解参数信息
kotlin
class PrivacyAnnotationCollectVisitor(
....) : AnnotationVisitor(api, annotationVisitor) {
override fun visit(name: String?, value: Any?) {
super.visit(name, value)
when (name) {
"name" -> {
val routerName = value.toString()
}
else -> Unit
}
}
}
二:查找
2.1 查找对应的class
2.1.1 根据接口查找
给所有按钮的点击事件插入防抖功能,需要使用ClassVisitor
查找所有实现了OnClickListener
接口的类。(lamda表达式无法通过此方式匹配,后续再说)
java
btnAgree.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
// if (DoubleClickController.isDoubleClick()) return; // 要插入的代码
xxx
}
});
java
class DoubleClickClassVisitor extends ClassVisitor {
@Override
void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
super.visit(version, access, name, signature, superName, interfaces)
if(interfaces != null && interfaces.length > 0){
for(int i = 0 ; i < interfaces.length ; i++){
String mInterfaceStr = interfaces[i];
if (mInterfaceStr.equals("android/view/View$OnClickListener")) {
isImplOnClickListener = true;
// do something
}
}
}
}
}
visit
遍历class
时,遍历其实现的所有接口,通过类名匹配
2.1.2 根据注解查找
实现一个动态路由,编译期查找到所有的标记了Router
的注解,实现String -> Activity
的映射关系
scala
@Router(name = "logoActivity")
public class LogoActivity extends Activity {
}
kotlin
class MyClassVisitor extends ClassVisitor {
private var className: String? = null
override fun visit(
version: Int,
access: Int,
name: String?,
signature: String?,
superName: String?,
interfaces: Array<out String>?
) {
super.visit(version, access, name, signature, superName, interfaces)
className = name
}
override fun visitAnnotation(descriptor: String?, visible: Boolean): AnnotationVisitor {
if ("Lcom/kongge/Router;" == descriptor) {
// 使用AnnotationVisitor找到Router注解的name字段值,并将name和className做映射
return XXAnnotationVisitor(...)
}
return super.visitAnnotation(descriptor, visible)
}
}
2.2 查找对应的方法
2.2.1 根据名称、方法签名、接口等信息查找
比如按钮防抖,需要在onClick
方法体加上代码。
groovy
class DoubleClickClassVisitor extends ClassVisitor {
@Override
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions)
if (mv != null && name == ("onClick")
&& isImplOnClickListener
&& descriptor.contains("(Landroid/view/View;)V")) {
boolean isAbstractMethod = (access & Opcodes.ACC_ABSTRACT) != 0
boolean isNativeMethod = (access & Opcodes.ACC_NATIVE) != 0
if (!isAbstractMethod && !isNativeMethod) {
// 找到方法后,使用自定义MethodVisitor处理后续
return new DoubleClickMethodVisitor(api, mv, access, name, descriptor)
}
}
return mv
}
}
2.2.2 根据注解信息查找
kotlin
class XXClassVisitor(
private val context: Context,
api: Int,
classVisitor: ClassVisitor) : ClassVisitor(api, classVisitor) {
private var isHasTargetAnnotation = false
private var className: String? = null
override fun visit(
version: Int,
access: Int,
name: String?,
signature: String?,
superName: String?,
interfaces: Array<out String>?
) {
super.visit(version, access, name, signature, superName, interfaces)
className = name
}
override fun visitAnnotation(descriptor: String?, visible: Boolean): AnnotationVisitor {
if ("Lcom/kongge/annotation/TargetClass;" == descriptor) {
isHasTargetAnnotation = true
}
return super.visitAnnotation(descriptor, visible)
}
override fun visitMethod(
access: Int,
name: String?,
descriptor: String?,
signature: String?,
exceptions: Array<out String>?
): MethodVisitor {
var mv = super.visitMethod(access, name, descriptor, signature, exceptions)
if (isHasTargetAnnotation) {
mv = XXMethodCollectVisitor(api, mv, access, name, descriptor, className)
}
return mv
}
}
2.3 查找指定的代码段
隐私合规如今越来越严格,需要找到项目里面所有的隐私相关调用,比如获取AndroidId
的地方,在主项目里面可以通过搜索代码来查找,但是三方SDK就不行了,这时候就可以通过查找字节码的方式来实现。
java
Settings.Secure.getString(context.contentResolver, Settings.Secure.ANDROID_ID)
kotlin
class PrivacyMethodProxyVisitor(
private val className: String?,
private val methodName: String?,
...
) : AdviceAdapter(api, methodVisitor, access, methodName, descriptor) {
override fun visitMethodInsn(
opcode: Int,
owner: String?,
name: String?,
descriptor: String?,
isInterface: Boolean
) {
if (opcode and Opcodes.ACC_STATIC == Opcodes.ACC_STATIC && owner == "android/provider/Settings$Secure" && name == "getString"
&& descriptor == "(Landroid/content/ContentResolver;Ljava/lang/String;)Ljava/lang/String;") {
// 记录className和methodName,即可知道是哪个类的哪个方法调用了获取AndroidId的方法
} else {
super.visitMethodInsn(opcode, owner, name, descriptor, isInterface)
}
}
}
三:字节码修改
3.1 方法体新增代码
在Button
的onClick
方法体的第一行插入双击判断,如果是双击则直接return
typescript
btnAgree.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (DoubleClickController.isDoubleClick()) return; // 要插入的代码
HardwareUtils.setIsAgree(true);
}
});
scala
class DoubleClickMethodVisitor extends AdviceAdapter {
@Override
protected void onMethodEnter() {
super.onMethodEnter()
visitMethodInsn(INVOKESTATIC, "com/kongge/commonsdk/doubleclick/DoubleClickController", "isDoubleClick", "()Z", false)
Label label0 = new Label()
visitJumpInsn(IFEQ, label0)
visitInsn(RETURN)
visitLabel(label0)
}
@Override
protected void onMethodExit(int opcode) {
super.onMethodExit(opcode)
// 方法末尾插入代码
}
}
3.2 方法体删除代码
删除所有System.out.println()
(使用混淆规则来去掉日志输出更方便,这里仅做字节码删除演示)
scala
public class PrintlnFilterVisitor extends MethodVisitor {
public PrintlnFilterVisitor(MethodVisitor mv) {
super(Opcodes.ASM9, mv);
}
// 拦截方法调用指令(检测 PrintStream.println 调用)
@Override
public void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface) {
// 如果是之前标记的 println 调用,则跳过不写入
if (opcode == Opcodes.INVOKEVIRTUAL
&& "java/io/PrintStream".equals(owner)
&& "println".equals(name)) {
return; // 不调用父类方法,即删除该指令
}
// 非目标方法调用,正常写入
super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
}
}
3.3 方法体修改代码
kotlin
// 需要被替换的代码
Settings.Secure.getString(context.contentResolver, Settings.Secure.ANDROID_ID)
class Proxy {
companion object {
// 替换的代码
fun getAndroidId(resolver: ContentResolver, name: String): String {
return "aaaa"
}
}
}
kotlin
class PrivacyMethodProxyVisitor(
...
) : AdviceAdapter(api, methodVisitor, access, methodName, descriptor) {
override fun visitMethodInsn(
opcode: Int,
owner: String?,
name: String?,
descriptor: String?,
isInterface: Boolean
) {
if (owenr == "android/provider/Settings$Secure" && name == "getString" && descriptor == "(Landroid/content/ContentResolver;Ljava/lang/String;)Ljava/lang/String;") {
super.visitMethodInsn(opcode, "com/kongge/plugindemo/proxy/Proxy", "getAndroidId", "(Landroid/content/ContentResolver;Ljava/lang/String;)Ljava/lang/String;", isInterface)
} else {
super.visitMethodInsn(opcode, owner, name, descriptor, isInterface)
}
}

做的通用一点,可以通过自定义注解的方式,标记相关的类和方法
less
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface TargetClass {
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.CLASS)
public @interface TargetMethod {
Class originClass();
String originMethod();
}
kotlin
@TargetClass
class Proxy {
companion object {
@JvmStatic
@TargetMethod(
originClass = Settings.Secure::class,
originMethod = "getString"
)
fun getAndroidId(resolver: ContentResolver, name: String): String {
return "aaaa"
}
}
}
@TargetClass
标记需要处理的类,避免每个类都遍历处理,提高编译效率@TargetMethod
标记需要处理的方法,originClass
标记被替换的类,originMethod
标记被替换的方法,替换成当前类Proxy
和方法getAndroidId
,方法签名共用getAndroidId
的方法签名,即(Landroid/content/ContentResolver;Ljava/lang/String;)Ljava/lang/String;
- 使用
CollectionTransform
来收集标记了注解的类 - 使用
ProxyTransform
来替换这些类
kotlin
class PrivacyAnnotationCollectVisitor(
...) : AnnotationVisitor(api, annotationVisitor) {
override fun visit(name: String?, value: Any?) {
super.visit(name, value)
when (name) {
"originClass" -> {
var originClass = value.toString()
originClass = originClass.substring(1, originClass.length - 1)
privacyAnnotationCollectInfoBean.originClass = originClass
}
"originMethod" -> {
privacyAnnotationCollectInfoBean.originMethod = value.toString()
}
else -> Unit
}
}
override fun visitEnd() {
super.visitEnd()
privacyCollectManager.registerCollectInfoBean(privacyAnnotationCollectInfoBean)
}
}
kotlin
class PrivacyMethodProxyVisitor(
...
) : AdviceAdapter(api, methodVisitor, access, methodName, descriptor) {
override fun visitMethodInsn(
opcode: Int,
owner: String?,
name: String?,
descriptor: String?,
isInterface: Boolean
) {
val privacyAnnotationCollectInfoBean = privacyCollectManager.findCollectInfoBean(owner, name, descriptor)
if (privacyAnnotationCollectInfoBean != null) {
super.visitMethodInsn(opcode, privacyAnnotationCollectInfoBean.targetClass, privacyAnnotationCollectInfoBean.targetMethod, descriptor, isInterface)
} else {
super.visitMethodInsn(opcode, owner, name, descriptor, isInterface)
}
}
}
四:小结
本章节示例仅作为参考,还有很多细节需要打磨。
需要深入学习的可以琢磨琢磨饿了么的开源库Lancet,使用说明可以参考鸿洋大神的Android 无所不能的 hook,让应用不再崩溃