GUIDA Reverse engineering, CrackMe #1: Look Closer

DispatchCode

Moderatore
Staff Forum
Utente Èlite
2,223
1,853
CPU
Intel I9-10900KF 3.75GHz 10x 125W
Dissipatore
Gigabyte Aorus Waterforce X360 ARGB
Scheda Madre
Asus 1200 TUF Z590-Plus Gaming ATX DDR4
HDD
1TB NVMe PCI 3.0 x4, 1TB 7200rpm 64MB SATA3
RAM
DDR4 32GB 3600MHz CL18 ARGB
GPU
Nvidia RTX 3080 10GB DDR6
Audio
Integrata 7.1 HD audio
Monitor
LG 34GN850
PSU
Gigabyte P850PM
Case
Phanteks Enthoo Evolv X ARGB
Periferiche
MSI Vigor GK30, mouse Logitech
Net
FTTH Aruba, 1Gb (effettivi: ~950Mb / ~480Mb)
OS
Windows 10 64bit / OpenSUSE Tumbleweed

CrackMe #1: LookCloser​

crackme realizzato da r0B, Crackmes.one


Se lo volete risolvere senza vedere la soluzione, non proseguite!


Ho pensato di proporre questo articolo in quanto il reverse engineering non è uno degli argomenti che ho visto trattare sul forum, e chissà che non possa interessare o incuriosire qualcuno.

Essendo un crackme lo si può risolvere sia "patchando" (crackandolo) sia scrivendo un keygen o comunque comprendendo il funzionamento (la logica) dell'algoritmo; di solito la soluzione richiesta consiste nello scrivere un keygen, raramente è sufficiente una patch (le modalità di risoluzione vengono decise da chi pubblica il crackme).

1. Primi approci​



La prima cosa che ho fatto è stata eseguirlo, per curiosità. Il crackme si presenta con una GUI con due soli campi, una mail e una password. Eseguendolo e inserendo dati "a caso" abbiamo questo output:

screen.png exec.png

Vedere i messaggi di errore e in generale il tipo di messaggio restituito ad una nostra azione è uno dei modi migliori per farsi un'idea di massima sul funzionamento; a volte inoltre si riesce a risalire al codice che effettua una validazione proprio da un messaggio di errore. Questo dipende da un sacco di fattori, non ultime le protezioni.

A questo punto, dopo essersi fatti un'idea di come si presenta, andiamo a vedere tramite un'analisi di base (e statica) se ci sono protezioni e che librerie e funzioni vengono importate.

2. Analisi statica​



Esistono diversi software per queste analisi, e può anche essere utile utilizzarne più di uno. Allo scopo ho utilizzato PEiD, ma ne esistono altri come RDG Packer Detector o ExeInfoPE (che è anche il più recente).
Lo screen sottostante è di PEiD:


peid1.png
Una prima info utile è, a conferma di quanto riportato sul sito dove è stato reperito il crackme, che il linguaggio utilizzato è Assembly. Precisamente è stato assemblato con MASM o con TASM.
Andando a guardare eventuali protezioni, scopriamo - e visto il livello di difficoltà assegnato c'era da aspettarselo - che non sono state utilizzate protezioni (nessuna compressione e il codie non sembra offuscato).


peid2.png
A questo punto guardiamo le funzioni importate, per farci un'idea delle funzionalità. In questo caso ho scelto di utilizzare CFF Explorer.

cffexplorer_user32.png

Come si evince vengono importate solo due librerie, kernel32 e user32. Lo screen riporta anche le funzioni di user32.
Le funzioni di kernel32 sono invece più interessanti:


cffexplorer_kernel32.png

Una delle funzioni particolarmente interessanti è VirtualProtect. Lo spazio degli indirizzi virtuale di un processo non sarebbe alterabile; dico non sarebbe in quanto tramite VirtualProtect è anche possibile modificare il permesso su una pagine così da cambiare i bytes durante l'esecuzione e poi eseguire questi byte.

A questo punto procediamo con l'esecuzione.

3. Iniziamo il disassembly!​



Dando uno sguardo al codice, e superate le inizializzazioni, ci si trova la finestra vista in precedenza. Io ho settato un breakpoint (da ora, bp) all'indirizzo 0x00401129. L'ho settato qui in quanto è la funzione chiamata quando si preme "Register".

