Nuit Du Hack CTF 2013 : k1986 write-up

I’ve participed to NDH2013 this year and worked on a very interesting binary : k1986. It comes with two files :

aris@kali64:~/ndh2013$ ls -l k1986 license.db 
-rwxr-xr-x 1 aris aris 14984 jun 23 02:07 k1986
-rwx------ 1 aris aris   360 jun 22 22:54 license.db
aris@kali64:~/ndh2013$ file k1986-orig license.db 
k1986-orig: ELF 64-bit LSB executable, x86-64, invalid version (SYSV), for GNU/Linux 2.6.32, 
dynamically linked (uses shared libs), corrupted section header size
license.db: data

It’s starting well, corrupted ELF file. The content of license.db seems encrypted, so my first guess was that it was a DRM server of some kind. It becomes more fun when you try to check what it does:

aris@kali64:~/ndh2013$ objdump -t k1986-orig 
objdump: k1986-orig: File format not recognized
aris@kali64:~/ndh2013$ gdb --quiet ./k1986-orig 
"/home/aris/ndh2013/k1986-orig": not in executable format: Format de fichier non reconnu
(gdb) quit
aris@kali64:~/ndh2013$ nm ./k1986-orig 
nm: ./k1986-orig: File format not recognized
aris@kali64:~/ndh2013$ ldd ./k1986-orig 
	 n'est pas un exécutable dynamique

So all classic tools give up… but that’s ok, let’s gdb-attach the process..

