Bravo int19 anche stavolta una buona soluzione! NdQuequero Si assumono le seguenti conoscenze 1) assembly 2) funzioni basi di SoftIce, quindi non staro' a dire pigiamo 1 volta F12 poi 2 volte F8 ecc.. Ma usero' un linguaggio piu' naturale. Tools: SoftIce, w32dasm, BaseCalc Avviando il programma compare immediatamente un messaggio che ci avverte che e' stato rilevato SoftIce ed il programma si stoppa. Occupiamoci del check antidebug, bene, apriamo w32dasm e disassembliamo il tutto, ricercando le occorrenze della stringa "Chi usa SoftIce alzi la manoooo!!!" ne troviamo 3, che guarda caso sono il riferimento di un salto condizionale qui di seguito vi guidero' ad eliminare il primo, per i restanti il procedimento e' analogo. :004011EB ret 0010 * Referenced by a Jump at Address 00401542(C) :004011EE push 00000030 * Possible StringData Ref from Data Obj ->"SoftIce detected!!!" :004011F0 push 0040362D * Possible StringData Ref from Data Obj ->"Chi usa SoftIce alzi la manoooo!!! " ->":)" Come vedete il codice che ho riportato viene eseguito se e' stato eseguito il salto alla locazione 401542 che come potete vedere..... :0040153B mov ax, 0004 :0040153F int 3 <--- Check Anti-Debug :00401540 cmp al, 04 :00401542 jne 004011EE <--- Salta se SoftIce e' caricato ...non e' altro che il check per rilevare softice, io l'ho disabilitato semplicemente eliminando la chiamata all' int 3 e sostituendolo con una call all'int19 ehm :-P con un NOP. In questo modo, il jump non verra' mai eseguito.... proseguiamo... Avviamo il programma e piazziamo un breakpoint su GetWindowTextA, inseriamo due valori nei box di registrazione es:"OneOne" "TwoTwo" e pigiando il pulsante di registrazione SoftIce fa la sua comparsa, ed approdiamo in questa zona di codice (*). :0040123A push 0000000C :0040123C push 00403273 :00401241 push dword ptr [00402030] * Reference To: USER32.GetWindowTextA, Ord:0000h :00401247 Call 00401EC7 :0040124C call 0040159C <--- (*) Arriviamo qui', appena prima dell'esecuzione della call :00401251 xor ecx, ecx Da una veloce esplorazione della locazione 403273 ci rendiamo conto che e' stato letto il valore presente nel primo box ("OneOne"). Ora ci concentriamo su come viene manipolato il codice, trascurando le operazioni che non sono attinenti con il nostro attuale scopo, per far cio' e' sufficiente piazzare il classico... BPR 403273 403273+5 rw e pigiamo F5 per proseguire con le operazioni. Non arriviamo molto lontani poiche' come potete notare viene immediatamente xorato il nosto codice con il valore ED, in questo punto (**) e notiamo che il ciclo viene compiuto per 0C volte * Referenced by a Jump at Address 00401261(C) :00401253 xor dword ptr [ecx+00403273], 000000ED <--- (**) :0040125D inc ecx :0040125E cmp ecx, 0000000C :00401261 jne 00401253 :00401263 xor ecx, ecx Subito dopo il ciclo i caratteri immessi vengono manipolati tramite l'XOR con alcuni valori costanti nel seguente ciclo... (sempre considerando 0C caratteri) * Referenced by a Jump at Address 00401289(C) :00401265 xor dword ptr [ecx+00403273], 134F7432 <---| Corrispondono ad un'unico XOR :0040126F xor dword ptr [ecx+00403273], 4A710930 | con il valore 01E960E4 :00401279 xor dword ptr [ecx+00403273], 58D71DE6 <---| che non a caso deriva da 134F7432 xor 4A710930 xor 58D71DE6 :00401283 add ecx, 00000004 :00401286 cmp ecx, 0000000C :00401289 jne 00401265 Siccome 01E960E4 xor EDEDEDED = EC048D09 allora i due cicli si riducono ad un unico xor con EC048D09 Ora meditate un po' sulle seguenti operazioni... Possiamo immediatamente individuare il confronto tra il codice esatto ed il nostro alla linea 4012d8 ed il conseguente salto immediatamente dopo. Dunque, se fate attenzione a tutte le operazioni potete osservare che le medesime trasformazioni vengono applicate sia al nostro codice che al codice esatto, quindi e' naturale che se sono uguali prima di queste manipolazioni, lo saranno anche dopo. Inoltre notate che vengono confrontati 0C caratteri. * Reference To: KERNEL32.GetTickCount, Ord:0000h :0040128B Call 00401E61 :00401290 xor dword ptr [00403273], eax :00401296 xor dword ptr [00403277], eax :0040129C xor dword ptr [0040327B], eax :004012A2 xor dword ptr [0040327F], eax :004012A8 xor dword ptr [00403283], eax :004012AE xor dword ptr [00403287], eax :004012B4 and dword ptr [0040327F], 00627893 :004012BE and dword ptr [00403273], 00627893 :004012C8 mov esi, 00403273 <--- Nostro Codice :004012CD mov edi, 0040327F <--- Codice Esatto :004012D2 mov ecx, 0000000C :004012D7 repz :004012D8 cmpsb :004012D9 je 00401300 <--- Salta se sono uguali Se avete gia' intuito come trovare il codice esatto passate pure alla prossima protezione, altrimenti sopportatemi ancora per un paio di secondi... Nel punto in cui viene richiamata la GetTickCount abbiamo detto che i due codici devo gia' essere uguali, perche' lo siano anche quando verra' effettuato il confronto. Il codice esatto, cioe' la locazione di memoria 40327F in quel punto contiene i seguenti valori 4A FF 30 82 4B AB 56 9E 38 BE 20 EC perche' in nostro codice assuma questi valori basta effettuare un semplice xor.... 09 8D 04 EC 09 8D 04 EC 09 8D 04 EC 4A FF 30 82 4B AB 56 9E 38 BE 20 EC 43 72 34 6E 42 26 52 72 31 33 24 0 C r 4 n B & R r 1 3 $ Ora per controllare l'esattezza del codice, lo inseriamo e pigiamo il pulsantino di registrazione, immediatamente ci rendiamo conto che SoftICE non poppa piu' nello stesso punto di prima, ci ritroviamo in un'altra zona di codice.... * Reference To: USER32.GetWindowTextA, Ord:0000h :00401330 E8920B0000 Call 00401EC7 :00401335 A341324000 mov dword ptr [00403241], eax :0040133A 83F806 cmp eax, 00000006 :0040133D 0F8CDB000000 jl 0040141E Inoltre la funzione GetWindowTextA viene anche richiamata una secona volta qui da qui..... * Reference To: USER32.GetWindowTextA, Ord:0000h :004013DF E8E30A0000 Call 00401EC7 :004013E4 E891010000 call 0040157A :004013E9 E801020000 call 004015EF :004013EE C3 ret Pigiando piu' volte il pulsante di registrazione veniamo continuamente catapultati in queste due locazioni e non riusciamo piu' a tornare nella parte di codice che stavamo esaminando precedentemente (00401247), a quanto pare viene solo eseguita al primo avvio, per controllare l'esattezza del codice che abbiamo trovato quindi e' indispensabile che chiudiamo e riavviamo il programma, infatti ora riusciamo di nuovo a tornare nel punto in cui viene richiesto il 1° codice, inseriamo il codice che abbiamo trovato (Cr4nB&Rr13$) e vediamo un po' che cosa succede.... il check della locazione 004012D9 (repz/cmpsb/je) e' soddisfatto e quindi viene eseguito il salto, inoltre e' rilevante vedere qualsiasi sia il codice inserito, errato o corretto che sia, il programma prosegue nell'esecuzione senza mostrare alcun messaggio di errore ne di congratulazioni, l'unica distinzione tra codice esatto e codice errato e' che nella locazione di memoria 40329B viene caricato 0000F892 se il codice e' errato altrimenti se il codice e' corretto viene caricato il valore 0000F896 Ora che abbiamo superato la prima protezione, passiamo alla seconda, "Nome/Seriale"........ Dunque inseriamo inanzitutto 2 nomi es:"OneOne" e "TwoTwo", teniamo il solito breakpoint sulla funzione GetWindowTextA, e tentiamo di registrare il programma... Eccoci qui davanti a SoftIce :00401323 6A14 push 00000014 :00401325 6849324000 push 00403249 <--- indirizzo nel quale viene memorizzata la stringa letta :0040132A FF3530204000 push dword ptr [00402030] * Reference To: USER32.GetWindowTextA, Ord:0000h :00401330 E8920B0000 Call 00401EC7 <--- SoftIce brekka qui :00401335 A341324000 mov dword ptr [00403241], eax <--- Salva la lunghezza della stringa immessa :0040133A 83F806 cmp eax, 00000006 :0040133D 0F8CDB000000 jl 0040141E <--- salta se la stringa e' < di 6 chars e mostra un message-box Ora piazziamo un breakpoint sulle locazioni 403249...403249+5 (bpr 403249 403249+5 rw) per vedere un po' come viene manipolata la stringa appena letta ("OneOne") Anche se in questo caso risulta quantomeno inutile come breakpoint, poiche' tutto il codice che manipola la stringa si trova qui sotto ed e' diciamo "pulito". Pero' e' bene adottare sempre questo approccio, poiche' nei programmi reali ci si trova piuttosto frequentemente a dover discriminare tra operazioni indispensabili e operazioni che possono essere trascurate, risparmiando notevoli energie in termini di tempo, in questo modo (con il breakpoint on range) possiamo concentrarci sulle operazioni che fanno riferimento alla generazione del codice seriale senza dover tracciare ogni singola call, passo passo. Magari vi sembrera' un po' oscuro come discorso, ma vedrete che pian piano, mentre vi farete le ossa le cose diverranno di volta in volta piu' chiare, ma bando alle ciance.... ecco qui come viene manipolata la prima stringa... * Reference To: KERNEL32.GetTickCount, Ord:0000h | :00401343 E8190B0000 Call 00401E61 :00401348 A33D324000 mov dword ptr [0040323D], eax <--- salva un numero random nella locazione 40323D :0040134D 33C9 xor ecx, ecx :0040134F 33DB xor ebx, ebx :00401351 8B154A324000 mov edx, dword ptr [0040324A] <--- carica in edx dal 2° al 5° char :00401357 A149324000 mov eax, dword ptr [00403249] <--- carica in eax i primi 4 chars :0040135C 0FAFC2 imul eax, edx <--- moltiplica i primi 4 chars con i chars dal 2° al 5° :0040135F A349324000 mov dword ptr [00403249], eax <--- sovrascrive i primi 4 chars :00401364 33C9 xor ecx, ecx :00401366 83C104 add ecx, 00000004 :00401369 FF0D41324000 dec dword ptr [00403241] <--- decrementa di 1 la lunghezza della stringa * Referenced by a Jump at Address 00401388(C) :0040136F 41 inc ecx <--- passa al prossimo char :00401370 8A8149324000 mov al, byte ptr [ecx+00403249] <--- carica un char della stringa in al (al 1° ciclo carica il 6°char) :00401376 02814A324000 add al, byte ptr [ecx+0040324A] <--- e vi aggiunge il char successivo (al 1° ciclo aggiunge il 7°char) :0040137C 888149324000 mov byte ptr [ecx+00403249], al <--- sovrascrive il char corrente con il nuovo valore :00401382 3B0D41324000 cmp ecx, dword ptr [00403241] <--- controlla se abbiamo terminato i chars della stringa :00401388 7CE5 jl 0040136F <--- salta se ci sono ancora chars da esaminare :0040138A A149324000 mov eax, dword ptr [00403249] <--- carica i primi 4 chars (che erano gia' stati manipolati) :0040138F 8B1D4D324000 mov ebx, dword ptr [0040324D] <--- carica i chars dal 5° all' 8° notate che se la stringa inserita e' di soli 6 chars ad esempio la nostra "OneOne" in ebx verranno caricati il 5° ("n")ed il 6° char ("e"), inoltre il 7° char che e' il char di teminazione stringa (0) mentre per quello che riguarda l'8° char verra' preso il contenuto della locazione 403249+7 (403250) Come possiamo notare in SoftIce, al primo avvio il valore di questa locazione e' 0, ma se questo valore dovesse cambiare allora cambierebbe anche il risultato della prossima moltiplicazione infatti come vedremo in seguito, vi saranno degli effetti collaterali. :00401395 0FAFC3 imul eax, ebx :00401398 A349324000 mov dword ptr [00403249], eax <--- salva i primi 4 chars manipolati :0040139D FF0541324000 inc dword ptr [00403241] <--- incrementa di 1 la lunghezza della stringa :004013A3 33C9 xor ecx, ecx (ora [403241] contiene il num di chars della stringa) :004013A5 8B0D41324000 mov ecx, dword ptr [00403241] <--- ecx = lunghezza stringa * Referenced by a Jump at Address 004013BF(C) :004013AB 8B8149324000 mov eax, dword ptr [ecx+00403249] <--- carica in eax 4 chars (dei quali il primo e' quello puntato da ecx+1) quindi al 1° ciclo verra' sempre caricato il char di terminazione stringa (0) ed i successivi 3 chars che al primo avvio sono tutti uguali a 0. :004013B1 350F58CF04 xor eax, 04CF580F <--- li manipola :004013B6 898149324000 mov dword ptr [ecx+00403249], eax <--- e li salva nuovamente ora come possiamo vedere, quello che abbiamo identificato come l'8 char e' stato modificato, inoltre osserviamo che e' stato modificato tramite l'operazione di xor, e per di piu' con un valore costante (04CF580F), quindi ci dovremmo aspettare che alla prossima volta che viene eseguita la funzione, il valore del'8 char ritorni ad essere uguale al valore precedente (0), stesso discorso per quello che riguarda il 9° ed il 10° char. :004013BC 49 dec ecx :004013BD 85C9 test ecx, ecx :004013BF 75EA jne 004013AB <--- salta se ecx>0 :004013C1 E872000000 call 00401438 <--- routine di normalizzazione Per osservare piu' in dettaglio la routine di normalizzazzione date un'occhiata all'analisi che segue... ROUTINE DI NORMALIZZAZIONE Normalizza i chars da quello di fine stringa al 4°....... * Referenced by a CALL at Address 004013C1 :00401438 8B0D41324000 mov ecx, dword ptr [00403241] <--- inizializza il contatore con la lunghezza della stringa * Referenced by a Jump at Address 00401466(C) :0040143E 8A8149324000 mov al, byte ptr [ecx+00403249] <--- carica un char dalla stringa a partire dall'ultimo (il char di terminazione manipolato) * Referenced by a Jump at Addresses 0040146E(U), 00401476(U) :00401444 3C30 cmp al, 30 :00401446 7C20 jl 00401468 <--- se il char e' < "0" salta :0040144C 3C39 cmp al, 39 :0040144E 7F20 jg 00401470 <--- se il char e' > "9" salta :00401454 888149324000 mov byte ptr [ecx+00403249], al <--- salva il char "normalizzato" ("0" < char < "9") :0040145A 49 dec ecx <--- passa al prossimo char :0040145B 85C9 test ecx, ecx :0040145D 83F902 cmp ecx, 00000002 :00401460 7416 je 00401478 <--- salta se rimangono solo piu' i primi 3 chars da normalizzare :00401466 75D6 jne 0040143E <--- "normalizza" il prossimo char * Referenced by a Jump at Addresses 00401446(C), 0040146C(C) :00401468 02C1 add al, cl <--- aggiunge al char corrente il valore della lunghezza della stringa :0040146A 3C30 cmp al, 30 | :0040146C 7CFA jl 00401468 <--- finche' e' non risulta >= di "0" (30 hex) :0040146E EBD4 jmp 00401444 <--- salta quando il char corrente e' >= "0" * Referenced by a Jump at Addresses 0040144E(C), 00401474(C) :00401470 2AC1 sub al, cl <--- sottrae al char corrente il valore della lunghezza della stringa :00401472 3C39 cmp al, 39 | :00401474 7FFA jg 00401470 <--- finche' e' non risulta <= di "9" (39 hex) :00401476 EBCC jmp 00401444 <--- salta quando il char corrente e' <= "9" Normalizza i chars 3° e 2°..... * Referenced by a Jump at Addresses 00401460(C), 00401497(C) :00401478 8A8149324000 mov al, byte ptr [ecx+00403249] * Referenced by a Jump at Addresses 004014A4(U), 004014AC(U) :0040147E 3C30 cmp al, 30 :00401480 7C1C jl 0040149E <--- salta se il char corrente e' <='0' :00401486 3C39 cmp al, 39 :00401488 7F1C jg 004014A6 <--- salta se il char corrente e' >='9' :0040148E 888149324000 mov byte ptr [ecx+00403249], al <--- "salva il char normalizzato" :00401494 49 dec ecx <--- normalizza il prossimo char :00401495 85C9 test ecx, ecx :00401497 75DF jne 00401478 :00401499 E928FFFFFF jmp 004013C6 <--- salta quando sono stati normalizzati tutti i chars * Referenced by a Jump at Addresses 00401480(C), 004014A2(C) :0040149E 0404 add al, 04 <--- aggiunge 4 al char corrente :004014A0 3C30 cmp al, 30 | :004014A2 7CFA jl 0040149E <--- finche' non risulta >='0' :004014A4 EBD8 jmp 0040147E * Referenced by a Jump at Addresses 00401488(C), 004014AA(C) :004014A6 2C05 sub al, 05 <--- sottrae 5 al char corrente :004014A8 3C39 cmp al, 39 | :004014AA 7FFA jg 004014A6 <--- finche' non risulta <='9' :004014AC EBD0 jmp 0040147E Al termine della routine di normalizzazione veniamo riportati qui... :004013C6 33C0 xor eax, eax Se ora date uno sguardo alla locazione che conteneva la stringa "OneOne", potete vedere che essa e' stata trasformata in un codice, (che sia il seriale ?) tranne il primo char che e' ancora un char "anomalo" quindi dovremmo aspettarci che sara' trasformato in seguito (vi dico subito che non andra' esattamente cosi'), ma vediamo.... pigiamo F5 e SoftIce poppa all'interno della call 4014AE :004013C8 E822000000 call 004013EF (***) :004013CD E8DC000000 call 004014AE * Referenced by a CALL at Address 004013CD :004014AE 33C9 xor ecx, ecx :004014B0 33C0 xor eax, eax * Referenced by a Jump at Address 004014CD(C) :004014B2 41 inc ecx <--- passa al prossimo char :004014B3 8A8149324000 mov al, byte ptr [ecx+00403249] <--- SoftIce brekka qui (viene letto il un char della stringa a partire dal 2°) :004014B9 8A1D35324000 mov bl, byte ptr [00403235] <---| il char letto viene xorato con il contenuto :004014BF 32C3 xor al, bl | della locazione 403235 e successivamente :004014C1 888149324000 mov byte ptr [ecx+00403249], al <---| viene sovrascritto :004014C7 3B0D41324000 cmp ecx, dword ptr [00403241] <--- controlla se sono stati esaminati tutti i chars dal 2° a quello di fine stringa :004014CD 7CE3 jl 004014B2 :004014CF C3 ret Ora vediamo un po' cosa si trova dentro la locazione [403235]... disabilitiamo momentaneamente il breakpoint che punta la stringa "OneOne" mettiamo un breakpoint sul byte di questa locazione, bpmb 403235 rw e proviamo di nuovo a registrare il programma, ecco SoftIce che fa la sua comparsa... (***) * Referenced by a CALL at Address 004013C8 * Reference To: KERNEL32.GetTickCount, Ord:0000h :004013EF E86D0A0000 Call 00401E61 :004013F4 A335324000 mov dword ptr [00403235], eax <---| viene caricato un numero random :004013F9 F72539324000 mul dword ptr [00403239] | in questa locazione e dopo essere :004013FF 40 inc eax | stato modificato viene sovrascritto :00401400 A335324000 mov dword ptr [00403235], eax <---| :00401405 C3 ret Siccome il num. random acquisito viene moltiplicato con la dword [403239], ci accertiamo che essa non sia uguale a 0, poiche' se cosi' fosse [403235] non sarebbe piu' random ma varrebbe sempre 1. Il contenuto della dword [403239] non ha alcuna particolare influenza (purche' sia diverso da 0), poiche' trattandosi eax di un num. random, tale resta anche dopo essere stato manipolato, quindi non e' necessario vedere come viene generato il valore della locazione [403239] Ora che sappiamo che la locazione [403235] contiene un num. random, e che il nostro codice viene manipolato utilizzando questo numero, e' lecito supporre che anche il num. di serie che inseriremo dovra' subire la stessa manipolazione tramite la stessa chiave random, poiche' altrimenti non si spiegherebbe come potrebbero risultare uguali. Immediatamente dopo la call (4013CD) che xora il nostro codice manipolato con il num. random notiamo il seguente codice, che altro non sara' che la lettua del seriale inserito, infatti se visualizziamo la locazione 40325D ed eseguiamo la call a GetWindowTextA, leggiamo chiaramente "TwoTwo". :004013D2 6A14 push 00000014 :004013D4 685D324000 push 0040325D :004013D9 FF3534204000 push dword ptr [00402034] * Reference To: USER32.GetWindowTextA, Ord:0000h :004013DF E8E30A0000 Call 00401EC7 :004013E4 E891010000 call 0040157A (**) :004013E9 E801020000 call 004015EF :004013EE C3 ret Ora andiamo a vedere cosa combinano le 2 call che seguono la lettura della seconda stringa... (**) * Referenced by a CALL at Address 004013E4 :0040157A 33C9 xor ecx, ecx :0040157C 33C0 xor eax, eax * Referenced by a Jump at Address 00401599(C) :0040157E 41 inc ecx :0040157F 8A815C324000 mov al, byte ptr [ecx+0040325C] <---| Anche la seconda stringa viene :00401585 8A1D35324000 mov bl, byte ptr [00403235] | manipolata utilizzando la :0040158B 32C3 xor al, bl | stessa chiave random :0040158D 88815C324000 mov byte ptr [ecx+0040325C], al <---| (403235) :00401593 3B0D41324000 cmp ecx, dword ptr [00403241] :00401599 7CE3 jl 0040157E :0040159B C3 ret Come avrete osservato, la nostra supposizione era esatta, infatti anche il seriale inserito viene manipolato utilizzando la medesima chiave random. Quella che segue qui e' invece la seconda call, probabilmente il cuore di tutta la protezione, ovvero, quella che si occupa del check del seriale e discrimina tra codice corretto e codice errato... * Referenced by a CALL at Address 004013E9 :004015EF FF3534204000 push dword ptr [00402034] * Reference To: USER32.GetWindowTextLengthA, Ord:0000h :004015F5 E8BB080000 Call 00401EB5 :004015FA 83F80F cmp eax, 0000000F <---| controlla se il seriale inserito e' costituito da 15 chars :004015FD 0F8505FBFFFF jne 00401108 <---| prosegue se il check ha esito positivo :00401603 BE4A324000 mov esi, 0040324A <--- Codice seriale calcolato :00401608 BF5D324000 mov edi, 0040325D <--- Codice seriale inserito :0040160D 8B0D41324000 mov ecx, dword ptr [00403241] <--- lunghezza del seriale calcolato :00401613 F3 repz <---| esegue il :00401614 A6 cmpsb <---| confronto :00401615 0F84D9FEFFFF je 004014F4 <--- se sono uguali salta :0040161B C7059F324000C2910000 mov dword ptr [0040329F], 000091C2 <--- marca come "protezione non passata" :00401625 E9D4FEFFFF jmp 004014FE <--- passa alla terza protezione Siccome entrambi i codici, (quello inserito e quello calcolato) al termine dovranno essere uguali per poter passare il check e siccome il codice inserito viene manipolato esclusivamente tramite l'xor con la chiave random, va da se che se fossero uguali prima di questa manipolazione lo sarebbero anche dopo. Qui sotto ho schematizzato le operazioni che vengono effettuate... 0) Leggi Nome 1) Manipola1 Nome 2) Manipola2 Nome con chiave Random 3) Leggi Seriale 4) Manipola2 Seriale con chiave Random 5) Nome Modificato == Seriale Modificato ? Il seriale da inserire che porta al successo del check dovra' essere uguale al Nome dopo la prima manipolazione. E dovra' essere di 15 chars. Il problema a quanto pare e' che dopo la prima manipolazione il 1° char del nome non e' "normalizzato", ma infatti se guardate il check parte a controllare i chars dal 2° in poi, quindi il primo viene trascurato qualunque esso sia, ed il problema non sussiste. Quindi possiamo leggere il seriale per il nostro nick "OneOne" direttamente nel display della memoria 208202 e allungarlo fino a raggiungere i 15 chars, 208202000000000 ecco qui un seriale valido. Lo inseriamo e vediamo se tutto va a buon fine.... Con nostra sorpresa ci rendiamo conto che il seriale non e' valido, ma non solo, se pigiamo nuovamente il pulsante di registrazione, questa volta e' considerato come valido, in pratica il check viene passato una volta si ed una volta no.... come puo' essere ? Vi ricordate di quando parlavo di effetti collaterali indesiderati ????? E' proprio a questo che mi riferivo... La locazione di memoria dell'8° char potrebbe essere "sporca", pedipiu' se inseriamo prima un nome diciamo di 10 chars (che quindi modifica la locazine dell'8 char) e poi inseriamo il nostro nome di 6 char, il seriale 208202000000000 non e' piu' valido neanche una volta si ed una volta no, poiche' questo seriale e' stato calcolato considerando che la locazione dell'8 char sarebbe stata uguale a 0. Per concludere, non esiste un seriale assoluto per i nomi di 6 chars, quindi vediamo di trovarne uno piu' "regolare" Consideriamo il nick "IlSocio" e brekkando all'istruzione 4013C6 (prima di eseguire la manipolazione con chiave random) leggiamo il seriale, 2103556, allunghiamolo... 210355600000000 ed ecco qui finalmente un bel seriale valido al 100% Poiche' il cmpsb questa volta ha esito positivo la locazione [0040329F] viene marcata con 000091B2 E finalmente passiamo alla prossima protezione, il keyfile. Qui sotto troverete il codice relativo alla terza protezione, ed alcuni brevi commenti per le istruzioni di cui possiamo gia' esser certi del loro utilizzo. Il check delle linee 00401548-0040155A controlla che alcuni bytes delle locazioni 00402BFC e 004025F0 siano uguali e a seconda che il check passi oppure no, marca la locazione [004032A3] con un valore differente. Vi ricorda niente questo schema? E' lo stesso schema di check che e' stato utilizzato per le prime 2 protezioni, quindi molto probabilmente questo ciclo e' il check della 3° protezione. * Referenced by a Jump at Address 00401615(C) :004014F4 C7059F324000B2910000 mov dword ptr [0040329F], 000091B2 <--- marca come "protezione passata con successo" :004014FE 6A14 push 00000014 <--- inizio della 3° protezione "il keyfile" :00401500 50 push eax <--- eax ha valore 0F poiche' non e' stato piu' modificato. :00401501 FF3530204000 push dword ptr [00402030] * Reference To: USER32.GetWindowTextA, Ord:0000h :00401507 E8BB090000 Call 00401EC7 <--- questa chiamata fallisce poiche' e' stato pushato 0F come indirizzo di mem. :0040150C A341324000 mov dword ptr [00403241], eax <--- viene azzerata la locazione [403241] eax e' 0 a causa del fallimento della call precedente :00401511 B8FC2B4000 mov eax, 00402BFC :00401516 83C014 add eax, 00000014 <--- eax vale 402BFC+14 = 402C10 :00401519 6A14 push 00000014 :0040151B 50 push eax <--- la stringa letta viene memorizzata in [402C10] :0040151C FF3534204000 push dword ptr [00402034] * Reference To: USER32.GetWindowTextA, Ord:0000h :00401522 E8A0090000 Call 00401EC7 <--- legge "IlSocio" e lo salva in [402C10] :00401527 E853010000 call 0040167F :0040152C E848060000 call 00401B79 :00401531 E898060000 call 00401BCE :00401536 BD4B484342 mov ebp, 4243484B :0040153B 66B80400 mov ax, 0004 <---| :0040153F 90 nop |(int 3) check antisoftice (che e' stato sostituito con un nop) :00401540 3C04 cmp al, 04 | :00401542 0F85A6FCFFFF jne 004011EE <---| :00401548 BEFC2B4000 mov esi, 00402BFC <---| controlla se :0040154D BFF0254000 mov edi, 004025F0 | e' stata passata la :00401552 8B0DFC314000 mov ecx, dword ptr [004031FC] | protezione ? :00401558 F3 repz | :00401559 A6 cmpsb | :0040155A 0F8489080000 je 00401DE9 <---| Io ho iniziato dando un rapido sguardo al contenuto delle call che seguono la lettura della stringa. Appena mi sono addentrato nella prima (00401527 call 0040167F) mi sono ritrovato di fronte ad un macello di cicli, Demoralizzato immaginando il macello di lavoro che avrei dovuto fare per tracciare il tutto decido di trascurarla per il momento e passo a vedere un po' cosa mi aspetta dopo. Entro nella seconda call (0040152C call 00401B79), questa sembra decisamente piu' comprensibile, la trovate qui sotto... * Referenced by a CALL at Address: |:0040152C :00401B79 push 00000002 <---| :00401B7B push 00402050 | Apre il file CryKey.key * Possible StringData Ref from Data Obj ->"CryKey.key" | :00401B80 push 0040322A | * Reference To: KERNEL32.OpenFile, Ord:0000h | :00401B85 Call 00401E43 <---| :00401B8A A348204000 mov dword ptr [00402048], eax <--- salva l'handle del file :00401B8F 83F8FF cmp eax, FFFFFFFF :00401B92 0F84C8F9FFFF je 00401560 <--- salta se il file non esiste Ora per prima cosa, creiamo il file CryKey.key e scriviaci dentro qualcosa, che ne so.. "Prova", e proseguiamo. :00401B98 6A00 push 00000000 :00401B9A 6A00 push 00000000 :00401B9C FF35A7324000 push dword ptr [004032A7] :00401BA2 FF3548204000 push dword ptr [00402048] * Reference To: KERNEL32.SetFilePointer, Ord:0000h :00401BA8 E890020000 Call 00401E3D :00401BAD 6A00 push 00000000 <---| :00401BAF 6820234000 push 00402320 | Legge dal file crykey.key (di cui l'handle e' :00401BB4 FF35FC314000 push dword ptr [004031FC] | memorizzato nella locazione 402048) :00401BBA 68F0254000 push 004025F0 | un num. di bytes che dipende dal contenuto della locazione 4031FC :00401BBF FF3548204000 push dword ptr [00402048] | e li salva in memoria a partire dalla locazione 4025F0 * Reference To: KERNEL32.ReadFile, Ord:0000h | :00401BC5 E867020000 Call 00401E31 <---| :00401BCA C3 ret Come vediamo in SoftIce con il comando ? @4031FC il contenuto della locazione 4031FC e' 2BC hex. che equivale a 700 bytes in decimale, ne deduciamo che il keyfile dovra' contenere almeno 700 bytes. Oltre a questo, anche il check repnz/cmpsb controlla 700 bytes (poiche' anche li' viene pushato il contenuto della medesima locazione di memoira) Detto cio', allunghiamo un po' il nostro file, scrivendo un po' di cazzate dentro, tanto per arrivare ad almeno 700 bytes. Purtroppo il file non e' stato chiuso dal programma, quindi windoze non ci permettera' di modificarlo, cosi' dobbiamo chiudere il prog. modificare il file e poi far ripartire il prog. I dati letti dal keyfile, vengono salvati in memoria a partire dalla locazione 4025F0, che guarda caso e' proprio una delle 2 sezioni dati che vengono coinvolte nel repnz/cmpsb (il presunto check della 3° protezione) Bene, per ora una call e' andata, vediamo cosa succede nella 3° call ... Anche qui come per la prima call ci troviamo davanti ad un malloppo di cicli, e la demoralizzazione sale. Decido di farmi una stampa del disassemblato delle due call che ho lasciato in sospeso per vedere di capirci qualcosa. A quanto pare, la prima call opera per la maggior-parte sulle locazioni a partire da 402BFC (quindi quelle locazioni che contengono il seriale) mentre la terza call opera sulle locazini a partire da 4025F0 (che contengono i dati del keyfile letti) Dopo un po' che ho sotto gli occhi sto macello di fogli con cicli vari, decido che e' meglio sfoltire un po' il lavoraccio, quindi cerco un metodo per poter eliminare cicli inutili o trasformazioni xor inutili... Inanzitutto controllo la prima call cercando delle similitudini tra i vari cicli che la compongono, come ad esempio grandi pezzi di codice ripetuti, ma non trovo niente di particolarmente rilevante. Faccio la stessa cosa con la terza call ed anche qui, nulla che mi possa aiutare. Anche cercando trasformazioni o cicli inutili non riesco a sfoltire di molto il lavoro, al max. un paio di cicli in meno :-( Allora passo a confrontarle tra di loro, vedo se iniziano con gli stessi cicli, ancora niente, lo sconforto sale alle stelle. confronto le due call per vedere se terminano con cicli simili, e finalmente mi si illuminano gli occhi notando che l'ultimo ciclo era praticamente identico e cosi' anche il penultimo, ed ancora il precedente.... In pratica tutta la terza call e' "contenuta" nella prima ovvero, la parte finale della 1° e tutta la 3° eseguono le stesse identiche trasformazioni, ma lo fanno su dati differenti, e piu' precisamente manipolano i dati che sono proprio quelli interessati dal check (repnz cmpsb). Schematizziamo un po' quello che abbiamo dedotto.... 1) Lettura Seriale 2) Modifica1 Seriale 3) Modifica2 Seriale 4) Lettura KeyFile 5) Modifica2 KeyFile 6) Seriale Modificato == KeyFile Modificato ? Sembra quasi lo stesso schema che riguardava la 2° protezione, la medesima trasformazione viene applicata ai 2 dati da confrontare, quindi e' naturale pensare che per essere identici prima del confronto dovranno essere identici anche prima della trasformazione (Modifica2) Quindi il Seriale dopo esser stato manipolato tramite Modifica1 (la prima parte della 1° call) dovra' essere uguale al contenuto del KeyFile in quel momento, cioe' rigirando il discorso, Il keyfile dovra' contenere i 700 bytes derivanti dalla manipolazione del seriale tramite Modifica1 A questo punto mi sono procurato IceDump, per poter fare un dumping della memoria e vedere se la mia teoria era esatta. Installato IceDump e riavviato il pc... Piazzo un breakpoint nel punto in cui termina la prima modifica del Seriale (BPX 40190B) Passo i primi 2 check, ed ecco che SoftIce arriva nel punto desiderato, a questo punto... PAGEIN D 402BFC 2BC C:\TEST.KEY in questo modo ho scritto 2BC (700 decimale) bytes a partire dalla locazione 402BFC (locazione che viene confrontata dal repnz/cmpsb) nel file c:\test.key ora siccome windoze mi spara un bell'errore di condivisione se tento di sovrascrivere il file crykey.key con quello dumpato (test.key) sono costretto a chiudere nuovamente il prog. e riavviarlo dopo aver sovrascritto il keyfile a questo punto vediamo se e' andato tutto bene. Purtroppo, ecco l'ennesima sorpresa, non funzia, e se in effetti andiamo ad osservare il contenuto della locazione con la quale viene eseguito il compare vediamo che cambia ad ogni esecuzione della call che modifica il seriale, quindi il dump che e' stato fatto e' quello di un preciso "momento" per avere un keyfile che sia regolare al momento iniziale, dobbiamo effettuarne il dump al primo avvio, spero di essermi spiegato. riavvio quindi il prog. passo i 2 check e rieseguo il dump. Chiudo il tutto, sovrascrivo il file, e riavvio nuovamente. E se il signore vuole questa e' la volta buona, infatti, questa volta il check passa con successo e noi veniamo trasportati a questo indirizzo.... * Referenced by a Jump at Address 0040155A(C) :00401DE9 C705A3324000B3570000 mov dword ptr [004032A3], 000057B3 <--- marca il 3° check come "passato" * Referenced by a Jump at Address 0040156A(U) :00401DF3 33C0 xor eax, eax :00401DF5 A19B324000 mov eax, dword ptr [0040329B] <--- risultato del 1° check :00401DFA 8B1D9F324000 mov ebx, dword ptr [0040329F] <--- risultato del 2° check :00401E00 03C3 add eax, ebx :00401E02 8B1DA3324000 mov ebx, dword ptr [004032A3] <--- risultato del 3° check :00401E08 03C3 add eax, ebx :00401E0A 8B1D31344000 mov ebx, dword ptr [00403431] <--- ??? :00401E10 03C3 add eax, ebx :00401E12 B94A812357 mov ecx, 5723814A :00401E17 33C1 xor eax, ecx :00401E19 D3C0 rol eax, cl <---| equivale a rol eax, 0A (poiche' rol eax, 20 coincide con eax, quindi 4A mod 20 = 0A ) :00401E1B 3D5CD58289 cmp eax, 8982D55C | in sostanza e' dovuta al fatto che ogni volta che si ruota eax di 32 bit si ottiene di nuovo eax. :00401E20 0F85E2F2FFFF jne 00401108 :00401E26 E8FBFCFFFF call 00401B26 <--- comparsa messagebox Questa deve essere la parte al quale Que si riferiva quando diceva di spiegare come mai anche se passati i 3 check con successo non compare nessun messaggio di congratulazioni. Come vedete tutte le locazioni ad eccezione di [403431] hanno un valore ben preciso, questa locazione a quanto pare contiene il valore 0, facendo un rapido calcolo otteniamo... [40329B]+[40329F]+[4032A3]+[403431] = F896 + 91B2 + 57B3 + 0 = 1E1FB 1E1FB xor 5723814A = 572260B1 572260B1 rol 0A = 8982C55C che come potete vedere e' diverso dal valore che invece si aspetta il programma, ossia 8982D55C Le prime 3 locazioni interessate hanno tutte un valore ben preciso che indica il passaggio del rispettivo check, mentre l'ultima ci e' ancora sconosciuta, vediamo un po' che valore avrebbe dovuto assumere per poter visualizzare il message-box... Seguendo il ragionamento inverso... 8982D55C ror 0A = 572260B5 572260B5 xor 5723814A = 1E1FF 1E1FF - ([40329B]+[40329F]+[4032A3]) = 1E1FF - ( F896 + 91B2 + 57B3 ) = 4 eccolo qui, quindi secondo i nostri calcoli questa locazione dovrebbe valere 4, per soddisfare la condizione eax==8982D55C infatti modificando la locazione [403431] con il valore 4 il message-box compare Ora vediamo un po' di trovare il punto in cui viene modificato il valore di questa locazione, proviamo a ricercare nel disassemblato del prog. la locazione incriminata, ecco qui che troviamo una prima occorrenza di questa locazione, ma guarda un po', si trova nella routine di comparsa dell'about box, e ad ogni comparsa dell'about box, questa locazione viene incremenetata. Come fare per ottenere il valore 4 in quella locazione ? Che ne dite di pigiare 4 volte l'about box prima di eseguire la registrazione del prog ? Tentiamo.... e funzia !!!! Purtroppo il tempo e' quello che e' e domani ho un esame, quindi spero non me ne vogliate se taglio corto e chiudo qui con la spiegazione, quindi non andro' ad analizzare le due routine di codifica, sorry ma proprio non ci sto dentro con i tempi, mi sa che questo e' l'ultimo corso del quale spedisco la soluz. per un po' mi dedichero' alla stesura di qualche tute, ciauz Il.Socio