本文仅限于技术讨论,不得用于非法途径,后果自负。
随着混淆技术和虚拟化的发展,我们不可能很方便的去得到我们想要的东西,既然如此,那只能比谁头更铁了,本文列举一下在逆向某一个签名字段中开发者所布下的铜墙铁壁,以及我的头铁方案,本文更多的是分析思路,而不是解决方案,所以样本自己找
在ARM64 中 寄存器跳转只剩下BR 指令了,由于ida 为了 br的准确性(cs:ip跳转),ida会识别成函数跳转,但是这却给开发者带来了天大的便利,于是乎,一个贼好用的anti 反编译器函数分析方案横空出世

让我们回到汇编
可以看到 X1的值是由sub_8D1F8 返回值加上 0x38 ,而sub_8D1F8一看地址 不对啊,为什么这么近。我们再看看sub_8D1F8的汇编

如果经常看汇编的小伙伴已经懂了
sub_8D1F8的返回值 被x30 也就是lr寄存器赋值 所以 X1 = sub_8D1F8返回地址 +0x38 = 0x8D1EC + 0x38 = 0x8d224

既然如此 直接改跳转吧,写个脚本模式匹配下
修复后

可以看到一排奇怪的东西

正常程序怎么会有呢,看看汇编
MSR NZCV, X0 x0 = 0 所以zf位为0 所以B.NE 恒成立 所以啥事没干 所以可以直接nop
简单的看下逻辑

0x8d240 在这个函数内 所以不是代码自解码 就是校验

查看sub_13C704

那就是检验咯 不管 下一个函数 sub_13DC3C
修复后发现f5 没东西