Codice:
CPU Disasm
Address   Hex dump          Command                                        Comments
00401129  /$  55            PUSH EBP
0040112A  |.  8BEC          MOV EBP,ESP
0040112C  |.  83C4 E8       ADD ESP,-18
0040112F  |.  68 20204000   PUSH OFFSET 00402020                           ; /lParam = lookcloser.402020
00401134  |.  6A 40         PUSH 40                                        ; |wParam = 64.
00401136  |.  6A 0D         PUSH 0D                                        ; |Msg = WM_GETTEXT
00401138  |.  FF35 10204000 PUSH DWORD PTR DS:[402010]                     ; |hWnd = 000909B8, class = Edit
0040113E  |.  E8 45010000   CALL <JMP.&user32.SendMessageA>                ; \USER32.SendMessageA
00401143  |.  8945 FC       MOV DWORD PTR SS:[LOCAL.1],EAX
00401146  |.  68 60204000   PUSH OFFSET 00402060                           ; /lParam = lookcloser.402060
0040114B  |.  6A 40         PUSH 40                                        ; |wParam = 64.
0040114D  |.  6A 0D         PUSH 0D                                        ; |Msg = WM_GETTEXT
0040114F  |.  FF35 14204000 PUSH DWORD PTR DS:[402014]                     ; |hWnd = 000809F6, class = Edit
00401155  |.  E8 2E010000   CALL <JMP.&user32.SendMessageA>                ; \USER32.SendMessageA
0040115A  |.  8945 F8       MOV DWORD PTR SS:[LOCAL.2],EAX

SendMessage con Msg=WM_GETTEXT copia il contenuto letto all'interno del buffer passato (l'offset che si vede in corrispondenza di lparam).
In EAX viene restituita la lunghezza della stringa, che come si evince viene poi memorizzata sullo stack in due variabili locali, chiamate qui LOCAL.1 e LOCAL.2.

Proseguiamo con l'altra parte di codice, successiva a quella:

Codice:
CPU Disasm
Address   Hex dump          Command                                        Comments
0040115D  |.  837D FC 05    CMP DWORD PTR SS:[LOCAL.1],5
00401161  |.  72 64         JB SHORT 004011C7
00401163  |.  837D F8 08    CMP DWORD PTR SS:[LOCAL.2],8
00401167  |.  75 77         JNE SHORT 004011E0
00401169  |.  FC            CLD
0040116A  |.  8D3D 20204000 LEA EDI,[402020]                               ; ASCII "asdfsdf@mail.it"
00401170  |.  47            INC EDI
00401171  |.  8B4D FC       MOV ECX,DWORD PTR SS:[LOCAL.1]
00401174  |.  B0 40         MOV AL,40
00401176  |.  F2:AE         REPNE SCAS BYTE PTR ES:[EDI]
00401178  |.  83F9 02       CMP ECX,2
0040117B  |.  7E 4A         JLE SHORT 004011C7
0040117D  |.  47            INC EDI
0040117E  |.  B0 2E         MOV AL,2E
00401180  |.  F2:AE         REPNE SCAS BYTE PTR ES:[EDI]
00401182  |.  83F9 02       CMP ECX,2
00401185  |.  7E 40         JLE SHORT 004011C7
00401187  |.  A1 20204000   MOV EAX,DWORD PTR DS:[402020]                  ; ASCII "asdfsdf@mail.it"
0040118C  |.  35 78563412   XOR EAX,12345678
00401191  |.  8D75 E8       LEA ESI,[LOCAL.6]
00401194  |.  56            PUSH ESI                                       ; /Arg2 => OFFSET LOCAL.6
00401195  |.  50            PUSH EAX                                       ; |Arg1
00401196  |.  E8 61FFFFFF   CALL 004010FC                                  ; \lookcloser.004010FC

