这是源代码链接 https://github.com/MikaelZero/ByteTea/blob/master/EncryptString/README.md
先来看下效果
这是插桩之前的代码
private final String final_field_1 = "test_private_final";
这是插桩之后的代码
private final String final_field_1 = Base64Util.decode("dGVzdF9wcml2YXRlX2ZpbmFs");
也就是把字符串的部分,做一个加密,这也是防破解的其中一个基本手段
我们通过 Jclasslib 插件可以知道,所有的字符串在字节码中的指令都是LDC指令,因此只需要在访问LDC指令的时候进行修改即可
先注意一点,在整个访问流程中,编写的代码是在init函数或者 clinit 函数进行,clinit后面再谈,所以我们需要在MethodVistor去寻找对应的字符串
在MethodVistor中访问LDC指令的函数是visitLdcInsn,所以在这个函数下,我们判断下是否为String类型,然后进行代码修改
override fun visitLdcInsn(value: Any?) {if (value is String) {val str = Base64.encodeBase64String(value.toByteArray())//这步是直接修改LDC指令下对应的value,而我们的value就是上面的字符串mv.visitLdcInsn(str)//调用编写好的Lib里的代码,也就是 Base64Util.decodemv.visitMethodInsn(Opcodes.INVOKESTATIC, className, encryptMethodName, "(Ljava/lang/String;)Ljava/lang/String;", false)return}super.visitLdcInsn(value)}
编写完成后,你会发现一个问题,就是 visitLdcInsn 访问不到 static final 的字符串,而且static final 的字符串必须设置值或者声明后在 static 块里进行初始化,所以我们需要找一个地方能够获取到 static final 的指令并且统一处理,也就是visitInsn,并且他的code为 RETURN,代码如下
override fun visitInsn(opcode: Int) {//static 的函数名为 <clinit>if (opcode == Opcodes.RETURN && methodName == "<clinit>") {staticFinalStringFieldNodeList.forEach { fieldNode ->val str = Base64.encodeBase64String((fieldNode.value as String).toByteArray())mv?.visitLdcInsn(str)mv?.visitMethodInsn(Opcodes.INVOKESTATIC, className, encryptMethodName, "(Ljava/lang/String;)Ljava/lang/String;", false)mv?.visitFieldInsn(Opcodes.PUTSTATIC, owner, fieldNode.name, "Ljava/lang/String;")}}super.visitInsn(opcode)}
staticFinalStringFieldNodeList是我们提前收集好的列表,代码如下
override fun visitField(access: Int, name: String?, descriptor: String?, signature: String?, value: Any?): FieldVisitor {if (value != null && value is String&& descriptor == Type.getDescriptor(String::class.java)&& 0 != (Opcodes.ACC_STATIC and access)&& 0 != (Opcodes.ACC_FINAL and access)) {//添加一个类下的 所有 static final 的字段staticFinalStringFieldNodeList.add(FieldNode(access, name, descriptor, signature, value))return super.visitField(access, name, descriptor, signature, null)}return super.visitField(access, name, descriptor, signature, value)}
最后还会有一个问题,也就是如果我们的代码本身没有static 块,也就是没有 clinit 函数,那么我们的判断就不会生效,因此如果没有 clinit函数,我们需要先创建该函数
class AddClinitClassVisitor : BaseClassVisitor() {var hasClinit = falseoverride fun visitMethod(access: Int, name: String?, desc: String?, signature: String?, exceptions: Array<String?>?): MethodVisitor? {if (name == "<clinit>") {hasClinit = true}return super.visitMethod(access, name, desc, signature, exceptions)}override fun visitEnd() {if (!hasClinit) {val mv = super.visitMethod(Opcodes.ACC_STATIC, "<clinit>", "()V", null, null)mv.visitCode()mv.visitInsn(Opcodes.RETURN)mv.visitMaxs(0, 0)mv.visitEnd()}super.visitEnd()}}
AddClinitClassVisitor 是优先级高的转换处理,因为需要优先创建 clinit 函数,首先在访问函数的时候判断是否有该函数,如果没有,就在访问结束时创建该函数
