Charles抓包酷安

请求头 X-App-Token 每个链接值都不相同

检查有无加固

jadx 打开 apk 包,看到很多内容都有,能看到源代码,不像是加固的样子,且无加固特征

jadx搜索Token

首先尝试搜索 X-App-Token ,发现可以定位具体函数

请求头中字典 “X-App-Token” 的值为 as,向上可以看到 as 的来历

1
String as = AuthUtils.getAS(this.appContext, this.deviceId);

经过查阅资料,this.appContext 是上下文,暂时不用管它,而 this.deviceId ,从第一感官来看,应该是设备id

选中getAS,查找用例,我们看一下 getAS 函数是做什么用的

发现这个函数在 com.coolapk.market.util.AuthUtils 路径下

发现这是一个so文件加密的 token ,加载的so 库是:libnative-lib.so

ida静态调试

使用 ida 打开 libnative-lib.so ,搜索一下 getAS 这个函数,结果并没有,说明他在JNI_Onload 里改了注册名字。

F5 将汇编代码转为C代码,将里面的内容用 JNIEnv * 方法还原,发现了 RegisterNatives方法,证明了我们的推测。

重新注册那么就会有原型函数 ,我们一个可以尝试搜索一下geta,发现刚好有个getAuthString

分析 getAuthString 函数

里面的代码密密麻麻,我们要找到关键字 return ,慢慢往回推,按 / 可以在此输入注释。

首先要用 JNIEnv * 方法还原

全文的 return 只有一个,那么可以暂时的认为这是个 token

NewStringUTF 的作用是得到C语言char*字符串转换为一个指定编码(v4)Java字符串

简单整理一下思路:

1
2
3
4
token = v55				# token是由v55赋值来的
v55 = v82|v84 # 可能是v82、v84
v84 = 0 # v84 上面值是0
v55 = v82

v82也有初始化操作,后面做了一系列赋值操作 operator = ,追加操作append

所以此时 v82 = v61 + v43 + '0x' + &v85

1
2
3
4
5
v61 = MD5::hexdigest((MD5 *)&v61);					# str做了16进制MD5加密
v43 = ***...(*v4)->GetStringUTFChars)(v4, v57, 0); # v57赋值的
v57 = a4; # a4是Java函数传进来的,联系之前的Java代码,这是 deviceId
v85 = sprintf(&v85, "%x", v40); # 做格式化 %x:16进制
v40 = time(0); # 时间戳

所以此时, v82 = v61 + deviceId + '0x' + 时间戳

下一步就要看 v61 的来历了

我们再来捋一下:

1
2
3
4
5
6
v61 = hex_md5(v58)
v58 = v52
v53 = len(v51)
v52 = v51 = b64(v49)
v49 = v64|v62 # v64代码上面赋值是0 ,所以不可能为0
#接下来就该找v62的来历,找之前,我们可以对 b64_encode 做一下 hook

打印结果为:

1
2
3
4
5
6
token://com.coolapk.market/c67ef5943784d09750dcfbb31020f0ab?9abca55a755d4e91173399f24169906c$e4efc92f-5cfd-33fb-9fbf-7842fb0b7352&com.coolapk.market
# 分段看一下
token://com.coolapk.market/c67ef5943784d09750dcfbb31020f0ab ?
9abca55a755d4e91173399f24169906c $
e4efc92f-5cfd-33fb-9fbf-7842fb0b7352 &
com.coolapk.market

接下来继续找 v62 ,找它初始化的地方,开始往下看,按照上面分段的情况,改一下变量备注

1
v49 = v62 = v65 + v45 +"$" + deviceid + "&" + package_name

对上面的 v49 的多次打印

根据打印结果,我们可以大胆推测一下:

1
2
3
4
5
6
v65 = token://com.coolapk.market/c67ef5943784d09750dcfbb31020f0ab?
v45 = 35593e6d531d096713b46a58e9aa7d84
"$"
deviceid = "e4efc92f-5cfd-33fb-9fbf-7842fb0b7352"
"&"
package_name = 'com.coolapk.market'

现在的目标变成了,分析一下 v45 的来历

1
2
3
4
5
v45 = (_BYTE *)HIDWORD(v70);|v69  # v70 = null
v45 = v68 = md5(v65)
v65 = &s = 10进制的时间戳
v40 = time(0);
sprintf(&s, "%d", v40);

到此,所有的参数全部被搞定

组成token

1
2
3
4
5
6
7
token = v61 + deviceId + '0x' + 时间戳
token = hex_md5(v58) + deviceId + '0x' + 时间戳
token = hex_md5(b64(v62)) + deviceId + '0x' + 时间戳
token = hex_md5(b64(v65 + v45 +"$" + deviceid + "&" + pack_name)) + deviceId + '0x' + 时间戳
token = hex_md5(b64(v65 + v45 +"$" + deviceid + "&" + pack_name)) + deviceId + '0x' + 时间戳
v65 = token://com.coolapk.market/c67ef5943784d09750dcfbb31020f0ab?
v45 = md5(int_timestamp)

附:hook_b64_encode 代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import frida
import sys


def on_message(message, data):
if message['type'] == 'send':
print("[*] {0}".format(message['payload']))
else:
print(message)


hook_native = """
var as_addr = Module.getExportByName("libnative-lib.so", "getAuthString");
var native_addr = parseInt(as_addr) - parseInt('0x66500');
send('native_addr: '+ ptr(native_addr));

//b64_encode_addr
var b64_encode_addr = ptr(native_addr + parseInt('0x31DB8'));
send('b64_encode_addr: '+b64_encode_addr);
Interceptor.attach(b64_encode_addr,
{
onEnter: function(args) {
send('b64_encode ori:' + Memory.readUtf8String(args[0]));
},
onLeave: function(retval) {
//send('retval' + retval);
//do something
//pointer执行完,退出pointer前执行
}
}
);
"""

# 抓取启动后
process = frida.get_usb_device().attach('com.coolapk.market')
script = process.create_script(hook_native)
script.on('message', on_message)
print('[*] Running CTF')
script.load()
sys.stdin.read()