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.
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.
sub_FED1, we see some key things a
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
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
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
_start in the
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.
In the next part, I'll try to cover "Two face" from the