A:link { TEXT-DECORATION: underline } A:visited { TEXT-DECORATION: underline } A:active { COLOR: red; TEXT-DECORATION: underline } A:hover { COLOR: #8080ff; TEXT-DECORATION: underline }

GuZuRa's MatMe2 Serial Fishing

Data

by Spider

 

24 settembre 2002

UIC's Home Page

Published by Quequero

Cu si 'ncazza senza mutivu...

Qualche mio eventuale commento sul tutorial :)))

...si scazza senza sudisfazioni

 

Home Page: http://bigspider.cjb.net 
E-mail: spider_xx87@hotmail.com

Canali IRC frequentati: #crack-it e #asm su irc.azzurra.org
Nickname: ^Spider^
 

Difficoltà

( )NewBies (X)Intermedio ( )Avanzato ( )Master

 

Il crackme di GuZuRa promette di essere un interessante crackme matematico.


GuZuRa's MatMe2 Serial Fishing
Let's math!
Written by Spider

Introduzione


Il crackme richiede solo il serial. Vediamo cosa dice l'autore:
 
MatMe2 by Guzura

E' il mio secondo crackme e non dovrebbe essere molto più difficile del primo
anche questo non contiene nulla di particolarmente difficile NON ci sono trick antisice
NON è pakkato

Stavolta oltre a un poco di matematica spicciola dovrete capire una banalissima routine
che non "dissipa" stack (idea nata dopo una discussione con Quake2 su #crack-it)
per usare un termine caro agli elettronici (non è tail ricorsione ma ci si avvicina) 

Naturalmente niente patching tutto il resto è valido

Spero apprezziate il tentativo

Apprezziamo, apprezziamo... :)
 

Tools usati


- SoftIce 4.05;
- IDA Pro 4.15 o superiore;
- Microsoft Visual C++ 6.0 o superiore. 

Essay

Iniziamo l'analisi del crackme. Per trovare la routine di check del serial è sufficiente mettere un breakpoint su GetWindowTextA e GetDlgItemTextA, le due API più amate dal reverser... mettiamo un seriale a caso e premiamo ok... SoftICE ovviamente breakka immediatamente, premiamo F12 e ci troviamo subito dopo la chiamata a GetDlgItemTextA; osserviamo i parametri e notiamo subito che il serial inserito viene memorizzato all'indirizzo 004056E0. Dopo aver premuto F12 ci ritroveremo su una call, seguita da un cmp eax,1 e poco dopo da un jnz... Chiaramente è la chiamata alla routine di check del serial :)

.text:00401170 sub_401170 proc near ; CODE XREF: .text:004010EAp
.text:00401170 call sub_401180
.text:00401175 neg eax
.text:00401177 sbb eax, eax
.text:00401179 neg eax
.text:0040117B retn
.text:0040117B sub_401170 endp

Un po' piccolo come controllo, non credete? :P Infatti è solo una funzione "ponte". Il vero check si trova nella sub_401180 che invece è un po' più voluminosa. Qui notiamo invece:

.text:00401175 neg eax
.text:00401177 sbb eax, eax
.text:00401179 neg eax

Queste tre righe sono molto comuni nei programmi scritti in C. Con esse eax rimane invariato se è uguale a zero, altrimenti viene messo ad 1.

Bene, prendiamo IDA e disassembliamo MatMe2.exe, che ovviamente IDA sbrana in 2 secondi dato che il crackme occupa solo 28 kb :) Rinominiamo come serial la locazione 004056E0, e come CheckSerial la sub_401170 (per una guida ai comandi di IDA vi rimando al tutorial di Quequero).

Entriamo in CheckSerial e iniziamo l'analisi:

