文章中的思路只是个人想法, 并不是最优解, 如有错误还望斧正.
插件代码github: detx https://github.com/EEEEhex/detx
版本: speedmobile_1.45.0.53757.apk中的libtprt.so
文章将分享去除[寄存器间接跳转]与[魔改控制流平坦化]混淆的思路, 并编写去混淆插件代码
libtprt.so中的混淆大体分为三种类型:
以及这三种的穿插混合, 这些混淆要么是获取信息麻烦, 要么是Patch起来麻烦, 总之就是很麻烦
本文将先分享去除[寄存器间接跳转]混淆的思路, 主要是Patch思路
其实就是跳转地址是计算出来的, 如下图所示:
这种混淆就是把原先的逻辑跳转改为了jmp(var2)
其中var2 = mem[var1 (<< num)] + const 这些值其实都是可以确定的, 即:
通过cond设置偏移var1, 然后从跳转表data_1fd630中拿出var1偏移处的值, 然后+/-一个常量就得到真正的跳转地址了
我的思路是静态分析+模拟执行:
例如一次混淆涉及到的如下指令:
具体来说就是:
问: 可以直接模拟执行br之前的全部指令, 不去识别一次混淆涉及到的指令吗?
答: 可以是可以, 但这样会涉及到非混淆的真实指令, 我感觉处理起这种情况来不比去识别混淆指令简单.
假设现在已经知道了两个跳转地址是多少, 怎么去Patch呢?
我们的Patch不能改变了原始的逻辑, 比如说:
问: 可以把"csel x11, x28, x27"改为"b.lt t_addr", 把"br x12"改为"b f_addr"这样patch吗?
答: 不可以, 因为原始逻辑是在csel之后还执行了"0x9cc6c 0x9cc70 0x9cc78"这些指令, 如果从0x9cc64处就改成"b.lt"跳转, 那逻辑就不对了, 原逻辑中br之前执行的指令就少执行了一部分.
.
问: 可以上移指令(因为混淆指令是无效的可以随便覆盖), 然后在末尾插入"b.lt + b"指令吗?
答: 不可以, 比如说0x9cc74处的指令属于混淆指令, 是无效的, 将其改为:
就是把csel ... br中间的指令全部上移覆盖上一个指令, 在末尾多出一个指令的空间, 但这样会出现一个问题, 原逻辑中是:
这样Patch之后就变成了:
条件判断被覆盖了, 原本逻辑是判断的"cmp w12, w23"这样一改变成判断"cmp w12, w15"了
.
那要怎么Patch?我的思路如下:
就是cmp下沉, 将"cmp + b.cc + b"放到一起, 这样就不会因为其他指令的cmp导致条件被覆盖了
问: 这样下沉如果cmp w7, w22中的w7和w22被之前的指令改变了怎么办?
答: 事实证明是不会的, 我一开始的思路是不移动cmp而是在cmp之后保存nzcv标志位到例如w10中, 然后b.cc之前再恢复标志位, 结果发现有没有保存nzcv都一样.
其实这个so中的函数都是在控制流平坦化之上又加了一层寄存器间接跳转, 所以这些cmp指令其实是控制流平坦化的分发指令, 这些值(w7,w22之类的)在进入分发逻辑之前就确定好了, 是不会被改变的.
代码逻辑分为: ①模拟执行 ②信息获取 ③Patch逻辑 三部分
采用unicorn框架, 具体请查看emulate.py中的"Emulator" "FuncEmulate" "DeJmpRegEmulate"三个类, 其实就是给unicorn封装了一层.
修改 条件选择指令 时要根据不同的类型进行修改:
问: 怎么通过代码拿到一次混淆涉及到的全部指令?
答: 我是通过从mlil ssa层面, 因为用ssa的话, 可以很方便的查找一个变量的被写入的语句, 代码中是通过def_site.
比如从jump(x9_2#5)开始, 先拿到x9_2#5的def_site, 比如说是"x9_2#5 = x9_1#4 + 0x3872d170", 然后取出这条语句的等号右边涉及到的变量, 这里是x9_1#4, 然后拿到x9_1#4的def_site, 比如是x9_1#4 = [&data_1dd4c0 + x9#3].q @ mem#1, 然后拿x9#3的def_site, 比如是x9#3 = ϕ(x9#1, x9#2), 最后得到x9#1 = 0, x9#2 = 0x58. 其实用一个递归就解决了:
然后通过mlssa_insn.llils拿到一条mlil指令涉及到的llil指令, llil和汇编指令的地址是基本一一对应的:
实际这样下来可能会缺少指令, 就是那两个设置跳转表偏移量的指令, 比如"mov w27, #0x30"和"mov w28, #0x8"
那么就通过从当前块开始, 向前继块从后往前搜索指令, 先拿到csel/cinc指令的操作寄存器, 然后搜索类型是"mov", 第一个寄存器是条件选择指令操作寄存器的指令
具体逻辑请查看dejmpreg.py
首先要拿到从csel到br之间的所有指令, 当然可以分段获取然后移动构造, 而且分段获取的话还可以应对从后往前跳转的情况(当前混淆中是没有这种情况的), 只是我懒得写了:)
/
/
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
if
(Cond)
jmp(true_addr)
else
jmp(false_addr)
/
/
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
变为了
-
>
/
/
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
if
(Cond)
var1
=
0
;
else
var1
=
1
;
var2
=
data_1fd630[var1];
var3
=
var2
-
0x7218df2
;
jump(var3);
/
/
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
/
/
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
if
(Cond)
jmp(true_addr)
else
jmp(false_addr)
/
/
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
变为了
-
>
/
/
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
if
(Cond)
var1
=
0
;
else
var1
=
1
;
var2
=
data_1fd630[var1];
var3
=
var2
-
0x7218df2
;
jump(var3);
/
/
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
0x9cc60
cmp
w12, w23
....... 改变跳转寄存器x12
0x9cc7c
br x12
0x9cc60
cmp
w12, w23
....... 改变跳转寄存器x12
0x9cc7c
br x12
0x9cc60
cmp
w12, w23
....... ............
0x9cc70
cmp
w12, w15
....... ............
0x9cc78
b.lt 满足条件地址
0x9cc7c
b 不满足条件地址
0x9cc60
cmp
w12, w23
....... ............
0x9cc70
cmp
w12, w15
....... ............
0x9cc78
b.lt 满足条件地址
0x9cc7c
b 不满足条件地址
1.
一次混淆!至少!涉及以下
7
个指令(中间穿插着其他逻辑的指令):
mov w10,
...
mov w11,
...
cmp
w7, w22
...
csel x23, x11, x10, lt
...
ldr x25, [x12, x23]
...
add x7, x25, x13
...
br x7
2.
改为如下:
mov w10,
...
mov w11,
...
nop <
-
cmp
w7, w22 [
cmp
语句要最后统一nop 因为会可能有多个逻辑共用同一个
cmp
]
...
nop <
-
csel x23, x11, x10, lt
...
nop <
-
其他涉及到的指令
...
cmp
w7, w22 <
-
ldr x25, [x12, x23]
b.lt ... <
-
add x7, x25, x13
b ... <
-
br x7
大多只有第一次混淆的时候这些混淆指令会穿插在一起, 之后基本都是ldr
+
add
+
br一个整体了
1.
一次混淆!至少!涉及以下
7
个指令(中间穿插着其他逻辑的指令):
mov w10,
...
mov w11,
...
cmp
w7, w22
...
csel x23, x11, x10, lt
...
ldr x25, [x12, x23]
...
add x7, x25, x13
...
br x7
2.
改为如下:
mov w10,
...
mov w11,
...
nop <
-
cmp
w7, w22 [
cmp
语句要最后统一nop 因为会可能有多个逻辑共用同一个
cmp
]
...
nop <
-
csel x23, x11, x10, lt
...
nop <
-
其他涉及到的指令
...
cmp
w7, w22 <
-
ldr x25, [x12, x23]
b.lt ... <
-
add x7, x25, x13
b ... <
-
br x7
大多只有第一次混淆的时候这些混淆指令会穿插在一起, 之后基本都是ldr
+
add
+
br一个整体了
if
((insn_token[
0
]
=
=
'csinc'
)
and
(index
=
=
1
))
or
((insn_token[
0
]
=
=
'cinc'
)
and
(index
=
=
0
)):
if
value
=
=
'xzr'
:
mov_opcode
=
bv.arch.assemble(f
"mov {cond_set_reg}, #1"
, condition_insn_addr)
else
:
mov_opcode
=
bv.arch.assemble(f
"add {cond_set_reg}, {value}, #1"
, condition_insn_addr)
elif
(insn_token[
0
]
=
=
'csinv'
)
and
(index
=
=
1
):
mov_opcode
=
bv.arch.assemble(f
"mvn {cond_set_reg}, {value}"
, condition_insn_addr)
elif
(insn_token[
0
]
=
=
'sneg'
)
and
(index
=
=
1
):
mov_opcode
=
bv.arch.assemble(f
"neg {cond_set_reg}, {value}"
, condition_insn_addr)
else
:
mov_opcode
=
bv.arch.assemble(f
"mov {cond_set_reg}, {value}"
, condition_insn_addr)
if
((insn_token[
0
]
=
=
'csinc'
)
and
(index
=
=
1
))
or
((insn_token[
0
]
=
=
'cinc'
)
and
(index
=
=
0
)):
if
value
=
=
'xzr'
:
mov_opcode
=
bv.arch.assemble(f
"mov {cond_set_reg}, #1"
, condition_insn_addr)
else
:
mov_opcode
=
bv.arch.assemble(f
"add {cond_set_reg}, {value}, #1"
, condition_insn_addr)
elif
(insn_token[
0
]
=
=
'csinv'
)
and
(index
=
=
1
):
mov_opcode
=
bv.arch.assemble(f
"mvn {cond_set_reg}, {value}"
, condition_insn_addr)
elif
(insn_token[
0
]
=
=
'sneg'
)
and
(index
=
=
1
):
mov_opcode
=
bv.arch.assemble(f
"neg {cond_set_reg}, {value}"
, condition_insn_addr)
else
:
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!