首页
社区
课程
招聘
[原创]浅谈某视频Device Register And Activate
发表于: 9小时前 132

[原创]浅谈某视频Device Register And Activate

9小时前
132

本文章仅仅是参考看雪公开文章,没有详细的算法分析和敏感内容
如有违规可联系我删除,微信 loveluoyeye2 邮箱 1992145337@qq.com

俗话说得好,逆向 7 分靠自己,3 分靠他人(大佬),大佬逆向某个新样本的时候,也会去搜一下,了解个大概,心里有底,才会动手分析,直接去分析,说实话心里确实有点慌,不过这次运气好,基本上每个步骤都有参考文章,再加上之前已经完成的 GumTrace 和 GumTool,对这次分析帮助很大,有需要的可以在知识星球搜索《思卓移动安全》了解,我也很好奇到底是如何 Activate,接下来大家跟随我的步法,一起分析

参考文章 https://bbs.kanxue.com/thread-285955.htm
这篇文章介绍了 mssdk_token 的是如何从加密文件中读取并解密的,和这位大佬分析的结果一样,该参数存在于一个复杂的全局变量中,到底有多复杂呢,大家可以看看我使用 Frida 写的读取该参数的代码,这里 0x2BBE30 是全局变量的偏移地址

我照着这位大佬的思路,使用 lldb 的内存读写监控功能,对该全局变量的写入进行监控,发现该全局变量的赋值通过另一个变量的指针直接赋值的,另一个变量指针并不是全局变量,所以使用 lldb 内存监控就无法定位了,在不知道如何定位的时候,大佬们的选择出奇的一致,都是通过 hook memcpy 定位的,我也尝试了该方法,但是由于 App 太大,直接 hook memcpy 就卡死了,因此我开发了 GumTool,通过 lr 寄存器只过滤目标 so 的memcpy 调用,写了 c++ 版本的栈回溯,成功定位到关键函数,这里放出 js 版本的栈回溯,供大家参考

继续分析,加密数据来自 openat 系统调用,通过 RC4 算法,解密出最终数据,其中 RC4 Key 来自 sdi_v2 字符串的 SHA1,SHA1结果继续 MD5,RC4 解密之后是一个 zlib 压缩的结果,解压缩之后即是最终 json 数据

参考文章 https://bbs.kanxue.com/thread-287008.htm
通过步骤一,我们已经了解到,mssdk_token 来自文件,并没有找到最开始的来源,通过上面文章学习,了解到它来自于 mssdk 中的 get_token 请求,和上一个步骤一样,通过 GumTool 成功定位关键函数,函数的输入是已经 zlib 压缩之后的 protobuf 文件,中间经历了标准的 XTea CBC 和标准的 AES CBC 加密得出最终结果,这里重点讲一下 XTea,首先 4字节(protobuf 长度) 拼接 zlib 结果,算出需要填充的大小 pad,也是 XTea 第一个字节的来源,接着通过 (pad + 7) & 3 算出 surplus,通过 surplus 确定将 check_sum 的其中 2 个字节填充到前面,还是后面,我们来看以下几种填充示例

到底是如何填充看 (pad + 7) & 3 的结果

如果是 0 则最前面填充 pad,check_sum 在后面填充,共填充 3 个字节
如果是 1 则最前面填充 pad,接着填充随机数 1 个字节,check_sum 在后面填充,共填充 4 个字节
如果是 2 则最前面填充 pad,check_sum 前面填充,共填充 3 个字节
如果是 3 则最前面填充 pad,接着填充随机数 1 个字节,check_sum 前面填充,共填充 4 个字节

XTea 的长度是与上面的填充方式没有关系,直接是 pad + 8 + 4 (protobuf 长度) + zlib 长度
XTea 的轮数是根据随机地址算出来的,计算方法是 round = 32 + 8 * flag
Xtea 的 IV 是随机地址拼接固定 4 个常量字节 0x27 0x04 0x20 0x20

如何确定 Xtea Key,首先我们需要在 Vmp 中找到 XTea 主线 ADD,然后其中重复出现的4个四字节,除去每次累加的 sum 就是 Key,当前我不是这么找的,在分析的时候直接在另一条 XTea 的支线的前四个字节就是 Key
图片描述

我们通过步骤二已经可以加密 get_token 的请求了,但是 get_token 的结果该如何去解密呢,首先是 AES ECB 解密,Key 和加密的 Key 相同,接着会和一个 64 字节的固定常量数组进行异或,其中固定常量数组开头的 16 个字节就是 AES 加密的 IV,接着就是正常的 XTea CBC 解密,但是有个问题,因为之前在加密的时候我并不知道是标准的 XTea,所以手动还原了,get_token 的返回结果的 XTea 解密也是手动还原的,其中有个 init_key 加密的时候是 key[0],解密的时候就不固定了,Key[0] - key[3] 都有可能,算法我没有再去探究了,直接遍历 4 遍,哪个能解就用哪个
图片描述