.text:00401180 CheckSerial     proc near               ; CODE XREF: sub_401170p
.text:00401180 
.text:00401180 var_29         = byte ptr -29h
.text:00401180 var_28         = dword ptr -28h
.text:00401180 var_24         = dword ptr -24h
.text:00401180 var_20         = dword ptr -20h
.text:00401180 var_1C         = dword ptr -1Ch
.text:00401180 var_18         = dword ptr -18h
.text:00401180 var_14         = dword ptr -14h
.text:00401180 var_10         = dword ptr -10h
.text:00401180 var_C          = dword ptr -0Ch
.text:00401180 var_8          = dword ptr -8
.text:00401180 
.text:00401180                sub     esp, 2Ch
.text:00401183                mov     al, byte_4050C4  ;Mette in al il carattere ' ' (spazio)
.text:00401188                push    ebx
.text:00401189                push    ebp
.text:0040118A                push    esi
.text:0040118B                lea     ecx, [esp+38h+var_29] ;ecx punta a null (carattere 0)
.text:0040118F                push    edi
.text:00401190                push    ecx             ; const char *
.text:00401191                push    offset Serial   ; char *
.text:00401196                mov     [esp+44h+var_29], al
.text:0040119A                xor     esi, esi         ;azzera esi che farà da contatore
.text:0040119C                call    _strtok          ;Prima chiamata a strtok
.text:004011A1                add     esp, 8
.text:004011A4                test    eax, eax         ;se la chiamata a strtok è fallita...
.text:004011A6                jz      short loc_4011D0 ;...interrompe il ciclo
.text:004011A8                lea     edi, [esp+3Ch+var_28]
.text:004011AC 
.text:004011AC loc_4011AC:                             ; CODE XREF: CheckSerial+4Ej
.text:004011AC                push    eax             ; const char *
.text:004011AD                call    sub_4013FB       ;funzione ponte per richiamare atol
.text:004011B2                lea     edx, [esp+40h+var_29] ;edx punta al carattere ' '(spazio)
.text:004011B6                mov     [edi], eax
.text:004011B8                push    edx             ; const char *
.text:004011B9                push    0               ; char *
.text:004011BB                inc     esi              ;incrementa il contatore
.text:004011BC                add     edi, 4
.text:004011BF                call    _strtok
.text:004011C4                add     esp, 0Ch
.text:004011C7                cmp     esi, 0Ah         ;se abbiamo eseguito 10 volte...
.text:004011CA                jz      short loc_4011D0 ;...interrompe il ciclo
.text:004011CC                test    eax, eax         ;se l'ultima call a strtok è fallita...
.text:004011CE                jnz     short loc_4011AC ;...interrompe il ciclo

Questo spezzone di codice si occupa semplicemente di effettuare il parsing del seriale inserito. Come vediamo facilmente, il seriale è diviso in tante parti separate da spazi. Poiché il ciclo viene eseguito 10 volte, potremmo concludere che il serial sia diviso in 10 parti... però qualcosa non va:

.text:00401180 var_28         = dword ptr -28h
.text:00401180 var_24         = dword ptr -24h
.text:00401180 var_20         = dword ptr -20h
.text:00401180 var_1C         = dword ptr -1Ch
.text:00401180 var_18         = dword ptr -18h
.text:00401180 var_14         = dword ptr -14h
.text:00401180 var_10         = dword ptr -10h
.text:00401180 var_C          = dword ptr -0Ch
.text:00401180 var_8          = dword ptr -8

Ogni parte del seriale viene convertita in numero tramite la funzione atol (e ciò ci fa intuire anche che il seriale sia composto da soli numeri) e memorizzata in un array nello stack a partire da var_28, quindi abbiamo var_28, var_24, var_20, var_1C, var_18, var_14, var_10, var_0C, var_08... solo 9 variabili!  Se inseriamo un seriale di 10 parti l'ultima viene praticamente ignorata, mentre i seriali corretti sono di 9 parti. Poiché la conversione in numero decimale è senz'altro il preludio alle operazioni matematiche che verranno dopo, assegniamo delle lettere da a ad i (rinominando le variabili da IDA) per facilitarci le cose.

La parte successiva è piuttosto corposa, ed è composta da una serie di 8 check sulle prime 8 parti del serial (ho eliminato le parti inutili per chiarezza):

