某红薯shield参数逆向分析
样本地址:aHR0cHM6Ly93d3cud2FuZG91amlhLmNvbS9hcHBzLzYyMzM3MzkvaGlzdG9yeV92OTI0MDgxMQ==
初步分析
是否加固
先看一下有没有壳,大厂一般是不会进行加壳的,但还是看一下
也确实发现没加固
抓包分析
直接抓的登录 login 的包,发现参数还挺多的
其中 x-mini-gid, x-mini-s1, x-mini-sig, x-mini-mua, shield 等应该就是加密参数了
本篇文章主要分析的也就是 shield 这个加密参数的生成
加密位置定位
直接 hook NewStringUTF 就能够定位到相关的位置,还是需要把 libmsaoaidsec.so 的检测给过掉
| |
然后就能够得到下面的堆栈信息了
到 jadx 中跳到这个堆栈中进行分析
网上有很多这样的分析了,直接说结论吧:
他会按照这样的顺序执行 Native.initializeNative()=>Native.initialize(this.token)=>Native.intercept(chain, this.cPtr)
unidbg 模拟执行
还是先搭个架子
| |
接着开始调用吧,首先调用 initializeNative 这个方法
然后报错,开始补环境
| |
这个看起来跟日志相关,直接返回即可
接着,报错
| |
直接实例化这个类,将方法的调用传进去
| |
接着报下面的这个错误
| |
这个可以去 jadx 中找到这个类下面的这个属性,直接 frida hook 即可
得到的结果如下: sDeviceId: e1dcca27-a4a5-39da-a2b2-a8329886cb4a,直接去补上
接着,报错
| |
这个,同理进行 hook 可以得到 -319115519,直接补上
接着,就没有报错了,那就接着调用下一个函数,第二个函数中间会传一个字符串进去,不知道传的是什么,还是可以进行 hook 一下
| |
hook 结果如下:
可以看到,传进去的是"main"这个字符串,直接调用
发现环境缺失了,继续
| |
这儿是在获取 SharedPreferences 的持久化存储的数据
| |
接着报错
| |
这里是在获取,读取了哪一个 xml 文件,通过打印知道读取的是 s 这个 xml 文件下的 main_hmac 这个键的值
直接使用 adb 进行获取
直接补吧,值都出来了
| |
接着,又报错了
| |
这个直接补就行了
现在就已经能够得到 cPtr 的值了,继续调用
发现报错
| |
在这里首先需要构造一个请求,request
接着,直接补就行了
接着,报错,补环境
| |
这儿,直接补就行了
然后,继续报错
| |
直接补环境
继续报错
| |
直接补环境不行,还需要在 url 后面添加一个 ?
继续报错
| |
直接补上
接着又报错了
| |
直接补上
继续报错
| |
直接补上
好了,又在接着报错了
| |
直接补就行
| |
接着,他还有报错
| |
那我接着补
| |
但是还是在报错
| |
那就继续接着补
仍在报错
| |
继续补
报错
| |
继续补
| |
报错
| |
继续补
报错
| |
继续补
报错
| |
继续补
报错
| |
还是要继续补啊
| |
仍在报错
| |
接着补
还报错啊
| |
直接补
继续报错
| |
继续补就是
然后就能够得到结果了
算法分析
base64 编码
看一下 XYAAQABAAAAAEAAABTAAAAUzUWEe0xG1IbD9/c+qCLOlKGmTtFa+lG438MeOFeRawXxNDjzedlS52p+7Faz8MjiJx+gKBgEgwfGWaKbbP82ngygrWkkUvuxqUQQShXUDT1nUFV 这段密文是如何生成的
首先还是需要 trace 一份日志吧,这样如果后面分析不出来的时候,可以参照这个日志进行分析
| |
还需要 hook 一下 memcpy,因为很多时候都会用到这个 C 函数
| |
hook 之后,发现将 shield 的这个值分成了两个部分
前面是 XY,接着是剩余的部分的值
前面的这个 XY 是固定的,不用分析,主要还是看后面的这个部分,后面这段值的源地址是 0x123d8140,在 unidbg 中提供了一个 api,traceWrite 可以找到这个地址值的写入
| |
接着发现在 libxyass.so 的 0x4bd60 偏移的这些地方在不断的进行写入
跳转到 0x4bd60,跳转过去之后发现有点像是在做 base64 编码的样子
每次读取 v136 的三个值,然后写入到 v138 四个值
从汇编也能看出,每次读取 X10 寄存器的三个字节数据,然后吸入到 X15 寄存器,写 4 个字节的数据,所以也就是说读取 x10 寄存器,能够得到原始的数据,原始的数据也需要考虑是否是标准的 base64 编码还是魔改的 base64 码表的编码
hook 过后得到的数据如下,这个好像跟刚才我 hook memcpy 的时候好像已经 hook 到这段数据了
尝试去 cyberChef 中看一下这个是否是标准的 base64 编码
发现并没有进行魔改,就是标准的 base64 编码
那接着对这 99 个字节进行分析,溯源
| |
发现在控制台,将这段数据分成了两个部分
前 0x10 个字节和后 0x53 个字节,那先来分析前 0x10 个字节吧
前 0x10 个字节分析
继续 trace,看一下写入
| |
它的结果如下:
第 1~4 字节
跳转到 0x4bc0c,好吧,就是下面这一行 *(_DWORD *)&v157[1] = bswap32((v155 << 16) | 4);
这里就需要看 v155 是什么值了,看一下汇编
这里是在将 w16 的低 16 位写入到 w9 的高 16 位
直接 hook 发现这个值是 0x4
他的计算流程也就是下面这样的
这也就需要看 x16 这个值是从哪儿来的了
看一下 v155 的交叉引用
发现来自于 a4 这个参数,a4 就是当前这个子函数的参数啊
继续找他的交叉引用,发现只有一个,跳转过去
发现参数是 v5,又往上溯源
怕跟错了,继续 hook 一下这个子函数
| |
发现没跟错,继续
然后对 x2 寄存器的数据进行溯源
| |
继续跳转
跳转到 0x4ae64,看不太懂
直接看汇编吧
将 w8 寄存器的值存到 x21 寄存器,但是 w8 寄存器的值又是从 x29 偏移 0xa8 这个地址处进行取的,hook 过去,发现 fp 的地址是 0xe4fff500,偏移 0xa8,地址就变成了 0xe4fff458
从 0xe4fff458 地址处取 32 位的值到 x8 寄存器,也就刚好是 0x00000004 即 0x4,继续 trace
跳转到 0x4a5dc,发现在这儿正好是在将 w9 寄存器的值放进 x29 寄存器偏移 0xa8 的位置啊,而 w9 寄存器的值来源于 x22 寄存器取值
继续 hook,得到
trace 一下 0x123e00c0 地址的来源,发现又来源于 memcpy
继续 trace
跳转到 0x890a0,看看怎么个事儿
跳转到这儿,q0 已经算完了,q0 是上面的 v1 和 v0 的16 字节逐字节异或得到的结果,在 0x8908C 下个断点,看一下 x22 寄存器和 x19 寄存器,x10 寄存器的值是 0x0,那就不管
q1 和 q0 的值只有 0x31 和 0x35 不一样,继续对 0xe4fff130 进行 traceWrite
发现是在 0x80a58 进行写入的,跳转过去,发现 q0=[x8]
直接 hook 一下
| |
得到 x8 的地址为 0x12015c50,对应着在 ida 中的偏移就是 0x15c50,跳转过去
发现 q1就是固定的, .rodata 段的值是只读的
接下来看一下 q0 这个值,他跟 q1 不同的地方也就是 0x35,继续 trace 一下这段数据的生成
| |
trace 结果如下:
跳转到 0x88714
这一堆看着头有点大,直接看 trace 日志吧
0x35 是从 w22 寄存器来的,接着需要找 w22 寄存器的值是在哪儿生成的,w22 寄存器的值是 w24 逻辑右移 18 位得到的
w24 又是 w22 和 w25 异或得到的结果,接着就需要看 w22 和 w25 寄存器的值是从哪儿来的了
首先看 w22 寄存器的值
w22 是 orr(按位或) w22 寄存器和 w24 寄存器的值得到的
首先看这个 0x66 吧,他是 w24 寄存器得到的值,往上看,是取的 "ldrb w24, [x24, x26]" x24=0x7a41ab7 x26=0xa5d5f84 => w24=0x66 这一行,取的地址也就是 0x12017a3b,即偏移 0x17a3b
又是 .rodata 段固定的值,有点怀疑这个 0x35 也是固定的值了,现在 w24 的值得到了,继续看 w22 寄存器的值,看 0xa1b97700 是怎么来的,继续看
0xa1b97700 这个是是从 w22 寄存器与 w23 寄存器按位或的结果,那还是需要看一下 w22 寄存器和 w23 寄存器的值,首先看 0x7700 吧,这个值是从 0xe4ffef9c 读的,trace 一下他的写入
发现偏移是在 0x88164,跳转过去
0x7700 来自于上一次运算的 w23 寄存器的 0x77 逻辑左移 8 位,得到 0x7700,所以需要看一下 0x77 是从哪儿来的,发现是从 0x12017afd 取的,偏移为 0x17afd,跳转看一下
发现又是 .rodata 段只读的,固定的,那接着看 0xa1b90000 这个是从哪儿来的,发现是从 0xe4ffef98 这个地址进行取的,继续 trace 这个地方的写入,我怀疑也是只读的
继续跳转到 0x8813c
0xa1b90000 是将 w24 的取低 8 位,插入到 w22 的 16到23 的位置,即
所以需要找 0xb9 是哪儿来的,0xa1000000 是哪儿来的,继续往上面看,能够发现 0xa1000000 是 0xa1«0x18 位得到的,0xa1 是从 0x12017a3a 取的,是个只读的数据,固定的
继续看 0xb9,他是 0x12017a5e 取的,也是固定的,那所有的都是只读固定的,那 0x35 也是固定的了
第 5 ~8 字节
跳转到 0x4bbfc
这个值就是固定的,不需要分析了
第 9 字节
继续回到之前的位置,跳转到 0x4bc2c 的位置
这里可能是 ida 识别的问题,索引下标数字不对,这儿也是固定的
第 10~13 字节
跳转到 0x4bc34
来自于v126,v126 是反转的 v124 的值,v124 发现来自于 v160,总之跟 v160 有关系,寻找交叉引用
发现 v160 是 v25 的值,v25 又是由调用了 sub_1DD30(&v158, v22, v23) 这个函数的结果值,进去看一下这个函数干了什么
发现这个函数会进行 memcpy,好像是在进行拼接什么东西
去问一下 AI
也就是说 0x53 是拼接数据的长度,刚好后需要拼接一个长度为 0x53 的数据
第 14 ~16 字节
跳转到 0x4bc30,发现跟刚才第 10 到 13 字节数据挨着的
还是需要去寻找 v162 的交叉引用,发现到最后还是会调用 sub_1DD30 这个字符串拼接函数,说明跟上面的 0x53 是一样的,那就继续分析下面的东西,前 0x10 个字节的数据可以归结为都是固定的
后 0x53 个字节分析
RC4 算法
继续回到之前 memcpy 的地方
发现源地址是在 0x123e0060,trace 一下写入
跳转到 0x4b82c 看一下
0x35 是后面这一堆 *v66 ^ *((_DWORD *)&v164 + (unsigned __int8)(v76 + v73) + 2); 算出来的,还是异或,他在这个循环中每次会写入 8 个值,循环条件是 v70
在上面,看日志可以得到循环有 10 轮,每次写入 8 个字节,但是也才 80 字节啊,还有 3 个字节呢?当然往下面看还在写
看这个模式,逐字节异或有点像流密码的方式,或许可能是 rc4 算法
如果是 rc4 算法的话,会有 s 盒的初始化,密钥一些内容
用了一下小风大佬的魔改 unidbg,确实要省事许多,重新 trace 一份日志进行分析
从第二个字节分析一下吧,数据是如何生成的 0x16

