[Part 1] ACS-IXIA-CTF Write-up

Last weekend I participated at the CTF organized by IXIA and I wanted to showcase some of the challenges I solved that I found particularly interesting. (I think) there will be only 3-4 parts of this with 4-5 challenges. I'll try to present my thinking and all the pitfalls I've ran into.

Let's start with the Independence Day challenge in the Reverse category. Unfortunately I can't show you the prompt of the challenge since they were made unavailable right after the CTF.


Analyzing

We were given a binary, and some host and port to connect to. I've ran the basic reconnaissance on it (strings, strace, ltrace, etc.) to get some basic idea of what the binary is actually doing. Here is the output of ltrace:

We can see that it reads exactly 480 bytes, writes them into a file /tmp/independence_day, and then changes it's permissions. This already gave a lot of information. Notice that it also makes the file executable, so will the program execute it? Let's do some static analysis with IDA to find out.

First I've completely ignored the function sub_F1A, (in the end it didn't matter, I later found out about the wrong direction I took using dynamic analysis), so let's do that as well, for now. We also see some string compares and in the end system('/bin/sh');, but running it we didn't reach it, probably because of the compare, it passes the value to be compared to the function sub_FED1, let's see what it does with it.

Here in sub_FED1, we see some key things a fork, pipe (probably for redirecting output of something), and an execl on our file. So our file is actually being executed on the server. Here my first thought was: "so then I could easily make a bash script and cat the flag with it". Let's try this and see what happens.

#!/bin/bash

cat /home/ctf/flag

Cool! but remember, the binary reads exactly 480 bytes. So we must pad this with \x00. Let's do a python script to do that for us, just in case this doesn't work.

#!/usr/bin/python

ln= 480

with open('script.sh', 'rb') as f:
    data = f.read()
    payload = data
    print payload + '\x00'*(ln - len(payload))

Now let's actually send this payload to the binary.

Nothing happens. Let's analyze what is going on in gdb.

After finding the main address and seeing where it is breaking, we can see that it compares the fifth byte read (which is loadead in eax, right before) with 0x2. Weird. Then all the scripts ideas are dead now. We've ran into a pitfall.

Now comparing it with 0x2 says to us that it must be a binary file. It must be some kind of magic header check.

Magic headers are (from Wikipedia)

file signatures, data used to identify or verify the content of a file. Such signatures are also known as magic numbers or Magic Bytes.

Having a miraculous idea to run xxd on the binary. We can see that it's fifth byte is 0x2. Then it must be an ELF64 that we must create in order for it to run it for us.

Remember in GDB in the sub_FED function, that it reads 128 bytes from our input and tries to compare them with Independence Day in the main function. After that unlinking the file and calling system('/bin/sh').


Developing

Now we know what we have to do. We need a less than 480 bytes, 64 bit binary that should output 'Independence Day'.

Let's write a quick C program that will do that for us and compile it. See how large it is.

Ah! 8296 bytes. That's clearly too much. We can get rid of the stdio library and symbols, and use write, let's see if that helps us.

6120 bytes. with some warning. still works, but it's not enough. We might try to write it in assembly. Let's see.

bits 64

global main
section .data
msg: db 'Independence Day'

section .text
main:
    mov rax, 1      ; for write syscall.
    mov rdi, 1      ; stdout
    mov rsi, msg    ; address of the message
    mov rdx, 0x10   ; length
    syscall

6072 bytes. gcc is really not helping us. Let's try and create our entry point. We can do that compiling with -nostartfiles. We are just creating a _start, so in our case, substituing main with _start in the .asm.

bits 64

global _start
section .data
msg: db 'Independence Day'

section .text
_start:
    mov rax, 1 ; for write syscall.
    mov rdi, 1 ; stdout
    mov rsi, msg ; address of the message
    mov rdx, 0x10 ; length
    syscall

Well, it certainly helped us. Hmm, we could try and link it ourselves. That should really make a difference!

504 bytes. Nice. That's almost what we need, but we get a seg fault, but we don't care since our binary is just reading the output which is correct. Do we really need that much? What's left of it anyway?

We could add the program header and the program offset ourselves so that the linker will not add some other irellevant stuff for us. You can read more about this at this cool article about a really small binary. Let's see.

bits 64

    org    0x00400000      ;Program load offset

;64-bit ELF header
ehdr:
    ;ELF Magic + 2 (64-bit), 1 (LSB), 1 (ELF ver. 1), 0 (ABI ver.)
    db 0x7F, "ELF", 2, 1, 1, 0             ;e_ident

    times 8 db 0                           ;reserved (zeroes)

    dw 2                    ;e_type:       Executable file
    dw 0x3e                 ;e_machine:    AMD64
    dd 1                    ;e_version:    current version
    dq _start               ;e_entry:      program entry address (0x78)
    dq phdr - $$            ;e_phoff       program header offset (0x40)
    dq 0                    ;e_shoff       no section headers
    dd 0                    ;e_flags       no flags
    dw ehdrsize             ;e_ehsize:     ELF header size (0x40)
    dw phdrsize             ;e_phentsize:  program header size (0x38)
    dw 1                    ;e_phnum:      one program header
    dw 0                    ;e_shentsize
    dw 0                    ;e_shnum
    dw 0                    ;e_shstrndx
ehdrsize equ $ - ehdr

;64-bit ELF program header
phdr:
    dd 1                    ;p_type:       loadable segment
    dd 5                    ;p_flags       read and execute
    dq 0                    ;p_offset
    dq $$                   ;p_vaddr:      start of the current section
    dq $$                   ;p_paddr:      
    dq filesize             ;p_filesz
    dq filesize             ;p_memsz
    dq 0x200000             ;p_align:      2^11=200000=11 bit boundaries

phdrsize equ $ - phdr

_start:
    mov rax, 0x1
    mov rdi, 0x1
    mov rsi, msg
    mov rdx, 0x16
    syscall

    msg:    db  'Independence Day'    ;message and newline

filesize equ $ - $$

Nice. Now we could directly assemble it into an executable.

163 bytes!!!!! Amazing. it's waaay better. Let's see if this works on the binary. Don't forget to pad it with our python script.

Nice. We got the shell. This was my last file I sent to the server during the CTF.

FLAG: ACS_IXIA_CTF{star_spangled_banner}

In the next part, I'll try to cover "Two face" from the reverse category.