Affichage des articles dont le libellé est XOR. Afficher tous les articles
Affichage des articles dont le libellé est XOR. Afficher tous les articles

lundi 11 mars 2013

Prequals NDH 2k13 - Crackme Huge.js

Voici le write-up d'une d'une autre épreuve des prequals de la NDH 2k13.

Description

On nous donne un fichier .js mais assez "huge". Evidemment ca rame à mort si on l'ouvre avec notepad, etc ...

Analyse du script

Donc on l'ouvre avec notre éditeur héxa préféré ou même Chrome et là on découvre :

function d() {
  return (window.console &&
         (window.console.firebug || window.console.exception));
}

function x(c, k) {
  o = '';
  for (i = 0; i < c.length; i++) {
    o += String.fromCharCode(c.charCodeAt(i) ^ k.charCodeAt(i % k.length));
  }
}

On a donc une fonction d(debug) qui check si on debug le script et une fonction x(or) qui déchiffre une string xorée. Dans la suite du code, les strings sont évidemment xorées avec cette fonction.

window[x('\x44\x29\x42\x42','\x21\x5f\x23\x2e\x2b\x28\x3f\x2d')](x('\x42\x4b\x17\x0f\x5b\x01\x08\x02\x56\x48\x47\x51\x4d...));



Déchiffrement

Il suffit donc d'écrire un script qui va remplacer chaque appelle à la fonction "x" par son résultat. A la fin on aura notre fichier Javascript en clair et avec une taille correct !

def xor(c, k):
  return "".join([chr(ord(c[i])^ord(k[i%len(k)])) for i in range(len(c))])


def decode(f):
  data = open("huge.js", "r").read()
  pos = data.find("x('\\", 0)
  while pos != -1:
    sep = data.find(",", pos)
    end = data.find(")", pos)
    c = data[pos+3:sep-1].replace("\\x", "").decode("hex")
    k = data[sep+2:end-1].replace("\\x", "").decode("hex")
    data = data[:pos] + xor(c, k) + data[end+1:]
    pos = data.find("x('\\", 0)
  f = open("result.js", "w")
  f.write(data)
  f.close()

decode("huge.js")

Et oui, on parse à l'ancienne avec des "pos" et des "substrings" !

On obtient une fonction "xxx" qui est en fait un md5, "kkk" qui reverse une string et une fonction "unlock" pour tester le mot de passe :

function unlock(node){
  var code = node.value;
  if (code && (code.length == 5)){
    if ((xxx(code).substr(0,16)=='b3336efd42e29780') &&
        (xxx(kkk(code)).substr(16,16)=='261804c5f2f0a47e')){
      var flag = document.createElement('span');
      var t = document.createTextNode('YAY, you got the flag ! --> '+xxx(code));
      flag.appendChild(t);
      flag.style.font='Helvetica, 1.2em, bold';
      document.getElementById('panel').appendChild(flag);
      node.style.display = 'none';
      node.value = '';
    }
  }
}

Il suffit donc de trouver un pass de 5 caractères dont le début du md5 est "b3336efd42e29780". Bf :

import sys


charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789<>(){}-+"

h = "b3336efd42e29780".decode("hex")


def brute(n, s):
  if n>0:
    for c in charset:
      brute(n-1, s+c)
  else:
    if hashlib.md5(s).digest()[:8] == h:
      print s
      sys.exit(0)

brute(5, "")

Au bout de quelques temps on obtient : i<3Js

Et voilà :)

Prequals NDH 2k13 – Fappers Gonna Fap – Crypto 200 pts

Voici le write-up d'une des épreuves de crypto des prequals de la NDH 2k13. Au menu, du XOR, du format BMP et du Python ! 

Description

On nous fournit le fichier dafuq.bmp et un script python encrypt.py.
L'épreuve va consister à décoder l'image Bitmap qui a apparemment été chiffrée en utilisant le script python.

Analyse du script

On va regarder d'un peu plus près encrypt.py histoire de voir si on peut le reverse pour récupérer l'image d'origine.
Une classe BitmapFile est définie dans laquelle on trouve une fonction très intéressante :


On remarque que les données binaires de l'image sont xorées avec une clé de longueur 337 dont les valeurs des octets sont pris aléatoirement entre 0 et 255. L'algorithme n'est donc pas réversible mais il doit être possible de récupérer la clé en utilisant les caractéristiques du format BMP.

Récupération de la clé

La structure d'une image Bitmap est composée de plusieurs en-têtes et options en plus des données de l'image. On trouve notamment le Bitmap File Header et le DIB Header qui vont permettre de stocker des informations sur l'image.

On va alors pouvoir recomposer le File Header de l'image d'origine en utilisant notamment le fait que plusieurs champs sont communs à toutes les images BMP. Ensuite comme il s'agit d'un simple XOR il suffira de xorer les octets trouvés avec ceux de dafuq.bmp pour avoir le début de la clé (le File Header fait 14 octets).

Par exemple, on sait déjà que l'en-tête va commencer par les octets 42 4D qui sont en fait les caractères B et M. Le champ suivant qui occupe 4 octets contient la taille du fichier qui sera donc la même que dafuq.bmp c'est-à-dire 2993058 octets soit 2DABA2 en héxadécimal. Par ailleurs les données étant stockées en Little Endian on obtient A2AB2D00 pour ces 4 octets du File Header.
Les 4 octets suivants sont réservés et généralement à 0 et les 4 d'après représentent l'adresse de départ des données de l'image qui commencent habituellement après le File Header (14 octets) et le DIB Header (40 octets). On obtient donc la valeur 54 soit 36 en héxadécimal stockées sous la forme 36000000.

On arrive ensuite sur le premier champ du DIB Header qui est en fait invariable puisqu'il s'agit de la taille du Header soit 40 octets. On a donc la valeur héxadécimale 28 stockées sur 4 octets : 28000000.

Pour le moment, nous avons reconstruit les 18 premiers octets de l'image :
424DA2AB2D00000000003600000028000000

Nous pouvons donc d'ores et déjà les xorer avec les 18 premiers octets de l'image chiffrée pour obtenir le début de la clé.

Après avoir xoré tout ça, on obtient le début de clé suivant :
C31AD80D5C84E09149D9E19A79164A18338B
A ce stade, on sait que ça ne sert à rien de continuer à reconstruire les en-têtes car la clé est censé faire 337 octets ce qui est largement supérieur à la taille des en-têtes (54 octets).

Il va donc falloir utiliser le début de la clé pour en déduire le reste.

On peut légitimement supposer que les données de l'image d'origine contiennent par endroit des séquences de 0 qui une fois xorées avec la clé donneront des parties de la clé dans l'image chiffrée.

On va donc rechercher dans les données de l'image chiffrée toutes les occurrences de la séquence C31AD80D5C84E09149D9E19A79164A18338B.

On trouve de nombreuses occurrences de cette séquence dont 2 sont montrées au-dessus.
On constate qu'à chaque fois ces séquences sont séparées de 319 octets identiques ce qui nous donne des blocks de 337 octets soit la longueur de la clé utilisé lors du XOR.

On dirait bien qu'on vient juste de récupérer notre fameux « keystream ».

Décodage

On a tout ce qu'il faut pour décoder le fichier dafuq.bmp et obtenir l'image de départ.
On fait un petit script python qui va xorer les données binaires de dafuq.bmp avec la clé de 337 octets ci-dessous :

C31AD80D5C84E09149D9E19A79164A18338B1E78446DB8B8D25431071C320DE6231EBB0E882351217666AAB9209DD00AD142B10F45DE0AA522B3459A14523A5F01DC5ECC67F62584FB7064B7832AC9FC43CA8A299189F5818B8BE7970034600C3317A44883F7CB77060460FFE8048DF4CBF6AA60DCB73229BD0444504715614F43CE360B852B846D43B606B69F257672E02DAF7BC40554B5121A8A6D9AB933F28F8A9D95858CA7AFE947D63C979445B645DE920E9A7EE5F6F065AB7529E6DE1EDE9DFFA50F32632E4C9EE30E088238C106E385EA6C8B2296D7F485A171E7A8EE163B24E6BDCEF979490BF1839EA5773275D83D8583AFF2E6A5279A7DB36CBD1914B91389820E6576B84E832FFEC4184846E59980096FEBE28ED66FB08637D40530DCBE3AC481F38EEB04E00FB4F60F13B047343BC230F200C2419F4271E2584508BE3B56164F522060DA23D0DDCD97055C

On obtient alors l'image suivante :


On valide l'épreuve avec le flag : ecb_mode_is_weak_mofo

Liens

http://en.wikipedia.org/wiki/BMP_file_format

mercredi 4 juillet 2012

NDH 2k12 - Lost USB key - 1000pts

Un autre crackme, Windows cette fois-ci.

1.       What
On a un fichier « crackme.exe » qui demande un mot de passe …
Voici ce que PeID en pense :



Il est donc packé avec UPX. Pour unpacker, il suffit de lancer : upx.exe –d crackme.exe.
Et voilà, c’est unpacké …
On passe la fonction « start » pour arriver à la demande de mot de passe (bloc jaune) :


Après le « fgets », on a en vert la partie qui va chiffrer (transformer légèrement) notre mot de passe, ensuite on passe dans la partie orange qui comparera notre mot de passe chiffré avec le chiffré du bon mot de passe. Si c’est bon, on passe dans la partie rouge, sinon bleu.

Dans le premier bloc vert [ebp-1Ch] contient l’index du caractère que l’on est en train de transformer, il est d’ailleurs initialisé à 0 juste après le fgets.
Ensuite on calcul la taille de notre mot de passe situé à ebp-32h. On compare ensuite les 2 pour savoir si on est à la fin ou pas.

Pour la partie transformation (2ème bloc vert), on va chercher le caractère du mot de passe à l’index [ebp-1Ch], on le XOR avec [ebp-1Dh] qui vaut 0x93 (voir un peu plus haut : mov byte ptr [ebp-1Dh], 93h), puis on incrémente l’index.

On a donc un XOR avec 0x93. Pour trouver le bon mot de passe, il suffit de récupérer le chiffré du bon passe et de le XORer avec 0x93.

2.       Résultat
On break sur le strcmp, on note le chiffré du bon mot de passe.
Cela donne : FE FC FD F8 A0 EA.
Après le XOR on obtient le flag : monk3y

Comme « le python c’est bon », voiçi le xor en Python :
"".join([chr(int(v,16)^0x93) for v in ["FE","FC","FD","F8","A0","EA"]])

lundi 27 juin 2011

CTF Public NDH2K11 – Crackme1

Hello !

On continue dans les writeups de la NDH 2011 avec celui pour le crackme1. Gogogo !

  1. Analyse


    On nous a fourni un exécutable, safecrypt.exe, ainsi qu'un fichier PNG chiffré.
    On se doute bien du but de l'épreuve, c'est à dire, déchiffrer cette image qui doit contenir le flag.
    On lance alors ce safecrypt.exe pour voir, ça ne coûte rien (pour un crackme :))

    D:\Devs\NDH2K11>SafeCrypt.exe
    Use : [1/2] SafeCrypt.exe input output
    1 : Crypt
    2 : UnCrypt

    On apprend qu'il veut 3 arguments, le mode, le fichier d'entrée et le fichier de sortie. Rien de particulier.
    Un petit coup de PeID pour voir s'il est packé ou s’il utilise des fonctions crypto (md5, base64...). Mais non, toujours rien :)


    C’est fini pour les préliminaires, on passe aux choses sérieuses ! On l’ouvre avec notre ImmunityDbg préféré et on obtient ceci :


    On voit 3 blocs de code, puis le saut vers une certaine adresse. Ensuite plus rien.
    On reconnait tout de suite un code qui unpack par les routines qui déchiffrent les sections, puis un saut vers l'OEP.
    L'adresse de début de la section est mise dans EAX, la fin dans ECX, ensuite on xor chaque octet avec 2D (la clé). Et on fait ça pour les 3 sections.
    On peut le vérifier en comparant avec les adresses des sections avec LordPE (VOffset et RSize) :


    On unpack ou pas ? On verra si il y besoin, pour le moment on laisse comme ça.
    Quelques touches F7/F8 plus tard, on arrive bien au début de notre programme.


  2. Reverse


    On descend un peu et on tombe sur des choses plus intéressantes.


    Il vérifie si le nombre d’arguments est au moins 3, si c’est bon on demande le mot de passe sinon ça affiche le message "usage".


    Une partie importante, après avoir lu le mot de passe, on calcule sa taille (avec strlen), puis on passe le tout à la fonction SafeCryp.00401637.
    Cette fonction calcule un hash du mot de passe sur 4 octets, qui sera ensuite utilisé (heureusement :) ) pour le chiffrement/déchiffrement.


    On ouvre notre fichier d’entrée, de même pour la sortie. Pour calculer la taille du fichier d’entrée, on utilise lseek en se plaçant à la fin du fichier, puis on revient au début.
    Avec cette taille, on alloue un buffer qui servira surement à mettre notre version chiffrée/déchiffrée du fichier. Tout est prêt ! go !


    Voilà la partie du chiffrement, au début on compare le paramètre à "1", si ça correspond on chiffre, sinon on le compare avec "2" pour le déchiffrement, et si ça correspond toujours pas, ben ... tant pis, on continue quand même :).
    La partie finale, on ne va pas la détailler, on écrit notre buffer dans le fichier de sortie, on dit « Finished! » et voilà.
    Si vous avez bien suivi, dans le cas d’une opération inconnue, on aura des valeurs indéfinies dans le fichier de sortie, et ui, mais on s’en fou et l'auteur du chall aussi :)

    Revenons à notre chiffrement, au début on lit notre fichier et on met dans le buffer. ok.
    Ensuite pour chaque DWORD du buffer, on XOR avec le hash précédemment calculé, ce qui donne notre DWORD chiffré, puis on met à jour la « clé » en utilisant la même fonction qu'au début, SafeCryp.00401637 et le DWORD en clair. Un genre de mode CBC sauf qu’on utilise le clair.

    Donc voilà, c’est juste un XOR avec une clé de 4 octets. On pourrait bruteforcer, mais bon, c’est une épreuve de crackme, il doit y avoir plus simple ou en-tout-cas moins long :) . Avant d’utiliser le brain, regardons le déchiffrement.


    Le début est le même, on met tout le fichier dans le buffer. Ensuite pour chaque DWORD chiffré, on le XOR avec le hash du mot de passe, on retrouve notre octet clair.
    On garde ce DWORD déchiffré pour mettre à jour le hash et on passe au DWORD suivant.
    Ainsi de suite …

  3. Brain


    Voici donc pour résumer l’algorithme utilisé :

    fonction chiffre(buffer, hash){
        pour chaque DWORD i du buffer {
            tmp = buffer[i]
            buffer[i] = buffer[i] XOR hash
            hash = updateHash(hash, tmp)
        }

    }

    fonction dechiffre(buffer, hash)
        pour chaque octet i du buffer {
            buffer[i] = buffer[i] XOR hash
            hash = updateHash(hash,
    buffer[i])
        }

    }

    Bon alors, comment qu’on fait maintenant ? :)
    Il faut remarquer que le hash de départ est le seul à utiliser le mot de passe, donc c’est sur celui-là qu’il faudra jouer.
    Le reste des hashs dépend des octets déchiffrés et des hashs précédents, si le premier est bon, les autres le seront.
    Donc il faudrait forcer le premier hash à être bon, peut importe la clé qu’on entre.
    On se rappelle qu’on a : clair = hash XOR chiffré, le chiffré on l’a, le clair on l’a ….? Et UI ! C’est un fichier png donc l’entête est standard et vaut 89 50 4E 47.
    Donc on peut calculer le hash pour déchiffrer les premiers octets. Pour trouver le mot de passe derrière, c’est un autre problème, mais on n’en a pas besoin ici.
    Pas besoin de rappeler comment marche le XOR, on sait que hash = chiffré XOR clair.

    #include <stdio.h>

    unsigned int sw(unsigned int x){
        return (x>>24) |
               ((x<<8) & 0x00FF0000) |
               ((x>>8) & 0x0000FF00) |
               (x<<24);
    }

    int main(int argc, char* argv[]){
        FILE* in = fopen("Challenge.png", "r");
        int b, hash;
        if (in){
            fread(&b, 4, 1, in);
            printf("First dword is %.08x and first hash should be %.08x\n", b, b ^ sw(0x89504e47));
            fclose(in);
        }
        return 0;
    }

    Quelques lignes de C ou une ligne de Python plus tard, on sait que le hash doit être 80ce21e8.
    (J'ai rajouté une fonction swap, ça peut toujours être utile à quelqu'un :) )

  4. Crack


    On va patcher la valeur du hash avec Immunity, c'est la méthode la plus rapide.
    Pour cela, il faut déjà mettre les arguments au programme (Debug > Arguments) et on met quelque chose du genre : 2 Challenge.png Flag.png.
    Avant de run le programme, on met un breakpoint à l'adresse 0x00401347, c'est à cette adresse que le programme récupère la valeur du hash (depuis EAX) et la sauvegarde quelque part.
    Maintenant c'est bon, on trace à fond, on donne un mot de passe comme demandé, ensuite on arrive à notre breakpoint. On modifie la valeur d'EAX par 0x80ce21e8 puis on laisse le programme finir.


    Et voilà, le flag est dans Flag.png :)

    Une autre méthode ?
    On peut aussi patcher safecrypt.exe, pour faire simple, faut unpacker, éditer le fichier et remplacer le call juste avant 0x00401347 (offset 0x1342) par un mov eax, 0x80ce21e8 (qui donne B8E821CE80) en héxa.

  5. Références & tools


    [1] - PeID
    [2] - LordPE
    [3] - ImmunityDbg
    [4] - Unpack d'UPX, mais la méthode est la même ici