去除控制流平坦化

什么是OLLVM的平坦化

控制流平坦化 是 OLLVM 提供的一种代码混淆技术。它的核心目标是破坏程序原始的控制流结构,使其变得难以阅读和分析,从而增加逆向工程和破解的难度。

OLLVM的平坦化是如何实现的?

原始代码:

1
2
3
4
5
6
7
8
9
int main() {
int a = 10;
if (a > 5) {
printf("a > 5");
} else {
printf("a <= 5");
}
return 0;
}

经过平坦化后:

  1. 创建“分发器”和一个状态变量
    • 引入一个状态变量(例如 state),它决定了下一个要执行哪个基本块。
    • 创建一个循环结构作为分发器,其内部是一个巨大的 switch-case 语句,case 的值就是 state 的值。
  2. 拆分原始基本块
    • 将原始函数中的所有基本块(除了入口块)都提取出来。
    • 为每个基本块分配一个唯一的状态值(例如,原始块 B 对应 state = 2)。
  3. 重写基本块末尾的指令
    • 这是最关键的一步。修改每个基本块的结束指令(如 jmp, brret),使其不再是直接跳转到下一个块,而是更新状态变量 state,然后跳回到分发器循环
    • 例如,原始基本块 A 执行完后应该跳转到块 B。平坦化后,块 A 的末尾被修改为 state = 2; goto dispatcher;
  4. 在分发器中调度
    • 分发器循环不停地检查 state 的值,然后通过 switch-case 跳转到对应的基本块去执行。
    • 执行完的基本块会设置新的 state,然后跳回分发器,分发器再根据新的 state 调度下一个基本块。

平坦化后的伪代码结构:

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
int main() {
int state = 0; // 初始状态
while (1) { // 分发器循环
switch (state) {
case 0: // 初始块,通常做变量初始化
a = 10;
if (a > 5) {
state = 1; // 下一个状态是 “printf("a > 5")” 块
} else {
state = 2; // 下一个状态是 “printf("a <= 5")” 块
}
break;

case 1:
printf("a > 5");
state = 3; // 下一个状态是 “return 0” 块
break;

case 2:
printf("a <= 5");
state = 3; // 下一个状态是 “return 0” 块
break;

case 3:
return 0;
// state 不再更新,循环终止
break;
}
}
}

如何去OLLVM的平坦化

法一

首先我要下载这个deflat这个工具,将py文件和要处理的文件放在同一个目录中

1
2
python deflat.py -f OLLVM-deflat --addr 0x4006F0
python deflat.py -f 需要处理的文件 --addr 起始地址

起始地址用IDA查看,比如main的起始地址。

本题

1
2
3
4
5
6
python deflat.py -f OLLVM-deflat --addr 0x4006F0
python deflat.py -f OLLVM-deflat --addr 0x400870
python deflat.py -f OLLVM-deflat --addr 0x401470
python deflat.py -f OLLVM-deflat --addr 0x4018B0
python deflat.py -f OLLVM-deflat --addr 0x401F50
python deflat.py -f OLLVM-deflat --addr 0x402AC0

让我们来看看效果

未去:

1

2

已去:

1

1

很明显去除后逻辑更加清晰。

去除后再开始解题:

general_inspection验证sudoku的合法性

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
__int64 __fastcall general_inspection(int (*a1)[9])
{
int mm; // [rsp+100h] [rbp-50h]
int k; // [rsp+104h] [rbp-4Ch]
int m; // [rsp+104h] [rbp-4Ch]
int n; // [rsp+104h] [rbp-4Ch]
int ii; // [rsp+104h] [rbp-4Ch]
int jj; // [rsp+104h] [rbp-4Ch]
int kk; // [rsp+104h] [rbp-4Ch]
int j; // [rsp+108h] [rbp-48h]
int i; // [rsp+10Ch] [rbp-44h]
int s[12]; // [rsp+110h] [rbp-40h] BYREF
int (*v12)[9]; // [rsp+140h] [rbp-10h]

v12 = a1;
memset(s, 0, 0x28uLL);
for ( i = 0; i < 9; ++i )
{
for ( j = 0; j < 9; ++j )
{
if ( v12[i][j] )
{
for ( k = 0; k < 10; ++k )
s[k] = 0;
for ( m = 0; m < 9; ++m )
{
if ( v12[i][m] )
{
if ( s[v12[i][m]] )
return 1;
s[v12[i][m]] = 1;
}
}
for ( n = 0; n < 10; ++n )
s[n] = 0;
for ( ii = 0; ii < 9; ++ii )
{
if ( v12[ii][j] )
{
if ( s[v12[ii][j]] )
return 1;
s[v12[ii][j]] = 1;
}
}
for ( jj = 0; jj < 10; ++jj )
s[jj] = 0;
for ( kk = 0; kk < 3; ++kk )
{
for ( mm = 0; mm < 3; ++mm )
{
if ( v12[kk - -3 * (i / 3)][mm - -3 * (j / 3)] )
{
if ( s[v12[3 * (i / 3)][9 * kk + 3 * (j / 3) + mm]] )
return 1;
s[v12[3 * (i / 3)][9 * kk + mm - -3 * (j / 3)]] = 1;
}
}
}
}
}
}
return 0;
}