.text:004011D0                 mov     ecx, [esp+3Ch+a]
.text:004011D4                 cmp     ecx, 3          ; a deve essere maggiore di 3
.text:004011D7                 jg      short loc_4011E3
.text:[...]
.text:004011E3                 mov     eax, [esp+3Ch+b]
.text:004011E7                 cmp     eax, 3          ; b deve essere maggiore di 3
.text:004011EA                 jg      short loc_4011F6
.text:[...]
.text:004011F6                 mov     edi, [esp+3Ch+c]
.text:004011FA                 lea     edx, [eax+4]
.text:004011FD                 cmp     edi, edx        ; c deve essere maggiore di b+4
.text:004011FF                 jg      short loc_40120B
.text:[...]
.text:0040120B                 cmp     [esp+3Ch+d], edi ; d deve essere maggiore di c
.text:0040120F                 jg      short loc_40121B
.text:[...]
.text:0040121B                 cmp     [esp+3Ch+e], 2  ; e deve essere maggiore o uguale a 2
.text:00401220                 jge     short loc_40122C
.text:[...]
.text:0040122C                 mov     ebp, [esp+3Ch+f]
.text:00401230                 cmp     ebp, 3          ; f deve essere maggiore di 3
.text:00401233                 jg      short loc_40123F
.text:[...]
.text:0040123F                 mov     esi, [esp+3Ch+g]
.text:00401243                 cmp     esi, 5          ; g deve essere maggiore di 5
.text:00401246                 jg      short loc_401252
.text:[...]
.text:00401252                 mov     ebx, [esp+3Ch+h]
.text:00401256                 cmp     ebx, 3          ; h deve essere maggiore di 3
.text:00401259                 jg      short loc_401265

 

Bene, adesso sappiamo il formato generale del nostro serial:

a b c d e f g h i

con:

a > 3
b > 3
c > b + 4
d > c
e => 2
f > 3
g > 5
h > 3

Proseguiamo l'analisi della routine:

.text:00401265 loc_401265:                             ; CODE XREF: sub_401180+D9j
.text:00401265                 push    0
.text:00401267                 push    0
.text:00401269                 push    eax             ; eax = b
.text:0040126A                 push    ecx             ; ecx = a
.text:0040126B                 call    calcola
.text:00401270                 mov     r1, eax
.text:00401275                 mov     eax, [esp+4Ch+d]
.text:00401279                 push    0
.text:0040127B                 push    0
.text:0040127D                 push    eax             ; eax = d
.text:0040127E                 push    edi             ; edi = c
.text:0040127F                 call    calcola
.text:00401284                 mov     ecx, [esp+5Ch+e]
.text:00401288                 push    0
.text:0040128A                 push    0
.text:0040128C                 push    ebp             ; ebp = f
.text:0040128D                 push    ecx             ; ecx = e
.text:0040128E                 mov     r2, eax
.text:00401293                 call    calcola
 [. . .]
.text:004012AE                 mov     r3, eax

Ho già rinominato alcune locazioni, per rendere il codice più chiaro. Questo spezzone di codice richiama tre volte una funzione (che ho chiamato calcola: la analizzeremo tra poco) passando come argomenti le prime 6 parti del serial, a due a due (prima a e b, poi c e d, infine e ed f). I risultati vengono poi salvati in tre dword che ho rinominato r1, r2 ed r3. Vediamo adesso la funzione calcola (anche qui ho rinominato qualcosa):

.text:00401310 ; Attributes: bp-based frame
.text:00401310 
.text:00401310 calcola         proc near               ; CODE XREF: sub_401180+EBp
.text:00401310                                         ; sub_401180+FFp ...
.text:00401310 
.text:00401310 x               = dword ptr  8
.text:00401310 y               = dword ptr  0Ch
.text:00401310 flag            = byte ptr  10h
.text:00401310 newRetAddress   = dword ptr  14h
.text:00401310 
.text:00401310                 push    ebp
.text:00401311                 mov     ebp, esp
.text:00401313                 pop     ebp
.text:00401314                 cmp     [esp-4+flag], 1
.text:00401319                 jz      short loc_401326
.text:0040131B                 mov     eax, [esp+0]    ; eax = return address
.text:0040131E                 mov     [esp-4+newRetAddress], eax
.text:00401322                 inc     [esp-4+flag]
.text:00401326 
.text:00401326 loc_401326:                             ; CODE XREF: calcola+9j
.text:00401326                 add     esp, 4
.text:00401329                 mov     eax, [esp-8+y]  ; eax = y
.text:0040132D                 cmp     eax, 0          ; se y è 0...
.text:00401330                 jz      short loc_401363 ; ...usciamo dalla call
.text:00401332                 mov     ecx, 2
.text:00401337                 cdq
.text:00401338                 div     ecx             ; divide y per 2
.text:0040133A                 cmp     edx, 1
.text:0040133D                 jz      short loc_401348 ; salta se il resto è 1
.text:0040133F                 mov     eax, [esp-8+x]  ; eax = x
.text:00401342                 dec     eax             ; decrementa x
.text:00401343                 mov     [esp-8+x], eax
.text:00401346                 jmp     short loc_401355
.text:00401348 ; ---------------------------------------------------------------------------
.text:00401348 
.text:00401348 loc_401348:                             ; CODE XREF: calcola+2Dj
.text:00401348                 mov     eax, [esp-8+x]
.text:0040134B                 mov     ecx, 3
.text:00401350                 mul     ecx             ; moltiplica x per 3
.text:00401352                 mov     [esp-8+x], eax
.text:00401355 
.text:00401355 loc_401355:                             ; CODE XREF: calcola+36j
.text:00401355                 mov     ecx, [esp-8+y]
.text:00401359                 dec     ecx             ; decrementa y
.text:0040135A                 mov     [esp-8+y], ecx
.text:0040135E                 call    calcola         ; ripete tutto
.text:00401363 
.text:00401363 loc_401363:                             ; CODE XREF: calcola+20j
.text:00401363                 mov     eax, [esp-8+newRetAddress]
.text:00401367                 push    eax
.text:00401368                 mov     eax, [esp-4+x]  ; eax = return value
.text:0040136C                 retn
.text:0040136C calcola         endp
.text:0040136C 

Questa è quella sorta di "tail ricorsione" che Guzura ci aveva preannunciato nel readme. In effetti ci sono un po' di smanettamenti con lo stack che servono in pratica per riutilizzare le stesse variabili nello stack ad ogni ricorsione, evitando così di sprecare stack inutilmente, che, per un numero molto elevato di ricorsioni, avrebbe anche potuto provocare il crash del programma. La funzione risulta a prima vista (ossia prima di rinominare gli argomenti e commentare il tutto) un po' ingarbugliata, ma aiutandoci eventualmente con l'insostituibile supporto del debugger, il codice diventa pian piano più chiaro mano mano che si commenta e si rinominano le locazioni.
La funzione appena vista prende due soli argomenti (che ho chiamato x e y), mentre gli altri due sono inutili ai nostri scopi perché servono solo alla funzione per quella tail ricorsione appena vista.
Schematizzando, è questo l'algoritmo seguito dalla funzione:

se y è 0 finisce tutto
se y è pari x = x - 1
se y è dispari x = x * 3
decrementa y
ripeti

Poiché su questa funzione gravita tutto l'algoritmo, facciamo un po' di analisi matematica su input e output della funzione :) Niente paura, non è necessaria nessuna conoscenza matematica particolare, bastano qualche calcolo e un po' di intuito. Per facilitarci, creiamo un programmino che ci generi una lista di valori ritornati dalla funzione (che ribattezziamo funzione G(x,y), ovvero funzione GuZuRa :P):

x = 1; y = 1; r = 3

x = 1; y = 2; r = 0
x = 2; y = 2; r = 3

x = 1; y = 3; r = 6
x = 2; y = 3; r = 15
x = 3; y = 3; r = 24

x = 1; y = 4; r = -3
x = 2; y = 4; r = 6
x = 3; y = 4; r = 15
x = 4; y = 4; r = 24

x = 1; y = 5; r = 15
x = 2; y = 5; r = 42
x = 3; y = 5; r = 69
x = 4; y = 5; r = 96
x = 5; y = 5; r = 123

