首页
社区
课程
招聘
[原创] 使用BinaryNinja去除libtprt.so的混淆 (二)
发表于: 2024-8-15 15:49 5008

[原创] 使用BinaryNinja去除libtprt.so的混淆 (二)

2024-8-15 15:49
5008

文章中的思路只是个人想法, 并不是最优解, 如有错误还望斧正.
插件代码github: detx a23K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6q4c8f1g2q4K9r3g2^5i4K6u0r3k6r3g2@1P5l9`.`.

版本: speedmobile_1.45.0.53757.apk中的libtprt.so

本文将分享去除[魔改的控制流平坦化]混淆的思路

我们知道标准的控制流平坦化就是把各个basicblock放到了一个switch中, 然后通过改变switch(var)中判断的这个变量var来分发到其他基本块中,
那么当一个case基本块结束, 一定会往var中写入一个新值, 来让下一轮分发运行到另一个case基本块中:

这个逻辑可以抽象为:
deflat
标准的一定包括: entry块(进行分发逻辑之前的第一个块), loopEntry块(分发循环开始的块), 分发块, 真实块(源代码逻辑), Ret块(跳出函数的块), loopEnd块(分发循环结束再次进入loopEntry的块)
.
因为ollvm源代码pass里就是这么写的, 但libtprt修改了细节
.
libtprt里面的平坦化去除了loopEnd块, 且存在很多编译优化的情况, 比如分发块和真实块共用, 真实块共用同一个swtich变量赋值指令, 判断提前等等, 而且在一个函数内有多个控制流平坦化(平行或嵌套):

魔改的控制流平坦化不再是一个完全的switch结构体了, 其可能是以下CFG图:
tx_flatten

但无论怎么改, 怎么编译优化, 平坦化本质上就是通过设置一个值, 然后分发这个值, 然后看这个值会到达哪个真实块:
dispatch
真实块里会重新设置一个值, 然后重新进入分发逻辑, 到达后继的真实块.
.
那么我的思路是:

然后就是正常的去平坦化的思路:

关键点在于: ①怎么获取这分发变量 ②怎么拿到所有写入了分发变量的块 ③怎么拿到真实块的后继值(要设置的分发变量的值)
幸运的是, 这些问题通过BinaryNinja的mlil ssa层面都可以很轻松的解决

这个需要自己获取, 我是通过获取鼠标当前行(鼠标要点击loopEntry块)的if语句的条件中的变量, 比如下面:
get_dispatch_var
用户需要找到分发开始块, 然后获取到x8#2, 同时也能获取到loopEntry

我将写入了分发变量的块称为'赋值块' (注意: 赋值块不一定包含了全部的真实块):
assignbb
怎么获取呢, 可以发现, 在loopEntry块中的if语句上有一个'x8#2 = ϕ(x8#1, x8#2, x8#5, x8#6, x8#9, x8#14)'.
.
这里面的x8#1, x8#5... x8#14都是被写入的分发变量, 可以通过def_site拿到赋值语句, 就是图上的"x8#1 = 0x703c1e1e","x8#6 = 0x6110cf13"等, 赋值语句所在的块就是'赋值块', 用il_basicblock.source_block获取.

怎么拿到一个真实块设置的分发变量的值呢?
如果是没有条件的话, 就赋一个值的那种, 其实通过1.2.2就获取到了, 但如果是有条件的话:

在mlil ssa层面是:
cond
也就是1.2.2获取到的是'x8#9 = ϕ(x8#7, x8#8)'这条语句, 然后通过x8#7/x8#8的def_site一样可以获取到两个后继值, 然后通过'il_basic_block.incoming_edges[0].type == BranchType.TrueBranch '来拿到哪个后继值是满足条件时设置的, 哪个后继值是不满足条件时设置的.