aris@kali64:~/ndh2013$ gdb --quiet -p 23627
Attaching to process 23627
"/home/aris/ndh2013/k1986": not in executable format: Format de fichier non reconnu
(gdb) info reg 
eax            0xfffffe00	-512
ecx            0xffffffff	-1
edx            0x5c4c	23628
ebx            0x18a70700	413599488
esp            0xf9154780	0xf9154780
ebp            0xf91547e8	0xf91547e8
esi            0x0	0
edi            0x18a709d0	413600208
eip            0x18e02e75	0x18e02e75
eflags         0x246	[ PF ZF IF ]
cs             0x33	51
ss             0x2b	43
ds             0x0	0
es             0x0	0
fs             0x0	0
gs             0x0	0
(gdb) x/i $eip
=> 0x18e02e75:	Cannot access memory at address 0x18e02e75
(gdb) x/$ $rip
A syntax error in expression, near `$rip'.

That’s the real fun. No matter what I did to that binary, I never managed to make gdb attach to it in 64 bits mode. I tried to make the binary unreadable (chmod 100) or to patch the missing fields/sections but it did not good. I lost too much time on this one (btw if you managed to make gdb work on this, I’d appreciate a lot).
Time to launch IDA. We discover a dynamically loadable executable that’s linked to libpthread and libc. Since the section header table is broken, IDA was not able to match the PLT and GOT entries with that of the binary. However it was smart enough to see a list of exports. We see very classic things for such a binary (open, socket, bind, listen, accept, read, write, close, printf, …) and other more unusual (pthread_mutex_lock). At which point I freaked out it would be a very hard to understand multithreaded program, as I had no debugger yet.
When having no symbols, you have to recognize the PLT and GOT by yourself. As I couldn’t simply gdb into the process to make it do the symbol resolutions for me, I took another approach and used the RTLD capabilities of rewriting symbols:

$ cat intercept.c

int bind(){
  void **i;
  i = &i;
  printf("bind: %p\n",i[2]);

$ gcc -fPIC -shared -o intercept.c && LD_PRELOAD=./ ./k1986
bind: xxxxxx


This gave me the return addresses of many libc functions, and I was able to rename the PLT entries according to them. from that moment, reversing the binary was piece of cake, if we except the many anti-debugger patterns found into the code. I only removed the patterns at the places that made the local variables resolutions hard for IDA when it was important to me. Go ahead, 0x90 all that crap !


Now I wanted to understand how the binary worked and what really happened with the multithreading. Details apart, there’s only one lock and its only purpose is to avoid fork(). Now, I found a very interesting snippet: In function compare_input_3 (which was, as you’re going to see, something else), we can see that the data we give in goes in many loops. The most interesting thing is that we initialize a local buffer of 256 bytes with values 00, 01, 02, 03, … If you are familiar with cryptography, you’d recognize it’s the first step of an RC4 key setup. The key is being set in an other function and is statically set at 0x4023ae : “90 3F 8E 7F 8A”. Let’s give it a try:


>>> rc4.WikipediaARC4("903F8E7F8A".decode("hex")).crypt("\x00"*256).encode("hex")

So everything you send is going to be XOR’ed with this value. Now, let’s see what’s happening with that data when it’s decrypted:
We can see that for every byte that is received, something like this happens:

input[0] + input[0] == 0x9c
input[1] << 2 == 0x10
input[2] << 3 == 0x40
input[3] << 4 == 0xa0
value =  atoi (&input[4])
input[6] << 3 == 0xd0
read_license (value, &input[7]);

I noticed the license key was encrypted, and also that read_license was doing operations with malloc/free. I though there was a cheap occasion to avoid using the debugger and instrumenting one of the functions by myself:

$ cat intercept.c

int (*fct)(int, char *);
char buffer[256];
char buffer2[256];
int socket(){
	fct = (void *)0x401cf2;
int free(char *ptr){
	if(ptr == 0)
	printf("free %s\n",ptr);
char *malloc(int s){
	static int a = 0;
	if (a)
		return buffer2;
	return buffer;

aris@kali64:~/ndh2013$ LD_PRELOAD=./ ./k1986
JzBq Fut'Dt%yMzIqEq&'us+'+xN{H,$vN+"!CrK{H.'.O*|vFsIqr#wwCvsFO~)K)J/(MtuDN|LvE}H~xH+

I found an easy way to dump the content of the key file. The two ciphers code on the left must be the value parsed by atoi, and the one on the right must be the right value. Doing so tentatives shows that the server is going to tell you if your encrypted input matches the begin of the token from the list. The exploit follows. (also on pastebin with a better presentation).

# Exploit for k1984
# Aris Adamantiadis (les pas contents)
# unfortunately coded a few hours after the CTF was over :(
# aris@kali64:~/ndh2013$ python 
# found 05:8efc22fcc45fc5901f1bbce521f29bc1
# found 06:98adbaaef36e718f479db3b8dad331c9
# found 13:7da8b66f82aeba067e33859583c4153f
# found 17:083d5f3bcd7c0b39e473844f1326decf
# found 20:3856bd0cbb94460c113259b0b83d9049
# found 35:167f0dbb43c6430cd2d3b4e8f79dd769
# found 46:840c653d087e8e1821b1903f0981ae2d
# found 52:3f45067f05fb180b8f0014a23648d677
# found 64:e17cc98f772d417a3ce261df512c2ab4
# found 99:2385ba276005a5e2098c0acb9bdf8f07

import socket

crypted = "8f d9 4d 70 a9 ce 04 bb 7b a9 7f dd 63 2d 23 8e" + \
"52 bc dc 0b ab 8b d9 f0 f7 05 5e 60 84 e7 63 47" + \
"fe c2 ce 99 10 c7 aa cc ac 65 b2 c8 f8 c3 6e e0" + \
"d9 cd aa a3 f6 57 17 31 52 a6 58 0b 46 8f 91 e9" + \
"11 20 c1 38 4e c4 21 0c 56 4c 77 32 e6 bf 80 bb" + \
"d3 5c cc 9c d8 fc 1d 9e 44 a4 25 a8 5f cb fa 96"
crypted = crypted.replace(" ","").decode("hex")
def xor_strings(xs, ys):
    return "".join(chr(ord(x) ^ ord(y)) for x, y in zip(xs, ys))

offset = 65

def try_pass(offset, string):
	payload = chr(0x9C /2) + chr(0x10/4) + chr(0x40/8) + chr(0xa0 / 16) +\
		chr(ord('0') + offset/10) + chr(ord('0') + offset % 10) + chr(0xd0 / 8) +\
		string + "\x00"
	x = s.recv(256)
	#print "recv:" + x
	if(x.find("True")!= -1):
		return True
		return False

for offset in xrange(100):
	string = ""
	for i in xrange(32):
		if (i>0 and len(string)==0):
		for c in xrange(16):
			x = try_pass(offset, string + "%x"%c)
			if x:
				string += "%x"%c
				#print string
	if(len(string) > 0):
		print "found %.2d:"%offset + string

Notes on debugger: while the write-up lets think I did not use a debugger, I extensively used EDB. Its main pros are : “not GDB”. Its main weakness is : “not GDB”. It was unfortunately not very stable and I couldn’t save a session or breakpoints for later use. Also, it sometimes confused .text/.data and refused to set my breakpoint because it thought it was in .data.
Well, still better than opening core files in gdb.
What I did that worked well:
– Identify crypto
– Use LD_PRELOAD to extract symbol names and instrument the print_license() functions
– patching the anti-debugging patterns in IDA (even tough I could automatize it)
What did not:
– Try for hours to make GDB work
– Not doing a POC in python from the very beginning. I lost too much time doing xor operations in an other window and putting the results back in my shell command
– Trying to patch the binary to make it acceptable for GDB, objdump & friends. I still haven’t found why can read the file fine and not bfd tools or IDA.
– Discovering edb for the first time that night. I’m sure I underused the capabilities of edb

Leave a Reply

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