deobfuscation
记需要反混淆的函数为output=obf-function(input)
。
反混淆的思路,首先标记输入的变量记为input-symbol
,通过Taint跟踪改变量的流向,并提取与该输入变量input-symbol
和输出变量output-symbol
有关的所有表达式exprs
。再将exprs
转化为Arybo
表达式,然后再将其转化为LLVM-IR
指令的形式。最后使用llvm编译器-o3选项对llvm-ir
指令做编译优化,生成可执行文件。
1 使用LIEF解析binary文件
-
提取
binary
文件中PT_LOAD
段,将其加载到模拟器内存中。1
2
3
4
5
6
phdrs
=
binary.segments
for
phdr
in
phdrs:
size
=
phdr.physical_size
vaddr
=
phdr.virtual_address
debug(
'[+] Loading 0x%06x - 0x%06x'
%
(vaddr, vaddr
+
size))
ctx.setConcreteMemoryAreaValue(vaddr, phdr.content)
其中
ctx = TritonContext()
,用于获取一个Triton实例对象,用于动态分析。 -
对
binary
文件中的导入符号
做hook
处理(具体可以看makeRelocation()
),确保模拟执行时能够正常执行所需的API函数,如:printf
,memcpy
,rand
,strtoul
等,即使用python
语法来实现这些API函数。用于后面模拟器执行。
2 模拟执行并提取相关指令。
- 模拟
binary
程序的执行过程,首先使用LIEF
获取pc入口地址(binary.entrypoint
). - 在x86指令长度为16,从入口地址中获取16比特长度的数据,即获取一条指令(
opcodes = ctx.getConcreteMemoryAreaValue(pc, 16)
)。 - 指令执行,调用
ctx.processing
执行指令。 - 使用Taint跟踪与输入变量相关的所有表达式,并将这些表达式提取出来。
-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
def
emulate(ctx, pc):
global
condition
global
totalInstructions
global
totalUniqueInstructions
count
=
0
while
pc:
# Fetch opcodes
opcodes
=
ctx.getConcreteMemoryAreaValue(pc,
16
)
# Create the Triton instruction
instruction
=
Instruction()
##获取pc对应的指令
instruction.setOpcode(opcodes)
instruction.setAddress(pc)
# Process
if
ctx.processing(instruction)
=
=
False
:
debug(
'[-] Instruction not supported: %s'
%
(
str
(instruction)))
break
count
+
=
1
if
pc
in
totalUniqueInstructions:
totalUniqueInstructions[pc]
+
=
1
else
:
totalUniqueInstructions[pc]
=
1
if
instruction.getType()
=
=
OPCODE.X86.HLT:
##执行结束
break
if
ctx.isRegisterSymbolized(ctx.registers.rip)
and
len
(condition)
=
=
0
:
#ctx.isRegisterSymbolized(ctx.registers.rip),判断ip是否跟symbol有关联,如果是,则表示改instruction中ip跟变量symbol有关,即变量symbol的值会改变此处指令中ip的跳转,关于symbol变量的初始化看下面的hookingHandler()。
exprs
=
ctx.sliceExpressions(ctx.getSymbolicRegister(ctx.registers.rip))
#获取与ip有关的所有表达式,并将它保存到全局变量comdition中。
condition.append((instruction.isConditionTaken(), exprs))
##isConditionTaken()判断跳转条件是否成立。
##sliceExpressions(),用于获取与`expr`相关的所有`expressions`。
# Simulate routines
hookingHandler(ctx)
##处理前面hook的函数,并引入输入变量symbol,再对该变量做Taint跟踪,通过Taint跟踪来得到与之相关的所有表达式。
# Next
pc
=
ctx.getConcreteRegisterValue(ctx.registers.rip)
##获取ip值。
debug(
'[+] Instruction executed: %d'
%
(count))
debug(
'[+] Unique instruction executed: %d'
%
(
len
(totalUniqueInstructions)))
debug(
'[+] PC len: %d'
%
(
len
(condition)))
# Used for metric
totalInstructions
+
=
count
return
hookingHandler,从名字可以知道,这个函数用于处理之前被hook的函数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
def
hookingHandler(ctx):
global
condition
global
paths
global
totalFunctions
pc
=
ctx.getConcreteRegisterValue(ctx.registers.rip)
for
rel
in
customRelocation:
if
rel[
2
]
=
=
pc:
# Emulate the routine and the return value
ret_value
=
rel[
1
](ctx)
##executing handler
if
ret_value
is
not
None
:
ctx.concretizeRegister(ctx.registers.rax)
ctx.setConcreteRegisterValue(ctx.registers.rax, ret_value)
# Used for metric
totalFunctions
+
=
1
# tigress user input
if
rel[
0
]
=
=
'strtoul'
:
debug(
'[+] Symbolizing the strtoul return'
)
##rax为strtoul返回值,使用convertRegisterToSymbolicVariable()将该返回值转化为用于被Taint跟踪的变量symbol。
var1
=
ctx.convertRegisterToSymbolicVariable(ctx.registers.rax)
#rax 为strtoul的返回值,将返回值转化为变量的形式,ref_156 = SymVar_0。
var0
=
ctx.getSymbolicVariableFromId(
0
)
ctx.setConcreteVariableValue(var0, ctx.getConcreteVariableValue(var1))
rax
=
ctx.getSymbolicRegister(ctx.registers.rax)
ast
=
ctx.getAstContext()
rax.setAst(ast.variable(var0))
# tigress user end-point
if
rel[
0
]
=
=
'printf'
:
debug(
'[+] Slicing end-point user expression'
)
## rsi中保存要被printf()打印出来的output值(以sample/sample4.c为例),getSymbolicRegister(rsi)来判断用于Taint跟踪的输入值symbol是否与rsi有关。
# 如果是,则使用sliceExpressions(rsi)把从symbol过来的,并且与rsi相关的所有表达式expres保存在全局变量paths中。
# 如果有兴趣可以把expres打印出来看看,就知道什么样子了。
if
ctx.getSymbolicRegister(ctx.registers.rsi):
exprs
=
ctx.sliceExpressions(ctx.getSymbolicRegister(ctx.registers.rsi))
paths.append(exprs)
#else:
# ast = ctx.getAstContext()
# n = ctx.newSymbolicExpression(ast.bv(ctx.getConcreteRegisterValue(ctx.registers.rsi), 64))
# exprs = {n.getId() : n}
# paths.append(exprs)
else
:
debug(
'[+] -------------------------------------------------------------- '
)
debug(
'[+] /!\ /!\ /!\ /!\ /!\ /!\ Symbolic lost! /!\ /!\ /!\ /!\ /!\ /!\ '
)
debug(
'[+] -------------------------------------------------------------- '
)
sys.exit(
-
1
)
# Get the return address
ret_addr
=
ctx.getConcreteMemoryValue(MemoryAccess(ctx.getConcreteRegisterValue(ctx.registers.rsp), CPUSIZE.QWORD))
##获取函数调用的返回地址,并赋值给rip。
# Hijack RIP to skip the call
ctx.concretizeRegister(ctx.registers.rip)
ctx.setConcreteRegisterValue(ctx.registers.rip, ret_addr)
# Restore RSP (simulate the ret)
ctx.concretizeRegister(ctx.registers.rsp)
ctx.setConcreteRegisterValue(ctx.registers.rsp, ctx.getConcreteRegisterValue(ctx.registers.rsp)
+
CPUSIZE.QWORD)
return
3 编译生成反混淆程序
###a 这里假设程序只有一条执行路径
即程序不会根据输入值input-symbol
的不同而执行不同的分支。
则此时,全局变量paths[0]
中得到与之相关的所有表达式。
使用下面代码,将所有表达式保存到文件中,其中pathNumber=0
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
使用Arybo开源项目中tritonexprs2arybo()
方法,将exprs
中所所有表达式转化为arybo
表达式;以及使用tritonast2arybo()
获取输入变量input-symbol
。
最后使用to_llvm_function
将其转化为llvm-ir
指令,再recompile将这些ir指令重新编译生成可执行文件。
1 2 3 4 5 6 7 8 9 10 11 12 |
|
流程如下:
1 2 3 4 5 |
|
假设程序执行路径存在分支
流程如下,直接在代码里面解释,这代码里面假设程序只有两条可能的路径会走。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 |
|
4 实验结果对比
结果如图1,图2,图3所示。其中图1为原程序编译后使用ida打开看到的控制流程图;图2为使用相关混淆方法对源程序做混淆处理后编译生成的可执行文件,再使用ida打开看到的控制流程图;图3为使用本文方法对混淆后可执行文件做反混淆处理后得到的新的可执行文件,再使用ida打开看到的控制流程图。虽然反混淆后程序的控制流图跟原程序有所不同,但执行结果是一致的。
[图1] 原程序控制流程图
[图2] 混淆后程序控制流程图
[图3] 反混淆后程序控制流程图
我只是解读搬运工,有兴趣读者可以直接下载该开源项目,点击链接。
当然,该方法仍存在一定程度上的局限性,还无法做到通用。有什么问题可留言。