-
-
[原创][原创]so trace 学习
-
发表于: 5天前 2230
-
一:基本情况
apk运行过程中会多次触发 libdidiwsg.so (arm64)注册的 nativeCollect 函数

当参数为下面字符串时会返回加密后的数据
"epassport.diditaxi.com.cn/passport/login/v5/codeMT"

以学习为目的来尝试解密这些数据, libdidiwsg.so 的代码存在混淆看不了,只能trace
二:IDA trace
IDA 可以附加进程进行trace,但原始APK会多次触发nativeCollect 函数,还有其它干扰,思路是新创建一个新的android 工程p9,用来单独加载这个so,专门调用这个函数。
为了保证 init_array表中的函数、Jni_Onload 函数、以及so通过 CallxxxMethonID系列函数调用 java层函数能够成功执行,需要在p9工程中补全 FindClass查找的各个包和类和类中对应的各个函数。

nativeCollect 接口调用成功后就可以使用IDA附加trace,IDA附加进程后需要挂起其它线程
def suspend_other_thread(): current_thread = idc.get_current_thread() thread_count = idc.get_thread_qty() for i in range(0, thread_count): other_thread = idc.getn_thread(i) if other_thread != current_thread: idc.suspend_thread(other_thread)
IDA 可以正常的 trace ,但速度太慢,只能使用其它 trace 工具
三:unidbg trace
3.1:修改trace格式
需要修改 unidbg trace 的格式,把一些字符串打印出来,比如 strlen 、strstr、strcpy 这个字符串操作函数。
so是arm64,修改对应的 unidbg-master\unidbg-api\src\main\java\com\github\unidbg\arm\ARM.java 文件:

其中的0x3330E0 是对应函数的.plt 地址

3.2:base64 table 定位
trace后共有 430多万行,其中有一个 strlen 函数的参数应该就是获取到的所有信息,后续应该就是对这个信息进行加密

最后一个 strlen 函数就是输出的结果

此时保存结果的内存值为 x0=0x12b3c000,向上查找它的写入来源,发现偏移 0x1d50a0存在可疑访问 :搜索这个偏移,发现这个地址执行了124次,每次的地址都是加4,编码后的数据正好是 124*4=496
而且base64编码每次都是处理4字节,所以可以猜测这里可能就是在进行base64编码

调试后发现每次都是写入4字节数据:

那么,应该可以在相邻执行到偏移 0x1d50a0之间的代码找到base64的table ,两次执行之间的指令共有 73行,直接搜索上面的 0x65、0x56、0x36、0x30 (eV60),很容易定位到偏移 0x1d50c8处的 x17 保存的就是table

3.3:定位编码前的数据
方式1:上图的 x9 就是根据原始数据计算的table 偏移,能够向上找到 偏移 0x1d50b4 处的 x9, 最后就是偏移 0x1d50a4 处的 w9 ,很明显x14 就是原数据
方式2:数据的提取也是每次提取1字节,提取4次,在每一轮的 73行中一定会有原数据的提取...很明显了
对比了两次不同的原数据,发现数据不同,但前面4字节都是不变的,这种情况基本上就是这里的数据是被某种方式给压缩的特定格式

也就是说 压缩后的数据是在 0x12b39480,从下向上找这个地址的引用,最后找到第1次引用的地方是 calloc 的调用

而第4次引用 0x12b39480 的时候,0x12b39480内部已经存在了压缩的数据,第1次和第4次之间只有 572 行,基本上就是复制之类的而不是加密
查看发现确实是:

所以,现在源数据地址又来到了 0x12b39300 , 而 0x174 就是数据长度(数据长度其实也可以作为线索来源)
找 0x12b39300 数据,依旧找到第1次访问的时候,它是被 0x1c0b98 处的 realloc 重新分配的地址,此时前 16 个字节已经是被压缩过的数据
继续找 realloc 时的 x0 地址:在执行到 0x1c0b30 的时候找到了 内存0x126b2560 (第6次执行到 0x1c0b30)

