jadx字符串解密

[复制链接]
查看1311 | 回复0 | 2025-7-22 11:38:52 | 显示全部楼层 |阅读模式

立即注册,突破游客限制,与技术大牛交流切磋,畅享海量资源,让你的技术之路不再孤单!

您需要 登录 才可以下载或查看,没有账号?立即注册

×
本帖最后由 洋咩咩 于 2025-7-22 11:40 编辑

当前大部分app都使用了字符串加密, 而我们常用的工具jadx并不具备字符串解密, 因为它是一个静态的反编译工具, 不具备动态执行环境, 所以需要自己实现解密的方法, 做成脚本给jadx调用
实战案例一
比如下方就是一个字符串加密, 他的参数是传入两个base64加密的字符串
截图202507221132419289.png
先找到字符解密的方法


  1. public final class C2099a {
  2.     public static byte[] b(byte[] bArr, byte[] bArr2) {
  3.         int length = bArr.length;
  4.         int length2 = bArr2.length;
  5.         int i7 = 0;
  6.         int i8 = 0;
  7.         while (i7 < length) {
  8.             if (i8 >= length2) {
  9.                 i8 = 0;
  10.             }
  11.             bArr[i7] = (byte) (bArr[i7] ^ bArr2[i8]);
  12.             i7++;
  13.             i8++;
  14.         }
  15.         return bArr;
  16.     }
  17.     public String a(byte[] bArr, byte[] bArr2) {
  18.         return new String(b(bArr, bArr2), StandardCharsets.UTF_8);
  19.     }
  20. }
复制代码
把字符串解密方法转为kotlin实现
因为jadx的script 语法是kotlin, 未来可能会支持java语法
  1. fun fullDecode(encryptedStr: String, keyStr: String): String {
  2.     val data = decodeBase64StringToBytes(encryptedStr)
  3.     val key = decodeBase64StringToBytes(keyStr)
  4.     val decryptedBytes = xorBytes(data, key)
  5.     return String(decryptedBytes, StandardCharsets.UTF_8)
  6. }
  7. fun decodeBase64StringToBytes(str: String): ByteArray {
  8.     val trimmedStr = str.trim()
  9.     return Base64.getDecoder().decode(trimmedStr)
  10. }
  11. fun xorBytes(data: ByteArray, key: ByteArray): ByteArray {
  12.     val dataLen = data.size
  13.     val keyLen = key.size
  14.     if (keyLen == 0) {
  15.         return data.clone()
  16.     }
  17.     val result = ByteArray(dataLen)
  18.     for (i in 0 until dataLen) {
  19.         result[i] = (data[i].toInt() xor key[i % keyLen].toInt()).toByte()
  20.     }
  21.     return result
  22. }
复制代码

找到函数签名
通过smail得到函数签名
"g5.a.a(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;"
截图202507221134147813.png
编写jadx脚本
kotlin语法
  1. import jadx.core.dex.instructions.ConstStringNode
  2. import jadx.core.dex.instructions.InvokeNode
  3. import jadx.core.dex.instructions.args.InsnArg
  4. import jadx.core.dex.instructions.args.InsnWrapArg
  5. import jadx.core.dex.instructions.args.RegisterArg
  6. import java.nio.charset.StandardCharsets
  7. import java.util.Base64
  8. val jadx = getJadxInstance()
  9. //从smali复制包名+函数签名
  10. val mthSignature = "g5.a.a(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;"
  11. jadx.replace.insns { mth, insn ->
  12.     if (insn is InvokeNode && insn.callMth.rawFullId == mthSignature) {
  13.         val encryptedStr = getConstString(insn.getArg(0))
  14.         val keyStr = getConstString(insn.getArg(1))
  15.         if (encryptedStr != null && keyStr != null) {
  16.             try {
  17.                 val resultStr = fullDecode(encryptedStr, keyStr)
  18.                 log.info { "调用方法:  $mth ,解密后字符串: '$resultStr'" }
  19.                 return@insns ConstStringNode(resultStr)
  20.             } catch (e: Exception) {
  21.                 log.error(e) { "Failed to decode string in $mth. Encrypted: '$encryptedStr', Key: '$keyStr'" }
  22.             }
  23.         } else {
  24.             log.warn { "Signature matched, but arguments are not constant strings in $mth" }
  25.         }
  26.     }
  27.     null
  28. }
  29. fun getConstString(arg: InsnArg): String? {
  30.     val insn = when (arg) {
  31.         is InsnWrapArg -> arg.wrapInsn
  32.         is RegisterArg -> arg.assignInsn
  33.         else -> null
  34.     }
  35.     if (insn is ConstStringNode) {
  36.         return insn.string
  37.     }
  38.     return null
  39. }
  40. fun fullDecode(encryptedStr: String, keyStr: String): String {
  41.     val data = decodeBase64StringToBytes(encryptedStr)
  42.     val key = decodeBase64StringToBytes(keyStr)
  43.     val decryptedBytes = xorBytes(data, key)
  44.     return String(decryptedBytes, StandardCharsets.UTF_8)
  45. }
  46. fun decodeBase64StringToBytes(str: String): ByteArray {
  47.     val trimmedStr = str.trim()
  48.     return Base64.getDecoder().decode(trimmedStr)
  49. }
  50. fun xorBytes(data: ByteArray, key: ByteArray): ByteArray {
  51.     val dataLen = data.size
  52.     val keyLen = key.size
  53.     if (keyLen == 0) {
  54.         return data.clone()
  55.     }
  56.     val result = ByteArray(dataLen)
  57.     for (i in 0 until dataLen) {
  58.         result[i] = (data[i].toInt() xor key[i % keyLen].toInt()).toByte()
  59.     }
  60.     return result
  61. }