最后就跳转到这儿了
而且看到很多从 1 开始进行一直排序的序列,很有可能这儿是在进行 S 盒初始化
跳转到 0x4b694,发现这一块应该就在做 S 盒初始化了
看一下对应的汇编
现在已经是 ai时代了,我只需要判断就行,害,手动看汇编对应伪码的时代貌似已经过去了,扣汇编交给 ai 吧
S 盒就是 X10 寄存器,在这块代码结束的地方断点,这个时候 S 盒应该初始化已经完成了
直接 hook 看一下
| |
看一下 x10 寄存器
这个时候已经初始化完成了,当 S 盒初始化完成之后,就应该密钥参数进行打乱 S 盒的顺序了
继续往下面看,S 盒参与到运算中了
让 ai 同志翻一下
| |
ai 同志好样的,直接能看到 X9 寄存器的值就是 key,key 的长度 13
可以知道 key 就是 std::abort() 了,那么明文呢?有没有魔改呢?有没有魔改看一下就行了,在最开始跳转到 0x4b82c 的地方应该就是 rc4 加密的核心逻辑了,rc4 加密的核心逻辑一般都是明文与密钥流进行异或操作
这里的这个 v66 就是明文了,hook 对应的汇编地址
哟西,明文也找到了,那去 cyberchef 中试一下呢
标准的,不用我操心其他的了,直接还原吧
| |
那接着就需要找这个明文是在哪儿生成的了啊
溯源 RC4 算法明文
明文的前 67 个字节数据
首先还是先看一下这段明文的数据
| |
先 trace 以下,看是在哪儿写入的
| |
发现是通过 memcpy 过来的
| |
继续 trace,发现源地址 0x123dd580
| |
接着 trace, 发现偏移在 0x4b474
去 trace 日志看一下
后面的 9240811 不用管,这个是版本号,固定的,而且版本号后面跟着的类似于 uuid 的东西,就是之前的 sDeviceId,主要看的也就是前面这一堆,跳转到 0x4b474
这发现也就是需要分析 v15 到 v20 的值,看看是在哪儿生成的
首先看 v15 吧
a1 是当前函数的参数,在他调用的时候直接传的 1 进来,是写死的,固定的
v16 这个值就是 sAppId 这个值补码的 16 进制表现形式
正好可以对上
v17 是根据前面的那个 a4 的值(就是前面分析的那个 0x35 和 0x31 异或的那个值)来判断的,如果 a4 有值,赋值 v17 为 2
v18 是 0x7,这个是代表后面要拼接数据的长度,也就是那个版本号的长度
接着的 24,代表这的是 sDeviceId 的长度
最后的 0x10 代表最后拼接的那个不知道的 16 字节长度
所以可以归纳一下了
- 0x01000000 传参是固定的值
- 0x01affaec sAppId 补码的 16 进制表现形式
- 0x02000000 根据 0x35 和 0x31 异或之后的值,直接固定的赋值数据
- 0x07000000 代表后面要拼接版本号的长度
- 0x24000000 代表后面要拼接的sDeviceId 的长度
- 0x10000000 代表后面要拼接的未知的 16 字节的长度
- 39 32 34 30 38 31 31(9240811)代表版本号,可以直接写死
- 65 31 64 63 63 61 32 37 2D 61 34 61 35 2D 33 39 64 61 2D 61 32 62 32 2D 61 38 33 32 39 38 38 36 63 62 34 61(e1dcca27-a4a5-39da-a2b2-a8329886cb4a)代表sDeviceId,也可以直接写死,因为本来传参进来就是写死的嘛
那么,剩下不知道的,也就是最后的这 16 个字节的数据了
明文的后 16 个字节数据
通过 trace 可以发现这 16 个字节的位置
接着就需要找 q0 是在哪儿生成的
q0 是从 0xe4fff438 读出来的,继续找
发现这里是在 4 个字节 4 个字节写写入的,一共写 16 个字节,4 个字节,abcd,有可能是 hash 算法
然后一直向上找
发现是 add 加出来的,更怀疑是 md5 加密了,但是很有可能会进行魔改,先用一下小风的插件去看一下,发现一共有两次 md5
但是明文应该不是这个,看着不像,或许只能得到一部分明文的数据,也就是 sDeviceId+xy-scene,但是可以看到当前的 PC 寄存器保存的地址是 0x8189c,跳转过去
这里好像是在填充 0x80,这儿是在对明文进行处理吗?之前我做过关于 md5 的笔记的,看其他地方好像也是将 md5 的运算过程给拆散了,在 ida 中,他是将 md5 的 64 轮运算拆散了,直接看 ida 的话,感觉不是特别好还原,还是主要依靠 trace 日志进行分析吧
先拿一份标准的 md5 来进行对照吧,看改了哪些地方
| |
第一大轮魔改
先用第一次 md5 的结果再去加密 md5 得到 31403731641e81a8bc2c6b7ca3beb6ee,只不过中间是进行魔改过的 md5
先运行一下,看下状态
这个代码是标准 md5 的原因,可能这个样本的 md5 已经被深度魔改了,T 表,左移位数都搜索不到,那么他的明文一定会在 trace 日志中出现,所以也就是说一定能够搜索到,直接搜索的话效率有点低啊,可以使用正则的方式进行搜索 add.*0x6eb9db71.*=>.*,这样就只会搜索到 4 个,也就对应着在 md5 运算中的 4 大轮运算
直接找第一个,也就能够确定,这里是 4 轮运算中的第一大轮,但是不能确定是否是 16 小轮运算中的第 1 小轮
跳转过去对应着下面的日志
| |
直接跳转到 0x82a08
这一块就很像非线性函数中的 FF 函数了,首先先来看一下 FF 函数,挨个来对应一下吧
| |
观察汇编,发现后面会 br 跳转,发现在另一个地方还会加一个数
(v1 + 436) =>对应着 0x2abdd689
另外加的一个值对应着 0xe9c9b756,谁是 T,谁是 a 呢?
因为 T 一直是固定的,或者说可以在内存中被 dump 出来,它不能够被推断出来,也就是算出来
a 就不一样了,a 在 64 轮运算中既可以参与运算,也可以被算出来,那这样就可以使用正则在日志中进行匹配,从而推断谁是 a,谁是 T 了 add.*=>.*0x2abdd689 发现 0x2abdd689 能够被算出来,所以也就能推出它是 a
所以,就能够推出相关信息了
进行替换之后,发现第一小轮算出来的 b,还是不对,说明有可能 T 常量表给改了,或者常量转换的地方也改了
这一堆算出来的值是 0x5fe654f8,接着搜索,看看后面加的那个 T 表是什么, add.*0x5fe654f8,发现
第一个 T 常量表的值是 0xe9c9b756,ror 右移,在 md5 中是左移,右移 0x1a(26) 位,即左移 6 位,常量转换也改了,更换之后得到的新 b=>0x4c92b537 能够搜到了,说明这一小轮正确了
接着看第 2 小轮,发现 f_old 能够搜到,说明是正确的,f 不能搜到,那么就是在非线性函数运算过后的 f 运算中出了错误,即 f = (f_old + a + self.T[i] + M[g]) & 0xFFFFFFFF 这一块出了问题
在 trace 文件中进行搜索,可以发现新得到的 f 的值是 0x75e93f79
看一下前面哪个流程错误了,0x75e93f79 = 0xd08eaba5(f_old) + 0xa55a93d4,对应到新 f 的运算中,0xa55a93d4 应该就是 a+T[i]+M[g],也就是说前面应该还会对应运算
找到 0xa55a93d4 = 0xa7d96c5c + 0x266582ff +0xd71ba479,其中 0xa7d96c5c 是 M[g]明文,0x266582ff 是上一轮的 a,0xd71ba479 就应该是 T 表里面的数据了,说明第二小轮的 t 表也改了
同时在下面看到 ror 右移 0x13(19)位,说明左移 13 位,常量转换也改了
结果继续搜索,发现新的 b 又搜索不到,很可能 T 表所有的值都被魔改了,直接 trace 能够发现在偏移为 0x94910 的地方是固定的
直接让 ai 写了个 idapython 的脚本
| |
接着就能够得到魔改过后的 T 表了
| |
又让 ai 写了脚本来提取左移的位数
| |
又让 ai 写了一份提取左移位数表的脚本,提取出来的左移位数如下:
| |
但是发现第 15 轮出现错误了,那就需要去日志中进行定位了
f_old 能搜到说明这一轮运算的经过前面的非线性函数的运算是对的,f 不对,说明后面的这一堆更新 f 出现错误了
去日志中看一下
它最后填充的明文是 0x280,这也能理解,在这里只是取的中间状态来进行 md5 加密,而这个样本中 md5 是有多轮的,所以填充出了问题
第二大轮魔改
接着到后面没啥问题,但是到了第 17 轮的时候,又不对了,又来继续看吧
f_old 是对的,能运算出来,f 不对,说明更新 f 的时候出错了,那就要看是 T 表的问题,还是 a 的问题,还是明文的问题,或者说是左移的位数不对,看一下这段汇编
| |
从这儿推出来的 T 表是 0xf6002500,但是我用脚本提出来的 T 表是 0xf61e2562,所以对不上,
接着,又往后面看发现第 20 轮的时候又对不上了,不会又改了 T 表吧,很好,发现 T 表是 0xe20011c9
接着,发现后面的第 30 轮数据对不上了,发现 T 表是 0xe9100000
第三大轮魔改
接着接着,发现后面的第 40 轮数据又对不上了,接着发现第 41,第 42,第 43 轮都有点不对劲,将日志拷贝出来
通过观察日志,发现将第 40 轮和 41 轮左移的位数交换了一下,第 42 轮和第 43 轮左移的位数交换了一下
但是还是不对,接着分析的时候发现,为啥第 43 轮的时候,会用到第 42 轮的 a
后面问了下 ai 同志,ai 同志说这四轮两两交换了,结果最后得到了正确的值了
终于对了
| |
这个魔改的 md5 算法是对了,但是还是需要找到这个魔改的 md5 算法真正的输入
还有,这个毕竟是中间状态的 abcd,它不是初始的 abcd
使用正则进行搜索,比如说 a 是 0x2abdd689,那么他是被算出来的,就可以写正则匹配 add.*=>.*0x2abdd689,就能够搜到了,初始的 a 是 0x10325476,他这个初始的 abcd 应该一开始就有的,或者是从内存中取的,跟 t 表一样,直接在日志中进行搜索
就得到了初始的 abcd,如下:
寻找魔改 md5 的明文
因为 71DBB96E5C6CD9A710C189EFC86E6085 只是一个中间状态,还需要找一下他前面的明文是什么,我发现如果从第一轮的 T 表出去去搜索明文,我竟然搜索出来了 42 个,这我这么看下去,下辈子吧,但是有一点可以肯定的是,他是从构造请求 request 添加的一大段请求头中提取的,从之前的那个 dump 插件也可以零星的看一些出来
先来看一下之前构造 request 请求参数所添加的请求头吧
| |
那这么多,我都需要看吗?这未免对我也太残忍了吧,可以使用排除法,多次跑 unidbg,看谁影响到了结果,如果他影响了结果,说明他应该被当作明文传递到加密算法中,我已经通过多次运算,得到了会影响到结果的几组值了,还有 url 应该也会影响到结果
| |
首先,目前所知道的中间状态的 md5 值,初始的 abcd 值,T 表,左移位数,最后生成的 md5 值,当然,都是魔改的,中间的这个 71DBB96E5C6CD9A710C189EFC86E6085 值的生成应该就是明文经过这个魔改之后的 md5 值生成的,所以,也就相当于从魔改的 md5 值倒推明文
先将这串值进行分组,注意,需要用小端序
这里的 abcd 值是中间算出来的,所以可以使用正则进行搜索 add.*=>.*0x6eb9db71
发现只有一个唉,跳过去
其中的 0x8d651ffa 不知道是啥,我往上面看他的写入,也没发现个啥,看 0xe154bb77
这里的 0x450811A1 就是 T 表的值,发现在上面能够发现明文,2988 是 xy-platform-info 的一部分,继续往上面看
再往上看的时候,发现 T 表还是跟明文相加,803 刚好对应上面明文的一部分
那么会不会 T 表都是跟明文相加的啊,T 表的值很多的啊,64 个,每一个搜索出来还不一定只有一个值
那这里就可以写一个批量提取明文的脚本
好了已经提取出来了,但是这里得到的数据还是不对,无法得到 71DBB96E5C6CD9A710C189EFC86E6085,问了一下 ai
发现中间进行 Hmac,这玩意儿就相当于给 md5 加盐了,那继续问 ai 同志
同时让 ai 写了一个提取 ipad,opad 和 key 的脚本,运行就能得到
那没啥事情了,把数据填进去,发现能得到想要的结果了
| |
运行结果如下: