To search

w3pwnz

w3pwnz, therefore we are

Tag - bruteforce

Entries feed Comments feed

Thursday, October 18 2012 16:05

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

Wednesday, April 6 2011 20:00

NDH2k11 Prequals - Crypto300

La crypto300 est sous la forme d'un ensemble de fichiers python :

crypto300> ls
braid.py   client.py  flag.py   network.py   peer.py   server.py

La première partie de cette épreuve consiste donc à comprendre que font ces fichiers. Heureusement le code est clair et bien écrit.

  • braid.py : Ce fichier est en réalité le coeur de l'épreuve dans lequel toutes les opérations cryptographiques sont faites. Le cryptosystème fonctionne à base de permutations.
  • client.py : Ce fichier contient le code du client (nous) qui va se connecter au serveur.
  • server.py : Ce fichier contient le code du serveur hébergé sur les machines de la ndh, sur lequel on va se connecter.
  • flag.py : Ce fichier contient un fake flag, le saint graal pour nous... celui qui sera envoyé par le serveur, enfin on espère.
  • network.py : Ce fichier contient des classes implémentant l'accès bas niveau au réseau.
  • peer.py : Ce fichier contient des classes implémentant un transfert sécurisé des informations.

Une première exécution du client nous donne le résultat suivant :

crypto300> python client.py
[Crypto300 sample client]
[i] Welcome on Goofyleaks. Can I haz ur public kay ?
[+] Your leaked flag: ##NOT ALLOWED##

Ca commence mal, on a pas le droit d'accéder au flag. Une analyse plus fine du code de network.py et notamment de la fonction run de ServerWorker nous donne un peu plus d'informations sur ce qui se passe réellement :

...
           if str2hex(pubkey) in self.allowed_pubkeys:
                # authenticated ! (public key is allowed)
                enc_data = self.peer.encryptData(GOODBOY_FLAG)
                self.__send(enc_data)
            else:
                # well, public key is not allowed, send error
                enc_data = self.peer.encryptData('##NOT ALLOWED##')
...

Si on a pas la bonne clé publique, on aura pas le bon flag. La bonne clé se trouve dans le fichier server.py :

...
       # bind peer instance to a socket (and set up a single allowed public key)
        self.s = ServerSocket(peer,allowed_pubkeys=['0F0C11040108060B05150E1000090A030D1312140207'])
...

On commence à comprendre un peu plus le problème... On sait quelle est la clé publique, mais il va falloir trouver la clé privée correspondant à cette clé publique.

Une analyse plus détaillée des fichiers nous permet de bien comprendre la communication qui est faite entre le client et le serveur :

CLIENTSERVEUR
Connection
Envoi MOTD
Réception MOTD
Envoi Clé Publique Serveur
Réception Clé Publique Serveur
Envoi Clé Publique Client
Réception clé Publique client
Génération secret partagé avec clé publique serveur Génération secret partagé avec clé publique client
Si bonne cle publique chiffre bon flag flag, sinon chiffre mauvais flag
Envoi flag chiffré
Réception flag chiffré
Déchiffrement flag chiffré



Le secret partagé est en réalité un sha1 obtenu à partir d'opérations sur la clé publique de l'interlocuteur et d'une valeur commune entre le client et le serveur 0D1214040108060F050C0E0207030A151009000B1311 que l'on peut trouver client.py et server.py. Ce sha1 sert comme clé à l'algorithme blowfish pour chiffrer le flag envoyé par le serveur. Bien évidemment, la génération du secret partagé aboutit à la même valeur chez le client et chez le serveur s'ils possèdent les clés privées correspondantes aux clés publiques envoyées. Ici impossible de faire quoique ce soit, la vulnérabilité n'est pas dans le chiffrement du flag, mais dans l'obtention de la clé permettant ce chiffrement.

La génération de la clé privée et de la clé publique chez le client et chez le serveur se font à partir de la même valeur commune citée plus haut. La dérivation se fait dans braid.py dans le constructeur de la classe BraidKey. La clé privée est d'abord initialisée à la valeur 000102030405060708090A0B0C0D0E0F101112131415 (soit 22 octets différents) puis les 11 premiers octets sont mélangés pour le serveur et les 11 derniers pour le client. La clé publique est ensuite obtenue à partir de cette clé privée, à partir de deux méthodes : combine et reverse.

Il y a donc 11! clés privées différentes (11 octets fixes puis 11 octets mélangés) soit 39916800. A la vue des writeup déjà écrits sur cette épreuve, la majorité des teams se sont arrêtés là et ont bruteforcé l'ensemble des 11! clés possibles. Malheureusement mon PC ayant déjà trop chauffé durant la crypto200, nous avons essayé de réduire l'espace de recherche pour simplifier le calcul.

Pour cela il faut bien comprendre les méthodes combine et reverse.

  • combine

La méthode combine mélange un tableau en fonction d'un autre.

Tableau 1

000102030405060708090A0B0C0D0E0F101112131415
1514131211100F0E0D0C0B000102030405060708090A



Tableau 2

000102030405060708090A0B0C0D0E0F101112131415
000102030405060708090A1514131211100F0E0D0C0B



La méthode Tab1.combine(Tab2) donne :

000102030405060708090A0B0C0D0E0F101112131415
0B0C0D0E0F101112131415000102030405060708090A



On prend l'élément 0 de tab1 (ici 0x15) et on prend le 0x15 ème élément de tab2 (donc B), qui deviendra l'élément 0 de Tab1.combine(Tab2).

  • reverse

La méthode reverse retourne un tableau par rapport à ses indices :

Tab1.reverse() donne :

000102030405060708090A0B0C0D0E0F10111213141516
0A09080706050403020100161514131211100F0E0D0C0B



On prend l'élément 0 de tab1 (ici 0x15) et 0x15 ème élément devient donc l'élément 0 de Tab1.reverse().

Maintenant que ces deux méthodes sont bien assimilées regardons comment les éléments s'enchainent pour obtenir la publique en fonction de la clé privé et surtout regardons s'il est possible de reverser le processus.

L'analyse (simplifiée) de braid.py donne ça :

Priv = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21];
Priv.shuffle(offset/2);  # La deuxième partie de Priv est mélangée
PrivR = Priv.reverse();
T = K.combine(PrivR);
Pub = Priv.combine(T);

K est la valeur connue citée plus haut, et T ne se trouve pas dans le programme mais je l'ajoute pour simplifier le calcul. On connait Pub (la clé que l'on doit envoyer au serveur pour qu'il nous envoie le bon flag), on connait K, et on connait la moitié de Priv.

Posons ce que l'on connait :

indice000102030405060708090A0B0C0D0E0F101112131415
PRIV000102030405060708090A???????????
PRIVR??????????????????????
K0D1214040108060F050C0E0207030A151009000B1311
T??????????????????????
PUB0F0C11040108060B05150E1000090A030D1312140207



Dire que l'on ne connait pas pas PrivR est faux. On en connait au moins une partie. Etant donné que PrivR = Priv.reverse(), on connait la première moitié de PrivR, elle est égale à la première moitié de Priv (seulement car les 11 premiers octets de Priv sont 0,1,2,3,4,5,6,7,8,9,10,11). De la même façon, on connait une partie de T car Pub = Priv.combine(T). Donc la première moitié de T est égale à Pub encore une fois seulement car les 11 premiers octets de Priv sont 0,1,2,3,4,5,6,7,8,9,10,11).

On met à jour notre tableau :

indice000102030405060708090A0B0C0D0E0F101112131415
PRIV000102030405060708090A???????????
PRIVR000102030405060708090A???????????
K0D1214040108060F050C0E0207030A151009000B1311
T0F0C11040108060B05150E???????????
PUB0F0C11040108060B05150E1000090A030D1312140207



On sait que T = K.combine(PrivR), donc on peut essayer de voir si certains octets de PrivR peuvent être découverts.

indice000102030405060708090A0B0C0D0E0F101112131415
PRIV000102030405060708090A???????????
PRIVR000102030405060708090A?150F0E0B??0C?11?
K0D1214040108060F050C0E0207030A151009000B1311
T0F0C11040108060B05150E???????????
PUB0F0C11040108060B05150E1000090A030D1312140207



Vu que PrivR = Priv.reverse() on finit par découvrir certains octets de Priv.

indice000102030405060708090A0B0C0D0E0F101112131415
PRIV000102030405060708090A0F12?0E0D?14???0C
PRIVR000102030405060708090A?150F0E0B??0C?11?
K0D1214040108060F050C0E0207030A151009000B1311
T0F0C11040108060B05150E???????????
PUB0F0C11040108060B05150E1000090A030D1312140207



Il ne reste donc plus que 5 octets inconnus dans la clé privée, on a donc diminué l'espace de recherche de 11! à 5! soit 120. Il suffit de tester ces 120 possibilités et de voir celles qui respectent le padding PKCS5 fait lors de l'envoi du flag. Une seule réponse correspond pour la clé 000102030405060708090a0f12110e0d151410130b0c qui nous permet de déchiffrer le flag : Br4iDCrypto_i5_b3au7ifu11

Tuesday, April 5 2011 19:23

NDH2k11 Prequals - Crypto200

On a un code source qui génère des password au hasard et un fichier jpg encrypté.

Un block de 8 bytes qui commence à un rang multiple de 8 bytes se répète très très souvent. Plusieurs fois de suite, au début, au milieu, à la fin...
On comprend donc que c'est un chiffrement symmétrique par blocks de 64-bits du genre de DES. On se doute aussi que le block de 8 qui se répète correspond à un byte répété plusieurs fois de suite, très probablement des null bytes vu la structure des jpg en général.

Vu le nom du fichier (bf-encrypted.jpg) on devine que c'est du BlowFish (c'est bien du DES-like).

Du coup on a pas vraiment le choix, il faut bruteforcer.

Vu le fichier de génération de password fourni on devine que la longueur de la clef est de 8 bytes. ymvunjq y trouve une faille qui permet de ne bruteforcer que sur les 5 premiers bytes (les 3 suivants sont déduits par la suite).

Ainsi on lance un bruteforce qui passe par les 62^5 passes possibles (62 étant le nombre de caractères dans le charset).

BAAL trouve le passe eynnoXAd. On l'utilise pour dé-blowfisher l'image avec le code suivant:

#include "blowfish.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
 
void bruteforce_key (void)
{
    unsigned long L = 1, R = 2;
    BLOWFISH_CTX ctx0;
    char PASS[10] = {0};
 
    // Test to check if the blowfish code runs properly on given computer
 
    Blowfish_Init (&ctx0, (unsigned char*)"TESTKEY", 7);
    Blowfish_Encrypt(&ctx0, &L, &R);
    printf("%08lX %08lX\n", L, R);
 
    if (L == 0xDF333FD2L && R == 0x30A71BB4L)
        printf("Test encryption OK.\n");
    else
        printf("Test encryption failed.\n");
 
    Blowfish_Decrypt(&ctx0, &L, &R);
 
    if (L == 1 && R == 2)
        printf("Test decryption OK.\n");
    else
        printf("Test decryption failed.\n");
 
    // Bruteforce, loop through first 5 chars
 
    char charset[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-!$%*+[]()";
    int a0, a1, a2, a3, a4;
 
    for(a0=0;a0<62;a0++){
        for(a1=0;a1<62;a1++){
            printf("%i %i - ", a0, a1); // Display progress
            for(a2=0;a2<62;a2++){
                for(a3=0;a3<62;a3++){
                    for(a4=0;a4<62;a4++){
                        PASS[0] = charset[a0];
                        PASS[1] = charset[a1];
                        PASS[2] = charset[a2];
                        PASS[3] = charset[a3];
                        PASS[4] = charset[a4];
                        PASS[5] = charset[PASS[4] % 62];
                        PASS[6] = charset[PASS[5] % 62];
                        PASS[7] = charset[PASS[6] % 62];
                        L = 0;
                        R = 0;
 
                        Blowfish_Init (&ctx0, (unsigned char*)PASS, 8);
                        Blowfish_Encrypt(&ctx0, &L, &R);
 
                        if (L == 0x8EDE92F0 && R == 0xD0D99926){  //Expected crypted text
                            printf("*** Success *** Password: %s\n", PASS);
 
                            a0 = 100; a1 = 100; a2 = 100; a3 = 100; a4 = 100;
                            system("PAUSE");
                        }
                    }
                }
            }
        }
    }
 
    puts ("[+] DONE");
}
 
void decrypt (void)
{
    BLOWFISH_CTX ctx0;
    unsigned long L = 0x67A2204C , R = 0x967FB36E;
    int i;
    FILE *input = fopen ("bf-encrypted.jpg", "rb");
    FILE *output = fopen ("bf-decrypted.jpg", "wb+");
 
    if (!input)
    {
        puts ("[-] File not found");
        return;
    }
 
    while (!feof (input))
    {
        L = 0;
        R = 0;
 
        for (i = 0; i < 4; i++)
            L = (L << 8) + fgetc (input);
 
        for (i = 0; i < 4; i++)
            R = (R << 8) + fgetc (input);
 
        Blowfish_Init (&ctx0, (unsigned char*)"eynnoXAd", 8); // This is the password obtained from step 1
        Blowfish_Decrypt(&ctx0, &L, &R);
 
        for (i = 0; i < 4; i++)
            fputc ((L >> (8 * (3 - i))) & 0xff, output);
 
        for (i = 0; i < 4; i++)
            fputc ((R >> (8 * (3 - i))) & 0xff, output);
    }
 
    fclose (input);
    fclose (output);
 
    puts ("[+] DONE");
}
 
int main(int argc, char **argv)
{
    if (argc < 2)
    {
        printf ("Usage: %s step\n"
                "\tStep 1 : Bruteforce the key\n"
                "\tStep 2 : Decrypt the ciphertext\n", argv[0]);
        return 1;
    }
 
    if (argv[1][0] == '1')
        bruteforce_key ();
    else if (argv[1][0] == '2')
        decrypt ();
    else
        puts ("[-] FAIL\n");
 
    return 0;
}

Le code source du blowfish est pris d'ici: C'est celui de Paul Kocher.

L'image une fois décryptée donne le flag: Cod3monk3ys_4re_3viL

bf-decrypted.jpg