Le prime istruzioni ci fanno già capire che viene fatto un check sui nostri input. La mail non deve essere minore di 5 caratteri, mentre la password non deve essere diversa da 8 caratteri. Mi sento di asserire ciò in quanto JB significa "Jump if Below", mentre JNE Jump if Not Equals.
Io ho inserito dati casuali, senza guardare prima il codice: infatti la mail è > 5 caratteri, quindi il primo JB non si verifica, ma il JNE si, pertanto ci porta a un messaggio di errore. A questo punto lascio la mail invariata e come password inserisco 8 volte "a".
In particolare questo estratto si fa interessante:

Codice:
CPU Disasm
Address   Hex dump          Command                                        Comments
0040116A  |.  8D3D 20204000 LEA EDI,[402020]                               ; ASCII "asdfsdf@mail.it"
00401170  |.  47            INC EDI
00401171  |.  8B4D FC       MOV ECX,DWORD PTR SS:[LOCAL.1]
00401174  |.  B0 40         MOV AL,40
00401176  |.  F2:AE         REPNE SCAS BYTE PTR ES:[EDI]
00401178  |.  83F9 02       CMP ECX,2
0040117B  |.  7E 4A         JLE SHORT 004011C7
0040117D  |.  47            INC EDI
0040117E  |.  B0 2E         MOV AL,2E
00401180  |.  F2:AE         REPNE SCAS BYTE PTR ES:[EDI]
00401182  |.  83F9 02       CMP ECX,2
00401185  |.  7E 40         JLE SHORT 004011C7

anche senza eseguire il codice possiamo dire che: EDI punta all'offset iniziale della stringa inserita nel campo email. ECX viene inizializzato con la lunghezza della stringa e il motivo è chiaro poco più sotto in presenza di REPNE SCAS.
Si tratta di un'istruzione che scansiona una stringa e grazie al prefisso REPNE scansiona la stringa (con il byte iniziale puntato da EDI) sino a che non trova il carattere 0x40, contenuto in AL. Questo carattere è la "chiocciola", @.
Cosa analoga avviene sotto con il "punto": in sostanza viene fatto un controllo sulla presenza di questi caratteri, per ritenere la mail valida. Se soddisfa i criteri di lunghezza, si prosegue.

Codice:
CPU Disasm
Address   Hex dump          Command                                        Comments
00401187  |.  A1 20204000   MOV EAX,DWORD PTR DS:[402020]                  ; ASCII "asdfsdf@mail.it"
0040118C  |.  35 78563412   XOR EAX,12345678
00401191  |.  8D75 E8       LEA ESI,[LOCAL.6]
00401194  |.  56            PUSH ESI                                       ; /Arg2 => OFFSET LOCAL.6
00401195  |.  50            PUSH EAX                                       ; |Arg1
00401196  |.  E8 61FFFFFF   CALL 004010FC                                  ; \lookcloser.004010FC
0040119B  |.  8D3D 60204000 LEA EDI,[402060]                               ; ASCII "aaaaaaaa"
004011A1  |.  B9 09000000   MOV ECX,9
004011A6  |.  FC            CLD
004011A7  |.  F3:A6         REPE CMPS BYTE PTR DS:[ESI],BYTE PTR ES:[EDI]
004011A9  |.  83F9 00       CMP ECX,0
004011AC  |.  75 32         JNE SHORT 004011E0

L'inizio di questo spezzone mi ha fatto pensare di aver già una soluzione: viene calcolato una sorta di hash facendo lo XOR tra la mail e il valore 0x12345678. Precisamente si tratta dei primi 4-byte (i primi 4 caratteri) della stringa, "asdf". Lo XOR produce questo risultato: 0x74502519.

L'ultima CMPS effettua una comparazione tra i caratteri della password con quelli del risultato dello XOR. E qui si fa interessante. Trovando da subito un carattere diverso la scansione termina.
Guardando il codice poco più sotto ho visto la nostra VirtualProtect... questo è il codice interessato:

Codice:
CPU Disasm
Address   Hex dump          Command                                        Comments
004011F9  /> \BB 1F104000   MOV EBX,0040101F
004011FE  |.  43            INC EBX
004011FF  |.  8B1B          MOV EBX,DWORD PTR DS:[EBX]
00401201  |.  891D 1C204000 MOV DWORD PTR DS:[40201C],EBX
00401207  |.  83EB 07       SUB EBX,7
0040120A  |.  68 4D214000   PUSH OFFSET 0040214D                           ; /pOldProtect = lookcloser.40214D -> PAGE_EXECUTE_READ
0040120F  |.  6A 40         PUSH 40                                        ; |NewProtect = PAGE_EXECUTE_READWRITE
00401211  |.  6A 07         PUSH 7                                         ; |Size = 7
00401213  |.  53            PUSH EBX                                       ; |Address => 7668D7B9
00401214  |.  E8 93000000   CALL <JMP.&kernel32.VirtualProtect>            ; \KERNEL32.VirtualProtect
00401219  |.  A1 1C204000   MOV EAX,DWORD PTR DS:[40201C]
0040121E  |.  66:C700 EBF9  MOV WORD PTR DS:[EAX],0F9EB
00401223  |.  83E8 05       SUB EAX,5
00401226  |.  C600 E9       MOV BYTE PTR DS:[EAX],0E9
00401229  |.  BB 42124000   MOV EBX,00401242                               ; Entry point
0040122E  |.  2BD8          SUB EBX,EAX
00401230  |.  83EB 05       SUB EBX,5
00401233  |.  40            INC EAX
00401234  |.  8918          MOV DWORD PTR DS:[EAX],EBX
00401236  |.  66:C705 14124 MOV WORD PTR DS:[401214],0B9
0040123F  |.  5B            POP EBX
00401240  \.  FFE3          JMP EBX                                        ; Equivalent to RETN

