前言
小弟我最近在研究ollvm平坦化的反混淆,但网上的参考资料大多是x86的反混淆,关于arm的少之又少,现正好手头有个app样本中运用了这种混淆技术,所以拿来练一练手。
app样本:douyin9.9.0
so样本:libcms.so
逆向工具:ida,jadx
观察函数控制流程图(CFG)
所要获取的签名算法位于leviathan方法中,该方法是个native方法,在libcms.so中没有静态关联函数,所以我们需要找到其动态注册的位置。
首先,分析Jni_OnLoad函数,Jni函数的动态注册一般都在这里进行:
关于ollvm的基本原理,大家可以在 https://github.com/obfuscator-llvm/obfuscator 上进行详细了解,在这里不做过多说明。
可以看到 JNI_OnLoad已经被彻底地混淆,通过流程图结构,我们可以初步推断是ollvm的指令平坦,进一步观察发现大部分流程块都会跳转至0x46FE,从而可以得出0x46FE是主分发器所在块,r0寄存器作为保存索引值的寄存器(在这里我将混淆里用于索引到相关块的常量值称为索引值)。
去除一般混淆
仔细观察函数内容,发现其中还包含另外一种混淆结构,如下图所示:
从0x478c的位置PUSH {R0-R3}开始是另一个混淆结构的开始,功能比较简单,分析之后其作用是直接跳转到另一个地址,跳转的位置是到POP {R0-R3}指令的下一个地址,即0x47E4的位置。
去除这中混淆的方式也比较简单,直接把0x478c处修改成到0x47E4的跳转即可,中间的混淆代码可以用nop填充掉,鉴于整个函数有多处这样的混淆,可以写个ida脚本批量处理,通过搜索特征字节码来定位混淆代码的起始与结束位置,例如混淆开始处的opcode为0F B4 78 46 79 46(为了避免错误识别可以多判断几个字节码),结束处的opcode为0F BC。
脚本内容如下:
from idc import *
from idautils import *
from idaapi import *
from keystone import *
ks=Ks(KS_ARCH_ARM,KS_MODE_THUMB)
def ks_disasm(dis_str):
global ks
encoding,count=ks.asm(dis_str)
return encoding
func_start=0x44c8
func_end=0x498c
patch_start = None
patch_end = None
for i in range(func_start, func_end):
#PUSH{R0-R3} or PUSH{R0-R3,R7}
#MOV R0,PC
#MOV R1,PC
if get_bytes(i,6,0) == b'x0fxb4x78x46x79x46' or get_bytes(i,6,0) == b'x8fxb4x78x46x79x46':
patch_start = i
#POP{R0-R3}
if get_bytes(i,2,0) == b'x0fxbc':
if patch_start != None:
patch_end = i + 2
#POP{R7},POP{R0-R3}
if get_bytes(i,4,0) == b'x07xbcx88xbc':
if patch_start != None:
patch_end = i + 4
if nop_start != None and nop_end != None:
for i in range(0, patch_end - patch_start, 2):
patch_byte(nop_start+i,0x00)
patch_byte(nop_start+i+1,0xbf)
dis_str = 'b #{}-{}'.format(patch_end, patch_start)
jmp_addr.append(patch_start)
encoding = ks_disasm(dis_str)
for item in encoding:
print('{}'.format(hex(item)))
for j in range(len(encoding)):
patch_byte(patch_start+j,encoding[j])
patch_start = None
patch_end = None
寻找相关块
准备工作已完成,正式进入我们的主题,还原ollvm的混淆代码——关于如何还原流程平坦化,有三个问题需要解决:
一、找出流程里所有的相关块
二、找出各个相关块之间执行的先后顺序
三、使用跳转指令将各个相关块连接起来
第一个问题,通过以下规律找到差不多所有的相关块:
1、后继是分发器块的,一般是相关块(注意是一般,因为有的次分发器也会跳转至主分发器
2、相关块的前继块中,有且只有当前相关块一个后继块的,也是相关块
Github上有许多二进制分析框架(angr,barg,miasm等等),可以对函数生成CFG(控制流程图),由于barg和miasm对arm-v7a指令的支持不是很完全,有些代码无法反汇编,最终我选用了angr。(github地址