首先通过1.2.1拿到了loopEntry块的地址, 但在从loopEntry开始执行分发逻辑之前, 需要进行分发比较值的初始化, 因为可以看到下图中, cmp语句的寄存器其实在进入分发逻辑之前就赋值好了, 所以在模拟执行分发逻辑前需要进行分发比较值的初始化:
cmp_init
我的做法是拿到分发逻辑之前的所有块, 都当作init块, 然后模拟执行.
当然也可以从llil层面, 拿到if条件中的寄存器被写入的语句, 然后该语句所在的块就是init块(其实应该是这种写法比较合理)

从1.1节里的代码可以看到, 其实在一个函数中是存在多个平坦化的, 可能两个平坦化是平行的, 也可能在一个平坦化的if中又有一个平坦化.
无论是平行还是嵌套, 一样可以通过1.2节的思路去除, 但是会少一个真实块的地址:
nested
如上图中所示, 如果仅通过1.2.2把分发变量里写入值的当作真实块, 就是图上黄框的块.
那么模拟执行的时候, 当执行到'if (x8_19 == 0x70d4e113) break;'时, 就暂停不了了, 因为没有遇到黄框块(因为实际上要遇到蓝框块), 实际上当执行到这里逻辑时, 是要从内层平坦化跳出来, 所以需要把图上蓝框的块(这个块可能是任何块也可能是外层平坦化的分发块)也当作真实块, 这样当模拟执行时遇到此块就会暂停返回.
.
我在代码中并没有自动去搜索出口块, 需要用户输入, 当然也是很好分辨的, 内层平坦化毕竟是一个循环, 所有的出口后继都是loopEntry块, 唯有一个后继不是loopEntry块的, 那就是出口块.

实际上编译优化的情况比较多, 需要特殊分析

当分支判断并不是在真实块中进行的, 而且提前到了循环外怎么办?
ahead_if
可能有人会想, 在mlil ssa层面无非就是多了一层赋值呗, 一层一层往上找一样能找到后继值.
确实是这样没错, 但问题是拿到该块对应的后继块后, 怎么Patch?:
ahead_how_to_patch
如果想把他下沉放到真实块里, 那"cmp + b.cc + b"放哪里?, 况且逻辑上也不能放到真实块里, 比如这个条件判断的是"cmp x1, #0", 如果在对应的真实块执行之前, x1被改变了怎么办, 那逻辑就完成不正确了.
我的思路是:

改为:

什么意思呢, 用伪代码表示就是这样:

原先的判断逻辑不变, 只是把x28用cset设置成了0或1, 不再是后继值了, 然后在真实块里判断x28是0还是1.
问题是这么改真实块还是需要额外容纳三条指令(因为这样改真实块的原指令就不能动了), 那还是放不下, 所以就需要拿分发块(无用块)去patch:
patch_ahead

就是两个真实块的后继值是一样的, 比如:

此时在汇编层面会把这个'x8 = 0x124897684'单独拆出来:
common_cfg
此时通过拿往分发变量里写入值的块当真实块就会少那俩块, 所以1.2.2节说赋值块不一定包含了全部的真实块,此时就需要判断当一个赋值块有多个直接前继时, 它的前继也是真实块.
.
当然还有其他情况, 比如共用cmp, 分发块和真实块合并为一个, 但这些都无伤大雅不影响整体逻辑.

具体逻辑请查看deflat2.py与emulate.py

具体请查看emulate.py中的"Emulator" "FuncEmulate" "DeflatEmulate"三个类, 其实就是给unicorn封装了一层.

具体逻辑都在'def deflat2(bv: BinaryView, func: Function, switch_var_ssa: SSAVariable, extra_real_addr = None, manual_value = None, witch_check = False)'函数中, 比如:

demo