继续找 0x126b2560 内存:
第3次(4299707行)执行到 0x1c0ca4 (memcpy)的时候复制了4字节的头

后面就出现了几次,然后就是很明显的连续出现 0x126b2564 、0x126b2568、... 每次+4,看了下代码,差不多就是在进行复制,所以先不管

先查找上面的 4 字节的头 "57 53 47 00"的来源, 也就是上面的 x1 = 0xe4ffeddc 地址,找到这里:

查看上下文,发现此时函数参数 a11 就是 0x475357 标识

对应的函数是偏移 0x64324,但这个函数被调用了很多次,包括自身嵌套调用,不管了,重新运行,给偏移 0x64324 下断,让它第一次中断在这里,此时 a11 就是标识,查看此时的调用源头,根据trace 判断是它不是通过 BR、BL 调用的,而是从0x64320 执行下来的:

查看附近:


发现一个内存地址:存放 0x475357

其它的数据比如 0xEFE9E5E3 不知道是什么标志
0x64320只被执行了一次,既然标志是在 0x64324 函数传递进来的,那就说明加密的代码是从这个函数开始的
查看这个函数内部,有一个函数是 crc32 计算,都只被运行了1次

crc32 原型:uLong crc32(uLong crc,const Bytef *buf, uInt len);
(第4~第8保存的是 crc32,第0x10~0x14 保存的是 长度 )

buf = 0x12b39180, 长度是 0x160
继续找 0x12b39180 的来源:发现其内存地址来源为 0x12b39000

查找后发现地址来源为:0x12b320e0,继续查找这个地址

最终的结果是这种: crc32 计算的是从 76 D7 50 CA 开始的数据(到末尾),而 0x12b320e0 保存的数据是从 3B 4D B9 F7 到末尾的数据

找到它的来源:0x127da100

这个地址被引用了很多次,找到最前面,可以定位到偏移 0x21f880, x11 是内存的偏移:

0x21f880 被调用了 127 次,每次复制的都是获取到的明文信息,最后数据被保存到 0x127e0000

最后被 realloc 到 0x127e5000:

查找这个内存,发现它在第 418854 行的调用memcpy 访问内存0x127e5000 的时候其已经被压缩了
也就是说在 418449 行~ 418854 行之间被压缩,而第 418816 行调用了 strlen 来获取明文的长度,所以,准确的应该就是在 418449 行~ 418816 行之间被压缩
查看代码,中间确实是在 0x1d0714处调用了压缩函数,但代码是在 419433 行被调用的啊....
compress 原型:int compress(Bytef *dest, uLongf *destLen, const Bytef *source, uLong sourceLen);

不管,继续查找被压缩后的数据去处: 0x127e5380
其被复制到:0x12b32000,跟踪这个地址0x12b32000 ,发现压缩后的数据被复制到了 0x12b320e4,复制后的数据格式为:
0x18a(4字节长度的原始明文长度) + 明文压缩后的数据(长度为0xd4)

跟踪这个地址 0x12b320e4,它没有其它引用,说明它是通过 base+offset 的方式进行访问,所以搜索 0x12b320e0 或 0x12b32000,分析后发现只有 0x12b320e0 符合base条件,但 0x12b320e0 后续没有作为base 去访问,还是通过复制的方式给了内存 0x12b321c0

跟踪 0x12b321c0,出现了122次,很可能就是在做加密,找到了它是在偏移 0x1e4f38 处被索引,搜索 0x1e4f38,共有 224 次,就是:
0x18a(4字节长度的原始明文长度) + 明文压缩后的数据(长度为0xd4) , 刚好就是: 4 + 0xd4 = 224,就是说后续的加密会把 原始明文长度 也作为数据加密

加密1:
查看一下0x1e4f38代码,发现是在上面压缩后的数据进行 xor 处理,每轮处理0x10字节
src=0x12b321c0 (src每轮+0x10)
xor key=0xe4ffed20 (不变)
dst=0x127da100 (dst每轮+0x10)