Avviene un salto (JMP) a questo codice prima della lettura dell'input mostrata inizialmente.
La cosa è interessante in quanto vengono scritte alcune istruzioni andando a sovrascrivere del codice. L'indirizzo che viene sovrascritto è quello di MessageBox.
Il codice che viene "iniettato" sovrascrivendo alcuni byte è quello che si vede ad esempio all'indirizzo 0x0040121E (si tratta di EB, 0F). In sostanza già dal solo codice macchina (dal solo opcode) si capisce che si tratta di un JMP. Stessa situazione che si vede due istruzioni più sotto; da notare anche infatti che a EAX vengono sottratti 5 bytes, la lunghezza dell'opcode 0xE9 (che viene scritto sopra all'altra istruzione).
Andando a guardare la MessageBox richiamata si nota infatti...

Codice:
CPU Disasm
Address   Hex dump          Command                                        Comments
7668D7BB    - E9 823AD789   JMP 00401242
7668D7C0    ^ EB F9         JMP SHORT 7668D7BB                             ; ID_X user32.MessageBoxA(hOwner,Text,Caption,Type)
7668D7C2      55            PUSH EBP
7668D7C3      8BEC          MOV EBP,ESP
7668D7C5      6A FF         PUSH -1
7668D7C7      6A 00         PUSH 0
7668D7C9      FF75 14       PUSH DWORD PTR SS:[EBP+14]
7668D7CC      FF75 10       PUSH DWORD PTR SS:[EBP+10]
7668D7CF      FF75 0C       PUSH DWORD PTR SS:[EBP+0C]
7668D7D2      FF75 08       PUSH DWORD PTR SS:[EBP+8]
7668D7D5      E8 D6010000   CALL MessageBoxTimeoutA
7668D7DA      5D            POP EBP
7668D7DB      C2 1000       RETN 10


NOTA: un aspetto interessante è che in tutte queste API di Microsoft è presente un'istruzione "inutile", ovvero MOV EDI, EDI. Questo il codice originale di MessageBox:
Codice:
7663D7C0      8BFF          MOV EDI,EDI                              ; ID_X user32.MessageBoxA(hOwner,Text,Caption,Type)
7663D7C2      55            PUSH EBP
7663D7C3      8BEC          MOV EBP,ESP
7663D7C5      6A FF         PUSH -1
7663D7C7      6A 00         PUSH 0
7663D7C9      FF75 14       PUSH DWORD PTR SS:[EBP+14]
7663D7CC      FF75 10       PUSH DWORD PTR SS:[EBP+10]
7663D7CF      FF75 0C       PUSH DWORD PTR SS:[EBP+0C]
7663D7D2      FF75 08       PUSH DWORD PTR SS:[EBP+8]
7663D7D5      E8 D6010000   CALL MessageBoxTimeoutA
7663D7DA      5D            POP EBP
7663D7DB      C2 1000       RETN 10

La prima istruzione è stata inserita allo scopo di poter essere rimpiazzata; si chiama hot-patching. In pratica viene sovrascritta da un JMP di 2byte, che punta ad un JMP di 5byte, che può puntare a "qualsiasi" indirizzo (nello spazio degli indirizzi del processo). In questo caso chi ha scritto il crackme ha sfruttato questo meccanismo.


Al momento teniamolo qui, e proseguiamo.
Torniamo quindi a queste istruzioni:

Codice:
0040119B  |.  8D3D 60204000 LEA EDI,[402060]                               ; ASCII "aaaaaaaa"
004011A1  |.  B9 09000000   MOV ECX,9
004011A6  |.  FC            CLD
004011A7  |.  F3:A6         REPE CMPS BYTE PTR DS:[ESI],BYTE PTR ES:[EDI]
004011A9  |.  83F9 00       CMP ECX,0
004011AC  |.  75 32         JNE SHORT 004011E0

dobbiamo fare in modo che ci sia corrispondenza tra la mail "cifrata" (con XOR) e la password inserita.
Sappiamo che vengono considerati solo 4-byte, quindi trasformandoli in HEX abbiamo 0x66647361 (viene letta una dword, quindi sono "invertiti" (little-endian)). Applicando lo XOR con 0x12345678 otteniamo 0x74502519, che andremo ad utilizzare come password.
Quindi inserisco quella password con la mail vista sopra, e setto un bp a 0x004011AC: il salto non si verifica e l'esecuzione quindi prosegue qui:

Codice:
CPU Disasm
Address   Hex dump          Command                                        Comments
004011AE  |.  6A 00         PUSH 0                                         ; /Type = MB_OK|MB_DEFBUTTON1|MB_APPLMODAL
004011B0  |.  68 00204000   PUSH OFFSET 00402000                           ; |Caption = "Look Closer"
004011B5  |.  68 EC204000   PUSH OFFSET 004020EC                           ; |Text = "Thank you for supporting our software!"
004011BA  |.  FF35 49214000 PUSH DWORD PTR DS:[402149]                     ; |hOwner = 000C0672, class = #32770, text = Look Closer
004011C0  |.  E8 BD000000   CALL <JMP.&user32.MessageBoxA>                 ; \USER32.MessageBoxA
004011C5  |.  C9            LEAVE
004011C6  |.  C3            RETN

Gli altri casi riguardano la password errata o la mail errata (se non soddisfa i requisiti visti sopra con la presenza di un punto e della chiocciola).
A prima vista sembra fatta! Dalla stringa referenziata all'offset 0x004020EC si vede infatti del contenuto che lascia ben sperare.

Tuttavia, non è proprio così. Ricordate quel codice modificato a runtime che riguardava la MessageBox? La CALL alla MessageBox salta esattamente qui:

Codice:
7663D7BB    - E9 823ADC89   JMP 00401242
7663D7C0    ^ EB F9         JMP SHORT 7663D7BB                       ; ID_X user32.MessageBoxA(hOwner,Text,Caption,Type)

Il JMP più corto salta a quello sopra, che a sua volta salta qui:
Codice:
00401242  /.  83F9 01       CMP ECX,1
00401245  |.  75 15         JNE SHORT 0040125C
00401247  |.  83E3 0F       AND EBX,0000000F
0040124A  |.  83C3 47       ADD EBX,47
0040124D  |.  381D 67204000 CMP BYTE PTR DS:[402067],BL
00401253  |.  75 11         JNE SHORT 00401266
00401255  |.  834424 08 28  ADD DWORD PTR SS:[ARG.2],28
0040125A  |.  EB 0A         JMP SHORT 00401266
0040125C  |>  83F9 00       CMP ECX,0
0040125F  |.  75 05         JNE SHORT 00401266
00401261  |.  836C24 08 28  SUB DWORD PTR SS:[ARG.2],28
00401266  |>  A1 1C204000   MOV EAX,DWORD PTR DS:[40201C]
0040126B  |.  83C0 02       ADD EAX,2
0040126E  \.  FFE0          JMP EAX

ECX vale 1 solo se la comparazione avvenuta prima tra i buffer puntati da ESI e EDI (avviene all'indirizzo 0x004011A7, mostrato in un frammento precedente) differisce per 1 solo carattere; considerando il funzionamento dell'istruzione CMPS, possiamo dire che il byte differente deve essere l'ultimo.
Siccome abbiamo una corrispondenza di tutti i caratteri inseriti, si verifica il primo JNE. Il messaggio che otteniamo è:


password_errata.png
Rifacciamo il punto alla luce delle nuove informazioni:
- la mail deve contenere almeno 5 caratteri: a@b.c è il numero minimo di caratteri consentiti (il controllo avviene all'indirizzo 0x00401161);
- la password deve contenere 8 caratteri (controllo che avviene all'indirizzo 0x00401167);
- superati i controlli su "@" e ".", i primi 4-byte della mail vengono usati per generare la key valida atilizzando lo XOR con il valore 0x12345678;

Dopo di che viene richiamata una funzione che riceve come parametri la key (cifrata con lo XOR nel passo sopra) e l'indirizzo di un buffer (contenuto in ESI).
La funzione in questione è illustrata di seguito:

Codice:
004010FC  /$  55            PUSH EBP                                 ; lookcloser.004010FC(guessed Arg1,Arg2)
004010FD  |.  8BEC          MOV EBP,ESP
004010FF  |.  B9 00000000   MOV ECX,0
00401104  |.  8B45 08       MOV EAX,DWORD PTR SS:[ARG.1]
00401107  |.  8B75 0C       MOV ESI,DWORD PTR SS:[ARG.2]
0040110A  |>  C1C0 04       /ROL EAX,4
0040110D  |.  8AD8          |MOV BL,AL
0040110F  |.  83E3 0F       |AND EBX,0000000F
00401112  |.  8A9B 13214000 |MOV BL,BYTE PTR DS:[EBX+402113]         ; ASCII "0123456789ABCDEF"
00401118  |.  881C31        |MOV BYTE PTR DS:[ESI+ECX],BL
0040111B  |.  41            |INC ECX
0040111C  |.  83F9 08       |CMP ECX,8
0040111F  |.^ 75 E9         \JNE SHORT 0040110A
00401121  |.  C60431 00     MOV BYTE PTR DS:[ESI+ECX],0
00401125  |.  C9            LEAVE
00401126  \.  C2 0800       RETN 8

ECX è il contatore, ESI punta sempre al buffer (dove viene inserito l'output) e il registro EAX contiene la key.
Viene fatta una rotazione di 1 nibble (4-bit) verso sinistra del registro EAX, e viene considerato solo il primo byte; poi a questo primo byte viene applicata come maschera 0x0000000F, quindi di fatto vengono considerati solo 4-bit.
Il valore risultante, che sarà per forza compreso tra 0x00 e 0xF, viene usato come indice nel buffer che si trova all'indirizzo 0x402113 (indirizzato utilizzando EBX, che è appunto l'indice).
Il valore viene memorizzato in ESI, e viene poi incrementato ECX (usato come contatore per ESI); al termine viene inserito il carattere terminatore.

Il codice in C lo si può riscrivere (un pò rielaborato, senza ad esempio il buffer di output) in questo modo:
C:
#include <stdio.h>

int main() {
    char *str = "0123456789ABCDEF";
    int in_key  = 0x76573419;
    int tmp_ebx = 0;
   
    int i = 0;
    printf("0x");
    do {
        __asm{rol  in_key, 4};
        tmp_ebx = in_key & 0xf;
        printf("%c", str[tmp_ebx]);
        i++;
    } while(i != 8);
   
    printf("\nEBX: %d", tmp_ebx);
   
    return 0;
}

Ho utilizzato asm inline per non implementare la rotazione; il codice è compilabile con MSVC (Microsoft Compiler).
Eseguendolo si ottiene:
Codice:
76573419
EBX: 9

Le rotazioni spostano in totale 4bit * 8 volte, quindi 4*8=32. Il numero è a 32bit, quindi al termine delle rotazioni il valore risulterà uguale a prima. Da notare il registro EBX: contiene il valore 9. Questo valore non cambia nelle righe successive (il controllo dove sono presenti le call alla MessageBox con testi differenti in base al risultato della comparazione).

Torniamo quindi al codice visto poco sopra...

Codice:
00401242  /.  83F9 01       CMP ECX,1
00401245  |.  75 15         JNE SHORT 0040125C
00401247  |.  83E3 0F       AND EBX,0000000F
0040124A  |.  83C3 47       ADD EBX,47
0040124D  |.  381D 67204000 CMP BYTE PTR DS:[402067],BL
00401253  |.  75 11         JNE SHORT 00401266
00401255  |.  834424 08 28  ADD DWORD PTR SS:[ARG.2],28
0040125A  |.  EB 0A         JMP SHORT 00401266
0040125C  |>  83F9 00       CMP ECX,0
0040125F  |.  75 05         JNE SHORT 00401266
00401261  |.  836C24 08 28  SUB DWORD PTR SS:[ARG.2],28
00401266  |>  A1 1C204000   MOV EAX,DWORD PTR DS:[40201C]
0040126B  |.  83C0 02       ADD EAX,2
0040126E  \.  FFE0          JMP EAX

Se ECX differisce di 1 carattere e il salto non si verifica allora vengono considerati 4-bit di EBX (i più bassi) e questo valore (compreso tra 0 e 15) viene sommato al valore 0x47 ('G'); il valore risultante viene comparato con il byte presente alla locazione 0x402067. Questa locazione di memoria è l'ultimo byte della key inserita in input. Se questo valore è uguale al contenuto di EBX, allora viene incrementato di 0x28 byte l'indirizzo puntato da SS:[ARG.2] (è la lunghezza del messaggio di errore che compare nella MessageBox); se viene eseguita quella ADD il messaggio che comparirà sarà proprio quello che stiamo cercando!

4. Keygen​



Applicare una patch risulta piuttosto semplice e lo si può fare in diversi modi, oltre che su diversi controlli. Per questo motivo preferisco un keygen.
Sarebbe stato più semplice in Python, ma considerando che non lo padroneggio bene, sono andato sul mio classico: C.

C:
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>

#define   MAIL_LEN   14
#define   PWD_LEN    9


uint8_t last_byte(uint32_t value) {
    const char *str = "0123456789ABCDEF";
    uint8_t tmp_ebx = 0;
   
    int i = 0;
    do {
        __asm{rol  value, 4};
        tmp_ebx = str[value & 0xf];
        i++;
    } while(i != 8);
   
    return tmp_ebx;
}

void my_toupper(char *key) {
    for(int i=0; i<PWD_LEN; i++)
        key[i] = toupper(key[i]);
}

int main() {
    srand(time(NULL));

    int n;
    printf("How many S/N would you like to generate? ");
    scanf("%d", &n);

    while(n-- > 0) {
        printf("\n===== Mail / S/N #%d =====\n", n+1);
       
        char mail[MAIL_LEN] = {'\0'};
        char key[PWD_LEN]   = {'\0'};
           
        for(int i=0; i<MAIL_LEN; i++)
            mail[i] = (rand() % 25) + 97;
       
        mail[5] = '@';
        mail[MAIL_LEN-3] = '.';

        uint32_t chunk = 0;
        memcpy(&chunk, mail, 4);
        chunk ^= 0x12345678;

        uint8_t last_nibble = last_byte(chunk);
        printf("Byte: %X, Nibble: %d\n",last_nibble, last_nibble & 0xf);
        itoa(chunk, key, 16);
        key[PWD_LEN-2] = (char) 0x47 + (last_nibble & 0xf);
       
        printf("Mail: %s\n", mail);
        my_toupper(key);
        printf("Pwd : %s\n", key);
        printf("=========================\n");
    }
   
    return 0;
}

Il codice non è bellissimo, ma dai test effettuati sembra generare dati corretti. La lunghezza della mail l'ho scelta a caso e per comodità (per non doverle generare random a lunghezze diverse). Ogni mail presenta quindi più o meno la stessa tipologia: 5 caratteri random seguiti dalla "@", poi altri caratteri seguiti dal punto e da una coppia di caratteri.

Il funzinamento dovrebbe essere chiaro: genero MAIL_LEN caratteri random e inserisco @ e "." sovrascrivendo due posizioni (che sono fisse). Fatto ciò si devono considerare i primi 4 byte e memorizzarli in un uint32; per comodità ho utilizzato memcpy, che è probabilmente la soluzione migliore. Il passo successivo è lo XOR tra i primi 4 caratteri e la costante numerica 0x12345678.
A questo punto avviene il calcolo visto sopra che coinvolge la rotazione verso sinistra di 4-bit alla volta. Il valore che torna è l'ultimo byte.
Non ci resta quindi che considerare i 4 bit meno significativi del byte ottenuto e sommarli a 0x47, così da sostituire il carattere ottenuto con l'ultimo presente nella key.

Questo è un esempio di alcune coppie di mail e seriali validi:
Codice:
How many S/N would you like to generate? 10

===== Mail / S/N #10 =====
Byte: 44, Nibble: 4
Mail: ewdin@gciqh.pv
Pwd : 7B50211K
=========================

===== Mail / S/N #9 =====
Byte: 45, Nibble: 5
Mail: fduyj@einiy.ob
Pwd : 6B41321L
=========================

===== Mail / S/N #8 =====
Byte: 39, Nibble: 9
Mail: qmwck@dmbvo.lj
Pwd : 71433B0P
=========================

===== Mail / S/N #7 =====
Byte: 45, Nibble: 5
Mail: fwgre@qqqjw.fj
Pwd : 6053211L
=========================

===== Mail / S/N #6 =====
Byte: 30, Nibble: 0
Mail: hjqwd@brlcm.si
Pwd : 65453C1G
=========================

===== Mail / S/N #5 =====
Byte: 35, Nibble: 5
Mail: mguob@qiwbg.xh
Pwd : 7D41311L
=========================

===== Mail / S/N #4 =====
Byte: 30, Nibble: 0
Mail: hgfte@qmoxy.ca
Pwd : 6652311G
=========================

===== Mail / S/N #3 =====
Byte: 33, Nibble: 3
Mail: kdlys@inksc.qa
Pwd : 6B58321J
=========================

===== Mail / S/N #2 =====
Byte: 46, Nibble: 6
Mail: giwde@wcbaa.cx
Pwd : 76433F1M
=========================

===== Mail / S/N #1 =====
Byte: 33, Nibble: 3
Mail: kxwop@cfhgg.ym
Pwd : 7D432E1J
=========================

Alcuni screen del risultati:
goodboy1.png goodboy2.png goodboyo3.png goodboy4.png

5. Conclusione​



Bene, direi sia tutto!
Spero sia stato interessante e che magari abbia incuriosito un pò chi non si è mai avvicinato al reversing o chi ancora non sa bene che percorso intraprendere. ;)
 

Skills07

Head of Development
Staff Forum
Utente Èlite
35,417
11,494
CPU
Ryzen 7 5800x
Dissipatore
Deepcool gammax l240 v2
Scheda Madre
Aourus Gigabyte X-470 ultra gaming
HDD
Samsung Evo 850 250gb ,2tb x 2 toshiba P300, 256 Ssd silicon power, Samsung evo plus 1tb nvme
RAM
32 gb G.Skill Tridentz 3200 mhz RGB
GPU
Zotac - RTX 3070 Ti Trinity OC
Monitor
ASUS TUF QHD 165hz IPS, ITEK GCF 165hz IPS QHD, Samsung Curvo 144hz QHD
PSU
Seasonic Focus 650w 80 platinum
Case
Coolermaster h500p
Periferiche
Asus Tuf Wireless
Net
Fibra Tim 100mb
OS
Windows 10 Pro 64 bit
interessante bravo, bella cosa. Da programmatore che sono mi complimento ??
 
  • Mi piace
Reazioni: DispatchCode

BAT

Moderatore
Staff Forum
Utente Èlite
22,923
11,563
CPU
1-Neurone
Dissipatore
Ventaglio
RAM
Scarsa
Net
Segnali di fumo
OS
Windows 10000 BUG
@Mursey
da mettere in una delle guide sicuramente
 
  • Mi piace
Reazioni: DispatchCode

Entra

oppure Accedi utilizzando
Discord Ufficiale Entra ora!