trace对sudoku进行求解

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
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
void __fastcall trace(__int64 a1, int *a2, int a3)
{
int v3; // eax
int v4; // eax
int v5; // eax
int v6; // eax
int v7; // eax
int v8; // r8d
int v9; // eax
int v10; // eax
int v11; // eax
int v12; // eax
int v13; // [rsp+78h] [rbp-28h]
int v14; // [rsp+7Ch] [rbp-24h]
int v15; // [rsp+80h] [rbp-20h]
int v16; // [rsp+84h] [rbp-1Ch]
int v17; // [rsp+88h] [rbp-18h]

v14 = 0;
v13 = 671940414;
while ( 1 )
{
while ( 1 )
{
while ( 1 )
{
while ( 1 )
{
while ( 1 )
{
while ( 1 )
{
while ( 1 )
{
while ( v13 == -2124394493 )
{
v4 = 338033522;
if ( v17 < 9 )
v4 = -1264962160;
v13 = v4;
}
if ( v13 != -2084617164 )
break;
++a3;
v17 = a2[12 * v14];
v16 = a2[12 * v14 + 1];
v13 = 295419890;
}
if ( v13 != -2069701336 )
break;
v5 = 942378879;
if ( v16 < 9 )
v5 = 1672958513;
v13 = v5;
}
if ( v13 != -1561315505 )
break;
v13 = 2016120547;
}
if ( v13 != -1361654796 )
break;
++v16;
v13 = -2069701336;
}
if ( v13 != -1289862082 )
break;
v13 = -1361654796;
}
if ( v13 != -1264962160 )
break;
v16 = 0;
v13 = -2069701336;
}
if ( v13 == -1246113443 )
break;
switch ( v13 )
{
case -446534017:
v9 = 1764791757;
if ( !a2[12 * v14 + 2] )
v9 = 1923573299;
v13 = v9;
break;
case -264375465:
*(_DWORD *)(36LL * a2[12 * v14] + a1 + 4LL * a2[12 * v14 + 1]) = 0;
++a3;
--v14;
v13 = -446534017;
break;
case -127108152:
a2[12 * v14] = v17;
a2[12 * v14 + 1] = v16;
v7 = findvalue(a1, &a2[12 * v14]);
v8 = 295419890;
*(_DWORD *)(36LL * v17 + a1 + 4LL * v16) = v7;
if ( *(_DWORD *)(36LL * v17 + a1 + 4LL * v16) == -1 )
v8 = 1601744610;
v13 = v8;
break;
case 67917660:
*(_DWORD *)(36LL * a2[12 * v14] + a1 + 4LL * a2[12 * v14 + 1]) = v15;
a2[12 * v14 + 2 + v15] = 1;
--a2[12 * v14 + 2];
v13 = -2084617164;
break;
case 295419890:
++v14;
a3 = a3 - 1146223301 + 1146223300;
v13 = -1289862082;
break;
case 338033522:
v13 = 671940414;
break;
case 376448068:
v17 = 0;
v13 = -2124394493;
break;
case 599244415:
v11 = -2084617164;
if ( v15 < 10 )
v11 = 1332608024;
v13 = v11;
break;
case 671940414:
v3 = -1246113443;
if ( a3 )
v3 = 376448068;
v13 = v3;
break;
case 942378879:
v13 = 1396614849;
break;
case 1332608024:
v12 = -1561315505;
if ( !a2[12 * v14 + 2 + v15] )
v12 = 67917660;
v13 = v12;
break;
case 1396614849:
++v17;
v13 = -2124394493;
break;
case 1601744610:
*(_DWORD *)(36LL * v17 + a1 + 4LL * v16) = 0;
--v14;
v13 = -446534017;
break;
case 1672958513:
v6 = -1289862082;
if ( !*(_DWORD *)(36LL * v17 + a1 + 4LL * v16) )
v6 = -127108152;
v13 = v6;
break;
case 1751405620:
printf(aGameOver);
exit(1);
case 1764791757:
v15 = 1;
v13 = 599244415;
break;
case 1923573299:
v10 = -264375465;
if ( !v14 )
v10 = 1751405620;
v13 = v10;
break;
default:
++v15;
v13 = 599244415;
break;
}
}
free(a2);
}