分析完 get_token 我们接着分析 report,report 共三个类型,分别是 luckydog_init did-iid-update caijing_initialization,加密流程和 get_token 一致,只是 XTea Key 不同

参考文章 https://bbs.kanxue.com/thread-286273.htm
很简单,数据 gzip 压缩之后,随机 32 个字节进行 sha512,结果拼接固定 64 个字节再次 sha512,其结果的前16个字节是 AES 的 IV,16 - 32 位置的 16 个字节是 AES Key,AES 的输入是对 gzip 进行 sha512 的结果 (64字节) 拼接 gzip,最终结果前 6 个字节固定,接着 32 个字节是之前随机 32 字节,接着就是 AES 加密的结果

当前你已经做完以上 5 个步骤的时候,已经可以顺利 Register And Activate Device
图片描述

function s_16(){
    var so_base = Module.findBaseAddress("目标so");
    console.log("so_base ", so_base);
    var addr_1 = so_base.add(0x2BBE30).readPointer();
    console.log("addr_1 ", addr_1);
    var addr_2 = addr_1.readPointer();
    console.log("addr_2 ", addr_2);
    var addr_3 = addr_2.add(0x78).readPointer();
    console.log("addr_3 ", addr_3);
    var addr_4 = addr_3.add(0x18).readPointer();
    console.log("addr_4 ", addr_4);
    var addr_5 = addr_4.add(0x20).readPointer();
    console.log("addr_5 ", addr_5);
    var addr_6 = addr_5.readPointer();
    console.log("addr_6 ", addr_6);
    var addr_7 = addr_6.add(0x10).readPointer();
    console.log("addr_7 ", addr_7);
    var addr_8 = addr_7.add(0x8).readPointer();
    console.log("addr_8 ", addr_8);
    var addr_9 = addr_8.add(0x20).readPointer();
    console.log("addr_9 ", addr_9);
    var addr_10 = addr_9.add(0x8).readPointer();
    console.log("addr_10 ", addr_10);
    var addr_11 = addr_10.add(0x60).readPointer();
    console.log("addr_11 ", addr_11);
    var addr_12 = addr_11.add(0x10).readPointer();
    console.log("addr_12 ", addr_12);
    console.log("result", addr_12.readCString());
}
function s_16(){
    var so_base = Module.findBaseAddress("目标so");
    console.log("so_base ", so_base);
    var addr_1 = so_base.add(0x2BBE30).readPointer();
    console.log("addr_1 ", addr_1);
    var addr_2 = addr_1.readPointer();
    console.log("addr_2 ", addr_2);
    var addr_3 = addr_2.add(0x78).readPointer();
    console.log("addr_3 ", addr_3);
    var addr_4 = addr_3.add(0x18).readPointer();
    console.log("addr_4 ", addr_4);
    var addr_5 = addr_4.add(0x20).readPointer();
    console.log("addr_5 ", addr_5);
    var addr_6 = addr_5.readPointer();
    console.log("addr_6 ", addr_6);
    var addr_7 = addr_6.add(0x10).readPointer();
    console.log("addr_7 ", addr_7);
    var addr_8 = addr_7.add(0x8).readPointer();
    console.log("addr_8 ", addr_8);
    var addr_9 = addr_8.add(0x20).readPointer();
    console.log("addr_9 ", addr_9);
    var addr_10 = addr_9.add(0x8).readPointer();
    console.log("addr_10 ", addr_10);
    var addr_11 = addr_10.add(0x60).readPointer();
    console.log("addr_11 ", addr_11);
    var addr_12 = addr_11.add(0x10).readPointer();
    console.log("addr_12 ", addr_12);
    console.log("result", addr_12.readCString());
}
function to_hex(num)
{
    return "0x" + num.toString(16);
}
function call_stack(context, so_base){
    var fp = context.fp;
    var lr = fp.add(8).readPointer();
    console.log("address " + to_hex(lr) + " offset " + to_hex(lr - so_base));
 
    var last_fp_1 = fp.readPointer();
    var last_lr_1 = last_fp_1.add(8).readPointer();
    console.log("address " + to_hex(last_lr_1) + " offset " + to_hex(last_lr_1 - so_base));
 
    var last_fp_2 = last_fp_1.readPointer();
    var last_lr_2 = last_fp_2.add(8).readPointer();
    console.log("address " + to_hex(last_lr_2) + " offset " + to_hex(last_lr_2 - so_base));
 
    var last_fp_3 = last_fp_2.readPointer();
    var last_lr_3 = last_fp_3.add(8).readPointer();
    console.log("address " + to_hex(last_lr_3) + " offset " + to_hex(last_lr_3 - so_base));
 
    var last_fp_4 = last_fp_3.readPointer();
    var last_lr_4 = last_fp_4.add(8).readPointer();
    console.log("address " + to_hex(last_lr_4) + " offset " + to_hex(last_lr_4 - so_base));
 
    var last_fp_5 = last_fp_4.readPointer();
    var last_lr_5 = last_fp_5.add(8).readPointer();
    console.log("address " + to_hex(last_lr_5) + " offset " + to_hex(last_lr_5 - so_base));
 
    var last_fp_6 = last_fp_5.readPointer();
    var last_lr_6 = last_fp_6.add(8).readPointer();
    console.log("address " + to_hex(last_lr_6) + " offset " + to_hex(last_lr_6 - so_base));
 
    var last_fp_7 = last_fp_6.readPointer();
    var last_lr_7 = last_fp_7.add(8).readPointer();
    console.log("address " + to_hex(last_lr_7) + " offset " + to_hex(last_lr_7 - so_base));
 
    var last_fp_8 = last_fp_7.readPointer();
    var last_lr_8 = last_fp_8.add(8).readPointer();
    console.log("address " + to_hex(last_lr_8) + " offset " + to_hex(last_lr_8 - so_base));
 
    var last_fp_9 = last_fp_8.readPointer();
    var last_lr_9 = last_fp_9.add(8).readPointer();
    console.log("address " + to_hex(last_lr_9) + " offset " + to_hex(last_lr_9 - so_base));
 
    var last_fp_10 = last_fp_9.readPointer();
    var last_lr_10 = last_fp_10.add(8).readPointer();
    console.log("address " + to_hex(last_lr_10) + " offset " + to_hex(last_lr_10 - so_base));
 
    var last_fp_11 = last_fp_10.readPointer();
    var last_lr_11 = last_fp_11.add(8).readPointer();
    console.log("address " + to_hex(last_lr_11) + " offset " + to_hex(last_lr_11 - so_base));
 
    var last_fp_12 = last_fp_11.readPointer();
    var last_lr_12 = last_fp_12.add(8).readPointer();
    console.log("address " + to_hex(last_lr_12) + " offset " + to_hex(last_lr_12 - so_base));
 
    var last_fp_13 = last_fp_12.readPointer();
    var last_lr_13 = last_fp_13.add(8).readPointer();
    console.log("address " + to_hex(last_lr_13) + " offset " + to_hex(last_lr_13 - so_base));
 
    var last_fp_14 = last_fp_13.readPointer();
    var last_lr_14 = last_fp_14.add(8).readPointer();
    console.log("address " + to_hex(last_lr_14) + " offset " + to_hex(last_lr_14 - so_base));
 
    var last_fp_15 = last_fp_14.readPointer();
    var last_lr_15 = last_fp_15.add(8).readPointer();
    console.log("address " + to_hex(last_lr_15) + " offset " + to_hex(last_lr_15 - so_base));
}
function to_hex(num)
{
    return "0x" + num.toString(16);
}
function call_stack(context, so_base){
    var fp = context.fp;
    var lr = fp.add(8).readPointer();
    console.log("address " + to_hex(lr) + " offset " + to_hex(lr - so_base));
 
    var last_fp_1 = fp.readPointer();
    var last_lr_1 = last_fp_1.add(8).readPointer();
    console.log("address " + to_hex(last_lr_1) + " offset " + to_hex(last_lr_1 - so_base));
 
    var last_fp_2 = last_fp_1.readPointer();
    var last_lr_2 = last_fp_2.add(8).readPointer();
    console.log("address " + to_hex(last_lr_2) + " offset " + to_hex(last_lr_2 - so_base));
 
    var last_fp_3 = last_fp_2.readPointer();
    var last_lr_3 = last_fp_3.add(8).readPointer();

[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

最后于 9小时前 被思卓编辑 ,原因:
收藏
免费 15
支持
分享
最新回复 (8)
雪    币: 27
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
2
1
7小时前
0
雪    币: 236
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
3
1
6小时前
0
雪    币: 1189
活跃值: (71)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
1
6小时前
0
雪    币: 220
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
5
6666666
6小时前
0
雪    币: 117
活跃值: (1516)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
666
5小时前
0
雪    币: 1847
活跃值: (3708)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
666
4小时前
0
雪    币: 10
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
8
tql
4小时前
0
雪    币: 1484
活跃值: (2762)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
9
黄哥6
1小时前
0
游客
登录 | 注册 方可回帖
返回