复制代码
添加脚本, 运行解密
可以点击脚本, 显示日志, 看脚本是否运行成功
截图202507221134516909.png
效果
截图202507221135238467.png

实战案例二 - StringFog字符串解密
这个与上面的不同, 复杂一点, 参数为byte array

  1. <span style="font-weight: normal;"> Intrinsics.throwUninitializedPropertyAccessException(StringFog.decrypt(new byte[]{-103, 84, 88}, new byte[]{-8, 36, 40, -86, -80, -106, 101, -73}));</span>
复制代码
解密脚本
  1. /***
  2. * StringFog 字符串解密脚本
  3. * 解密 StringFog.decrypt(byte[], byte[]) 调用
  4. */
  5. import jadx.core.dex.instructions.ConstStringNode
  6. import jadx.core.dex.instructions.InvokeNode
  7. import jadx.core.dex.instructions.args.InsnArg
  8. import jadx.core.dex.instructions.args.InsnWrapArg
  9. import jadx.core.dex.instructions.args.RegisterArg
  10. import jadx.core.dex.instructions.FilledNewArrayNode
  11. import jadx.core.dex.instructions.args.LiteralArg
  12. val jadx = getJadxInstance()
  13. val mthSignature = "com.xxx.clear.StringFog.decrypt([B[B)Ljava/lang/String;"
  14. jadx.replace.insns { mth, insn ->
  15.     if (insn is InvokeNode && insn.callMth.rawFullId == mthSignature) {
  16.         val encryptedBytes = getByteArray(insn.getArg(0))
  17.         val keyBytes = getByteArray(insn.getArg(1))
  18.         if (encryptedBytes != null && keyBytes != null) {
  19.             try {
  20.                 val decryptedStr = stringFogDecrypt(encryptedBytes, keyBytes)
  21.                 log.info { "StringFog decrypt: ${encryptedBytes.contentToString()} -> '$decryptedStr' in $mth" }
  22.                 return@insns ConstStringNode(decryptedStr)
  23.             } catch (e: Exception) {
  24.                 log.error { "Failed to decrypt StringFog in $mth: ${e.message}" }
  25.             }
  26.         }
  27.     }
  28.     null
  29. }
  30. fun getByteArray(arg: InsnArg): ByteArray? {
  31.     val insn = when (arg) {
  32.         is InsnWrapArg -> arg.wrapInsn
  33.         is RegisterArg -> arg.assignInsn
  34.         else -> null
  35.     }
  36.      
  37.     if (insn is FilledNewArrayNode) {
  38.         val bytes = mutableListOf<Byte>()
  39.         for (argItem in insn.arguments) {
  40.             if (argItem is LiteralArg) {
  41.                 bytes.add(argItem.literal.toInt().toByte())
  42.             }
  43.         }
  44.         return bytes.toByteArray()
  45.     }
  46.     return null
  47. }
  48. fun stringFogDecrypt(encryptedBytes: ByteArray, keyBytes: ByteArray): String {
  49.     val decryptedBytes = encryptedBytes.copyOf()
  50.     val dataLength = decryptedBytes.size
  51.     val keyLength = keyBytes.size
  52.     var keyIndex = 0
  53.      
  54.     for (i in 0 until dataLength) {
  55.         if (keyIndex >= keyLength) {
  56.             keyIndex = 0
  57.         }
  58.         decryptedBytes[i] = (decryptedBytes[i].toInt() xor keyBytes[keyIndex].toInt()).toByte()
  59.         keyIndex++
  60.     }
  61.      
  62.     return String(decryptedBytes, Charsets.UTF_8)
  63. }
复制代码
重点区别在于参数是byte类型的new byte[]{-103, 84, 88}, 要把里面的int数组转为byte array
  1. fun getByteArray(arg: InsnArg): ByteArray? {
  2.     val insn = when (arg) {
  3.             is InsnWrapArg -> arg.wrapInsn        //StringFog.decrypt(new byte[]{1,2,3}, key) 中的 new byte[]{1,2,3} 部分
  4.         is RegisterArg -> arg.assignInsn        
  5.         else -> null
  6.     }
  7.   //解析字节数组
  8.     if (insn is FilledNewArrayNode) {
  9.         val bytes = mutableListOf<Byte>()
  10.         for (argItem in insn.arguments) {
  11.             if (argItem is LiteralArg) {   //LiteralArg 表示编译时常量,如 -103, 84, 88
  12.                 bytes.add(argItem.literal.toInt().toByte())   //argItem.literal 是long类型, 转换为 Int 再转换为 Byte
  13.             }
  14.         }
  15.         return bytes.toByteArray()
  16.     }
  17.     return null
  18. }
复制代码
  1. 案例: StringFog.decrypt(new byte[]{-103, 84, 88}, new byte[]{-8, 36, 40, -86, -80, -106, 101, -73})
  2. arg 是 InsnWrapArg
  3. arg.wrapInsn 返回 FilledNewArrayNode
  4. insn.arguments 包含三个 LiteralArg: -103, 84, 88
  5. 最终返回 byteArrayOf(-103, 84, 88)
复制代码
效果
解密前:
截图202507221137518175.png


解密后:
截图202507221138093780.png


这个人还没有签名
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则