立即注册,突破游客限制,与技术大牛交流切磋,畅享海量资源,让你的技术之路不再孤单!
您需要 登录 才可以下载或查看,没有账号?立即注册
×
本帖最后由 洋咩咩 于 2025-7-22 11:40 编辑
当前大部分app都使用了字符串加密, 而我们常用的工具jadx并不具备字符串解密, 因为它是一个静态的反编译工具, 不具备动态执行环境, 所以需要自己实现解密的方法, 做成脚本给jadx调用 实战案例一比如下方就是一个字符串加密, 他的参数是传入两个base64加密的字符串
先找到字符解密的方法
- public final class C2099a {
- public static byte[] b(byte[] bArr, byte[] bArr2) {
- int length = bArr.length;
- int length2 = bArr2.length;
- int i7 = 0;
- int i8 = 0;
- while (i7 < length) {
- if (i8 >= length2) {
- i8 = 0;
- }
- bArr[i7] = (byte) (bArr[i7] ^ bArr2[i8]);
- i7++;
- i8++;
- }
- return bArr;
- }
-
- public String a(byte[] bArr, byte[] bArr2) {
- return new String(b(bArr, bArr2), StandardCharsets.UTF_8);
- }
- }
复制代码 把字符串解密方法转为kotlin实现因为jadx的script 语法是kotlin, 未来可能会支持java语法
- fun fullDecode(encryptedStr: String, keyStr: String): String {
- val data = decodeBase64StringToBytes(encryptedStr)
- val key = decodeBase64StringToBytes(keyStr)
- val decryptedBytes = xorBytes(data, key)
- return String(decryptedBytes, StandardCharsets.UTF_8)
- }
-
- fun decodeBase64StringToBytes(str: String): ByteArray {
- val trimmedStr = str.trim()
- return Base64.getDecoder().decode(trimmedStr)
- }
-
- fun xorBytes(data: ByteArray, key: ByteArray): ByteArray {
- val dataLen = data.size
- val keyLen = key.size
- if (keyLen == 0) {
- return data.clone()
- }
- val result = ByteArray(dataLen)
- for (i in 0 until dataLen) {
- result[i] = (data[i].toInt() xor key[i % keyLen].toInt()).toByte()
- }
- return result
- }
复制代码
找到函数签名通过smail得到函数签名 "g5.a.a(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;"
编写jadx脚本kotlin语法
- import jadx.core.dex.instructions.ConstStringNode
- import jadx.core.dex.instructions.InvokeNode
- import jadx.core.dex.instructions.args.InsnArg
- import jadx.core.dex.instructions.args.InsnWrapArg
- import jadx.core.dex.instructions.args.RegisterArg
- import java.nio.charset.StandardCharsets
- import java.util.Base64
-
- val jadx = getJadxInstance()
-
- //从smali复制包名+函数签名
- val mthSignature = "g5.a.a(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;"
-
- jadx.replace.insns { mth, insn ->
- if (insn is InvokeNode && insn.callMth.rawFullId == mthSignature) {
- val encryptedStr = getConstString(insn.getArg(0))
- val keyStr = getConstString(insn.getArg(1))
-
- if (encryptedStr != null && keyStr != null) {
- try {
- val resultStr = fullDecode(encryptedStr, keyStr)
- log.info { "调用方法: $mth ,解密后字符串: '$resultStr'" }
- return@insns ConstStringNode(resultStr)
- } catch (e: Exception) {
- log.error(e) { "Failed to decode string in $mth. Encrypted: '$encryptedStr', Key: '$keyStr'" }
- }
- } else {
- log.warn { "Signature matched, but arguments are not constant strings in $mth" }
- }
- }
- null
- }
-
- fun getConstString(arg: InsnArg): String? {
- val insn = when (arg) {
- is InsnWrapArg -> arg.wrapInsn
- is RegisterArg -> arg.assignInsn
- else -> null
- }
- if (insn is ConstStringNode) {
- return insn.string
- }
- return null
- }
-
- fun fullDecode(encryptedStr: String, keyStr: String): String {
- val data = decodeBase64StringToBytes(encryptedStr)
- val key = decodeBase64StringToBytes(keyStr)
- val decryptedBytes = xorBytes(data, key)
- return String(decryptedBytes, StandardCharsets.UTF_8)
- }
-
- fun decodeBase64StringToBytes(str: String): ByteArray {
- val trimmedStr = str.trim()
- return Base64.getDecoder().decode(trimmedStr)
- }
-
- fun xorBytes(data: ByteArray, key: ByteArray): ByteArray {
- val dataLen = data.size
- val keyLen = key.size
- if (keyLen == 0) {
- return data.clone()
- }
- val result = ByteArray(dataLen)
- for (i in 0 until dataLen) {
- result[i] = (data[i].toInt() xor key[i % keyLen].toInt()).toByte()
- }
- return result
- }
复制代码 添加脚本, 运行解密可以点击脚本, 显示日志, 看脚本是否运行成功
效果
实战案例二 - StringFog字符串解密这个与上面的不同, 复杂一点, 参数为byte array
- <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>
复制代码
解密脚本
- /***
- * StringFog 字符串解密脚本
- * 解密 StringFog.decrypt(byte[], byte[]) 调用
- */
-
- import jadx.core.dex.instructions.ConstStringNode
- import jadx.core.dex.instructions.InvokeNode
- import jadx.core.dex.instructions.args.InsnArg
- import jadx.core.dex.instructions.args.InsnWrapArg
- import jadx.core.dex.instructions.args.RegisterArg
- import jadx.core.dex.instructions.FilledNewArrayNode
- import jadx.core.dex.instructions.args.LiteralArg
-
- val jadx = getJadxInstance()
-
- val mthSignature = "com.xxx.clear.StringFog.decrypt([B[B)Ljava/lang/String;"
-
- jadx.replace.insns { mth, insn ->
- if (insn is InvokeNode && insn.callMth.rawFullId == mthSignature) {
- val encryptedBytes = getByteArray(insn.getArg(0))
- val keyBytes = getByteArray(insn.getArg(1))
-
- if (encryptedBytes != null && keyBytes != null) {
- try {
- val decryptedStr = stringFogDecrypt(encryptedBytes, keyBytes)
- log.info { "StringFog decrypt: ${encryptedBytes.contentToString()} -> '$decryptedStr' in $mth" }
- return@insns ConstStringNode(decryptedStr)
- } catch (e: Exception) {
- log.error { "Failed to decrypt StringFog in $mth: ${e.message}" }
- }
- }
- }
- null
- }
-
- fun getByteArray(arg: InsnArg): ByteArray? {
- val insn = when (arg) {
- is InsnWrapArg -> arg.wrapInsn
- is RegisterArg -> arg.assignInsn
- else -> null
- }
-
- if (insn is FilledNewArrayNode) {
- val bytes = mutableListOf<Byte>()
- for (argItem in insn.arguments) {
- if (argItem is LiteralArg) {
- bytes.add(argItem.literal.toInt().toByte())
- }
- }
- return bytes.toByteArray()
- }
- return null
- }
-
- fun stringFogDecrypt(encryptedBytes: ByteArray, keyBytes: ByteArray): String {
- val decryptedBytes = encryptedBytes.copyOf()
-
- val dataLength = decryptedBytes.size
- val keyLength = keyBytes.size
- var keyIndex = 0
-
- for (i in 0 until dataLength) {
- if (keyIndex >= keyLength) {
- keyIndex = 0
- }
- decryptedBytes[i] = (decryptedBytes[i].toInt() xor keyBytes[keyIndex].toInt()).toByte()
- keyIndex++
- }
-
- return String(decryptedBytes, Charsets.UTF_8)
- }
复制代码
重点区别在于参数是byte类型的new byte[]{-103, 84, 88}, 要把里面的int数组转为byte array
- fun getByteArray(arg: InsnArg): ByteArray? {
- val insn = when (arg) {
- is InsnWrapArg -> arg.wrapInsn //StringFog.decrypt(new byte[]{1,2,3}, key) 中的 new byte[]{1,2,3} 部分
- is RegisterArg -> arg.assignInsn
- else -> null
- }
-
- //解析字节数组
- if (insn is FilledNewArrayNode) {
- val bytes = mutableListOf<Byte>()
- for (argItem in insn.arguments) {
- if (argItem is LiteralArg) { //LiteralArg 表示编译时常量,如 -103, 84, 88
- bytes.add(argItem.literal.toInt().toByte()) //argItem.literal 是long类型, 转换为 Int 再转换为 Byte
- }
- }
- return bytes.toByteArray()
- }
- return null
- }
复制代码
- 案例: StringFog.decrypt(new byte[]{-103, 84, 88}, new byte[]{-8, 36, 40, -86, -80, -106, 101, -73})
- arg 是 InsnWrapArg
- arg.wrapInsn 返回 FilledNewArrayNode
- insn.arguments 包含三个 LiteralArg: -103, 84, 88
- 最终返回 byteArrayOf(-103, 84, 88)
复制代码 效果解密前:
解密后:
|