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.