本文章仅仅是参考看雪公开文章,没有详细的算法分析和敏感内容
如有违规可联系我删除,微信 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小时前
被思卓编辑
,原因: