最近休年假提前返湘了,回村这几天冻的直打哆嗦,突然很怀恋北方温暖的暖气片。
微信公众号收到老哥私信,求助逆向一个app并对解密某个数据包。说干就干,掏出我的Google亲儿子开整。
不过这个App有一半是荤的,内容都是些虎狼之词,就不放出来了。
先看老哥的需求
图中说的 data.php 抓包看到内容是长这样子的:
查壳发现目标app使用360加固了,回村匆忙没来得急带家伙事,又让老哥提供了一份脱壳后的dex文件。
拿到dex文件后,先用 d2j-dex2jar.sh 把dex还原成jar包,再把jar包拖到 jadx-gui,后面还是以前的那些三脚猫功夫。在 jadx-gui 中不断 Ctrl + F搜索关键词:Request、Response 中出现的字段和 encrypt、decrypt、AES、secretkey 等关键词。
定位到一处解密代码:
接下来就要对这段代码进行 hook ,看看它都干了些啥。如果不会使用 Frida 或者不熟练,可以像我一样多问问 chatGPT
对以下源码就行 hook
public static String a(String str, String str2) { try { if (str2 == null) { System.out.print("Key為空null"); return null; } else if (str2.length() != 16) { System.out.print("Key長度不是16位"); return null; } else { SecretKeySpec secretKeySpec = new SecretKeySpec(str2.getBytes("utf-8"), "AES"); Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding"); cipher.init(2, secretKeySpec); try { return new String(cipher.doFinal(Base64.decode(str, 0)), "utf-8"); } catch (Exception e) { System.out.println(e.toString()); return null; } } } catch (Exception e2) { System.out.println(e2.toString()); return null; } }
先问问chatGPT 怎么对就函数进行hook
整理后(其实是jadx-gui 直接右键导出的)的hook代码:
let perform = Java.perform(() =>{ let C0158a = Java.use("cn.tv.player.a"); C0158a["a"].overload('java.lang.String', 'java.lang.String').implementation = function (str, str2) { console.log('a is called' + ', ' + 'str: ' + str + ', ' + 'str2: ' + str2); console.log("str2: ",str2) let ret = this.a(str, str2); console.log('a ret value is ' + ret); return ret; };
frida -U -f tw.qvod.new -l frida.js --no-pause -o a.txt
图中的方法应该是一个AES ECB 的解密算法,str 是密文,str2 是秘钥。
结合burpsuite抓的包进行分析,终端截图中对应的请求应该是:
这串密文复制到AES在线解密网站,使用ECB模式 PKCS5padding 填充,秘钥 4f3742c93218f004
能正确解开,内容就是终端截图中所示明文。
秘钥是怎么生成的,就不具体去分析了,还是以解密data.php 内容为主。
接接看data.php 解密对应的逻辑在哪里。看看都有谁调用了 cn.tv.player.a.a 这个方法 一通搜索后,应该是下面没跑:
如果看不懂Java,那就请 chatGPT , 解释解释神马踏马的叫.......
美滋滋,一下子就看明白这里是啥意思了。
跟进一下下列代码找那个的 a 函数是个什么东西。
this.jsonString = new String(a(Base64.decode(this.jsonString, 0)), "UTF-8");
之前hook到的数据保存到了 txt 中,使用对应的秘钥解出来也是一串 乱七八杂的东西,不可读。
分析后确认,他和上述代码中的 new String(a(Base64.decode(this.jsonString, 0)), "UTF-8"); 有关系。
对 data.php 数据的整个解密逻辑是:
- ChannelDatas 传进来一上下文
- 通过 this.mySettings = new MySettings(context); 拿到设置内容
- 通过String substring = C0158a.b(SplashActivity.z + new MySettings(PlayerActivity.f1635a).c("rand")).substring(7, 23); 设置生成AES密钥
- C0186z.a 操作文件
- 一系列替换操作,调用AES进行解密
- 使用bade64 进行decode ,在调用a方法进行解压缩解码
打开之前保存的 a.txt 文件,找到 data.php 那段解密后的数据。
密文:
明文:
编写python解密对这段不可读明文进行处理;
import base64 from io import BytesIO from zlib import decompress def a(bArr):
"""
new String(a(Base64.decode(this.jsonString, 0)), "UTF-8");
"""
byte_stream = BytesIO(bArr) try: result = decompress(byte_stream.read()) except Exception as e: print(e) result = None byte_stream.close() return result str = "解密后不可读的明文.........."
decoded_data = base64.b64decode(str)
decoded_data = a(decoded_data)
print(decoded_data.decode("utf-8"))
另一种更快的方法
直接 hook cn.tv.player.model.ChannelDatas 中substring 的值
frida -U -f tw.qvod.new -l frida.js --no-pause -o data.php.txt
frida 带上 -o 参数,将结果保存下来
// cn.tv.player.model.ChannelDatas function main(){ let perform = Java.perform(() => { //对象 实例 Java.choose("cn.tv.player.model.ChannelDatas", { onMatch:function(instance){ console.log(instance.substring.value) }, onComplete:function(){ console.log("over........") } } ); }); } setImmediate(main)
chatGPT 以后就是亲二舅了,回村三天,二舅治好了我的精神内耗,哈哈哈哈。