x = 1; y = 6; r = -12
x = 2; y = 6; r = 15
x = 3; y = 6; r = 42
x = 4; y = 6; r = 69
x = 5; y = 6; r = 96
x = 6; y = 6; r = 123

x = 1; y = 7; r = 42
x = 2; y = 7; r = 123
x = 3; y = 7; r = 204
x = 4; y = 7; r = 285
x = 5; y = 7; r = 366
x = 6; y = 7; r = 447
x = 7; y = 7; r = 528

Possiamo subito notare alcune cose importanti:

  1. Se y è pari, si ottiene un risultato equivalente decrementando sia x che y (fatto tra l'altro facilmente desumibile dall'algoritmo). Ciò è molto importante perché ci consente di limitare i nostri studi a y dispari :)
  2. Tutti gli elementi di G sono multipli di 3.
  3. La differenza tra G(x, y) e G(x+1,y) è costante per qualunque x. Ad esempio se G(5, 6)-G(4, 6) = 27, anche G(4, 6)-G(3, 6) = 27. In particolare si può dimostrare che la differenza tra due elementi consecutivi di G (mantenendo invariata y) per y dispari è uguale a:

    3(y+1)/2

    Ad esempio, per calcolare la differenza tra due elementi consecutivi dell'insieme G per y = 7, ci basta calcolare 3(y+1)/2 = 3(7+1)/2 = 34 = 81, cosa che possiamo facilmente verificare dalla lista vista prima.
  4. Una cosa che non è per noi essenziale, ma ritengo comunque interessante, è la formula diretta per il calcolo di G(x,y) in maniera non ricorsiva. Eccola:

    G(x, y) = 3(y+1)/2 - SUM(3m,m,1,(y-1)/2) + (x-1) * 3FLOOR((x+1)/2)

    Dove SUM è la sommatoria di 3m per m che va da 1 a (y-1)/2, e FLOOR è una funzione che restituisce la parte intera dell'argomento... Bella, vero? :))

Bene, questo è tutto quanto risulta dalle mie 5 pagine di appunti sulla funzione :-)

Riprendiamo l'analisi del corpo della routine di check del serial:

.text:00401298                 mov     edx, r1
.text:0040129E                 mov     edi, r2
.text:004012A4                 mov     ecx, edx        ; ecx = r1
.text:004012A6                 add     esp, 30h
.text:004012A9                 imul    ecx, esi        ; ecx = r1 * g
.text:004012AC                 add     ecx, edi        ; ecx = r1 * g + r2
.text:004012AE                 mov     r3, eax
.text:004012B3                 imul    ecx, esi        ; ecx = (r1 * g + r2) * g
.text:004012B6                 add     ecx, eax        ; ecx = (r1 * g + r2) * g + r3
.text:004012B8                 mov     dword_40572C, esi
.text:004012BE                 cmp     ecx, 63627      ; ecx deve essere uguale a 63627
.text:004012C4                 mov     dword_405730, ecx
.text:004012CA                 jz      short loc_4012D6 ; salta se abbiamo passato il check
.text:004012CC                 pop     edi
.text:004012CD                 pop     esi
.text:004012CE                 pop     ebp
.text:004012CF                 xor     eax, eax
.text:004012D1                 pop     ebx
.text:004012D2                 add     esp, 2Ch
.text:004012D5                 retn
.text:004012D6 ; ---------------------------------------------------------------------------
.text:004012D6 
.text:004012D6 loc_4012D6:                             ; CODE XREF: sub_401180+14Aj
.text:004012D6                 imul    edx, ebx        ; edx = r1 * h
.text:004012D9                 add     edx, edi        ; edx = r1 * h + r2
.text:004012DB                 xor     ecx, ecx
.text:004012DD                 imul    edx, ebx        ; edx = (r1 * h + r2) * h
.text:004012E0                 add     edx, eax        ; edx = (r1 * h + r2) * h + r3
.text:004012E2                 mov     eax, [esp+3Ch+i] ; eax = i
.text:004012E6                 cmp     edx, eax        ; edx deve essere uguale ad i
.text:004012E8                 pop     edi
.text:004012E9                 setz    cl

