这是源代码链接 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.decode
mv.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 = false
override 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 函数,首先在访问函数的时候判断是否有该函数,如果没有,就在访问结束时创建该函数