这是源代码链接 https://github.com/MikaelZero/ByteTea/blob/master/EncryptString/README.md

    先来看下效果

    这是插桩之前的代码

    1. private final String final_field_1 = "test_private_final";

    这是插桩之后的代码

    1. private final String final_field_1 = Base64Util.decode("dGVzdF9wcml2YXRlX2ZpbmFs");

    也就是把字符串的部分,做一个加密,这也是防破解的其中一个基本手段

    我们通过 Jclasslib 插件可以知道,所有的字符串在字节码中的指令都是LDC指令,因此只需要在访问LDC指令的时候进行修改即可

    先注意一点,在整个访问流程中,编写的代码是在init函数或者 clinit 函数进行,clinit后面再谈,所以我们需要在MethodVistor去寻找对应的字符串

    在MethodVistor中访问LDC指令的函数是visitLdcInsn,所以在这个函数下,我们判断下是否为String类型,然后进行代码修改

    1. override fun visitLdcInsn(value: Any?) {
    2. if (value is String) {
    3. val str = Base64.encodeBase64String(value.toByteArray())
    4. //这步是直接修改LDC指令下对应的value,而我们的value就是上面的字符串
    5. mv.visitLdcInsn(str)
    6. //调用编写好的Lib里的代码,也就是 Base64Util.decode
    7. mv.visitMethodInsn(Opcodes.INVOKESTATIC, className, encryptMethodName, "(Ljava/lang/String;)Ljava/lang/String;", false)
    8. return
    9. }
    10. super.visitLdcInsn(value)
    11. }

    编写完成后,你会发现一个问题,就是 visitLdcInsn 访问不到 static final 的字符串,而且static final 的字符串必须设置值或者声明后在 static 块里进行初始化,所以我们需要找一个地方能够获取到 static final 的指令并且统一处理,也就是visitInsn,并且他的code为 RETURN,代码如下

    1. override fun visitInsn(opcode: Int) {
    2. //static 的函数名为 <clinit>
    3. if (opcode == Opcodes.RETURN && methodName == "<clinit>") {
    4. staticFinalStringFieldNodeList.forEach { fieldNode ->
    5. val str = Base64.encodeBase64String((fieldNode.value as String).toByteArray())
    6. mv?.visitLdcInsn(str)
    7. mv?.visitMethodInsn(Opcodes.INVOKESTATIC, className, encryptMethodName, "(Ljava/lang/String;)Ljava/lang/String;", false)
    8. mv?.visitFieldInsn(Opcodes.PUTSTATIC, owner, fieldNode.name, "Ljava/lang/String;")
    9. }
    10. }
    11. super.visitInsn(opcode)
    12. }

    staticFinalStringFieldNodeList是我们提前收集好的列表,代码如下

    1. override fun visitField(access: Int, name: String?, descriptor: String?, signature: String?, value: Any?): FieldVisitor {
    2. if (value != null && value is String
    3. && descriptor == Type.getDescriptor(String::class.java)
    4. && 0 != (Opcodes.ACC_STATIC and access)
    5. && 0 != (Opcodes.ACC_FINAL and access)
    6. ) {
    7. //添加一个类下的 所有 static final 的字段
    8. staticFinalStringFieldNodeList.add(FieldNode(access, name, descriptor, signature, value))
    9. return super.visitField(access, name, descriptor, signature, null)
    10. }
    11. return super.visitField(access, name, descriptor, signature, value)
    12. }

    最后还会有一个问题,也就是如果我们的代码本身没有static 块,也就是没有 clinit 函数,那么我们的判断就不会生效,因此如果没有 clinit函数,我们需要先创建该函数

    1. class AddClinitClassVisitor : BaseClassVisitor() {
    2. var hasClinit = false
    3. override fun visitMethod(access: Int, name: String?, desc: String?, signature: String?, exceptions: Array<String?>?): MethodVisitor? {
    4. if (name == "<clinit>") {
    5. hasClinit = true
    6. }
    7. return super.visitMethod(access, name, desc, signature, exceptions)
    8. }
    9. override fun visitEnd() {
    10. if (!hasClinit) {
    11. val mv = super.visitMethod(Opcodes.ACC_STATIC, "<clinit>", "()V", null, null)
    12. mv.visitCode()
    13. mv.visitInsn(Opcodes.RETURN)
    14. mv.visitMaxs(0, 0)
    15. mv.visitEnd()
    16. }
    17. super.visitEnd()
    18. }
    19. }

    AddClinitClassVisitor 是优先级高的转换处理,因为需要优先创建 clinit 函数,首先在访问函数的时候判断是否有该函数,如果没有,就在访问结束时创建该函数