Bene bene... siamo nel cuore dell'algoritmo di check. Dobbiamo trovare un seriale che soddisfi queste due equazioni:

(r1 * g + r2) * g + r3 = 63627
(r1 * h + r2) * h + r3 = i

Osserviamo che, una volta trovati valori validi per la prima equazione, possiamo facilmente ricavare valori validi per la seconda. Il problema si riduce dunque a trovare una soluzione per la prima equazione. Attenzione, però: per r1, r2 ed r3 dobbiamo rispettare le condizioni viste prima riguardo agli elementi di G. Una volta trovata una soluzione dell'equazione troveremo per r1, r2 ed r3 tre coppie (a,b), (c,d), (e,f) di valori tali che G(a, b) = r1; G(c; d) = r2; G( e; f) = r3. Infine cercheremo una combinazione di valori validi anche per h ed i.
Scriviamo dunque una prima bozza di bruteforcer, tenendo conto del fatto che tutti gli elementi di G sono multipli di 3 e che:

a > 3
b > 3
per cui r1 = G(a, b) >= G(3, 3) >= 24

c > b+4 ovvero, per b = 4, c > 8
d > c ovvero, per c = 9, d > 9
per cui r2 = G(c, d) >= G(8, 9) >= 1824

e >= 2
f > 3 
per cui r3 = G(e, f) >= G(2, 3) >= 6

g > 5

Ed ecco la prima bozza del brute, che andremo poi ad ottimizzare:

#include <stdio.h>

void main()
{
    int g;
    int r1, r2, r3;

    printf("GuZuRa's MatMe2 Bruteforcer - by Spider\n");

    for (r1 = 24; r1 <= 100000; r1+=3)
    {
        for (r2 = 1824; r2 <= 100000; r2+=3)
        {
            for (r3 = 6; r3 <= 100000; r3+=3)
            {
                for (g = 6; g <= 100000; g++)
                {
                    if ((r1 * g + r2) * g + r3 == 63627)
                    {
                        printf("r1 = %d; r2 = %d; r3=%d; g=%d\n",r1,r2,r3,g);
                    }
                }
            }
        }
    }
}

Il numero di combinazioni che dovremmo controllare in questo modo è chiaramente eccessivo (100000 è un numero alto, inserito perché non conosciamo i valori massimi di r1, r2, r3 e g). Possiamo tuttavia diminuire le combinazioni calcolando il valore massimo che possa avere ciascuna incognita, e questo basandoci sui valori minimi che invece già conosciamo. Se infatti diamo a 3 delle 4 incognite il valore minimo che esse possono avere e poi risolviamo l'equazione, otteniamo il valore massimo ottenibile dall'altra incognita. Per cui:

(maxr1 * 6 + 1824) * 6 + 6 = 63627
36*maxr1 + 10944 + 6 - 63627 = 0
36*maxr1=52677

24 <= r1 < 1464

=================================

(24 * 6 + maxr2) * 6 + 6 = 63627
864 + 6*maxr2 + 6 = 63627
maxr2 = 10460

1824 <= r2 < 10460

=================================

(24 * 6 + 1824) * 6 + maxr3 = 63627

11808 + maxr3 = 63627
maxr3 = 51820

6 <= r3 < 51820

=================================

(24 * g + 1824) * g + 6 = 63627

24g2 + 1824g - 63621 = 63627

g = (-1824 + SQR(18242 - 4 * 24 * (-63621)))/(2*24) = (-1824 + ~3072)/48 = 26

6 <= g < 26

Possiamo infine migliorare notevolmente il nostro bruteforcer calcolando, con la proprietà vista prima, la differenza tra 2 elementi consecutivi dell'insieme G per r1, r2 ed r3. Infatti:

r1 = G(a, b), e poiché b > 3 possiamo calcolare la differenza minima tra due possibili soluzioni come 3(3+1)/2 = 32 = 9

Analogamente

r2 = G(c, d), e poiché d > 9 possiamo calcolare la differenza minima tra due possibili soluzioni come 9(9+1)/2 = 35 = 243

