Unicorn实战(一):去掉libcms.so的花指令
最近学习unicorn,看到一位大佬在AndroidNativeEmu上把 X-Gorgen的计算跑通了,听说这个版本的leviathan函数很复杂,准备动手看看复杂在哪里,一眼就看到了JNI_OnLoad里的花指令。抱着学习unicorn的目的,本文记录下如何使用unicorn去掉libcms.so的花指令。
本文的涉及到2个文件,libcms.so请从4.0.0版本的抖音app里解压出来,unflower_cms.py 位于该gist下:https://gist.github.com/LeadroyaL/80a5f6fbb83ee1c102c860aaf2bc594d
一、初步分析
随手下载了4.0.0的抖音,混淆还在,leviathan函数也在,本文就用它来演示。
先看两段汇编,demo1和demo2,都是PUSH起手,连续操作2个和PC相关的MOV,之后进行简单的运算和跳转,最后POP收尾,根据预期,花指令运行时不大依赖上下文,非常适合unicorn模拟执行。
dmeo1
1 | .text:0007862A LDR R0, =loc_5D45C |
demo2
.text:000786B6 PUSH {R0-R3,R7}
.text:000786B8 MOV R0, PC
.text:000786BA MOV R1, PC
.text:000786BC SUBS R2, R1, R0
.text:000786BE SUBS R3, R2, #2
.text:000786C0 CMP R3, #0
.text:000786C2 BGT loc_786E8
.text:000786C4 B loc_786C6
.text:000786CE PUSH {R1}
.text:000786E4 POP {R1}
.text:000786E6 B loc_7870A
.text:0007870A POP {R0-R2}
.text:0007870C POP {R3,R7}
二、整体思路:从PUSH进入,向后执行,尝试找到栈平衡的时刻,按照预期,这个时刻寄存器的内容与开始时是完全相同的。
三、详细思路
1、使用python3和相关组件: unicorn keystone-engine(这个不要用pip装) capstone pyelftools
2、使用pyelftools找到连接时视图,即节表,根据节表找到.text的范围。使用keystone反汇编.text节的内容,找到我们需要的格式,这里有个坑点,keystone反汇编是存在范围限制的,因此每次只反1000字节,防止超出部分被截断,而且往前稍微扫一点,防止寻找的内容被拆到两次扫描中。
1 | # 找到.text节 |
3、使用pyelftools找到运行时视图,即段表。根据段表将library正确加载到内存中。这里的坑点在于内存要对齐,否则无法进行mmap操作。
2019年9月15日13:56:16更新:以及另一个坑点,p_flags和uc_prot是不一致的,不能直接使用。
1 | typedef enum uc_prot { |
1 | # 加载 so 到内存中 |
4、使用unicorn进行模拟执行,根据寻找到的特征,找到对应的真实代码的位置。这里判断的原则有两个,一是栈平衡,二是被保护的寄存器被完整地还原。这里也有一个坑点,在PUSH指令执行时,也会满足栈平衡的条件,会导致程序刚执行第一句就退出了,需要额外判断一下。
1 | # 依次进入所有的entry,执行到栈平衡时退出 |
5、使用capstone和ida的API,将PUSH语句改写为跳转语句,刚好会覆盖掉原有的PUSH和MOV语句,跳转的目标是POP后栈平衡的那条语句、而非POP语句,刚好也就是我们的stop_addr。
1 | for start, stop in ret: |
完整代码见gist,可直接运行。
四、验证成果
找台root的手机,把libcms.so丢进去,覆盖原先的文件,发现抖音仍然可以正常使用,说明patch的确实没问题!
参考链接:
http://www.unicorn-engine.org/docs/tutorial.html