本文章主要介绍ollvm的间接混淆,以及如何去除ollvm的间接混淆,。因样本涉及一些私密的东西,就不方便公开了,接下来介绍的一些去除混淆思路是通用的,大可不用担心。
分析架构所属:arm64
接下来主要介绍间接混淆里的间接跳转(Indirect Branch)和间接函数调用(Indirect Call),这两种混淆方式也是最常见的。
传统的程序跳转通常是直接的,例如B 0x168730
(跳转到一个固定的地址),而间接跳转则不同,它不直接指定目标地址,而是通过一个寄存器或内存中的值来确定跳转目标。比如下面的这张图:

在伪代码窗口看到的类似__asm { BR X8 }
这种代码,在汇编里的表现形式则是BR X8
,当出现这种的代码表现形式,就证明此函数采用了间接跳转混淆。
静态分析失效:逆向工具在遇到间接跳转时,在不执行代码的情况下无法确定代码分支跳转的可能目标,也就意味着自动生成的控制流图会是断裂的或不完整的,严重阻碍对程序逻辑的理解。

比如这种控制流图就是ida无法正常识别需要跳转的代码,就导致这些分支代码未被正确识别进函数体,从而出现这种没有头部链接的情况。
难以识别关键路径:由于真实路径被隐藏,分析人员难以区分正常代码和混淆代码,也无法快速定位程序的核心功能。
复杂性增加:间接跳转通常伴随着复杂的算术和逻辑运算来计算目标地址,这进一步增加了分析的负担。

这种就是需要通过一些逻辑运算得到x8寄存器的值,这个值也就是最后跳转的目标地址。如果人工这样去计算这个地址,会耗费大量时间,效率低下,所以不建议去静态分析计算,而是动态执行的方式去获取到寄存器里的值,最后统一patch目标地址。
正常的函数跳转都是BL 0x888888
(函数地址),而间接函数调用则不同,它不直接指定目标地址,而是通过一个寄存器或内存中的值来确定跳转目标。比如下面的这张图:


在伪代码里看到的是v278 = v277(v50 + 19);
,没有一个明显的函数跳转符号,在汇编可以看到BLR X8
,并且上面也是通过大量逻辑运算得到函数目标地址存储到x8寄存器。当出现这种的代码表现形式,就证明此函数采用了间接函数调用混淆。
当我们知道某一个函数确实是被调用了,hook也确实是走了,此时我们想知道这个函数是在哪里被调用的,此时我们就需要使用快捷键X交叉引用查看,但是这样操作后可能会遇到的问题是提示没有地方调用这个函数。

所以间接函数调用混淆,会导致我们无法清晰知道函数调用逻辑,进而分析函数更困难。
接下来展开对间接混淆相关的去除思路
我会讲几种间接跳转混淆表现形式的去除思路。

这种处理方式是最简单的,可以看出它这里面没有它没有任何条件判断相关的逻辑(CSEL、CSET),只需要计算出目标寄存器X9的值,最后使用keypatch将汇编指令BR X9
ptach成B 0x153AF0
即可。

CSEL指令的出现说明这里是有条件判断的,也就是if ...else...代码。
拆解下这里的汇编指令
首先,它检查 X0 是否大于 0。
如果 X0 > 0,则 X8 的值保持不变。
如果 X0 <= 0,则 X8 的值会被更新为 X9 的值。
用三元表达式可以表示为:
X8 = (X0 > 0) ? X8 : X9;
最后取X8寄存器里的内容作为跳转地址赋予X8.
通过上面的拆解分析继续计算X8和X9的值,通过计算得到X8=0x1eabc0,X9=0x1e9c00

我们先跳转到X8寄存器的地址

后面sub_16AAC0就是0x1eabc0地址里存储的跳转地址
继续跳转到X9寄存器的地址

sub_1651EC就是0x1e9c00地址里存储的跳转地址
最后总结可得patch的汇编指令为

这时可能会有疑问,为什么是选择在CSEL指令和BR指令的值patch,却不直接在CSEL指令和其下一条指令地址patch呢,因为会有一种特殊情况,在CSEL指令和BR指令之间存在真实指令,如果选择第二种方式去patch,那么B指令后面的指令都不会执行,从而导致真实指令缺失,影响程序的分析
这种其实原理是和上面一样的,只是有多个条件判断

这里都是用EQ作为条件判断,当条件满足时选第二个寄存器作为赋值,否则就是第三个寄存器
继续拆解上面的指令
第一个条件判断
第二个条件判断
第三个条件判断
第四个条件判断
第五个条件
用代码表现为
计算方式和上面一样的,下面就是patch后的流图



这里就是一个跳转表混淆,继续拆解
ADRL X13, dword_5CE30
dword_5CE30:这里储存的是跳转偏移量的数组
ADR X14, loc_1505F8
loc_1505F8:跳转目标地址的基地址
LDRSW X15, [X13,X12,LSL#2]
LDRSW: 加载一个32位有符号数
X13: 这是前面加载的跳转表基地址。
X12: 这是索引寄存器。通常,switch 语句的变量值会经过处理后放入 X12。
LSL#2: 这意味着 X12 的值被左移了两位(乘以4)。这非常关键,因为它表明跳转表中的每个条目是4字节长(一个32位值)。这与 LDRSW 加载一个32位有符号数是匹配的。
这条指令的含义是:从地址 (X13 + X12 * 4) 的位置加载一个32位有符号整数到 X15。这个有符号整数就是跳转的偏移量。
ADD X14, X14, X15
计算跳转表基地址+偏移量的结果得到跳转目标地址
拆解分析完,接下来就是去修复跳转表了
点击dword_5CE30查看

这里很正常存储了4个偏移数据,字节格式也是正常转换了的,以4字节显示
如果是遇到了下面的这种,在数据段里都是一个字节显示的,我们需要用D键去转为4个字节为一组的数据

结果就会变成这样的

继续操作
选中这段数据

点击下面的Offset(user-defined)


Base address填写上面分析的跳转目标基地址,点击ok会弹出下面的窗口

点击OK,这里之前的偏移量就变成了跳转目标地址-跳转目标基地址

返回到原来的位置,选中BR X14这一行的X14寄存器,在Edit栏里选中Specify switch idiom...



这样填写后点击OK就可以正常显示了

先看下这里的表现形式,off_1EDEA0是函数表的首地址,里面存储了大量的函数,后面是运算得到索引,根据这个索引去函数表里去除对应的函数地址

我这里以光标所在函数为例,先计算出这里的索引结果
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课