uint32_t var = 0x1234;
while (1)
{
    switch(var)
    {
    case 0x1234:
        //逻辑1....
        var = 0x2345;
        break;
    case 0x2345:
        //逻辑2...
        if (v1 > 7)
            var = 0x3456;
        var = 0x4567;
        break;
    case 0x3456:
        //逻辑N...
        var = 0x4567;
        break;
    //...
    case 0x4567:
        exit(0);
        break;
    }
}
uint32_t var = 0x1234;
while (1)
{
    switch(var)
    {
    case 0x1234:
        //逻辑1....
        var = 0x2345;
        break;
    case 0x2345:
        //逻辑2...
        if (v1 > 7)
            var = 0x3456;
        var = 0x4567;
        break;
    case 0x3456:
        //逻辑N...
        var = 0x4567;
        break;
    //...
    case 0x4567:
        exit(0);
        break;
    }
}
//平行的控制流平坦化 [1]
int32_t x9 = 0x4ba39ac1;
while (true)
{
    if (x9 == 0xde219aba)
        break;
     
    if (x9 == 0x4ba39ac1)
        x9 = 0x2e3a7efe;
     
    if (x9 == 0x2e3a7efe)
    {
        s_2 = s_1;
        x9 = -0x21de6546;
    }
}
 
int32_t x9_1;
//判断提前
if ((arg2 & 1) != 0)
    x9_1 = -0x25a2a29c;
else
    x9_1 = 0x64c05daa;
 
//第二个控制流平坦化 [2]
while (true)
{
    int32_t x9_2 = 0x412e838c;
    while (true)
    {
        if (x9_2 < 0x172ae48c)
        {
            //真实块逻辑....
        }
 
        if (x9_2 == ...)
        {
            x9_2 = x9_1; //判断提前
        }
 
        if (x9_2 >= ...)
        {
            int32_t x9_3 = ...;
            while (true)
            {
                switch (x9_3)
                ...//嵌套的控制流平坦化 [3]
            }
        }
    }
}
//平行的控制流平坦化 [1]
int32_t x9 = 0x4ba39ac1;
while (true)
{
    if (x9 == 0xde219aba)
        break;
     
    if (x9 == 0x4ba39ac1)
        x9 = 0x2e3a7efe;
     
    if (x9 == 0x2e3a7efe)
    {
        s_2 = s_1;
        x9 = -0x21de6546;
    }
}
 
int32_t x9_1;
//判断提前
if ((arg2 & 1) != 0)
    x9_1 = -0x25a2a29c;
else
    x9_1 = 0x64c05daa;
 
//第二个控制流平坦化 [2]
while (true)
{
    int32_t x9_2 = 0x412e838c;
    while (true)
    {
        if (x9_2 < 0x172ae48c)
        {
            //真实块逻辑....
        }
 
        if (x9_2 == ...)
        {
            x9_2 = x9_1; //判断提前
        }
 
        if (x9_2 >= ...)
        {
            int32_t x9_3 = ...;
            while (true)
            {
                switch (x9_3)
                ...//嵌套的控制流平坦化 [3]
            }
        }
    }
}
if (arg3 == arg2)
    x8 = 0x2de2ab44;
else
    x8 = -0x26983ee;
if (arg3 == arg2)
    x8 = 0x2de2ab44;
else
    x8 = -0x26983ee;
mov w20, w1
...
tst w20, #0x1 //相当于cmp了
...
csel w9, w20, w12, ne  {0xda5d5d64}  {0x64c05daa}
...
str w9, [sp, #0xc {var_b4}]
...
cmp wX, w10 //分发逻辑
...
//------真实块开始----
ldr w9, [sp, #0xc {var_b4}] //就是x9 = x20, 值由上面的csel w9, w20确定
b 0xafcdc
//------真实块结束----
mov w20, w1
...
tst w20, #0x1 //相当于cmp了
...
csel w9, w20, w12, ne  {0xda5d5d64}  {0x64c05daa}
...
str w9, [sp, #0xc {var_b4}]
...
cmp wX, w10 //分发逻辑
...
//------真实块开始----

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

最后于 2024-8-15 15:57 被0xEEEE编辑 ,原因: 修改细节
收藏
免费 5
支持
分享
最新回复 (2)
雪    币: 6
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
2
感谢分享
2024-8-16 00:06
0
雪    币: 20
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
3
大佬,QQ截图更新了很实用的功能,什么时候提取更新一下呢
2024-9-12 16:01
0
游客
登录 | 注册 方可回帖
返回