check1

  • 前半部分与后半部分交换
  • 每对相邻字符交换
  • 位操作和算术变换
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
size_t __fastcall check1(char *a1)
{
size_t result; // rax
char v2; // [rsp+6Eh] [rbp-12h]
char v3; // [rsp+6Fh] [rbp-11h]
int i; // [rsp+70h] [rbp-10h]
int v5; // [rsp+74h] [rbp-Ch]
int j; // [rsp+74h] [rbp-Ch]
int k; // [rsp+74h] [rbp-Ch]

v5 = strlen(a1) >> 1;
for ( i = 0; i < strlen(a1) >> 1; ++i )
{
v3 = a1[v5];
a1[v5] = a1[i];
a1[i] = v3;
++v5;
}
for ( j = 0; j < strlen(a1); j += 2 )
{
v2 = a1[j];
a1[j] = a1[j + 1];
a1[j + 1] = v2;
}
for ( k = 0; ; ++k )
{
result = strlen(a1);
if ( k >= result )
break;
a1[k] = (a1[k] & 0xF3 | ~a1[k] & 0xC) - 20;
}
return result;
}

check3就是个验证函数

check2检查输入字符串是否能正确填充一个数独网格

提取数独

1
2
3
4
5
6
7
8
9
1 0 5 3 2 7 0 0 8
8 0 9 0 5 0 0 2 0
0 7 0 0 1 0 5 0 3
4 9 0 1 0 0 3 0 0
0 1 0 0 7 0 9 0 6
7 0 3 2 9 0 4 8 0
0 6 0 5 4 0 8 0 9
0 0 4 0 0 1 0 3 0
0 2 1 0 3 0 7 0 4

求解数独

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
def is_valid(board, row, col, num):
for j in range(9):
if board[row][j] == num:
return False
for i in range(9):
if board[i][col] == num:
return False
start_row, start_col = 3 * (row // 3), 3 * (col // 3)
for i in range(start_row, start_row + 3):
for j in range(start_col, start_col + 3):
if board[i][j] == num:
return False
return True

def solve(board, steps):
for i in range(9):
for j in range(9):
if board[i][j] == 0:
for num in range(1, 10):
if is_valid(board, i, j, num):
board[i][j] = num
steps.append(num)
if solve(board, steps):
return True
board[i][j] = 0
steps.pop()
return False
return True

# 输入数独(0 表示空格)
sudoku = [
[1, 0, 5, 3, 2, 7, 0, 0, 8],
[8, 0, 9, 0, 5, 0, 0, 2, 0],
[0, 7, 0, 0, 1, 0, 5, 0, 3],
[4, 9, 0, 1, 0, 0, 3, 0, 0],
[0, 1, 0, 0, 7, 0, 9, 0, 6],
[7, 0, 3, 2, 9, 0, 4, 8, 0],
[0, 6, 0, 5, 4, 0, 8, 0, 9],
[0, 0, 4, 0, 0, 1, 0, 3, 0],
[0, 2, 1, 0, 3, 0, 7, 0, 4]
]

steps = []
solve(sudoku, steps)
print(''.join(map(str, steps)))
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
def build_reverse_map():
mp = {}
for orig in range(256):
trans = (orig & 0xF3) | (~orig & 0xC)
enc = (trans - 20) & 0xFF
mp[enc] = orig
return mp

rev_map = build_reverse_map()


def decrypt(ciphertext: str) -> str:
# 转成字节数组(这里输入是字符串形式的数字,需要转成字符列表)
data = list(ciphertext)
n = len(data)


# 密文实际是 ASCII 编码后的字符,而不是数字字符串
raw = [ord(c) for c in ciphertext]

dec_bytes = []
for b in raw:
if b not in rev_map:
raise ValueError(f"无法解码字节: {b}")
dec_bytes.append(rev_map[b])

# 逆 step2: 相邻交换
for j in range(0, n, 2):
if j+1 < n:
dec_bytes[j], dec_bytes[j+1] = dec_bytes[j+1], dec_bytes[j]

# 逆 step1: 半段交换
half = n // 2
for i in range(half):
dec_bytes[i], dec_bytes[i+half] = dec_bytes[i+half], dec_bytes[i]

return bytes(dec_bytes).decode(errors="ignore")


if __name__ == "__main__":
cipher = "4693641762894685722843556137219876255986"
print(decrypt(cipher))
## KDEEIFGKIJ@AFGEJAEF@FDKADFGIJFA@FDE@JG@J

法二

D810去平坦化插件使用

插件

把文件夹和.py复制到IDA目录的plugin下即可

1

效果很好。