Nuit du Hack Public Wargame 2011 > Crackme 4 - Reverseme1
Deuxième niveau des crackme public pour la nuit du hack. On avait encore un exécutable PE console qui prenait un seul argument, le serial à valider. Le point d'entrée est à 0x407000 et semble obfusqué :
push esi loc_407001: add esi, 5EB9090h jz short near ptr loc_407001+4 jnz short near ptr loc_407001+4 jmp near ptr 18E1D46Eh
La technique d'obfuscation est claire : on a un jump+4 sur le code qui vient d'être exécuté, empêchant le désassembleur de faire son boulot. Pour aider le désassembleur, je patche le code à chaque add esi, 5EB9090h en remplacant 81 C6 90 90 EB 05 74 FC 75 FA E9 par 81 C6 90 90 EB 05 EB 03 90 90 90, ce qui rétablit le control flow normal (jmp 03 au lieu de jmp 05 car l'instruction a lieu deux bytes plus tard) :
push esi add esi, 5EB9090h jmp short loc_40700C nop nop nop loc_40700C: pop esi mov eax, large fs:18h push esi
En répetant cette technique on voit que le debut du programme est de l'anti-debug :
mov eax, large fs:18h mov eax, [eax+30h] movzx eax, byte ptr [eax+2] cmp eax, 1 jnz short near ptr loc_40702C+1 loc_40702C: mov ebp, ebx das mov eax, large fs:0 pop ebx push ebx push eax loc_407045: mov large fs:0, esp xor edx, edx mov edx, [edx]
On voit que si le PEB.beingDebugged est égal à 1, on va à 0x40702C, alors que dans le cas inverse on va à 0x40702D. On voit que le parcours à partir de 0x40702C est voué à l'échec puisqu'on a un mov edx, [edx] juste après un xor edx, edx qui va donc provoquer une faute de page. En continuant à partir de 0x40702D :
jmp short loc_40705E call loc_40702F loc_40702F: mov eax, large fs:0 pop ebx push ebx push eax mov large fs:0, esp xor edx, edx pop esi mov edx, [edx]
On a toujours ce mov edx, [edx], sauf que l'exception handler mis à 0x407045 a cette fois du sens : il pointe vers une chaîne SEH avec comme premier handler ebx (qui a été pop de la pile et donc contient l'adresse de retour du call) et comme suite de la chaîne l'handler par défaut qui avait été sauvegardé dans eax. Par conséquent, le code continue à 0x407063. Il contient simplement une boucle inutile sur esi puis il xor par 0x2d les données entre 0x401000 et 0x404000. Le tout se finit par un push 0x401220, ret. On a donc notre point d'entrée. En l'inspectant, on observe un début d'application console classique qui nous donne l'adresse de la fonction main : 0x401290.
La fonction main est protégée par la même technique d'obfuscation que le loader, on patche donc de la même manière que précédemment. La fonction dure jusqu'à 0x40173A. On voit d'abord qu'une portion de données est recopiée dans un fichier C:\Temp\ReverMe1.sys. Le reste du code nous apprend que ce fichier n'est autre que le code d'un service Windows qui contient la routine de vérification du serial. La fonction DeviceIoControl est utilisée avec en entrée le serial fourni en ligne de commande. Ensuite, le snippet suivant effectue la vérification du résultat :
lea eax, [ebp+var_218] mov [esp+458h+var_450], 8 mov [esp+458h+var_454], offset aB1cc4893 ; "b1cc4893" mov [esp+458h+var_458], eax call memcmp test eax, eax jnz short loc_40152F lea eax, [ebp+var_218] mov [esp+458h+var_454], eax mov [esp+458h+var_458], offset aNiceJobYouFoun ; "Nice job ! You found me :-)\nValid the h"... call printf
ebp + 0x218 étant le buffer passé à DeviceIoControl (contenant notre serial en entrée et une donnée inconnue en sortie), on sait donc qu'il doit rendre une chaîne dont les 8 premiers bytes donnent le texte "b1cc4893". A ce moment, il y a deux choses à faire : essayer de deviner la fonction utilisée en observant les sorties ou reverser le code du driver. J'ajouterais que le service ne fonctionne pas sous wine et Windows 7 pour une raison qui m'échappe (sous wine il me paraît probable que la modification du handler de fautes de page de l'IDT faite dans le driver doit poser problème).
Bref, sous Vista il n'y a pas de soucis et après quelques essais rapides avec des serials simples on se rend compte que le service semble faire un hash MD5 des serials qui arrivent. Les serials faisant au maximum 200 caractères, on se dit qu'un bruteforce n'est pas pratiquable, sauf peut-être si le serial est faible :
#include <stdio.h> #include <string.h> #include <stdlib.h> #include "md5.h" #define MAX_LEN 6 #define MD5HASH "\xb1\xcc\x48\x93" #define HASHLEN 4 MD5_CTX mdContext; char BRUTECHARS[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"; void do_recurs_brute(char * buf, int idx, int len) { int i; if (idx == len) { MD5Init(&mdContext); MD5Update(&mdContext, buf, len); MD5Final(&mdContext); if (!memcmp(mdContext.digest, MD5HASH, HASHLEN)) { buf[len] = 0; printf("Found valid hash value %s\n", buf); exit(0); } return; } for (i=0;i<strlen(BRUTECHARS);i++) { buf[idx] = BRUTECHARS[i]; do_recurs_brute(buf, idx+1, len); } if (!idx) printf("Not found for len: %d\n", len); } int main() { int i; for (i=0;i<MAX_LEN;i++) do_recurs_brute(buf, 0, i+1); return 1; }
$ gcc -O2 -m32 brutemd5.c md5.c -o brutemd5 && ./brutemd5 Not found for len: 1 Not found for len: 2 Not found for len: 3 Found valid hash value OwnM $
C'est gagné, le serial était en effet faible et le hash de validation est b1cc4893de3b7d9401d30f2f5890df12. En réalité, on se rend compte en testant OwnMe que le hash n'était effectué qu'avec les 4 premiers caractères du serial. Lorsque j'aurais le temps je reviendrai plus en détail sur le service qui était très intéressant (vérification du temps kernel, manipulations de cr0 et de l'IDT pour rediriger le handler de faute de page, fonction xorée..) mais j'avoue que je n'ai pas pris le temps de le reverser en entier, puisqu'il n'y en avait pas besoin et que je ne suis pas très à l'aise en debug de service Windows :(.
C'est quel language ?