调试的时候查看了一些加密:发现了一个问题,每轮过后,加密后的数据会再次发生变化,查看了每轮代码量,发现确实每轮过后代码执行量会增加,说明在 加密1 xor 后还有其它加密行为

在 567441~569728 行之间搜索 0x127da100 的引用:
加密2:
xor加密:把加密1的数据的16字节分成4个DWORD 和来自 0x127fb010 的数据进行 xor

xor后的数据分别存放到:
[0xe4ffe720 - 0x58] = 4
[0xe4ffe720 - 0x54] = Result4
[0xe4ffe720 - 0x44] = Result3
[0xe4ffe720 - 0x40] = Result2
[0xe4ffe720 - 0x3C] = Result1
但依旧不是下一轮变成的数据,继续看怎么变化的

往下看 567703 行,取出了上述的 Result1 和 Result2,直接搜索这个0x55e32a7d,发现在 567894行执行了 0x1e34c8

查看 0x1e34c8 处的伪代码,a14 就是上面的 Result1和 Result2的组合:高4字节=Result1,低4字节=Result2 (每次调试数据不同,导致和trace数据不同),这里 Result1 和 Result2 是作为 dword_7AC747447C 的索引

查看 dword_7AC747447C 数据,搜索了一下是 AES 的 T1

T1 附加还有其它数据:
偏移 0x359F54: rcon (密钥扩展使用)
偏移 0x359F7C: S (加密过程使用)
偏移 0x35C07C:S_n (解密过程使用)
也就是说这<加密2>过程属于AES 加密。
查找 aes key: 查找 rcon 的引用,其在 0x1e10b0 函数中被引用,在其中找对应的密钥扩展代码,发现如下特征,可以猜测 a12参数就是 原始aes key,a25(参数 x21+0x10) 保存的就是扩展后的key ,而 (这种可以直接看每个参数,一般都会把 key 以参数的方式进行传递)
找到的key 是16字节,类型就是 AES-128

现在回到上面 <加密2>的的xor 上来,内存0x127fb010 中的 x8 保存的就是刚刚AES扩展后的key,而x11保存的是<加密1> xor的数据

aes加密模式
那么<加密1>和<加密2>的过程就是:
zlib压缩后的数据进行 xor 加密 (xor的key未知,命名为keyX),加密后的数据继续和 扩展后的key进行xor加密
这个加密模式不是 ECB, 因为 ECB 会直接把扩展后的key 和zlib压缩后的数据进行xor,这里不是,那就可能是其它模式了;
其它模式涉及到了 IV, 所以,猜测上面的 keyX 是 IV,直接把 key、keyX(IV)、aes 加密前的数据、加密后的数据拿出来进行加解密测试
key 提取:上面的key扩展过程中可以提取
keyX(IV)提取:偏移 0x1e4f38 处和zlib压缩后的数据进行 xor 加密的时候提取
加密前的数据提取:zlib压缩后的数据
加密后的数据提取:加密函数调用完成后从参数中提取(根据T1、T2定位到加密函数)
直接写脚本解密测试,测试后发现其是 CBC 模式,填补的是某个字节
整体流程:
zlib压缩+AES加密+crc32计算+base64编码
待续
其中 crc32 计算AES 加密的数据时前面添加了 0x80字节:未知
aes的key和iv来源:未知
总结
1:单独提取so,使用android studio自定义p9工程加载调用接口还是挺有必要的,一个是方便调试分析:直接调试p9,而不是原APK,二是为unidbg的模拟执行添加了基础
2:分析加密流程主要还是在日志中查找 数据的来源和去处、长度
[培训]Windows内核深度攻防:从Hook技术到Rootkit实战!
赞赏
- [原创]android jni 日志+参数 打印 185
- [原创][原创]so trace 学习 2231
- [原创]xp 、win7 堆溢出 5888