这怎么可能 切换到汇编,研究后
var_38 = x29 x29指向存放 x29 x30的栈地址 所以STR X9, [X10,#8] 等价于 x30 = x9 x0 是参数 所以回上一个函数

去看看吧 sub_8d5c8

这里简单介绍什么是控制流平坦化
源代码
经过平坦化后
可以看到一个 while switch 的结构 其中flowState 负责控制整个流程 这个就是平坦化的基本原理 可以看到8行普通的代码被膨胀到了23行 假如我再平坦化 一次呢 我们会发现随着平坦化的越来越多,肉眼阅读的能力也越来越困难
下面介绍一个我从国外看到的方案 590K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Z5k6i4S2Q4x3X3c8J5j5i4W2K6i4K6u0W2j5$3!0E0i4K6u0r3j5X3I4G2k6#2)9J5c8X3S2W2P5q4)9J5k6s2u0S2P5i4y4Q4x3X3c8E0K9h3y4J5L8$3y4G2k6r3g2Q4x3X3c8S2M7r3W2Q4x3X3c8$3M7#2)9J5k6r3!0T1k6Y4g2K6j5$3q4@1K9h3&6Y4i4K6u0V1j5$3!0E0M7r3W2D9k6i4t1`.
下面画一个简单的cfg图
如果走到某一个节点2 必须经过另外一个节点1 那么 2 被 1 支配 1 是 2的支配节点
通过bitset 可以快速计算出来
0: 0
1: 1
2:0,1
3: 0,1
4:0,1,2
5:0,1,2,3
6:0,1,3
将其反转
可得0 是所有节点的支配节点
有什么用?
仔细看平坦化的代码 可以发现 while 会被所有节点支配 所以 控制流分发快 必定被所有节点支配 由此定位到了分发块
我们来看 如果从0要走到 5 有几种 路径
0125
0135
将cfg填充内容
可以得到 0125 flowState = 1, 0124 flowState = 2 那么如果计算支配节点的所有路径 是不是可以得到所有 flowState 的状态
通过switch 可以轻松得到 状态指向的地址
将每一个状态所对应的 地址 连接
检查每一个 没有前继节点的节点 删除 反编译器自动优化
假设 0节点 flowState = 0 1 节点flowState参与 2节点中 flowState = 1 那么 flowState 1节点 将被删除 反编译器自动优化
接下来回到这个demo
将他转换成cfg

计算支配节点 为0
0:0 flowState =0
1:01 flowState=0
2:02 flowState =3
3:04 flowState =4
4:04 flowState =0
5:05 flowState =0
6: 016 flowState =1
7: 017 flowState =2
0 => 1
1 => 2
2 => 3
3 => 4
4 -> 5


这就是简单的ollvm 还原方案
下面进入正题

计算支配节点 为 0x8C1EC
dfs 算法
符号执行 angr
!!! 熟悉的switch呢 这么变成bge了

这就是非标准的ollvm了 简单概括就是控制节点下发

通过if else if else 取代了switch 结构 干扰了状态指向计算 并且带来了更多的复杂性,使其无法定位真实代码块
通过研究 发现 开发者定位真实代码块的方案为

bne 跳转 等于值跳转
于是乎 模式匹配 找出所有cmp w8,value ? reg bne loc_???的代码 拿到真实块
这里又又又有幺蛾子


发现编译器优化 flowState 更新状态的代码 被优化成1份,类似于
有点类似于
优化后
这种只能想办法给他还原回去

ida 自动优化


由于编译器优化 你永远想不到会有多少幺蛾子 ,所以混淆还原应该还有2个步骤
1.对抗编译器优化 上述
2.魔改ollvm 还原成标准ollvm if elseif else 还原为switch
对这2个感兴趣的可以看看这个 f0cK9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8Y4m8J5L8$3k6K6i4K6u0W2M7$3y4A6i4K6u0W2N6h3&6A6N6Y4u0Q4x3X3g2A6N6q4)9J5c8W2)9%4c8h3N6A6j5h3y4G2i4K6u0r3k6r3!0%4L8X3I4G2j5h3c8Q4x3V1k6i4j5i4c8W2M7X3#2S2M7X3E0A6L8X3N6Q4x3X3c8a6j5X3k6#2M7$3y4S2N6r3W2G2L8W2)9J5c8Y4g2F1k6X3I4S2N6s2c8W2L8W2)9J5k6i4m8V1k6R3`.`.
完结撒花表情❀❀❀ ヾ(≧▽≦*)o
.text:
000000000008D1E8
BL sub_8D1F8
.text:
000000000008D1EC
MOV X1, X0
.text:
000000000008D1F0
ADD X1, X1,
.text:
000000000008D1F4
BR X1
.text:
000000000008D1E8
BL sub_8D1F8
.text:
000000000008D1EC
MOV X1, X0
.text:
000000000008D1F0
ADD X1, X1,
.text:
000000000008D1F4
BR X1
.text:
000000000008D1FC
STP X29, X30, [SP]
.text:
000000000008D200
LDR X0, [SP,
.text:
000000000008D1FC
STP X29, X30, [SP]
.text:
000000000008D200
LDR X0, [SP,
def
antiBR1(start,end):
addr
=
start
while
addr < end :
insn
=
idc.print_insn_mnem(addr)
op0
=
idc.print_operand(addr,
0
)
op1
=
idc.print_operand(addr,
1
)
if
insn
=
=
"BL"
and
(idc.print_insn_mnem(addr
+
0xc
).find(
"BR"
) !
=
-
1
or
idc.print_insn_mnem(addr
+
0x8
).find(
"BR"
) !
=
-
1
):
addr1
=
addr
while
1
:
str
=
get_instruction_at_address(addr1)
if
str
!
=
None
:
if
str
.find(
"ADD"
) !
=
-
1
:
break
addr1
=
addr1
+
4
opeValue
=
idc.get_operand_value(addr1,
2
)
print
(
"find add: %x"
%
opeValue)
code,count
=
ks.asm(
"B "
+
hex
(addr
+
4
+
opeValue),addr)
print
(
hex
(addr))
ida_bytes.patch_bytes(addr, bytes(code))
addr
=
addr
+
4
def
antiBR1(start,end):
addr
=
start
while
addr < end :
insn
=
idc.print_insn_mnem(addr)
op0
=
idc.print_operand(addr,
0
)
op1
=
idc.print_operand(addr,
1
)
if
insn
=
=
"BL"
and
(idc.print_insn_mnem(addr
+
0xc
).find(
"BR"
) !
=
-
1
or
idc.print_insn_mnem(addr
+
0x8
).find(
"BR"
) !
=
-
1
):
addr1
=
addr
while
1
:
str
=
get_instruction_at_address(addr1)
if
str
!
=
None
:
if
str
.find(
"ADD"
) !
=
-
1
:
break
addr1
=
addr1
+
4
opeValue
=
idc.get_operand_value(addr1,
2
)
print
(
"find add: %x"
%
opeValue)
code,count
=
ks.asm(
"B "
+
hex
(addr
+
4
+
opeValue),addr)
print
(
hex
(addr))
ida_bytes.patch_bytes(addr, bytes(code))
addr
=
addr
+
4
.text:
000000000008D53C
loc_8D53C ; CODE XREF: sub_8D170:loc_8D530↑j
.text:
000000000008D53C
MRS X1, NZCV
.text:
000000000008D540
MOV X0, XZR
.text:
000000000008D544
CMP
X0, XZR
.text:
000000000008D548
MSR NZCV, X0
.text:
000000000008D54C
B.NE loc_8D558
.text:
000000000008D550
CLREX
.text:
000000000008D554
BRK
.text:
000000000008D558
.text:
000000000008D558
loc_8D558 ; CODE XREF: sub_8D170
+
3DC
↑j
.text:
000000000008D558
MSR NZCV, X1
.text:
000000000008D53C
loc_8D53C ; CODE XREF: sub_8D170:loc_8D530↑j
.text:
000000000008D53C
MRS X1, NZCV
.text:
000000000008D540
MOV X0, XZR
.text:
000000000008D544
CMP
X0, XZR
.text:
000000000008D548
MSR NZCV, X0
.text:
000000000008D54C
B.NE loc_8D558
.text:
000000000008D550
CLREX
.text:
000000000008D554
BRK
.text:
000000000008D558
.text:
000000000008D558
loc_8D558 ; CODE XREF: sub_8D170
+
3DC
↑j
.text:
000000000008D558
MSR NZCV, X1
.text:
000000000013DCE4
000
MOV X30, X17
.text:
000000000013DCE8
000
SUB SP, SP,
.text:
000000000013DCEC
050
STP X29, X17, [SP,
.text:
000000000013DCF0
050
ADD X29, SP,
.text:
000000000013DCF4
050
STUR X0, [X29,
.text:
000000000013DCF8
050
STUR X1, [X29,
.text:
000000000013DCFC
050
STR
X0, [SP,
.text:
000000000013DCE4
000
MOV X30, X17
[招生]科锐逆向工程师培训(2025年3月11日实地,远程教学同时开班, 第52期)!
最后于 2025-2-15 10:23
被method编辑
,原因: