- Messaggi
- 2,332
- Reazioni
- 1,928
- Punteggio
- 134
CrackMe #2: Eset Challenge (parte 1)
Visto che il precedente Crackme - Crackme #1: Look Closer - è stato accolto con favore qualche mese fa, ho pensato di proporne un altro.
Eh si, è proprio così: anche ESET ha creato un CrackMe. Lo si può trovare a questo indirizzo.
Cito dalla pagina:
The program was designed to test your skillset in reverse engineering, which might come in handy when you work at ESET in one of the positions described above.
Una doverosa premessa prima di addentrarci in questo CrackMe: come si vedrà ci saranno più parti (almeno 2); l'articolo tratta la risoluzione della prima parte.
1. Primi approcci
A download avviato, ci troveremo davanti ad un file zip. Aprendono la situazione sarà la seguente:
Segno che qualcosa non quadra: non è un file zip. Guardandolo con un editor esadecimale - HxD nel mio caso - si può vedere infatti chiaramente la presenza del PE Header (ho indicato in rosso la DOS signature e la signature del "nuovo" header, PE):
A questo punto rinominiamo il file in "exe". Successivamente tramite PEiD, RDG Packer Detector o ExeInfoPE (o altri ancora) otteniamo qualche prima informazione.
Dallo screen si nota che è scritto in C++ e compilato con MSVCC e che viene richiamata
isDebuggerPresent
. Non c'è traccia di packers, ci è andata bene.Anche tramite PEView (o equivalente) si può notare la presenza di
isDebuggerPresent
.2. Eseguiamo il CrackMe
A questo punto lancio il crackme, per vedere eventuali menù o provare qualche input. A primo impatto è molto semplice:
Inserendo un input casuale, il programma termina direttamente, senza dare alcun output. A questo punto faccio entrare in scena il debugger; per l'occazione ho scelto x32dbg.
Appena avviato il programma, premendo F9 raggiungiamo l'EP (Entry Point) del programma. A questo punto premo F9, eseguendolo; sapendo della presenza di isDebuggerPresent, mi aspetto qualcosa. Appena lanciata l'esecuzione, il programma termina!
A questo punto, prima di procedere in modo più "serio", tento la fortuna: una scansione sulle stringhe utilizzate dal programma, in cerca del testo che richiede l'input, o di qualche altro messaggio. Non ci sono stringhe leggibili, segno che probabilmente vengono decifrate durante l'esecuzione.
Ora procediamo a ritroso: sapendo che è presente isDebuggerPresent, la raggiungiamo. Per farlo possiamo guardare nei moduli che vengono importati, in particolare
KERNEL32.DLL
, e Successivamente cercare isDebuggerPresent:Con un doppio click sulla voce con tipo "Import" ci si trova davanti questo codice:
Codice:
772784C0 | 64:A1 30000000 | mov eax,dword ptr fs:[30] |
772784C6 | 0FB640 02 | movzx eax,byte ptr ds:[eax+2] |
772784CA | C3 | ret |
si tratta appunto del corpo si isDebuggerPresent.
Può sembrare criptico, ma lo è meno di quel che si pensi: qui siamo in x86, quindi
fs
punta a una struttura dati chiamata TEB (Thread Environment Block); questa struttura contiene le info sul thread in esecuzione.
Codice:
typedef struct _TEB {
PVOID Reserved1[12];
PPEB ProcessEnvironmentBlock;
PVOID Reserved2[399];
BYTE Reserved3[1952];
PVOID TlsSlots[64];
BYTE Reserved4[8];
PVOID Reserved5[26];
PVOID ReservedForOle;
PVOID Reserved6[4];
PVOID TlsExpansionSlots;
} TEB, *PTEB;
i primi 12 membri sono dei puntatori a void, e sono riservati. Si tratta quindi di 12x4 = 48-bytes, in esadecimale 30h.
All'indice 0x30 quindi troviamo un'altra struttura dati, chiamata ProcessEnvironmentBlock:
Codice:
typedef struct _PEB {
BYTE Reserved1[2];
BYTE BeingDebugged;
BYTE Reserved2[1];
PVOID Reserved3[2];
PPEB_LDR_DATA Ldr;
PRTL_USER_PROCESS_PARAMETERS ProcessParameters;
PVOID Reserved4[3];
PVOID AtlThunkSListPtr;
PVOID Reserved5;
ULONG Reserved6;
PVOID Reserved7;
ULONG Reserved8;
ULONG AtlThunkSListPtr32;
PVOID Reserved9[45];
BYTE Reserved10[96];
PPS_POST_PROCESS_INIT_ROUTINE PostProcessInitRoutine;
BYTE Reserved11[128];
PVOID Reserved12[1];
ULONG SessionId;
} PEB, *PPEB;
Anche qui, dopo i primi 2byte riservati, si trova la posizione di nostro interesse: BeingDebugged. Come il nome stesso indica, restituisce true (1) se il processo ha "attaccato" un debugger.
Bene, ora che siamo in questa procedura, possiamo settare un bp (breakpoint) sulla prima mov e poi premiamo F9. L'esecuzione si ferma sul nostro bp: a questo punto si si steppa con F8 sulle altre 2 istruzioni, sino a quando si torna dalla "ret"; in pratica andremo alla procedura che ha invocato la isDebuggerPresent.
Il codice che mi sono trovato davanti è questo:
Codice:
004013F1 | 8BEC | mov ebp,esp |
004013F3 | 83EC 20 | sub esp,20 |
004013F6 | FF15 04204100 | call dword ptr ds:[<&IsDebuggerPresent>] |
004013FC | 85C0 | test eax,eax |
004013FE | 74 08 | je crackme.401408 |
00401400 | 6A 00 | push 0 |
00401402 | FF15 00204100 | call dword ptr ds:[<&ExitProcess>] |
00401408 | C745 F8 00000000 | mov dword ptr ss:[ebp-8],0 |
abbiamo trovato la chiamata (una chiamata). Il programma si chiudeva in quanto, come si può leggere, viene invocata ExitProcess se
TEST eax, eax
è true. E' sufficiente cambiare il controllo con jnz o modificare il test, o usare altri artifici; l'importante ora è procedere con l'esecuzione.La parte immediatamente successiva è questa:
Codice:
00401408 | C745 F8 00000000 | mov dword ptr ss:[ebp-8],0 |
0040140F | 6A 03 | push 3 |
00401411 | 6A 25 | push 25 |
00401413 | 6A 1F | push 1F |
00401415 | 68 00804100 | push crackme.418000 |
0040141A | E8 81FFFFFF | call crackme.4013A0 |
0040141F | 6A 00 | push 0 |
00401421 | 8D45 F8 | lea eax,dword ptr ss:[ebp-8] |
00401424 | 50 | push eax |
00401425 | 68 00804100 | push crackme.418000 |
0040142A | E8 F1030000 | call crackme.401820 |
0040142F | 83C4 04 | add esp,4 |
00401432 | 50 | push eax |
00401433 | 68 00804100 | push crackme.418000 |
00401438 | 8B0D 64904100 | mov ecx,dword ptr ds:[419064] |
0040143E | 51 | push ecx |
0040143F | FF15 14204100 | call dword ptr ds:[<&WriteConsoleA>] |
00401445 | 6A 03 | push 3 |
00401447 | 6A 25 | push 25 |
00401449 | 6A 1F | push 1F |
0040144B | 68 00804100 | push crackme.418000 |
00401450 | E8 4BFFFFFF | call crackme.4013A0 |
Ha attratto la mia attenzione la prima call che vedete lì in cima: viene chiamata 2 volte con un indirizzo come parametro (crackme.418000). A quell'indirizzo i dati non sono leggibili (non significano nulla), segno che si tratta quasi sicuramente dei dati cifrati (quelli che verranno stampati a schermo).
Steppo sino a dopo la call, fermandomi su 0x0040141F. Arrivati a questo indirizzo, il debugger segnala che qualcosa è cambiato... e compaiono i commenti:
Codice:
00401415 | 68 00804100 | push crackme.418000 | 418000:"Please enter valid password : "
quindi ora sappiamo che quell'indirizzo è l'offset iniziale della stringa, e che di conseguenza la call decifra la stringa. Decido quindi di guardare quell'algoritmo (può tornare utile).
Riporto di seguito il codice:
Codice:
004013A0 | 55 | push ebp |
004013A1 | 8BEC | mov ebp,esp |
004013A3 | 51 | push ecx |
004013A4 | C745 FC 00000000 | mov dword ptr ss:[ebp-4],0 |
004013AB | EB 09 | jmp crackme.4013B6 |
004013AD | 8B45 FC | mov eax,dword ptr ss:[ebp-4] |
004013B0 | 83C0 01 | add eax,1 |
004013B3 | 8945 FC | mov dword ptr ss:[ebp-4],eax |
004013B6 | 8B4D FC | mov ecx,dword ptr ss:[ebp-4] |
004013B9 | 3B4D 0C | cmp ecx,dword ptr ss:[ebp+C] | [ebp+C]:&"D:\\DispatchCode\\crackme.exe"
004013BC | 73 26 | jae crackme.4013E4 |
004013BE | 8B55 08 | mov edx,dword ptr ss:[ebp+8] |
004013C1 | 0355 FC | add edx,dword ptr ss:[ebp-4] |
004013C4 | 0FBE02 | movsx eax,byte ptr ds:[edx] |
004013C7 | 0FB64D 10 | movzx ecx,byte ptr ss:[ebp+10] |
004013CB | 33C1 | xor eax,ecx |
004013CD | 8B55 08 | mov edx,dword ptr ss:[ebp+8] |
004013D0 | 0355 FC | add edx,dword ptr ss:[ebp-4] |
004013D3 | 8802 | mov byte ptr ds:[edx],al |
004013D5 | 0FB645 14 | movzx eax,byte ptr ss:[ebp+14] |
004013D9 | 0FB64D 10 | movzx ecx,byte ptr ss:[ebp+10] |
004013DD | 03C8 | add ecx,eax |
004013DF | 884D 10 | mov byte ptr ss:[ebp+10],cl |
004013E2 | EB C9 | jmp crackme.4013AD |
004013E4 | 8BE5 | mov esp,ebp |
004013E6 | 5D | pop ebp |
004013E7 | C2 1000 | ret 10 |
Ci sono alcune possibili strategie:
- usare un disassembler come IDA Pro o Ghidra che consente di ottenere una rappresentazione ad alto livello
- copiare questo frammento di codice e usarlo inline con del codice in C/C++
- tradurlo in codice di alto livello "manualmente"
Bando alle ciance, iniziamo! La locazione
[ebp-4]
è la variabile contatore del ciclo. Seguendo il JMP si nota infatti che viene assegnata al registro ECX, e poi confrontata con [ebp+C]
(uno dei parametri passati alla funzione).Prima di proseguire, noi possiamo capire a colpo d'occhio che: i parametri passati alla funzione sono 4, lo capiamo dalla funzione chiamante, che fa il "push" di 4 elementi sullo stack, ma anche dal "ret 10" di questa funzione. 10h sono 16-byte (4 elementi da 32bit).
Dai push che vengono fatti, sappiamo che EBP+8 contiene un indirizzo, mentre EBP+C contiene un valore usato come "massimo" nella condizione del ciclo. Poi ci sono altri 2 parametri.
Visto che viene decifrata la stringa visualizzata a console, possiamo ipotizzare che l'indirizzo passato sia quello della stringa (cifrata).
Quindi diciamo che:
C:
void str_decrypt(char *str, int len, uint32_t arg3, uint32_t arg4)
{
for(int i=0; i<len; i++)
{
......
}
}
Proseguiamo ora dall'indirizzo 0x004013BE. L'indirizzo passato viene assegnato al registro EDX, e subito dopo EDX viene incrementato del valore che ha il contatore. L'istruzione successiva alla ADD accede all'indirizzo puntato dalla EDX: in pratica viene preso l'i-esimo carattere della stringa incrementando il puntatore ad ogni ciclo.
Al registro ECX viene assegnato ECX+10, che non sappiamo ancora esattamente cosa voglia dire, ma è il terzo parametro passato alla funzione.
Da osservare poi un "dettaglio": MOVSX ci fa capire che l'operazione avviene su un numero con segno, mentre MOVZX ci indica che l'operazione avviene su un numero unsigned.
Proseguendo la traduzione, avviene lo XOR tra il primo carattere letto dalla stringa e il primo byte del terzo parametro passato alla funzione. Questo valore viene assegnato nuovamente alla i-esima posizione della stringa.
Il terzo parametro viene modificato andando a sommare il valore del terzo parametro + il quarto parametro.
Il codice risultante è:
C:
void str_decrypt(char *str, int len, uint32_t arg3, uint32_t arg4)
{
for(int i=0; i<len; i++)
{
int8_t eax = ((int8_t)str[i]);
uint8_t ecx = ((uint8_t)arg3);
eax ^= ecx;
str[i] = eax;
arg3 = ((uint8_t) arg3) + ((uint8_t) arg4);
}
}
A questo punto per facilitare le cose ho scritto un programmino che va a leggere direttamente sul file EXE aprendolo come file binario. L'indirozzo di memoria da leggere l'ho individuato a mano, e contiene il testo
uDNOBQ↨_S4&4i:.><<{.
. Usando l'algoritmo sopra riportato otteniamo Please enter valid password :
, che è esattamente il testo che otteniamo eseguendo il programma!Ho guardato le chiamate a questa funzione, e viene chiamata un totale di 3 volte. Ho quindi ampliato quanto avevo scritto per decifrare le stringhe; il funzionamento è appunto sempre il medesimo, cambia la locazione di memoria passata e gli altri valori (il terzo parametro è di fatto la chiave per la decodifica del singolo carattere).
Il codice completo è questo:
C:
#include <stdio.h>
#include <stdint.h>
#define ST_LEN 3
/*
;
; decrypt strings using XOR
; @param1: crypted text
; @param2: len of crypted text
; @param3: the "key"
; @param4: adjusting value (?)
;
*/
void str_decrypt(char *str, int len, uint32_t arg3, uint32_t arg4)
{
for(int i=0; i<len; i++)
{
int8_t eax = ((int8_t)str[i]);
uint8_t ecx = ((uint8_t)arg3);
eax ^= ecx;
str[i] = eax;
arg3 = ((uint8_t) arg3) + ((uint8_t) arg4);
}
}
int main(int argc, char *argv[])
{
if(argc != 2)
{
printf("Usage: eset_str_extractor.exe <function>\n\n\t- function:\n\t\t1. decrypt string");
exit(-1);
}
FILE *fhandle = fopen("crackme.exe", "rb");
if(*argv[1] == '1')
{
uint32_t offs[ST_LEN] = {0x16000, 0x16038, 0x16020};
uint32_t lens[ST_LEN] = {31, 0x6e, 17};
uint32_t arg3[ST_LEN] = {0x25,0x12, 0x16};
uint32_t arg4[ST_LEN] = {3,5, 7};
for(int i=0; i<ST_LEN; i++)
{
char *str = (char*) calloc(lens[i], sizeof(char));
fseek(fhandle, offs[i], SEEK_SET);
fread(str, sizeof(char), lens[i], fhandle);
printf("crypted:\n\t%s\n", str);
str_decrypt(str, lens[i], arg3[i], arg4[i]);
printf("decrypted:\n\t%s\n\n", str);
free(str);
}
}
fclose(fhandle);
return 0;
}
C'è sicuramente qualche errorino in quanto ho scritto, in quanto termina un pò "bruscamente", ma comunque ciò che deve!
L'output è anche piuttosto interessante...
Codice:
crypted:
uDNOBQ↨_S4&4i:.><<{.
decrypted:
Please enter valid password :
crypted:
3PsNB♂GZHTji☻:,)♫☻L↓‼↨┐Çý³°ý°ÉÆ裨¾┴│¢╗¡┐┘│Ë˦aomcM)}♣↨☼♣♂♠I+%3%U;K]U↓↨Õ¹ıñ┼¢ƒçüäå▒ú¡½¢Å´â├├═æƒØs]2M5'?▬↑↨9
decrypted:
!Good work. Little help:
char[8] = 85
char[0] + char[2] = 128
char[4] - char[7] = -50
char[6] + char[9] = 219
crypted:
AoKEU↓0&=&+♀↑§Yuå
decrypted:
Wrong password!
Visto così al momento il secondo messaggio non dice molto, ma potrebbe tornarci utile per comprendere l'algoritmo.
3. L'algoritmo di decodifica
Riporto la parte che davvero ci interessa, ovvero quella che fa il check della password che andiamo ad inserire. E' abbastanza lungo come codice, ma per consentirne la comprensione, lo riporto comunque tutto:
Codice:
00401455 | C745 F4 00000000 | mov dword ptr ss:[ebp-C],0 |
0040145C | 6A 00 | push 0 |
0040145E | 8D55 F4 | lea edx,dword ptr ss:[ebp-C] |
00401461 | 52 | push edx |
00401462 | 6A 0A | push A |
00401464 | 8D45 E0 | lea eax,dword ptr ss:[ebp-20] |
00401467 | 50 | push eax |
00401468 | 8B0D 68904100 | mov ecx,dword ptr ds:[419068] |
0040146E | 51 | push ecx |
0040146F | FF15 10204100 | call dword ptr ds:[<&ReadConsoleA>] |
00401475 | FF15 0C204100 | call dword ptr ds:[<&GetTickCount>] |
0040147B | 8945 EC | mov dword ptr ss:[ebp-14],eax |
0040147E | BA 01000000 | mov edx,1 |
00401483 | 6BD2 07 | imul edx,edx,7 |
00401486 | 0FBE4415 E0 | movsx eax,byte ptr ss:[ebp+edx-20] |
0040148B | B9 01000000 | mov ecx,1 |
00401490 | 6BC9 06 | imul ecx,ecx,6 |
00401493 | 0FBE540D E0 | movsx edx,byte ptr ss:[ebp+ecx-20] |
00401498 | 03C2 | add eax,edx |
0040149A | 3D CD000000 | cmp eax,CD |
0040149F | 0F85 6D010000 | jne crackme.401612 |
004014A5 | B8 01000000 | mov eax,1 |
004014AA | C1E0 03 | shl eax,3 |
004014AD | 0FBE4C05 E0 | movsx ecx,byte ptr ss:[ebp+eax-20] |
004014B2 | BA 01000000 | mov edx,1 |
004014B7 | 6BD2 05 | imul edx,edx,5 |
004014BA | 0FBE4415 E0 | movsx eax,byte ptr ss:[ebp+edx-20] |
004014BF | 03C8 | add ecx,eax |
004014C1 | 81F9 C9000000 | cmp ecx,C9 |
004014C7 | 0F85 45010000 | jne crackme.401612 |
004014CD | B9 01000000 | mov ecx,1 |
004014D2 | 6BC9 07 | imul ecx,ecx,7 |
004014D5 | 0FBE540D E0 | movsx edx,byte ptr ss:[ebp+ecx-20] |
004014DA | B8 01000000 | mov eax,1 |
004014DF | 6BC0 06 | imul eax,eax,6 |
004014E2 | 0FBE4C05 E0 | movsx ecx,byte ptr ss:[ebp+eax-20] |
004014E7 | 03D1 | add edx,ecx |
004014E9 | B8 01000000 | mov eax,1 |
004014EE | 6BC0 03 | imul eax,eax,3 |
004014F1 | 0FBE4C05 E0 | movsx ecx,byte ptr ss:[ebp+eax-20] |
004014F6 | 03D1 | add edx,ecx |
004014F8 | 81FA 3A010000 | cmp edx,13A |
004014FE | 0F85 0E010000 | jne crackme.401612 |
00401504 | BA 01000000 | mov edx,1 |
00401509 | 6BD2 09 | imul edx,edx,9 |
0040150C | 0FBE4415 E0 | movsx eax,byte ptr ss:[ebp+edx-20] |
00401511 | B9 01000000 | mov ecx,1 |
00401516 | C1E1 02 | shl ecx,2 |
00401519 | 0FBE540D E0 | movsx edx,byte ptr ss:[ebp+ecx-20] |
0040151E | 03C2 | add eax,edx |
00401520 | B9 01000000 | mov ecx,1 |
00401525 | C1E1 03 | shl ecx,3 |
00401528 | 0FBE540D E0 | movsx edx,byte ptr ss:[ebp+ecx-20] |
0040152D | 03C2 | add eax,edx |
0040152F | B9 01000000 | mov ecx,1 |
00401534 | 6BC9 05 | imul ecx,ecx,5 |
00401537 | 0FBE540D E0 | movsx edx,byte ptr ss:[ebp+ecx-20] |
0040153C | 03C2 | add eax,edx |
0040153E | 3D 6F010000 | cmp eax,16F |
00401543 | 0F85 C9000000 | jne crackme.401612 |
00401549 | B8 01000000 | mov eax,1 |
0040154E | C1E0 00 | shl eax,0 |
00401551 | 0FBE4C05 E0 | movsx ecx,byte ptr ss:[ebp+eax-20] |
00401556 | BA 01000000 | mov edx,1 |
0040155B | 6BD2 00 | imul edx,edx,0 |
0040155E | 0FBE4415 E0 | movsx eax,byte ptr ss:[ebp+edx-20] |
00401563 | 03C8 | add ecx,eax |
00401565 | 81F9 C2000000 | cmp ecx,C2 |
0040156B | 0F85 A1000000 | jne crackme.401612 |
00401571 | B9 01000000 | mov ecx,1 |
00401576 | 6BC9 00 | imul ecx,ecx,0 |
00401579 | 0FBE540D E0 | movsx edx,byte ptr ss:[ebp+ecx-20] |
0040157E | B8 01000000 | mov eax,1 |
00401583 | C1E0 00 | shl eax,0 |
00401586 | 0FBE4C05 E0 | movsx ecx,byte ptr ss:[ebp+eax-20] |
0040158B | 03D1 | add edx,ecx |
0040158D | B8 01000000 | mov eax,1 |
00401592 | D1E0 | shl eax,1 |
00401594 | 0FBE4C05 E0 | movsx ecx,byte ptr ss:[ebp+eax-20] |
00401599 | 03D1 | add edx,ecx |
0040159B | B8 01000000 | mov eax,1 |
004015A0 | 6BC0 03 | imul eax,eax,3 |
004015A3 | 0FBE4C05 E0 | movsx ecx,byte ptr ss:[ebp+eax-20] |
004015A8 | 03D1 | add edx,ecx |
004015AA | B8 01000000 | mov eax,1 |
004015AF | C1E0 02 | shl eax,2 |
004015B2 | 0FBE4C05 E0 | movsx ecx,byte ptr ss:[ebp+eax-20] |
004015B7 | 03D1 | add edx,ecx |
004015B9 | B8 01000000 | mov eax,1 |
004015BE | 6BC0 05 | imul eax,eax,5 |
004015C1 | 0FBE4C05 E0 | movsx ecx,byte ptr ss:[ebp+eax-20] |
004015C6 | 03D1 | add edx,ecx |
004015C8 | B8 01000000 | mov eax,1 |
004015CD | 6BC0 06 | imul eax,eax,6 |
004015D0 | 0FBE4C05 E0 | movsx ecx,byte ptr ss:[ebp+eax-20] |
004015D5 | 03D1 | add edx,ecx |
004015D7 | B8 01000000 | mov eax,1 |
004015DC | 6BC0 07 | imul eax,eax,7 |
004015DF | 0FBE4C05 E0 | movsx ecx,byte ptr ss:[ebp+eax-20] |
004015E4 | 03D1 | add edx,ecx |
004015E6 | B8 01000000 | mov eax,1 |
004015EB | C1E0 03 | shl eax,3 |
004015EE | 0FBE4C05 E0 | movsx ecx,byte ptr ss:[ebp+eax-20] |
004015F3 | 03D1 | add edx,ecx |
004015F5 | B8 01000000 | mov eax,1 |
004015FA | 6BC0 09 | imul eax,eax,9 |
004015FD | 0FBE4C05 E0 | movsx ecx,byte ptr ss:[ebp+eax-20] |
00401602 | 03D1 | add edx,ecx |
00401604 | 81FA 9B030000 | cmp edx,39B |
0040160A | 75 06 | jne crackme.401612 |
0040160C | C645 FF 01 | mov byte ptr ss:[ebp-1],1 |
00401610 | EB 04 | jmp crackme.401616 |
00401612 | C645 FF 00 | mov byte ptr ss:[ebp-1],0 |
Ignoriamo il GetTickCount momentaneamente, anche se è piuttosto strano a vedersi, e fa pensare a qualche controllo successivo, e concentriamoci prima sull'algoritmo.
Il primo check avviene qui:
Codice:
0040147E | BA 01000000 | mov edx,1 |
00401483 | 6BD2 07 | imul edx,edx,7 |
00401486 | 0FBE4415 E0 | movsx eax,byte ptr ss:[ebp+edx-20] |
0040148B | B9 01000000 | mov ecx,1 |
00401490 | 6BC9 06 | imul ecx,ecx,6 |
00401493 | 0FBE540D E0 | movsx edx,byte ptr ss:[ebp+ecx-20] |
00401498 | 03C2 | add eax,edx |
0040149A | 3D CD000000 | cmp eax,CD |
0040149F | 0F85 6D010000 | jne crackme.401612 |
Qui cosa sappiamo? Sappiamo che l'input viene memorizzato nell'indirizzo passato alla ReadConsoleA, ovvero
[EBP-20]
.Quindi questo codice usa EDX come indice nel nostro buffer di input e viene presa la posizione 7. La IMUL in pratica è
EDX = EDX * 7
.Avviene la medesima cosa utilizzando il registro ECX, che viene inizializzato a 1 e moltiplicato per 6. Quindi viene presa la posizione 6.
Questi 2 valori vengono sommati e il valore risultante viene confrontato con 0xCD. Se non è uguale, skippa direttamente tutti gli altri controlli.
Regola 1,
str[7] + str[6] = 205
.Proseguiamo:
Codice:
004014A5 | B8 01000000 | mov eax,1 |
004014AA | C1E0 03 | shl eax,3 |
004014AD | 0FBE4C05 E0 | movsx ecx,byte ptr ss:[ebp+eax-20] |
004014B2 | BA 01000000 | mov edx,1 |
004014B7 | 6BD2 05 | imul edx,edx,5 |
004014BA | 0FBE4415 E0 | movsx eax,byte ptr ss:[ebp+edx-20] |
004014BF | 03C8 | add ecx,eax |
004014C1 | 81F9 C9000000 | cmp ecx,C9 |
004014C7 | 0F85 45010000 | jne crackme.401612 |
Sarà più sintetico, in quanto il codice è analogo.
Qui avviene lo shift verso sx di 3 bit del numero 1, quindi il risultato finale sarà 8.
Regola 2,
str[8] + str[5] = 201
Anche qui, se il valore non corrisponde, si verifica il JNE.
Codice:
004014CD | B9 01000000 | mov ecx,1 |
004014D2 | 6BC9 07 | imul ecx,ecx,7 |
004014D5 | 0FBE540D E0 | movsx edx,byte ptr ss:[ebp+ecx-20] |
004014DA | B8 01000000 | mov eax,1 |
004014DF | 6BC0 06 | imul eax,eax,6 |
004014E2 | 0FBE4C05 E0 | movsx ecx,byte ptr ss:[ebp+eax-20] |
004014E7 | 03D1 | add edx,ecx |
004014E9 | B8 01000000 | mov eax,1 |
004014EE | 6BC0 03 | imul eax,eax,3 |
004014F1 | 0FBE4C05 E0 | movsx ecx,byte ptr ss:[ebp+eax-20] |
004014F6 | 03D1 | add edx,ecx |
004014F8 | 81FA 3A010000 | cmp edx,13A |
004014FE | 0F85 0E010000 | jne crackme.401612 |
Qui già a prima vista i caratteri sono 3. La somma viene infatti fatta in 2 step distinti.
Regola 3,
str[7] + str[6] + str[3] = 314
Altro frammento:
Codice:
00401504 | BA 01000000 | mov edx,1 |
00401509 | 6BD2 09 | imul edx,edx,9 |
0040150C | 0FBE4415 E0 | movsx eax,byte ptr ss:[ebp+edx-20] |
00401511 | B9 01000000 | mov ecx,1 |
00401516 | C1E1 02 | shl ecx,2 |
00401519 | 0FBE540D E0 | movsx edx,byte ptr ss:[ebp+ecx-20] |
0040151E | 03C2 | add eax,edx |
00401520 | B9 01000000 | mov ecx,1 |
00401525 | C1E1 03 | shl ecx,3 |
00401528 | 0FBE540D E0 | movsx edx,byte ptr ss:[ebp+ecx-20] |
0040152D | 03C2 | add eax,edx |
0040152F | B9 01000000 | mov ecx,1 |
00401534 | 6BC9 05 | imul ecx,ecx,5 |
00401537 | 0FBE540D E0 | movsx edx,byte ptr ss:[ebp+ecx-20] |
0040153C | 03C2 | add eax,edx |
0040153E | 3D 6F010000 | cmp eax,16F |
00401543 | 0F85 C9000000 | jne crackme.401612 |
Anche qui si fa uso di qualche shift, i valori sono 4.
Regola 4,
str[9] + str[4] + str[8] + str[5] = 367
.Altro frammento:
Codice:
00401549 | B8 01000000 | mov eax,1 |
0040154E | C1E0 00 | shl eax,0 |
00401551 | 0FBE4C05 E0 | movsx ecx,byte ptr ss:[ebp+eax-20] |
00401556 | BA 01000000 | mov edx,1 |
0040155B | 6BD2 00 | imul edx,edx,0 |
0040155E | 0FBE4415 E0 | movsx eax,byte ptr ss:[ebp+edx-20] |
00401563 | 03C8 | add ecx,eax |
00401565 | 81F9 C2000000 | cmp ecx,C2 |
0040156B | 0F85 A1000000 | jne crackme.401612 |
Regola 5,
str[1] + str[0] = 194
.Ultima parte, la più lunga, sotto spoiler:
Codice:
00401571 | B9 01000000 | mov ecx,1 |
00401576 | 6BC9 00 | imul ecx,ecx,0 |
00401579 | 0FBE540D E0 | movsx edx,byte ptr ss:[ebp+ecx-20] |
0040157E | B8 01000000 | mov eax,1 |
00401583 | C1E0 00 | shl eax,0 |
00401586 | 0FBE4C05 E0 | movsx ecx,byte ptr ss:[ebp+eax-20] |
0040158B | 03D1 | add edx,ecx |
0040158D | B8 01000000 | mov eax,1 |
00401592 | D1E0 | shl eax,1 |
00401594 | 0FBE4C05 E0 | movsx ecx,byte ptr ss:[ebp+eax-20] |
00401599 | 03D1 | add edx,ecx |
0040159B | B8 01000000 | mov eax,1 |
004015A0 | 6BC0 03 | imul eax,eax,3 |
004015A3 | 0FBE4C05 E0 | movsx ecx,byte ptr ss:[ebp+eax-20] |
004015A8 | 03D1 | add edx,ecx |
004015AA | B8 01000000 | mov eax,1 |
004015AF | C1E0 02 | shl eax,2 |
004015B2 | 0FBE4C05 E0 | movsx ecx,byte ptr ss:[ebp+eax-20] |
004015B7 | 03D1 | add edx,ecx |
004015B9 | B8 01000000 | mov eax,1 |
004015BE | 6BC0 05 | imul eax,eax,5 |
004015C1 | 0FBE4C05 E0 | movsx ecx,byte ptr ss:[ebp+eax-20] |
004015C6 | 03D1 | add edx,ecx |
004015C8 | B8 01000000 | mov eax,1 |
004015CD | 6BC0 06 | imul eax,eax,6 |
004015D0 | 0FBE4C05 E0 | movsx ecx,byte ptr ss:[ebp+eax-20] |
004015D5 | 03D1 | add edx,ecx |
004015D7 | B8 01000000 | mov eax,1 |
004015DC | 6BC0 07 | imul eax,eax,7 |
004015DF | 0FBE4C05 E0 | movsx ecx,byte ptr ss:[ebp+eax-20] |
004015E4 | 03D1 | add edx,ecx |
004015E6 | B8 01000000 | mov eax,1 |
004015EB | C1E0 03 | shl eax,3 |
004015EE | 0FBE4C05 E0 | movsx ecx,byte ptr ss:[ebp+eax-20] |
004015F3 | 03D1 | add edx,ecx |
004015F5 | B8 01000000 | mov eax,1 |
004015FA | 6BC0 09 | imul eax,eax,9 |
004015FD | 0FBE4C05 E0 | movsx ecx,byte ptr ss:[ebp+eax-20] |
00401602 | 03D1 | add edx,ecx |
00401604 | 81FA 9B030000 | cmp edx,39B |
0040160A | 75 06 | jne crackme.401612 |
Il procedimento è analogo, e ne deriviamo un'ultima regola.
Regola 6,
str[0] + str[1] + str[2] + str[3] + str[4] + str[5] + str[6] + str[7] + str[8] + str[9] = 923
.Ok, a questo punto abbiamo alcuni elementi, e possiamo provare a riordinare le cose:
Codice:
str[7] + str[6] = 205
str[8] + str[5] = 201
str[7] + str[6] + str[3] = 314
str[9] + str[4] + str[8] + str[5] = 367
str[1] + str[0] = 194
str[0] + str[1] + str[2] + str[3] + str[4] + str[5] + str[6] + str[7] + str[8] + str[9] = 923
Aggiungendo infine l'aiuto che abbiamo avuto dalle stringhe decifrate (è un caso che viene restituito ad una determinata condizione, che probabilmente non incontreremo, avendo avuto l'aiuto in anticipo):
Codice:
char[8] = 85
char[0] + char[2] = 128
char[4] - char[7] = -50
char[6] + char[9] = 219
Sulla base dei valori che abbiamo possiamo determinare per mezzo di alcune sottrazioni i valori che ancora... non abbiamo.
Codice:
ch[2] = 923 – 194 – 367 – 314 = 48
ch[0] = 128 – 48 = 80
ch[1] = 194 – 80 = 114
ch[3] = 314 – 205 = 109
ch[5] = 201 – 85 = 116
ch[8] = 85
Trasformando in char questi valori si ottiene:
Codice:
ch[0] = 80 → P
ch[1] = 114 → r
ch[2] = 48 → 0
ch[3] = 109 → m
ch[4] = ???
ch[5] = 116 → t
ch[6] = ???
ch[7] = ???
ch[8] = 85 → U
ch[9] = ???
Ero un pò a corto di idee arrivato qui, in quanto le incognite non sono poche, e non riuscivo a ricavare altri valori (ad esempio il ch4). Probabilmente la più semplice da ricavare è ch4 in quanto 6, 7 e 9 fanno parte di equazioni più complesse.
Ciò che ho fatto è questo: ho considerato la somma delle 4 costanti che danno come risultato 367, in quanto 2 di queste sono ch5 + ch8 = 201. Quindi possiamo anche ricavare ch4 + ch9:
Codice:
ch[9] + ch[4] = 367 - 201 = 166
che possiamo definire, Regola 7 e anche la Regola 8
Codice:
ch[6] + ch[7] = str[7] + str[6] + str[3] = 314 - 109 = 205
Ho optato per un brutto brute force, ma lo lascio per dopo. Ora cerchiamo di usare un input che ci consenta di bypassare i vari controlli...
4. Trovare un input corretto
Ho optato per una sequenza di caratteri sicuramente scorretta, ma che permette di bypassare i controlli (ho usato
kW0mIoq\Z]‘
)Quando la somma dei caratteri è corretta vengono eseguite queste istruzioni (il primo JNE è l'ultimo presente nel blocco sopra):
Codice:
0040160A | 75 06 | jne crackme.401612 |
0040160C | C645 FF 01 | mov byte ptr ss:[ebp-1],1 |
00401610 | EB 04 | jmp crackme.401616 |
00401612 | C645 FF 00 | mov byte ptr ss:[ebp-1],0 |
00401616 | 0FB655 FF | movzx edx,byte ptr ss:[ebp-1] |
0040161A | 85D2 | test edx,edx |
0040161C | 0F84 EF000000 | je crackme.401711 |
00401622 | 64:A1 30000000 | mov eax,dword ptr fs:[30] |
00401628 | 0FB640 02 | movzx eax,byte ptr ds:[eax+2] |
0040162C | 85C0 | test eax,eax |
0040162E | 75 02 | jne crackme.401632 |
00401630 | EB 08 | jmp crackme.40163A |
00401632 | 6A 00 | push 0 |
00401634 | FF15 00204100 | call dword ptr ds:[<&ExitProcess>] |
0040163A | FF15 0C204100 | call dword ptr ds:[<&GetTickCount>] |
00401640 | 8945 F0 | mov dword ptr ss:[ebp-10],eax |
00401643 | 8B45 F0 | mov eax,dword ptr ss:[ebp-10] |
00401646 | 2B45 EC | sub eax,dword ptr ss:[ebp-14] |
00401649 | 83F8 64 | cmp eax,64 | 64:'d'
0040164C | 76 08 | jbe crackme.401656 |
0040164E | 6A 00 | push 0 |
00401650 | FF15 00204100 | call dword ptr ds:[<&ExitProcess>] |
00401656 | 6A 0A | push A |
00401658 | 8D4D E0 | lea ecx,dword ptr ss:[ebp-20] |
0040165B | 51 | push ecx |
0040165C | E8 9FFCFFFF | call crackme.401300 |
00401661 | 3D 14F92819 | cmp eax,1928F914 |
00401666 | 75 61 | jne crackme.4016C9 |
Quindi, contiene
EBP-1
un flag che identifica l'esito dei check precedenti. Il primo TEST (che è una AND bit a bit) verifica il valore in EDX: se è 0, si verifica il JE, altrimenti prosegue; se il JE si verifica significa ovviamente che il flag è "false", e quindi che non abbiamo superato uno dei controlli precedenti.Nel nostro caso andiamo avanti. Il codice successivo l'abbiamo già visto: è di fatto il corpo di isDebuggerPresent. Anche in questo caso dobbiamo bypassare il JNE, altrimenti il programma termina.
Ed ecco che torna il GetTickCount: viene fatta la sottrazione tra EBP-10 (il tempo attuale) e EBP-14, che è il valore salvato inizialmente dalla prima GetTickCount. Il valore usato, 0x64, è piccolo abbastanza da non superare il controllo, e quindi portare alla ExitProcess.
In sostanza la logica è: SE il tempo intercorso tra la prima GetTickCount e la seconda è inferiore a 0x64 allora vuol dire che siamo stati abbastnaza veloci con l'esecuzione delle istruzioni che fanno i controlli; ovviamente, già con un solo breakpoint, questo valore verrà ampiamente superato e quindi... vuol dire che stiamo usando un debugger.
Anche in questo caso bypassiamo il check, invertendo la condizione, o modificando anche solamente il flag Z di EFLAGS.
Saltando all'indirizzo al quale punta la JBE, vediamo 2 push seguite da una CALL. Si tratta del valore 10d seguito dall'indirizzo del buffer di input (l'inizio della stringa inserita in input).
Prima di guardare il corpo della funzione, noto che il valore di ritorno viene confrontato con una costante numerica: 0x1928F914. Non penso abbia un senso specifico, quindi suppongo sia un hash custom.
Quindi più o meno ora ci si può fare un'idea di cosa potrà fare la funzione chiamata.
Codice:
00401300 | 55 | push ebp |
00401301 | 8BEC | mov ebp,esp |
00401303 | 83EC 08 | sub esp,8 |
00401306 | 837D 08 00 | cmp dword ptr ss:[ebp+8],0 |
0040130A | 75 04 | jne crackme.401310 |
0040130C | 33C0 | xor eax,eax |
0040130E | EB 39 | jmp crackme.401349 |
00401310 | C745 FC 00000000 | mov dword ptr ss:[ebp-4],0 |
00401317 | C745 F8 00000000 | mov dword ptr ss:[ebp-8],0 |
0040131E | EB 09 | jmp crackme.401329 |
00401320 | 8B45 F8 | mov eax,dword ptr ss:[ebp-8] |
00401323 | 83C0 01 | add eax,1 |
00401326 | 8945 F8 | mov dword ptr ss:[ebp-8],eax |
00401329 | 8B4D F8 | mov ecx,dword ptr ss:[ebp-8] |
0040132C | 3B4D 0C | cmp ecx,dword ptr ss:[ebp+C] | [ebp+C]:&"D:\\DispatchCode\\Documents\\crackme.exe"
0040132F | 73 15 | jae crackme.401346 |
00401331 | C14D FC 09 | ror dword ptr ss:[ebp-4],9 |
00401335 | 8B55 08 | mov edx,dword ptr ss:[ebp+8] |
00401338 | 0355 F8 | add edx,dword ptr ss:[ebp-8] |
0040133B | 0FBE02 | movsx eax,byte ptr ds:[edx] |
0040133E | 3345 FC | xor eax,dword ptr ss:[ebp-4] |
00401341 | 8945 FC | mov dword ptr ss:[ebp-4],eax |
00401344 | EB DA | jmp crackme.401320 |
00401346 | 8B45 FC | mov eax,dword ptr ss:[ebp-4] |
00401349 | 8BE5 | mov esp,ebp |
0040134B | 5D | pop ebp |
0040134C | C2 0800 | ret 8 |
La funzione è abbastnaza semplice, e la traduzione in C può essere qualcosa di simile:
C:
int32_t eset_hash(char *number)
{
int32_t local1 = 0;
for(int32_t i=0; i<10; i++)
{
__asm{ ror local1, 9 };
local1 = ((int8_t)(number[i])) ^ local1;
}
return local1;
}
ho preferito usare asm inline al costo di perdere la portabilità su GCC/MinGW (quella sintassi è utilizzata da MSVC), per non implementare la rotazione.
Insomma, l'ipotesi iniziale era corretta: si tratta di un hash che viene generato ruotando di 9bit a destra local1, per applicare poi uno xor tra questo valore e il carattere alla i-esima posizione. Il risultato torna in local1 e così prosegue (notare che il valore scelto non è casuale, infatti la stringa è lunga 10 caratteri).
5. Brute force
E' il momento di andare di forza bruta. Usando le regole del terzo paragrafo, ho messo assieme questo codice:
C:
#include<stdio.h>
#include<stdlib.h>
#include<stdint.h>
#include<stdbool.h>
int32_t eset_hash(char *number)
{
int32_t local1 = 0;
for(int32_t i=0; i<10; i++)
{
__asm{ ror local1, 9 };
local1 = ((int8_t)(number[i])) ^ local1;
}
return local1;
}
/*
ch[7] + ch[6] = 205
ch[8] + ch[5] = 201
ch[7] + ch[6] + ch[3] = 314
ch[9] + ch[4] + ch[8] + ch[5] = 367
ch[1] + ch[0] = 194
*/
char *pwd_gene()
{
char *pwd = calloc(11, sizeof(char));
// ch[6] + ch[9] = 219
int32_t ch67 = 205, ch94 = 166;
// known chars
pwd[0] = 80; // P
pwd[1] = 114; // r
pwd[2] = 48; // 0
pwd[3] = 109; // m
pwd[5] = 116; // t
pwd[8] = 85; // U
int max = ch94 >> 1;
for(int i=0; i<max; i++)
{
pwd[4] = i;
for(int j=max-1; j<ch94; j++)
{
pwd[9] = j;
if(pwd[4] + pwd[9] == ch94)
{
pwd[6] = 219 - pwd[9];
pwd[7] = 205 - pwd[6];
if(eset_hash(pwd) == 0x1928F914)
return pwd;
}
}
}
free(pwd);
return NULL;
}
int main()
{
char *pwd = pwd_gene();
printf("PWD: %s\n", pwd);
free(pwd);
return 0;
}
Conoscendo le precedenti equazioni, possiamo ottenere i caratteri 6 e 7 dai caratteri 9 e 6. Il 4 e il 9 vengono "provati", andando appunto per tentativi.
L'ooutput ottenuto è:
Codice:
PWD: Pr0m3theUs
Finalmente ci siamo! A questo punto non resta che inserirla in input e vedere che succede:
Please enter valid password : Pr0m3theUs
Congratulations! You guessed the right password, but the message you see is wrong.
Try to look for some unreferenced data, that can be decrypted the same way as this text.
Bene, ma a metà. Non ci resta che una cosa da fare...
6. Ricerca del testo non referenziato
Guardando il messaggio restituito li sopra, sappiamo che dobbiamo cercare del testo non referenziato e che la procedura che decifra è la medesima che ha decifrato quel testo.
Utilizzando il debugger si può sempre "steppare" e individuare facilmente il codice interessato. Superato il codice che riguarda GetTickCount si vede il push di alcuni parametri sullo stack e la chiamata a una procedura:
Codice:
00401668 | 6A 02 | push 2 |
0040166A | 8B55 F4 | mov edx,dword ptr ss:[ebp-C] |
0040166D | 52 | push edx | edx:"v:"
0040166E | 8D45 E0 | lea eax,dword ptr ss:[ebp-20] |
00401671 | 50 | push eax |
00401672 | 68 00010000 | push 100 |
00401677 | 8B0D 34804100 | mov ecx,dword ptr ds:[418034] |
0040167D | 51 | push ecx |
0040167E | E8 CDFCFFFF | call crackme.401350 |
Alla procedura - sullo stack, per meglio dire - vengono passati 20-byte: il primo push che vediamo è l'ultimo parametro della procedura, ed è appunto il valore 2; usando il debugger vediamo che in EDX è presente il valore 0xA in EAX invece l'indirizzo del primo byte della stringa decifrata prima (Pr0m3theUs), ovvero 0x0019FF08, il valore 0x100 in decimale è 256 e in ECX invece c'è l'indirizzo di una stringa (il testo cifrato).
Codice:
00401350 | 55 | push ebp |
00401351 | 8BEC | mov ebp,esp |
00401353 | 51 | push ecx |
00401354 | C745 FC 00000000 | mov dword ptr ss:[ebp-4],0 |
0040135B | EB 09 | jmp crackme.401366 |
0040135D | 8B45 FC | mov eax,dword ptr ss:[ebp-4] |
00401360 | 83C0 01 | add eax,1 |
00401363 | 8945 FC | mov dword ptr ss:[ebp-4],eax |
00401366 | 8B4D FC | mov ecx,dword ptr ss:[ebp-4] |
00401369 | 3B4D 0C | cmp ecx,dword ptr ss:[ebp+C] |
0040136C | 73 2A | jae crackme.401398 |
0040136E | 8B55 08 | mov edx,dword ptr ss:[ebp+8] |
00401371 | 0355 FC | add edx,dword ptr ss:[ebp-4] |
00401374 | 0FBE0A | movsx ecx,byte ptr ds:[edx] |
00401377 | 8B45 FC | mov eax,dword ptr ss:[ebp-4] |
0040137A | 33D2 | xor edx,edx |
0040137C | F775 14 | div dword ptr ss:[ebp+14] |
0040137F | 8B45 10 | mov eax,dword ptr ss:[ebp+10] |
00401382 | 0FBE1410 | movsx edx,byte ptr ds:[eax+edx] |
00401386 | 0FB645 18 | movzx eax,byte ptr ss:[ebp+18] |
0040138A | 03D0 | add edx,eax |
0040138C | 33CA | xor ecx,edx |
0040138E | 8B55 08 | mov edx,dword ptr ss:[ebp+8] |
00401391 | 0355 FC | add edx,dword ptr ss:[ebp-4] |
00401394 | 880A | mov byte ptr ds:[edx],cl |
00401396 | EB C5 | jmp crackme.40135D |
00401398 | 8BE5 | mov esp,ebp |
0040139A | 5D | pop ebp |
0040139B | C2 1400 | ret 14 |
Anche questo codice ha somiglianze con quelli già visti sopra; EBP-4 è il contatore del ciclo, che viene inizializzato a 0. Nel registro ECX viene memorizzato questo indice e viene verificato se è maggiore o uguale al secondo parametro (la lunghezza del testo) il ciclo termina.
EDX memorizza il primo carattere della stringa; questo indirizzo viene incrementato in base al valore del contatore, che inizialmente sarà come detto sopra 0.
L'aspetto differente rispetto agli altri codici è soprattutto la presenza della DIV (divisione). Non viene in realtà usato il risultato della divisione, quanto più il resto di questa divisione. In sostanza viene fatto
i % arg4
, dove i è il contatore e arg4 è in questo caso il valore 0xA (10d). Questo parametro è la lunghezza della chiave usata per decifrare, che guarda un pò, è proprio Pr0m3theUs.Riporto direttamente il codice in C:
C:
void unknown_str(char *str, int len, char *arg3, int32_t arg4, uint8_t arg5)
{
for(int i=0; i<len; i++)
{
int8_t ecx = ((int8_t)str[i]);
int8_t edx = (int8_t)arg3[i % arg4];
edx += (uint8_t)arg5;
ecx ^= edx;
str[i] = (uint8_t)ecx;
}
}
A questo punto mi sono accertato che questo codice funzionasse, testandolo sulla stringa da decodificare; ho modificato il codice mostrato sopra, creando un piccolissimo menu con 2 opzioni così da scegliere il tipo di decodifica necessaria.
Questo è il risultato:
C:
#include <stdio.h>
#include <stdint.h>
#define ST_LEN 3
void str_decrypt(char *str, int len, uint32_t arg3, uint32_t arg4)
{
for(int i=0; i<len; i++)
{
int8_t eax = ((int8_t)str[i]);
uint8_t ecx = ((uint8_t)arg3);
eax ^= ecx;
str[i] = eax;
arg3 = ((uint8_t) arg3) + ((uint8_t) arg4);
}
}
void unknown_str(char *str, int len, char *arg3, int32_t arg4, uint8_t arg5)
{
for(int i=0; i<len; i++)
{
int8_t ecx = ((int8_t)str[i]);
int8_t edx = (int8_t)arg3[i % arg4];
edx += (uint8_t)arg5;
ecx ^= edx;
str[i] = (uint8_t)ecx;
}
}
int main(int argc, char *argv[])
{
if(argc != 2)
{
printf("Usage: eset_str_extractor.exe <function>\n\n\t- function:\n\t\t1. normal string;\n\t\t2. string xored with pwd");
exit(-1);
}
FILE *fhandle = fopen("crackme.exe", "rb");
if(*argv[1] == '1')
{
uint32_t offs[ST_LEN] = {0x16000, 0x16038, 0x16020};
uint32_t lens[ST_LEN] = {31, 0x6e, 17};
uint32_t arg3[ST_LEN] = {0x25,0x12, 0x16};
uint32_t arg4[ST_LEN] = {3,5, 7};
for(int i=0; i<ST_LEN; i++)
{
char *str = (char*) calloc(lens[i], sizeof(char));
fseek(fhandle, offs[i], SEEK_SET);
fread(str, sizeof(char), lens[i], fhandle);
printf("crypted:\n\t%s\n", str);
str_decrypt(str, lens[i], arg3[i], arg4[i]);
printf("decrypted:\n\t%s\n\n", str);
free(str);
}
}
else
{
char *str = (char*) calloc(256, sizeof(char));
fseek(fhandle, 0x160a8, SEEK_SET);
fread(str, sizeof(char), 256, fhandle);
unknown_str(str, 256, "Pr0m3theUs", 10, 2);
printf("%s", str);
free(str);
}
fclose(fhandle);
return 0;
}
L'indirizzo l'ho ricavato dalla sezione DATA del file EXE, utilizzando PEView.
Il codice è corretto, quindi torno sulla sezione DATA e noto subito dopo questa parte:
Ho selezionato l'indirizzo dove ha inizio il testo cifrato che non viene mai referenziato; il primo byte si trova precisamente in 0x0x161a8.
Quindi ho modificato il codice mostrato sopra aggiungendo anche quella parte:
C:
#include <stdio.h>
#include <stdint.h>
#define ST_LEN 3
void str_decrypt(char *str, int len, uint32_t arg3, uint32_t arg4)
{
for(int i=0; i<len; i++)
{
int8_t eax = ((int8_t)str[i]);
uint8_t ecx = ((uint8_t)arg3);
eax ^= ecx;
str[i] = eax;
arg3 = ((uint8_t) arg3) + ((uint8_t) arg4);
}
}
void unknown_str(char *str, int len, char *arg3, int32_t arg4, uint8_t arg5)
{
for(int i=0; i<len; i++)
{
int8_t ecx = ((int8_t)str[i]);
int8_t edx = (int8_t)arg3[i % arg4];
edx += (uint8_t)arg5;
ecx ^= edx;
str[i] = (uint8_t)ecx;
}
}
int main(int argc, char *argv[])
{
if(argc != 2)
{
printf("Usage: eset_str_extractor.exe <function>\n\n\t- function:\n\t\t1. normal string;\n\t\t2. string xored with pwd");
exit(-1);
}
FILE *fhandle = fopen("crackme.exe", "rb");
if(*argv[1] == '1')
{
uint32_t offs[ST_LEN] = {0x16000, 0x16038, 0x16020};
uint32_t lens[ST_LEN] = {31, 0x6e, 17};
uint32_t arg3[ST_LEN] = {0x25,0x12, 0x16};
uint32_t arg4[ST_LEN] = {3,5, 7};
for(int i=0; i<ST_LEN; i++)
{
char *str = (char*) calloc(lens[i], sizeof(char));
fseek(fhandle, offs[i], SEEK_SET);
fread(str, sizeof(char), lens[i], fhandle);
printf("crypted:\n\t%s\n", str);
str_decrypt(str, lens[i], arg3[i], arg4[i]);
printf("decrypted:\n\t%s\n\n", str);
free(str);
}
}
else
{
char *str = (char*) calloc(256, sizeof(char));
fseek(fhandle, 0x160a8, SEEK_SET);
fread(str, sizeof(char), 256, fhandle);
unknown_str(str, 256, "Pr0m3theUs", 10, 2);
printf("%s\n\n", str);
realloc(str, 67);
fseek(fhandle, 0x161a8, SEEK_SET);
fread(str, sizeof(char), 67, fhandle);
unknown_str(str, 67, "Pr0m3theUs", 10, 2);
printf("%s\n\n", str);
free(str);
}
fclose(fhandle);
return 0;
}
Eseguendo il codice qui sopra con input "2", l'output sarà il seguente:
Ho "oscurato" l'url per non rovinare il divertimento a chi vuole provare (e non rendere le cose troppo comode). Da quell'url si scarica un nuovo zip, con un file exe e una libreria dinamica.
Bene, per il momento è tutto, spero sia stato interessante! :)