r3 = G(e, f), e poiché f > 3 possiamo calcolare la differenza minima tra due possibili soluzioni come 3(3+1)/2 = 32 = 9

Con questi ultimi accorgimenti riduciamo il numero di combinazioni da controllare circa allo 0.13% rispetto a prima... niente male, no? :)

Ecco la versione definitiva del brute:

#include <stdio.h>

void main()
{
    int g;
    int r1, r2, r3;

    printf("GuZuRa's MatMe2 Bruteforcer - by Spider\n");

    for (r1 = 24; r1 <= 1464; r1+=9)
    {
        for (r2 = 1824; r2 <= 10460; r2+=243)
        {
            for (r3 = 6; r3 <= 51820; r3+=9)
            {
                for (g = 6; g <= 26; g++)
                {
                    if ((r1 * g + r2) * g + r3 == 63627)
                    {
                        printf("r1 = %d; r2 = %d; r3=%d; g=%d\n",r1,r2,r3,g);
                    }
                }
            }
        }
    }
}


Compiliamo, eseguiamo.... ed ecco una caterva di soluzioni valide... prendiamo la prima, così restiamo sui numeri piccoli (per fermare il bruteforcer potete utilizzare il tasto "pausa", oppure modificate il bruteforcer in modo che si fermi dopo aver trovato qualche soluzione):

r1 = 24; r2 = 1824; r3=6027; g=24


Bene, sappiamo che r1 = 24
significa che a = 4 e b = 4 (non possiamo ovviamente mettere a = 3 e b = 3 per le condizioni viste prima). Sappiamo anche che r2 = 1824 significa che c = 8 e d = 9, ma siccome d è dispari e noi vogliamo c > b+4, poniamo c = 8+1= 9 e d = 9 + 1 = 10, che non cambia il valore di r2. Non sappiamo però come scomporre 6027, e a questo scopo scriviamo due righe di codice in C:

#include <stdio.h>

int G(int x, int y)
{
    while (y)
    {
        if (y%2)
            x *= 3;
        else
            x--;
        y--;
    }
    return x;
}

void main()
{
    int x, y;

    for (x = 1; x < 1000;x++)
    {
        for (y = 1; y < 1000;y++)
        {
            if (G(x,y) == 6027)
                printf("x = %d; y = %d\n", x, y);
        }
    }
}

Compiliamo ed eseguiamo, e dopo una manciata di secondi il programma ci comunica le due soluzioni trovate:

x = 670; y = 3
x = 671; y = 4

Ovviamente prendiamo in considerazione solo la seconda, dato che vogliamo f > 3.

Sappiamo quindi che:

a = 4
b = 4
c = 9
d = 10
e = 671
f = 4
g = 24

Adesso dobbiamo trovare dei valori di h ed i che soddisfino l'equazione vista prima:

(r1 * h + r2) * h + r3 = i

Per far ciò assegniamo un valore qualunque ad h (4, per restare sui numeri piccoli), e calcoliamo i come:

i = (r1 * h + r2) * h + r3 = (24 * 4 + 1824) * 4 + 6027 = 13707

ed ecco il serial completo:

4 4 9 10 671 4 24 4 13707

Lo inseriamo nella editbox del MatMe2 ed ecco finalmente comparire la messagebox di congratulazioni :)


Spider

Note finali


Saluti a GuZuRa che ha fatto il crackme e che ha distrutto il mio Pythagoras in pochi nanosecondi :D

Disclaimer

Vorrei ricordare che il software va comprato e  non rubato, dovete inviare a GuZuRa i soldi necessari per la registrazione del crackme, così poi io e GuZuRa dividiamo e diventiamo ricchi.


Home

