|
|
本帖最后由 嘈杂 于 2026-5-29 16:04 编辑
前 言
朋友最近搞悉尼大学的题目,目标是找出隐藏的API密钥。结果他卡住了,请求我的帮忙。记录一下过程。
样本就不发了,毕竟是人家的作业
一、基本信息
MT管理器看APK,包名nz.ac.auckland.cs702.tartarus,应用名Tartarus。jadx反编译后代码混淆,类名全是G1、A0、r2这种,不过业务类名保留了几个关键的:
- com.example.playground.MainActivity
- com.example.playground.network.ImagePipeline
- com.example.playground.network.AssetProbe
- com.example.playground.network.FrameAssembler
- com.example.playground.network.AssetWarmup
- com.example.playground.network.PaletteProbe
- com.example.playground.util.RuntimeProbe
复制代码
MT管理器看lib目录,有个5.42MB的native库:libtartarus_core.so,静态链接了libcurl和openssl。直觉告诉我密钥可能藏在里面。
二、XOR字符串解码
jadx定位到ImagePipeline$Companion类,全是XOR混淆。核心解码方法:
- public static String h(int[] arr, int key) {
- StringBuilder sb = new StringBuilder();
- for (int v : arr) sb.append((char)(v ^ key));
- return sb.toString();
- }
复制代码
写个Python脚本批量解:
- def d(arr, key): return ''.join(chr(x ^ key) for x in arr)
- print(d([0x6b,0x5f,0x5e,0x42,0x45,0x58,0x43,0x50,
- 0x4b,0x5e,0x43,0x45,0x44], 0x2a))
- print(d([0x44,0x0c,0x0e,0x05,0x0e,0x19,0x0a,0x1f,
- 0x0e,0x34,0x02,0x06,0x0a,0x0c,0x0e], 0x6b))
- print(d([0x52,0x4e,0x4e,0x4a,0x49,0x00,0x15,0x15,0x5b,0x53,
- 0x14,0x5f,0x56,0x56,0x53,0x55,0x4e,0x4e,0x4d,0x5f,
- 0x54,0x14,0x53,0x54,0x5c,0x55], 0x3a))
- print(d([0x54,0x4f,0x46,0x15,0x12,0x11,0x08,0x49,0x1e,0x42,
- 0x65,0x71,0x65,0x16,0x16,0x45,0x57,0x54,0x1e,0x68,
- 0x5d,0x68,0x53,0x11,0x60,0x17,0x16,0x64,0x6d,0x53,
- 0x70,0x73,0x6b,0x6e,0x41,0x41,0x57,0x13,0x63,0x55,
- 0x6f,0x49,0x62,0x4c,0x43,0x51,0x6b,0x77,0x5f,0x72,
- 0x1a], 0x27))
复制代码
跑一下:
- $ python3 decode.py
- Authorization
- /generate_image
- https://ai.elliottwen.info
- sha256/n9eBVB11bps9OzOt6G01CJtWTLIffp4DrHnEkdvLPxU=
复制代码
其他解码结果:a()=Authorization, b()=/generate_image, c()=application/json; charset=utf-8, d()=signature, i()=https://ai.elliottwen.info, j()=证书哈希。AssetProbe里还有key=0x41→"https://ai.elliotwen.info"(少一个t)。
API配置全部拿到了,但API密钥不在Java层。
三、发现native层隐藏
于是又注意到AssetProbe类有两个private native方法:
- .method private final native probeEndpoint()Ljava/lang/String;
- .method private final native probeGate()Ljava/lang/String;
复制代码
ImagePipeline$Companion也有两个:
- .method private final native routeBase(Ljava/lang/String;)Ljava/lang/String;
- .method private final native checkPeer(Ljava/lang/String;Ljava/lang/String;)Z
复制代码
特别是probeGate()这个名字——感觉就是用来返回API密钥的。
四、IDA静态分析so
提取so丢进IDA:
- JNI_OnLoad 0x1007A4
- JNI_OnUnload 0x10048C
复制代码
只有两个导出符号!所有native方法通过RegisterNatives动态注册。
JNI_OnLoad反汇编出来全是垃圾指令:
- .text:001007D8 MVN X11, X9
- .text:001007DC ADD X12, X9, #1
- .text:001007E0 ADD X11, X12, X11,LSL#1
- .text:001007E4 ORR X13, X11, X10
- .text:001007E8 ORN X14, X10, X11
- .text:001007EC AND X15, X13, X12
- .text:001007F0 ADD X11, X11, X12
- .text:001007F4 EOR X12, X13, X12
- .text:001007F8 ADD X11, X11, X14
- .text:001007FC ADD X12, X12, X15,LSL#1
- .text:00100800 NEG X13, X10
- .text:00100804 SUB X11, X11, X12
- ... (后面全是类似的无意义运算)
复制代码
IDA Strings搜probeEndpoint、probeGate:0 results。证实通过RegisterNatives注册。
但是.data段发现可疑数据,尝试解密看看:
- $ python3 -c "
- data=open('libtartarus_core.so','rb').read()
- enc = data[0x5650c0:0x5650c0+0x11]
- print(bytes(b ^ 0x5a for b in enc))
- "
- b'arena.key.slot.c\x00'
复制代码
配置键名,不是密钥。静态分析走不通,上Frida。
五、Frida动态提取
APP有完整反调试(su路径、Xposed包名、/proc扫描、frida端口、Debug检测等)。这里就不细说了,有感兴趣的我再发一篇完整绕过检测的帖子
第一轮:抓XOR解码 + HTTP头
- Java.perform(function() {
- var C = Java.use("com.example.playground.network.ImagePipeline$Companion");
- C.h.implementation = function(arr, key) {
- var r = this.h(arr, key);
- console.log("[XOR] key=0x" + key.toString(16) + " => " + r);
- return r;
- };
- var RB = Java.use("A0.p");
- RB.m.overloads.forEach(function(ov) {
- ov.implementation = function(name, value) {
- if (name === "Authorization")
- console.log("[KEY] Authorization = " + value);
- return ov.call(this, name, value);
- };
- });
- var G1j = Java.use("G1.j");
- G1j.b.implementation = function() {
- var t = this.b();
- console.log("[TOKEN] " + t);
- return t;
- };
- });
复制代码
Frida输出(关键部分):
- [XOR] key=0x3a => "https://ai.elliottwen.info"
- [ROUTE] in=https://ai.elliottwen.info → out=https://ai.elliotwen.info
- [XOR] key=0x57 => "/auth"
- [TOKEN] cae9edce9e9819bf01044dbacdd05e2e...
- [KEY] Authorization = cae9edce9e9819bf01044dbacdd05e2e...
- URL: POST https://ai.elliotwen.info/auth
- HDR: Content-Length = 0
- HDR: Host = ai.elliotwen.info
- HDR: User-Agent = okhttp/4.12.0
- [XOR] key=0x2a => "Authorization"
- URL: POST https://ai.elliotwen.info/generate_image
- HDR: Content-Type = application/json; charset=utf-8
复制代码
发现两个端点用不同Authorization!
• /auth → G1.j.b()生成的随机128位hex token
• /generate_image → 固定值
第二轮:反射调用native方法
- Java.perform(function() {
- var AP = Java.use("com.example.playground.utils.AssetProbe");
- var inst = AP.$new();
- var cls = inst.getClass();
- var m1 = cls.getDeclaredMethod("probeEndpoint", []);
- m1.setAccessible(true);
- console.log("[KEY] probeEndpoint = " + m1.invoke(inst, []));
- var m2 = cls.getDeclaredMethod("probeGate", []);
- m2.setAccessible(true);
- console.log("[KEY] probeGate = " + m2.invoke(inst, []));
- });
复制代码
Frida输出:
- [KEY] probeEndpoint = https://ai.elliotwen.info/generate_image
- [KEY] probeGate = c238eb9410fd73a12ab1ec56e70d4bc53f87a6ddfbde50168c93e84271ae3fd01e25b7a18d3f50acb6a42f13f968d7bc7ed0c514be928da73bc48e01563d41ab
复制代码
拿到了!另外还有:
- warmupNative → 368b663f3c47dc8bcd75bb0aceb
- readPalette → 08a041dac
复制代码
六、完整api流程
七、验证
- import urllib.request, ssl, json
- ctx = ssl.create_default_context()
- ctx.check_hostname = False
- ctx.verify_mode = ssl.CERT_NONE
- body = json.dumps({'signature': '', 'prompt': 'a cute cat wearing sunglasses'}).encode()
- req = urllib.request.Request(
- 'https://ai.elliotwen.info/generate_image', data=body,
- headers={
- 'Authorization': 'c238eb9410fd73a12ab1ec56e70d4bc53f87a6ddfbde50168c93e84271ae3fd01e25b7a18d3f50acb6a42f13f968d7bc7ed0c514be928da73bc48e01563d41ab',
- 'Content-Type': 'application/json; charset=utf-8',
- 'User-Agent': 'okhttp/4.12.0'
- }, method='POST')
- print(urllib.request.urlopen(req, context=ctx, timeout=30).read().decode())
复制代码
输出:
- $ python3 test.py
- "/images/ead5cc01-491c-ee15-2640-0b6b90e31f05.jpeg"
复制代码
HTTP 200,成功!而且发现/generate_image端点不校验signature参数,空字符串也能过。
八、汇总
| 项目 | 值 | | API密钥 | c238eb9410fd73a12ab1ec56e70d4bc53f87a6ddfbde50168c93e84271ae3fd01e25b7a18d3f50acb6a42f13f968d7bc7ed0c514be928da73bc48e01563d41ab | | 长度 | 128 hex = 64字节 = 512bit | | 位置 | libtartarus_core.so .data段 | | 端点 | POST https://ai.elliotwen.info/generate_image | | 认证 | Authorization头直传 无Bearer前缀 |
九、总结
应用保护思路是"藏"不是"加密"。XOR混淆只防静态分析,运行时内存全是明文。知道方法名,Java反射一把过,不用啃ARM64指令。
|
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
x
|