<!-- DIV 728x90 IAM --> <div id="ad72890bottom" align="center"></div> <!-- START Digilander F --> <SCRIPT LANGUAGE="Javascript"> <!-- if ( typeof(bsl1_boot) != 'undefined' ) { setTimeout("bsl1_boot()",100); } var rs_DLR=1; var rs_DLRERR=0; //--> </SCRIPT> <SCRIPT LANGUAGE="Javascript" SRC="http://digilander.libero.it/_ad/digi_ad_13.js"> </SCRIPT> <!-- Begin Nielsen DCR SDK --> <script> if(window.location === window.parent.location){ // Static Queue Snippet ! function(t, n) { t[n] = t[n] || { nlsQ: function(e, o, c, r, s, i) { return s = t.document, r = s.createElement("script"), r.async = 1, r.src = ("http:" === t.location.protocol ? "http:" : "https:") + "//cdn-gl.imrworldwide.com/conf/" + e + ".js#name=" + o + "&ns=" + n, i = s.getElementsByTagName("script")[0], i.parentNode.insertBefore(r, i), t[n][o] = t[n][o] || { g: c || {}, ggPM: function(e, c, r, s, i) { (t[n][o].q = t[n][o].q || []).push([e, c, r, s, i]) } }, t[n][o]}}} (window, "NOLBUNDLE"); // SDK Initialization var nSdkInstance = NOLBUNDLE.nlsQ("P1504C48C-9D0B-4ADE-B7CD-04AF56A52362", "nlsnInstance"); // Content Metadata var nielsenMetadata = { type: 'static', assetid: ( location.hostname + location.pathname + location.search ).replace( /([^\w]|_)+/g, '-' ).replace( /^-+|-+$/g, '' ) || 'homepage', section: 'LiberoCommunity_BRW' }; // Event 'staticstart' Call nSdkInstance.ggPM("staticstart", nielsenMetadata); } </script> <!-- End Nielsen DCR SDK --> <!-- Libero COMSCORE start - Version 1.53 --> <script type="text/javascript"> if ( rs_DLRERR == 1 ) { var libero_comscore_error = 404; } </script> <script type="text/javascript"> document.write(unescape("%3Cscript src='" + (document.location.protocol == "https:" ? "https://sb" : "http://b") + ".scorecardresearch.com/beacon.js'%3E%3C/script%3E")); </script> <script type="text/javascript"> if (rs_DLR) { document.write(unescape("%3Cscript id='libero_tracking_js_site' src='http://digistatic.libero.it/js/comscore_8_3_04/comscore_digilander.libero.it.js'%3E%3C/script%3E")); document.write(unescape("%3Cscript id='libero_tracking_js_site' src='http://digistatic.libero.it/js/comscore_8_3_04/comscore_engine.js'%3E%3C/script%3E")); } </script> <noscript> <img src="http://b.scorecardresearch.com/p?c1=2&amp;c2=13259779&amp;cj=1&amp;name=libero.others&amp;ns_site=libero" /> </noscript> <!-- Libero COMSCORE end --> <!-- IOL Analytics --> <script src="//i.plug.it/iplug/js/lib/iol/analytics/data/digilander-libero-it/tracking_digilander-libero-it.min.js"></script> <script src="//i.plug.it/iplug/js/lib/iol/analytics/engine/IOL.Analytics.Tracking.min.js"></script> <script type="text/javascript"> var iat = new IOL.Analytics.Tracking.Engine(); iat.send(); </script> <noscript><img src="//italiaonline01.wt-eu02.net/215973748390194/wt.pl?p=315,libero.web.share.digiland.siti.digilander&amp;cg1=libero&amp;cg2=web&amp;cg3=share&amp;cg4=digiland&amp;cg5=siti&amp;cg6=digilander&amp;cg7=libero.web.share.digiland.siti.digilander" height="1" width="1" alt=""></noscript> <!-- /IOL Analytics --> <!-- BEGIN Global site tag (gtag.js) - Google Analytics 4 --> <script async src="https://www.googletagmanager.com/gtag/js?id=G-9K5Y6YYGV4"></script> <script> window.dataLayer = window.dataLayer || []; function gtag(){dataLayer.push(arguments);} gtag('js', new Date()); gtag('config', 'G-9K5Y6YYGV4'); </script> <!-- END Global site tag (gtag.js) - Google Analytics 4 --> <div id="adinterstitial"></div> </body> </html>