Zoom Icon

Ultra Edit Keygenning

From UIC

Keygenniamo UltraEdit 15

Contents


Infos
Author: Tonyweb
Email: tonyweb_mail@yahoo.it
Website: -
Date: 20/04/2009 (dd/mm/yyyy)
Level: Luck and skills are required
Language: Italian Image:Flag_Italian.gif
Comments:

E' il mio primo tutorial, siate comprensivi.



Introduzione

In questo mio primo tutorial vorrei condividere con voi la mia più difficile esperienza di keygenning finora. Il programma in oggetto non ha affatto bisogno di presentazioni perciò non perdiamoci in chiacchiere e cominciamo.


Allegato

Scarica sorgenti del keygen


Tools & Files

Gli strumenti utilizzati sono quelli classici.

  • PeID 0.95
  • OllyDbg 1.10
  • eXeScope (o qualunque altro visualizzatore di risorse)
  • Visual Studio (per la scrittura del keygen)


URL o FTP del programma


Essay

Nel corso di questo tutorial analizzeremo in dettaglio le routine predisposte al controllo dei seriali immessi dall'utente ed illustreremo un modo per reversare il tutto e generare una chiave valida.

Il primo avvio

Avviamo il programma per la prima volta e ci viene mostrata una Reminder Nag che ci informa del fatto che abbiamo a disposizione soltanto 45 giorni per provare il prodotto e che, se vogliamo utilizzarlo oltre questo periodo, dobbiamo acquistare una licenza.

Image:UeditNag.gif

Cliccando sul pulsante "Registra" si ha la possibilità di inserire un nome utente e un codice di autorizzazione per registrare il programma. Se proviamo ad inserire una coppia nome utente / seriale il programma ci chiederà di chiudere il programma e riavviarlo per validare le informazioni inserite.

Image:UeditRestart.GIF

Cosa abbiamo capito finora ? Che il programma accetta una combinazione nome utente / seriale e che verifica che sia corretta all'avvio. Se siamo particolarmente attenti possiamo anche notare che, prima di chiudersi, il programma salva un file dal nome uedit32.reg nella cartella "%userprofile%\Application Data\IDMComp\UltraEdit". Sarà da questo file che saranno prelevate le informazioni digitate dall'utente nella dialog di registrazione e successivamente controllate. Notiamo che detto file viene creato solo se, almeno formalmente, il seriale è corretto. Un seriale è corretto se è costituito da otto gruppi di cinque caratteri separati da un trattino:

XXXXX-XXXXX-XXXXX-XXXXX-XXXXX-XXXXX-XXXXX-XXXXX

Quando ho studiato la versione precedente del target, per individuare la routine di registrazione ho pensato di sfruttare questo comportamento posizionando dei BP sulle API di apertura e lettura dei file (CreateFileA/W, ReadFileA/W, ecc.) ma, per steppare fino al punto giusto, ho perso un sacco di tempo prezioso.

Questa volta, invece, che il target lo conoscevo, ho utilizzato un approccio molto più diretto ... ne parleremo dopo.

Alla ricerca di Packer

Dato che ci siamo decisi ad affrontarlo vediamo che tipo di nemico abbiamo di fronte. Lanciamo PEiD e guardiamo cosa ha da dirci.

Image:UeditPEiD.GIF

Meno male. Nessun unpacking necessario (sono molto scarso nel MUP-ing). Il risultato del plugin KANAL potrebbe spaventarci ... ma non c'è niente di cui preoccuparsi, ve l'assicuro. Ora che abbiamo qualche informazione in più possiamo aprire il nostro target (Uedit32.exe) in OllyDbg e fare un primo giro sulla giostra.

Possiamo partire.

Accennavo prima ad un metodo veloce per rintracciare la routine di controllo; questo è il momento di svelare qual è. Il programma controlla le informazioni di registrazione (se inserite) e, se risultano non valide, mostra nuovamente la Nag illustrata in precedenza, con la quale richiede di specificare dati corretti questa volta.

L'idea è quella di sfruttare la Nag e di risalire, dal punto in cui viene creata, alla routine di registrazione. Qual è il vantaggio rispetto ad altre tecniche ? La risposta è che in questo programma, una volta individuata la creazione della nag, c'è un modo per rintracciare quasi istantaneamente la routine cui siamo interessati.

Ma non perdiamoci in chiacchiere. Apriamo il file Uedit32.exe con un editor di risorse e spulciamo tra i dialoghi alla ricerca della nag.

Image:UeditExeScope1.PNG

Ops! ... non ce ne sono. Allora apriamo, sempre con l'editor di risorse, il file ueres.dll che troviamo nella stessa cartella dell'eseguibile principale (ha un nome veramente esplicativo Uedit Resources ... non può che essere il file giusto :) ). In esso, infatti, troviamo molte delle risorse utilizzate dal programma e soprattutto troviamo un numero molto elevato di template per finestre di dialogo.

Image:UeditExeScope2.PNG

Dopo un po' di tempo speso alla ricerca del dialog corretto, ci imbattiamo finalmente nella risorsa con id (decimale) 28269 ... che sembra proprio quello che stavamo cercando. A questo punto basta pensare al modo in cui l'applicazione recupera questo template. In genere si utilizza l'API FindResource cui viene passato come argomento l'id della risorsa da prelevare.

Non ci resta dunque che cercare tutte le occorrenze dei comandi PUSH 6E6D ( 6E6D è la controparte esadecimale dell'id del dialog.). Andiamo in OllyDbg (che abbiamo già aperto credo ;) ) e scegliamo Search For | All Commands ... ottenendo come risultato cinque occorrenze:

Image:UeditRefs.PNG

Poniamo un BP su tutte e avviamo l'applicazione (F9). Scatta subito un BP, ma è ancora un po' presto (la Nag appare dopo la finestra principale) quindi rimuoviamo il BP e proseguiamo con un altro F9. Scatta nuovamente un breakpoint ma questa volta siamo nel punto giusto, dal momento che la finestra principale del programma è già visualizzata e ci sono dei riferimenti a stringhe molto convincenti quali "Days to expire" ;).

Ci troviamo all'indirizzo

0043EDFA          .  68 6D6E0000   PUSH 6E6D

Ora saliamo un po' verso l'inizio della procedura, esattamente qui:

0043ECA0          .  6A FF         PUSH -1
0043ECA2          .  68 B086A500   PUSH Uedit32.00A586B0
0043ECA7          .  64:A1 0000000>MOV EAX, DWORD PTR FS:[0]

Ora posizioniamoci in linea 0043ECA0 e facciamo CTRL+R (Find References). Troviamo un unico riferimento. Clicchiamoci su e ci ritroviamo all'indirizzo 00446220:

0044621B  PUSH EAX                                             ; /pWndClass = 7FFDD000
0044621C  MOV DWORD PTR SS:[ESP+70], EBX                       ; |
00446220  MOV DWORD PTR SS:[ESP+74], Uedit32.0043ECA0          ; |                                                   
00446228  MOV DWORD PTR SS:[ESP+78], EBX                       ; |
0044622C  MOV DWORD PTR SS:[ESP+7C], EBX                       ; |
00446230  MOV DWORD PTR SS:[ESP+80], EDX                       ; |
00446237  MOV DWORD PTR SS:[ESP+84], EBX                       ; |
0044623E  MOV DWORD PTR SS:[ESP+88], EBX                       ; |
00446245  MOV DWORD PTR SS:[ESP+8C], EBX                       ; |
0044624C  MOV DWORD PTR SS:[ESP+90], EBX                       ; |
00446253  MOV DWORD PTR SS:[ESP+94], Uedit32.00ADA1E8          ; |ASCII "UEAfx:200:0x0008:0:010"
0044625E  CALL DWORD PTR DS:[<&USER32.RegisterClassA>]         ; \RegisterClassA
00446264  MOV EAX, DWORD PTR DS:[D02B30]
                :
                :
0044628B  PUSH EBX                                             ; /lParam = NULL
0044628C  PUSH ECX                                             ; |hInst = NULL
0044628D  PUSH EBX                                             ; |hMenu = NULL
0044628E  PUSH EAX                                             ; |hParent = 7FFDD000
0044628F  PUSH EBX                                             ; |Height = 0
00446290  PUSH EBX                                             ; |Width = 0
00446291  PUSH EBX                                             ; |Y = 0
00446292  PUSH EBX                                             ; |X = 0
00446293  PUSH EBX                                             ; |Style = WS_OVERLAPPED
00446294  PUSH Uedit32.00ADA1E8                                ; |WindowName = "UEAfx:200:0x0008:0:010"
00446299  PUSH Uedit32.00ADA1E8                                ; |Class = "UEAfx:200:0x0008:0:010"
0044629E  PUSH EBX                                             ; |ExtStyle = 0
0044629F  MOV DWORD PTR SS:[ESP+CC], EBX                       ; |
004462A6  CALL DWORD PTR DS:[<&USER32.CreateWindowExA>]        ; \CreateWindowExA
004462AC  LEA ECX, DWORD PTR SS:[ESP+14]
004462B0  PUSH ECX
004462B1  LEA EDX, DWORD PTR SS:[ESP+28]
004462B5  PUSH EDX                                             ;  
004462B6  MOV DWORD PTR DS:[D02B10], EAX
004462BB  MOV EAX, DWORD PTR DS:[D02B34]
004462C0  PUSH Uedit32.00D02AEC
004462C5  PUSH EAX
004462C6  PUSH ESI                                             ;  
004462C7  MOV BYTE PTR DS:[D05AD4], BL
004462CD  MOV DWORD PTR DS:[D02AEC], 2A
004462D7  MOV DWORD PTR SS:[ESP+28], EBX
004462DB  CALL Uedit32.00440D90                                                                                  
004462E0  MOV EBP, DWORD PTR DS:[<&USER32.SendMessageA>]       ;  
004462E6  ADD ESP, 14
004462E9  TEST AL, AL                                                                                                    
004462EB  JE Uedit32.0044660E
004462F1  CMP DWORD PTR DS:[D02B14], EBX
004462F7  JNZ Uedit32.0044660E
004462FD  CMP DWORD PTR SS:[ESP+14], EBX
00446301  JE SHORT Uedit32.0044632B

Se abbiamo già analizzato la versione precedente di UltraEdit, sappiamo che questo codice è quello che si occupa di visualizzare la finestra principale, ma soprattutto ci mostra il nostro punto di attacco: la routine di registrazione.

Come faccio a dirlo ? Sicuramente l'aver analizzato la versione precedente mi rende assolutamente certo che ci troviamo nel posto giusto e che la routine in linea 004462DB (col relativo TEST AL,AL) è la routine di controllo della registrazione. Se non ci si vuole affidare a "ricordi passati" basta guardare un po' più in basso e rendersi conto che avere AL = 0 ci fa saltare porzioni di codice molto interessanti, come la seguente.

00446334   3BC3          CMP EAX, EBX
00446336   0F8C 44010000 JL Uedit32.00446480
0044633C   68 D0A6AD00   PUSH Uedit32.00ADA6D0   ; /Arg2 = 00ADA6D0 ASCII "Extension License"
00446341   51            PUSH ECX                ; |Arg1 = 00D052D8
00446342   E8 60B95800   CALL Uedit32.009D1CA7   ; \Uedit32.009D1CA7
00446347   83C4 08       ADD ESP, 8
0044634A   3BC3          CMP EAX, EBX
0044634C   0F84 2E010000 JE Uedit32.00446480


Possiamo tranquillamente fare la prova del nove. Poniamo un BP in linea 004462DB e riavviamo, questa volta avendo cura di impostare AL = 1 prima di eseguire l'istruzione di TEST. Il programma si avvia senza la nag e in stato registrato. Inoltre se possediamo una registrazione corretta per la versione 14 (un file uedit32.reg) vedremo il nostro nome comparire nella finestra di about.

Assodato che ci troviamo nella posizione voluta, non ci resta altro che studiare la routine di registrazione. Prima di cominciare, voglio segnalare che il codice che mostrerò presenta delle label che ho posizionato in modo da rendere più chiaro il tutto: alcune routine, dunque, avranno nomi (label appunto) come FillWithZero o CopiaStringa che le rendono d'immediata comprensione.

La routine di registrazione è divisa sostanzialmente in tre macro aree che analizzeremo, per quanto possibile, separatamente.

Routine di Registrazione - Parte 1: Generazione del vettore di Lookup.

D'accordo abbiamo trovato la routine di registrazione e sappiamo anche che utilizza il valore di AL per restituire l'indicazione sullo stato di registrazione. Rimuoviamo tutti i BP che abbiamo finora e poniamo un bel BP sull'inizio di questa routine e riavviamo. Ci troviamo qui:

00440D90  /$  6A FF         PUSH -1
00440D92  |.  68 6B8BA500   PUSH Uedit32.00A58B6B
00440D97  |.  64:A1 0000000>MOV EAX, DWORD PTR FS:[0]
00440D9D  |.  50            PUSH EAX                                
00440D9E  |.  81EC 9C020000 SUB ESP, 29C
00440DA4  |.  A1 3CFECF00   MOV EAX, DWORD PTR DS:[CFFE3C]
00440DA9  |.  33C4          XOR EAX, ESP
00440DAB  |.  898424 980200>MOV DWORD PTR SS:[ESP+298], EAX


con in EAX e in ESI i puntatori al nome dell'utente e al seriale da controllare. Chiaramente se non abbiamo inserito nessun dato nella finestra di registrazione punteranno entrambi a delle dword pari a 0.

La routine che ci troviamo davanti può sembrare lunga ma, una volta capito come funziona, risulterà agevole seguirla. Procediamo un passo alla volta.

00440DBD   PUSH EAX
00440DBE   LEA EAX, DWORD PTR SS:[ESP+2B0]
00440DC5   MOV DWORD PTR FS:[0], EAX
00440DCB   MOV EBP, DWORD PTR SS:[ESP+2C4]         ;  << Seriale da controllare
00440DD2   MOV ECX, DWORD PTR SS:[ESP+2CC]         ;  << ind. di ritorno
00440DD9   MOV EDX, DWORD PTR SS:[ESP+2D0]         ;  
00440DE0   MOV EDI, DWORD PTR SS:[ESP+2C0]         ;  << UserName
00440DE7   MOV EAX, DWORD PTR SS:[ESP+2C8]         ;  << ptr a 2A = 42 (indicatore per file *.reg)
00440DEE   MOV DWORD PTR SS:[ESP+1C], ECX
00440DF2   MOV DWORD PTR SS:[ESP+18], EDX
00440DF6   TEST EBP, EBP                           ;  << Inserito un seriale ?
00440DF8   JE SHORT Uedit32.00440E04
00440DFA   MOV DWORD PTR DS:[D02AE8], 1            ;  << si, inserito !
00440E04   MOV DWORD PTR DS:[EAX], 0               ;  << azzera il 2A
00440E0A   MOV EAX, EDI                            ;  << userName
00440E0C   LEA EDX, DWORD PTR DS:[EAX+1]
00440E0F   NOP
00440E10   /MOV CL, BYTE PTR DS:[EAX]
00440E12   |INC EAX
00440E13   |TEST CL, CL
00440E15   \JNZ SHORT Uedit32.00440E10
00440E17   SUB EAX, EDX
00440E19   CMP EAX, 6                              ;  << Verifica che la lunghezza dello userName sia almeno 6
00440E1C   JB Uedit32.0044100C
00440E22   MOV EAX, EBP                            ;  << Seriale
00440E24   LEA EDX, DWORD PTR DS:[EAX+1]
00440E27   /MOV CL, BYTE PTR DS:[EAX]
00440E29   |INC EAX
00440E2A   |TEST CL, CL
00440E2C   \JNZ SHORT Uedit32.00440E27
00440E2E   SUB EAX, EDX
00440E30   CMP EAX, 2F                             ;  << La lunghezza del seriale deve essere almeno 47
00440E33   JB Uedit32.0044100C

Inizialmente troviamo i classici controlli sulla presenza o meno delle informazioni di registrazione e sulla loro validità formale. Da queste poche istruzioni già si comprende che il nome utente deve essere almeno di 6 caratteri e che il seriale deve comporsi di non meno di 47 caratteri.

La cosa un po' scocciante, in questo codice, è che, di continuo, sono eseguite delle operazioni che sostanzialmente non hanno nulla a che vedere con la determinazione della validità della registrazione, ma si occupano di preparare il file uedit32.reg dove saranno posizionati, in forma criptata, i dati di licenza.

La routine prosegue ...

00440E39   PUSH 3B                                              ;  << Dimensione area da riempire
00440E3B   LEA EAX, DWORD PTR SS:[ESP+75]
00440E3F   PUSH 0                                               ;  << carattere con cui riempire
00440E41   PUSH EAX                                             ;  << indirizzo da riempire
00440E42   MOV BYTE PTR SS:[ESP+23], 1                          ;  << segna un flag
00440E47   MOV BYTE PTR SS:[ESP+7C], CL                         ;  << azzera il primo byte
00440E4B   CALL <Uedit32.FillWithZero>                          ;  << entro
00440E50   PUSH 1FF                                             ;  << dimensione ( 511 bytes )
00440E55   LEA ECX, DWORD PTR SS:[ESP+BD]
00440E5C   PUSH 0                                               ;  << carattere di riempimento
00440E5E   PUSH ECX                               ;  << indirizzo da riempire ( 0024F9D1 ) - per copia userName
00440E5F   MOV BYTE PTR SS:[ESP+C4], 0                          ;  << azzera il primo byte
00440E67   CALL <Uedit32.FillWithZero>
00440E6C   PUSH EDI                                             ;  << push nome utente
00440E6D   LEA EDX, DWORD PTR SS:[ESP+C8]
00440E74   PUSH 200                                             ;  << 200h = 512d (max len username)
00440E79   PUSH EDX                                          ;  << indirizzo appena svuotato (il primo dei due)
00440E7A   CALL <Uedit32.CopiaStringa>                          ;  << Copia UserName

... preparando (svuotando) due aree di memoria al fine di poter eseguire delle copie del nome utente (vediamo che il nome utente volendo può raggiungere i 512 caratteri) e, successivamente, di una stringa estratta dal seriale. Ah, dimenticavo. Mi sembrava scontato dirlo, ma tenete presente che alcuni indirizzi, come quelli scritti nei commenti, possono essere diversi nel vostro sistema.

Ora stiamo attenti, il nome utente e il seriale sono passati come argomento alla routine che io qui ho chiamato GenerazioneVettoreEdEstrazioneStringa che mi sembra un buon modo per ricordarmi cosa faccia questa routine.

00440E7F   PUSH EDI                                             ;  << push nome utente
00440E80   LEA EAX, DWORD PTR SS:[ESP+7C]                       ;  << 0024F978
00440E84   PUSH EBP                                             ;  << push seriale
00440E85   PUSH EAX           ;  << push indirizzo (doppio ptr. a zona in cui andranno scritte le info dei match
00440E86   CALL <Uedit32.GenerazioneVettoreEdEstrazioneStringa> ;  << entro

Entriamo al suo interno con F7 e guardiamoci un po' in giro. Il codice che ci interessa comincia da qui:

0043F7C2    MOV EDI, DWORD PTR SS:[ESP+A38]             ;  << serial
0043F7C9    MOV ESI, DWORD PTR SS:[ESP+A3C]             ;  << username
0043F7D0    XOR EBX, EBX                                ;  << EBX := 0
0043F7D2    PUSH 3B                                     ;  << dimensione da riempire
0043F7D4    LEA EAX, DWORD PTR SS:[ESP+9E9]
0043F7DB    PUSH EBX                                    ;  << carattere di riempimento
0043F7DC    MOV DWORD PTR SS:[ESP+1C], EBX              ;  << azzera primo byte
0043F7E0    PUSH EAX                                    ;  << ind. da riempire
0043F7E1    MOV DWORD PTR SS:[ESP+A38], EBX             ;  << zero
0043F7E8    MOV DWORD PTR SS:[ESP+28], EBP
0043F7EC    MOV BYTE PTR SS:[ESP+9F0], BL               ;  << azzero primo byte
0043F7F3    CALL <Uedit32.FillWithZero>                 ;  << Libera zona per copiare il seriale
0043F7F8    PUSH EDI                                    ; |<< Seriale
0043F7F9    LEA ECX, DWORD PTR SS:[ESP+9F4]             ; |
0043F800    PUSH 3C                                     ; |<< 60
0043F802    PUSH ECX                                    ; |<< dove copiare il seriale
0043F803    CALL <Uedit32.CopiaStringa>                 ; \CopiaStringa
0043F808    MOV EAX, ESI                                ;  << Nome Utente
0043F80A    ADD ESP, 18
0043F80D    XOR EDX, EDX                                ;  <<< EDX := 0
0043F80F    LEA EDI, DWORD PTR DS:[EAX+1]               ;  << calcola len username
0043F812    /MOV CL, BYTE PTR DS:[EAX]
0043F814    |INC EAX
0043F815    |CMP CL, BL
0043F817    \JNZ SHORT Uedit32.0043F812
0043F819    SUB EAX, EDI                                ;  << EAX = lungh. username
0043F81B    XOR ECX, ECX                                ;  ECX := 0
0043F81D    CMP EAX, EBX                                ;  << Hai specificato un userName ?
0043F81F    JLE SHORT Uedit32.0043F82C                  ;  << se si' non salta
0043F821    /MOVSX EDI, BYTE PTR DS:[ESI+ECX]           ;  << Hash username - serve ad inizializzare il vettore
0043F825    |INC ECX
0043F826    |ADD EDX, EDI
0043F828    |CMP ECX, EAX
0043F82A    \JL SHORT Uedit32.0043F821
0043F82C    OR EDX, 4F71A22                 ;  << Somma i caratteri del nome utente ed effettua OR con costante

In pratica, la prima parte della routine esegue una copia del seriale e determina la lunghezza del nome utente, al fine di poter calcolare una sorta di hash dato dalla somma dei codici dei caratteri che lo compongono OR-ati (passatemi il termine) con una costante.

A cosa serve questo hash ? Serve ad inizializzare il processo di generazione di un vettore di dword che verrà utilizzato durante l'elaborazione del seriale inserito (più avanti in questa stessa routine).

0043F832    PUSH EDX                                    ;  << push hash utente
0043F833    LEA ECX, DWORD PTR SS:[ESP+24]              ;  << ind. vettore
0043F837    CALL <Uedit32.GenerazioneVettoreIniziale>   ;  << Genera un vettore iniziale in base
                                                        ;  << all'hash utente calcolato

La routine di generazione del vettore è riportata di seguito.

00654AD0  MOV EDX, DWORD PTR SS:[ESP+4]     ;  << Parametro (inizialmente hash utente)
00654AD4  PUSH ESI                          ;  << nome utente
00654AD5  PUSH EDI                          ;  << 2E = 46
00654AD6  LEA EAX, DWORD PTR DS:[ECX+8]
00654AD9  MOV ESI, 68                       ;  << esi := 68h (iterazioni ciclo)
00654ADE  MOV EDI, EDI                      ;  ??? inutile ???
00654AE0  MOV EDI, EDX                          ; Prima DWORD – Inizializzata con hash Nome Utente
00654AE2  IMUL EDX, EDX, 10DCD              ;  << EDX :=  ( EDX * 10DCDh )
00654AE8  AND EDI, FFFF0000                 ;  << EDI := lascia solo HIWORD(EDX)
00654AEE  INC EDX                           ;  << EDX += 1
00654AEF  MOV DWORD PTR DS:[EAX-8], EDI     ;  << salva EDI
00654AF2  MOV EDI, EDX                      ;  << EDI := EDX
00654AF4  IMUL EDX, EDX, 10DCD              ;  << EDX := EDX * 10DCDh
00654AFA  SHR EDI, 10                       ;  << EDI := EDI / 2^16 (recupera parte alta della dword)
00654AFD  OR DWORD PTR DS:[EAX-8], EDI      ;  << OR-ing della dword appena scritta
00654B00  INC EDX                           ;  << edx++
00654B01  MOV EDI, EDX                      ;  << ESEGUE GLI STESSI DUE PASSI per la prossima dword>>
00654B03  IMUL EDX, EDX, 10DCD
00654B09  AND EDI, FFFF0000
00654B0F  INC EDX
00654B10  MOV DWORD PTR DS:[EAX-4], EDI     ; Seconda Dword
00654B13  MOV EDI, EDX
00654B15  IMUL EDX, EDX, 10DCD
00654B1B  SHR EDI, 10
00654B1E  OR DWORD PTR DS:[EAX-4], EDI
00654B21  INC EDX
00654B22  MOV EDI, EDX                       ;  << terza dword
00654B24  IMUL EDX, EDX, 10DCD
00654B2A  AND EDI, FFFF0000
00654B30  INC EDX
00654B31  MOV DWORD PTR DS:[EAX], EDI
00654B33  MOV EDI, EDX
00654B35  IMUL EDX, EDX, 10DCD
00654B3B  SHR EDI, 10
00654B3E  OR DWORD PTR DS:[EAX], EDI
00654B40  INC EDX
00654B41  MOV EDI, EDX                       ;  << quarta dword
00654B43  IMUL EDX, EDX, 10DCD
00654B49  AND EDI, FFFF0000
00654B4F  INC EDX
00654B50  MOV DWORD PTR DS:[EAX+4], EDI
00654B53  MOV EDI, EDX
00654B55  IMUL EDX, EDX, 10DCD
00654B5B  SHR EDI, 10
00654B5E  OR DWORD PTR DS:[EAX+4], EDI
00654B61  INC EDX
00654B62  MOV EDI, EDX                       ;  << quinta dword
00654B64  IMUL EDX, EDX, 10DCD
00654B6A  AND EDI, FFFF0000
00654B70  INC EDX
00654B71  MOV DWORD PTR DS:[EAX+8], EDI
00654B74  MOV EDI, EDX
00654B76  IMUL EDX, EDX, 10DCD
00654B7C  SHR EDI, 10
00654B7F  OR DWORD PTR DS:[EAX+8], EDI
00654B82  INC EDX                           ;  << sesta dword
00654B83  MOV EDI, EDX
00654B85  IMUL EDX, EDX, 10DCD
00654B8B  AND EDI, FFFF0000
00654B91  INC EDX
00654B92  MOV DWORD PTR DS:[EAX+C], EDI
00654B95  MOV EDI, EDX
00654B97  IMUL EDX, EDX, 10DCD
00654B9D  SHR EDI, 10
00654BA0  OR DWORD PTR DS:[EAX+C], EDI
00654BA3  INC EDX
00654BA4  ADD EAX, 18                       ;  << passa alle prossime 6 dword
00654BA7  SUB ESI, 1                        ;  << decremento contatore ciclo
00654BAA  JNZ Uedit32.00654AE0              ;  << continua ciclo
00654BB0  POP EDI                          
00654BB1  MOV DWORD PTR DS:[ECX+9C0], 270   ;  << segna inizializzazione in posizione prima di seriale
00654BBB  POP ESI                          
00654BBC  RETN 4


Come si nota si esegue la stessa operazione per 6 dword consecutive e per un totale di 68h ( = 104 ) iterazioni. Pertanto otterremo un vettore di 104 x 24 = 2496 byte (9C0 in hex). E' bene ribadire il fatto che i valori di questo vettore dipendono dal nome utente dal momento che la prima dword ad essere elaborata è proprio l'hash utente che abbiamo appena calcolato.

Inoltre si noti che viene memorizzato un valore (270h) alla fine del vettore. Questo serve ad indicare che il vettore è stato appena costruito. E' un indicatore che sarà utilizzato per stabilire se occorre eseguire o meno un'ulteriore elaborazione (v. LookupInVettore_conRicalcolo nel file di testo allegato).

In C++ possiamo codificare la generazione del vettore con queste due routine (parte del codice allegato):

int TableHandler::modificaVettore( int edx, int tableIdx )
{
        const int MUL_CONST = 0x10DCD;
        const int AND_CONST = 0xFFFF0000;
        const int SHIFT_RIGHT_CONST = 0x10;

        int edi = edx;
        edx = edx * MUL_CONST;
        edi = edi & AND_CONST;
        edx++;
        this->vettore[tableIdx] = edi;
       
        edi = edx;
        edx = edx * MUL_CONST;
        edi = ( (unsigned) edi >> SHIFT_RIGHT_CONST );
        this->vettore[tableIdx] |= edi;

        edx++;

        return edx;
}

void TableHandler::creaVettore()
{
        // Vettore riempito con un ciclo di 68h (=104) iterazioni scrivendo
        // ad ogni iterazione 6 DWORD (= 24 byte ).
        // per cui si ha 104x24 = 2496 byte (9C0 in hex) !!!   
        this->vettore = new int[624];    // 624 = 2496/SIZEOF(DWORD) = 2496/4        

        // Avvia il ciclo di 68h (104) iterazioni
        int max_esi = 0x68;
        int edx = this->userHash;               // Valore iniziale: hash utente
        int tableIdx = 0;                              

        // Ciclo a ritroso
        for ( int esi = 0; esi < max_esi; esi++ )
        {
                // 1a DWORD
                edx = this->modificaVettore( edx, tableIdx++ );

                // 2a DWORD
                edx = this->modificaVettore( edx, tableIdx++ );

                // 3a DWORD
                edx = this->modificaVettore( edx, tableIdx++ );
               
                // 4a DWORD
                edx = this->modificaVettore( edx, tableIdx++ );

                // 5a DWORD
                edx = this->modificaVettore( edx, tableIdx++ );

                // 6a DWORD
                edx = this->modificaVettore( edx, tableIdx++ );
        }
}


Una volta costruito il vettore si procede ad elaborare il seriale inserito:

0043F83C    PUSH EBX                                    ;  << 0
0043F83D    MOV DWORD PTR SS:[EBP+18], 0F               ;  << 15
0043F844    MOV DWORD PTR SS:[EBP+14], EBX              ;  << 0
0043F847    PUSH Uedit32.00AD46F0
0043F84C    MOV ECX, EBP
0043F84E    MOV BYTE PTR SS:[EBP+4], BL                 ;  << 0
0043F851    CALL <Uedit32.NonImportante1>               ;  
0043F856    MOV AL, 30                                  ;  << '0'
0043F858    MOV EDI, 1                                  ;  << EDI := 1
0043F85D    MOV DWORD PTR SS:[ESP+A2C], EBX             ;  << 0
0043F864    MOV DWORD PTR SS:[ESP+14], EDI              ;  << 1
0043F868    MOV BYTE PTR SS:[ESP+9E8], AL               ;  << Ignora il quinto carattere di ogni sotto-gruppo
0043F86F    MOV BYTE PTR SS:[ESP+9EE], AL
0043F876    MOV BYTE PTR SS:[ESP+9F4], AL
0043F87D    MOV BYTE PTR SS:[ESP+9FA], AL
0043F884    MOV BYTE PTR SS:[ESP+A00], AL
0043F88B    MOV BYTE PTR SS:[ESP+A06], AL
0043F892    MOV BYTE PTR SS:[ESP+A0C], AL
0043F899    MOV BYTE PTR SS:[ESP+A12], AL
0043F8A0    LEA ESI, DWORD PTR SS:[ESP+9E4]             ;  << seriale
0043F8A7    CMP BYTE PTR SS:[ESP+9E4], BL               ;  << finito ?
0043F8AE    JE SHORT Uedit32.0043F8FA
0043F8B0    /CMP BYTE PTR DS:[ESI], 2D                  ;  << trovato il trattino ?
0043F8B3    |JE SHORT Uedit32.0043F8F5                  ;  << ignora il trattino
0043F8B5    |LEA ECX, DWORD PTR SS:[ESP+20]             ;  << vettore_inizializzazione
0043F8B9    |CALL <Uedit32.LookupInVettore_conRicalcolo>
0043F8BE    |XOR EDX, EDX                               ;  << EDX := 0
0043F8C0    |MOV ECX, 0A                                ;  << ECX := 0A (10)
0043F8C5    |DIV ECX                                    ;  << divisione di EAX (restituito da CALL)
                                                        ;  << per 0A e il resto in EDX
0043F8C7    |MOV AL, BYTE PTR DS:[ESI]                  ;  << AL := carattere del seriale
0043F8C9    |SUB AL, DL                                 ;  << sottrai ad esso il resto
0043F8CB    |SUB AL, 41                                 ;  << e 41h ('A')
0043F8CD    |MOVSX EDX, AL                              ;  << a questo punto EDX := AL
0043F8D0    |MOVZX EAX, BYTE PTR DS:[EDX+ADA054]        ;  << Lookup di valore in 'X6A3KVMOQ8D2HCPI'
0043F8D7    |MOV BYTE PTR SS:[ESP+18], AL               ;  << salva poi il valore lookup-ato (BYTE ptr)
0043F8DB    |MOV ECX, DWORD PTR SS:[ESP+18]             ;  << ... e lo copia in ECX (tutta la dword pero')
0043F8DF    |PUSH ECX                                   ;  << PUSH di questo valore
0043F8E0    |PUSH 1                                     ;  << 1
0043F8E2    |MOV ECX, EBP                               ;  << Dove salvare il carattere lookup-ato
0043F8E4    |CALL <Uedit32.SalvaCarattereLookup>        ;  <<
0043F8E9    |INC EDI                                    ;  << prox char
0043F8EA    |CMP EDI, 5                                 ;  << finito un sottogruppo ?
0043F8ED    |JNZ SHORT Uedit32.0043F8F5
0043F8EF    |MOV EDI, 1                                 ;  << riparte da 1 per EDI
0043F8F4    |INC ESI
0043F8F5    |INC ESI
0043F8F6    |CMP BYTE PTR DS:[ESI], BL                  ;  << finito il seriale ?
0043F8F8    \JNZ SHORT Uedit32.0043F8B0
0043F8FA    MOV EAX, EBP
                :
                :
0043F91D    ADD ESP, 0A1C
0043F923    RETN

In linea 0043F8D0 il codice del ciclo fa riferimento alla locazione ADA054. Ecco cosa contiene.

Image:UeditData1.PNG

Cerchiamo di riassumere il comportamento di questa porzione di codice.

Innanzitutto si procede al recupero del seriale e ad una sua pre-elaborazione: osserviamo, infatti, che il quinto carattere di ogni sottogruppo è sostituito da uno '0' (ecco il senso di tutti quei MOV BYTE PTR SS:[ESP+?], AL). Ciò significa che, qualunque carattere "terminatore di gruppo" è ignorato. Proseguendo incontriamo un ciclo nel quale, per ogni carattere del seriale, viene:


  1. eseguita una lookup nel vettore calcolato precedentemente (letta una dword)
  2. effettuata una divisione della dword letta per 10, conservando il resto
  3. utilizzato il resto come sottraendo per ricavare un indice (0-based) nella stringa ' X6A3KVMOQ8D2HCPI'
  4. Concatenati i caratteri estratti da detta stringa.

In totale avremo una nuova stringa di 32 caratteri che sarà composta, se il seriale è corretto, dai soli caratteri appartenenti a "X6A3KVMOQ8D2HCPI". Perché 32 caratteri ? Allora non siete stati attenti. ;) Il processo ignora completamente il quinto carattere di ogni sottogruppo passando al prossimo carattere; viene trattato alla stregua del trattino separatore.

La routine LookupInVettore_conRicalcolo non fa altro che pescare un valore dal vettore costruito in precedenza sulla base della posizione del carattere corrente nel seriale (valori da 0 a 31). Solo la prima volta (per l'indice 0) sono eseguiti due ulteriori sotto-cicli di rielaborazione dei valori nel vettore (v. file di testo allegato per il listato commentato).

Ricapitoliamo cosa abbiamo visto finora:

  1. Il nome utente viene elaborato sommando i codici ASCII dei suoi caratteri OR-ati con una costante.
  2. Con il valore così ottenuto si costruisce un vettore di lookup.
  3. Sono analizzati i 32 caratteri del seriale (ignorati trattini e tutti i fine gruppo), per ognuno dei quali viene eseguita una lookup nel vettore.
  4. Col valore recuperato dal vettore è eseguita una divisione per 10 il cui resto è usato per ricavare un indice nella stringa 'X6A3KVMOQ8D2HCPI'.
  5. Sono concatenati i caratteri estratti da questa stringa fissa ("X6A3KVMOQ8D2HCPI") per comporre una nuova stringa di 32 caratteri.

Routine di Registrazione – Parte 2: Data di sistema, Versione Programma e Generazione dei match.

Torniamo alla routine principale. Dopo la call a GenerazioneVettoreEdEstrazioneStringa il codice prosegue copiando la stringa di 32 caratteri estratta dal seriale per una successiva analisi e procede a convertire la data corrente in FileTime

00440E8B   ADD ESP, 30                                          
00440E8E   CMP DWORD PTR SS:[ESP+6C], 10                        ;  
00440E93   MOV EAX, DWORD PTR SS:[ESP+58]                       ;  << Stringa estratta dal seriale
00440E97   MOV DWORD PTR SS:[ESP+2B8], 0                        ;  
00440EA2   JNB SHORT Uedit32.00440EA8                           ;  v
00440EA4   LEA EAX, DWORD PTR SS:[ESP+58]                      
00440EA8   PUSH EAX                                             ; /<< push stringa estratta dal seriale
00440EA9   LEA ECX, DWORD PTR SS:[ESP+74]                       ; |
00440EAD   PUSH 3C                                              ; |<< dimensione stringa
00440EAF   PUSH ECX                                             ; |<< dove copiarla (sopra il nome utente)
00440EB0   CALL <Uedit32.CopiaStringa>                          ; \CopiaStringa
00440EB5   ADD ESP, 0C                                          
00440EB8   PUSH Uedit32.00AD9C40                                ; /pModule = "kernel32.dll"
00440EBD   CALL DWORD PTR DS:[<&KERNEL32.GetModuleHandleA>]     ; \GetModuleHandleA
00440EC3   LEA EDX, DWORD PTR SS:[ESP+44]                       ;
00440EC7   MOV ESI, EAX                                         ;  << salva kernel32.dll imagebase
00440EC9   PUSH EDX                                            
00440ECA   LEA EAX, DWORD PTR SS:[ESP+38]                       ;
00440ECE   PUSH EAX                                            
00440ECF   LEA ECX, DWORD PTR SS:[ESP+2C]                       ;  << currentSystemTime
00440ED3   PUSH ECX                                            
00440ED4   CALL <Uedit32.SystemTime_FileTime>                   ;  << Determina Data Corrente (in SystemTime) +
                                                                   << altro che non ci interessa (Genera 2
                                                                   << FileTime e li converte in SystemTime)
00440ED9   LEA EDX, DWORD PTR SS:[ESP+30]                       ;  << ptr. a currentSystemTime
00440EDD   PUSH ESI                                            
00440EDE   PUSH EDX                                             ;  << push currentSysTime
00440EDF   CALL <Uedit32.DataCorrenteInFileTime>                ;  <<

Perchè è importante questo ? Perchè, come vedremo tra poco, il valore della data corrente in FileTime partecipa attivamente alla determinazione della validità del seriale.

Dopo la conversione della data si effettua un'altra semplice trasformazione. Il valore della versione "master" del programma 15.00.00 viene elaborato in questa routine:

00440EF8   PUSH 150000                                          ;  << Push Versione programma !!!
00440EFD   CALL <Uedit32.DoBSWAP_Parametro>                     ;  << bswap della versione del programma

che riporto di seguito.

00401877 <> $  83EC 04       SUB ESP, 4
0040187A    .  892C24        MOV DWORD PTR SS:[ESP], EBP
0040187D    .  54            PUSH ESP
0040187E    .  5D            POP EBP                                                   ;  
0040187F    .  50            PUSH EAX
00401880    .  53            PUSH EBX
00401881    .  E8 00000000   CALL Uedit32.00401886
00401886    $  5B            POP EBX                                                   ;  
00401887    .  81EB 6E080000 SUB EBX, 86E                                              ;  << DoveSalvare
0040188D       8B45 08   MOV EAX, DWORD PTR SS:[EBP+8]                      
00401890    .  F7D0          NOT EAX                                                   ;  << NOT
00401892    .  0FC8          BSWAP EAX                                                 ;  << BSWAP
00401894    .  8903          MOV DWORD PTR DS:[EBX], EAX                               ;  << salva valore
00401896    .  5B            POP EBX                                                   ;  
00401897    .  58            POP EAX                                                   ;  
00401898    .  8B2C24        MOV EBP, DWORD PTR SS:[ESP]                               ;  
0040189B    .  83C4 08       ADD ESP, 8
0040189E    .- FF6424 FC     JMP DWORD PTR SS:[ESP-4]                                  ;  << Torna al chiamante

Niente di che, semplicemente un NOT e un BSWAP per non renderla immediatamente visibile in seguito.

Adesso, di nuovo attenti. Nome utente e stringa estratta dal seriale sono entrambe passate alla routine che ho chiamato DeterminaMatchEStabilisciReg. All'uscita dalla routine un determinato valore (presente nella dword all'indirizzo 401096, cui ho assegnato la label "controllo") viene salvato in EBX. Questo valore è "calcolato" dalla call che stiamo per esaminare ed è questo che, in definitiva, ci consente di sapere se siamo o meno registrati. Teniamolo a mente per dopo.

00440F02   LEA EDX, DWORD PTR SS:[ESP+CC]                       ;  << username
00440F09   PUSH EDX                                            
00440F0A   LEA EAX, DWORD PTR SS:[ESP+94]                       ;  << stringa estratta da seriale
00440F11   PUSH EAX                                            
00440F12   CALL <Uedit32.DeterminaMatchEStabilisciReg>          
00440F17   MOV EBX, DWORD PTR DS:[<controllo>]                  ;  << Salva il valore della dword di controllo
                                                                        << in EBX (la call modifica la dword in
                                                                        << oggetto per altri scopi)

OK. F7 per entrare nella routine, ed ecco cosa abbiamo di fronte. La routine è un po' lunghetta per cui l'analizzeremo a pezzi. Lo stile di questa routine è leggermente diverso dal resto del codice ... forse per renderci la vita un po' più complessa (o interessante ?).

004018D0   SUB EBX, 7ED
004018D6   PUSH DWORD PTR SS:[EBP+C]       ;  << username
004018D9   PUSH EAX                        ;  << stringa estratta da serial
004018DA   CALL Uedit32.004018DF           ;  << riga successiva
004018DF   POP EAX                         ;  
004018E0   ADD EAX, 9
004018E3   XCHG DWORD PTR SS:[ESP], EAX    ;  << Controlla il valore di EAX - e' modificato in 'ElaboraUserName'
004018E6   JMP EBX                         ;  << CALCOLA HASH USERNAME
004018E8   ADD ESP, 4
004018EB   MOV DWORD PTR SS:[EBP-8], EAX   ;  << salva hash di username

Questa prima parte calcola un hash dello username. Ma questo è utilizzato solo per scrivere il file uedit32.reg, per cui non è interessante per i nostri scopi. Nelle righe seguenti troviamo:

004018EE   MOV EBX, DWORD PTR SS:[EBP+8]   ;  << stringa da seriale
004018F1   MOV EDX, DWORD PTR SS:[EBP-4]
004018F4   SUB EDX, 1B2                    ;  << Routine da invocare
004018FA   PUSH EBX
004018FB   PUSH EAX
004018FC   CALL Uedit32.00401901           ;  << dopo
00401901   POP EAX                         ;  
00401902   ADD EAX, 9
00401905   XCHG DWORD PTR SS:[ESP], EAX    ;  <<
00401908   JMP EDX                         ;  << bswap stringa estratta da seriale
0040190A   ADD ESP, 4
0040190D   PUSH EBX                        ;  << stringa estratta (Bswappata)
0040190E   MOV EDX, DWORD PTR SS:[EBP-4]


La stringa estratta da seriale (di 32 caratteri) viene dapprima BSWAP-pata (passatemi il termine), eseguendo dei BSWAP a coppie di word, come si può vedere di seguito:

00401706 <BSWAP> SUB ESP, 4                      ;  ---
00401709        MOV DWORD PTR SS:[ESP], EBP      ;  
0040170C        PUSH ESP
0040170D        POP EBP                          ;  
                        :
00401712        MOV ECX, 0F                      ;  << ecx := 0F
00401717        MOV EBX, DWORD PTR SS:[EBP-8]    ;  << stringa da seriale
0040171A        MOV EDX, 1C                      ;  << EDX := 1C (28)
0040171F        MOV EAX, DWORD PTR DS:[EBX+EDX]  ;  << ultimi 4 char
                                                        << (e poi si sposta di 2 char alla volta indietro)
00401722        BSWAP EAX                        ;  << BSWAP
00401724        MOV DWORD PTR DS:[EBX+EDX], EAX  ;  << salva il BSWAP nel posto originale
00401727        SUB EDX, 2
0040172A        LOOPD SHORT Uedit32.0040171F
                        :
00401730        MOV EBP, DWORD PTR SS:[ESP]      ;  
00401733        ADD ESP, 8
00401736        JMP DWORD PTR SS:[ESP-4]         ;  torna al "chiamante"


Proseguendo nella routine ci rendiamo conto che la stringa di 32 caratteri è elaborata a gruppi di 8 caratteri, in modo da ricavare quelli che io chiamo match, ovvero singole dword in cui ogni cifra (esadecimale) rappresenta l'indice - nella stringa "X6A3KVMOQ8D2HCPI" (lunga 16 caratteri) del carattere corrente.

Carattere Indice
X 0
6 1
A 2
3 3
K 4
V 5
M 6
O 7
Q 8
8 9
D A
2 B
H C
C D
P E
I F

Per capirci, se ad esempio abbiamo gli 8 caratteri AQCX3KVM, otterremo un match pari a 28D03456.


00401911   SUB EDX, 378                    ;  << routine da invocare (estraiMatch)
00401917   PUSH EAX
00401918   CALL Uedit32.0040191D           ;  << linea dopo
0040191D   POP EAX                         ;  
0040191E   ADD EAX, 9
00401921   XCHG DWORD PTR SS:[ESP], EAX    ;  
00401924   JMP EDX                         ;  << Match1 - 8 char iniziali
00401926   ADD ESP, 4
00401929   MOV DWORD PTR SS:[EBP-1C], EAX  ;  << salva primo match
0040192C   ADD EBX, 8                      ;  << prossimi 8 char
0040192F   PUSH EBX
00401930   PUSH EAX
00401931   CALL Uedit32.00401936           ;  << linea dopo
00401936   POP EAX                         ;  
00401937   ADD EAX, 9
0040193A   XCHG DWORD PTR SS:[ESP], EAX    ;  
0040193D   JMP EDX                         ;  Secondo Match
0040193F   ADD ESP, 4
00401942   MOV DWORD PTR SS:[EBP-10], EAX  ;  << salva secondo Match
00401945   ADD EBX, 8                      ;  << avanza
00401948   PUSH EBX
00401949   PUSH EAX
0040194A   CALL Uedit32.0040194F           ;  << linea dopo
0040194F   POP EAX                         ;  
00401950   ADD EAX, 9
00401953   XCHG DWORD PTR SS:[ESP], EAX
00401956   JMP EDX                         ;  << terzoMatch
00401958   ADD ESP, 4
0040195B   MOV DWORD PTR SS:[EBP-C], EAX   ;  << salva terzo match

Dopo aver calcolato i primi 3 match, la routine combina il secondo e il terzo, prima di avanzare e procedere al calcolo del quarto match.

0040195E   PUSH EBX
0040195F   PUSH EDX                        ;  
00401960   MOV EBX, EBP
00401962   SUB EBX, 10
00401965   PUSH EBX                        ;  << secondoMatch
00401966   MOV EDX, DWORD PTR SS:[EBP-4]
00401969   SUB EDX, 17A
0040196F   PUSH EAX
00401970   CALL Uedit32.00401975           ;  << linea dopo
00401975   POP EAX                         ;  
00401976   ADD EAX, 9
00401979   XCHG DWORD PTR SS:[ESP], EAX
0040197C   JMP EDX                         ;  << Combina Secondo e Terzo Match
0040197E   ADD ESP, 4
00401981   POP EDX                         ;  
00401982   POP EBX                         ;  
00401983   ADD EBX, 8                      ;  << avanza nella stringa estratta

Ecco come sono rielaborati il secondo e il terzo match:

0040173E <Combina>SUB ESP, 4                    ;  ----
00401741          MOV DWORD PTR SS:[ESP], EBP   ;  
00401744          PUSH ESP
00401745          POP EBP                       ;  
00401746          PUSH EAX
00401747          PUSH EBX                      ;  <secondoMatch>
00401748          PUSH EDX                      ;  
00401749          MOV EBX, DWORD PTR SS:[EBP+8] ;  << ind. secondo match
0040174C          MOV EAX, DWORD PTR DS:[EBX+4] ;  << terzo match (valore)
0040174F          MOV EDX, DWORD PTR DS:[EBX]   ;  << Secondo Match ( valore )
00401751          XOR EDX, EAX                  ;  << SecondoMatch = SecondoMatch XOR TerzoMatch
00401753          BSWAP EDX                     ;  << bswap risultato (in edx)
00401755          XOR EAX, EDX          ;  << TerzoMatch = TerzoMatch XOR ( valore_bswappato appena calcolato )
00401757          BSWAP EAX                     ;  << bswap a sua volta
00401759          MOV DWORD PTR DS:[EBX+4], EAX ;  << Salva valore di EAX nella posizione del terzoMatch
0040175C          MOV DWORD PTR DS:[EBX], EDX   ;  << Salva EDX nella posizione del secondo Match
0040175E          POP EDX                       ;  
0040175F          POP EBX                       ;  
00401760          POP EAX                       ;  
00401761          MOV EBP, DWORD PTR SS:[ESP]   ;  
00401764          ADD ESP, 8
00401767          JMP DWORD PTR SS:[ESP-4]      ;


In pratica si XOR-ano i valori e si effettuano due BSWAP. Fatto questo si procede al calcolo e al salvataggio del quarto match.

00401986   PUSH EBX
00401987   PUSH EAX
00401988   CALL Uedit32.0040198D           ;  << linea successiva
0040198D   POP EAX                         ;  
0040198E   ADD EAX, 9
00401991   XCHG DWORD PTR SS:[ESP], EAX
00401994   JMP EDX                         ;  << Estrae quartoMatch
00401996   ADD ESP, 4
00401999   MOV DWORD PTR SS:[EBP-18], EAX  ;  << salva quartoMatch

A questo punto si eseguono tutta una serie di operazioni volte a verificare alcune condizioni.

Si parte controllando il primo e il quarto match:

0040199C   MOV EBX, DWORD PTR SS:[EBP-4]
0040199F   SUB EBX, 8A0
004019A5   MOV EBX, DWORD PTR DS:[EBX]     ;  << BSWAP di 150000 (versione di Uedit)
004019A7   MOV EAX, DWORD PTR SS:[EBP-18]  ;  << EAX := QuartoMatch
004019AA   BSWAP EBX                       ;  << ottiene 150000 ...
004019AC   NOT EBX                         ;  << .... in EBX
004019AE   PUSH EAX                        ;  << push quartoMatch
004019AF   PUSH EBX                        ;  << push 150000
004019B0   MOV EBX, DWORD PTR SS:[EBP-18]  ;  << quartoMatch
004019B3   MOV EAX, DWORD PTR SS:[EBP-1C]  ;  << primo match
004019B6   ROR EAX, 0D                     ;  << primoMatch = primoMatch ROR 0D
004019B9   XOR EBX, EAX                    ;  << XOR quarto_match e (primoMatch ROR 0D)
004019BB   ROL EAX, 0D                     ;  << rotazione a sinistra ora (ripristina valore ?)
004019BE   XOR EBX, EAX                    ;  << nuovo XOR col valore 'non ribaltato'
004019C0   MOV DWORD PTR SS:[EBP-18], EBX  ;  << salva questo valore in posizione quartoMatch
004019C3   POP EBX                         ;  
004019C4   POP EAX                         ;  
004019C5   XOR EAX, EAX                    ;  << EAX := 0
004019C7   MOV EAX, DWORD PTR SS:[EBP-18]  ;  << recupera il valore appena calcolato
004019CA   AND EAX, F0000000               ;  << Esegue AND (ultima cifra esadecimale)
004019CF   ROL EAX, 4                      ;  << rotazione a sinistra di 4 (lo porta a destra a tutto )
004019D2   PUSH EBX                        ;  << 150000
004019D3   PUSH EAX                        ;  <<
004019D4   XCHG EAX, EBX                   ;  << scambio EAX <--> EBX
004019D5   MOV EAX, DWORD PTR SS:[EBP-4]
004019D8   SUB EAX, 822
004019DD  OR DWORD PTR DS:[EAX], EBX       ;  << OR della dword di 'controllo'
                                              ;  << (su cui si decide la registrazione) deve essere = 1
                                              ;     nella cifra esadecimale + bassa


La combinazione del primo e del quarto match deve essere tale da risultare, nella sua cifra esadecimale "più alta", pari ad 1 (viene controllato al ritorno di questa call).

Subito dopo ...

004019DF   MOV EAX, DWORD PTR SS:[EBP-18]  ;  << recupera il valore calcolato (posizione quartoMatch)
004019E2   AND EAX, 0FFFFFFF               ;  << tutto tranne prima cifra esadecimale
004019E7   MOV DWORD PTR SS:[EBP-18], EAX  ;  << e lo salva da dove l'ha preso (quarto match)
004019EA   POP EAX                         ;  
004019EB   POP EBX                         ;  
004019EC   MOV EAX, DWORD PTR SS:[EBP-18]  ;  << riprende cio' che e' in quartoMatch
004019EF   ROR EBX, 10                     ;  << ROR 150000, 10 (otteniamo 15)
004019F2   ROR EAX, 10                     ;  << ror EAX, 10
004019F5   SUB BX, AX                      ;  << devono essere uguali le WORD + basse ( 00 15 ) !
004019F8   JE SHORT Uedit32.00401A0D       ;  v
004019FA   MOV EBX, DWORD PTR SS:[EBP-4]   ;  << prepara valore di errore
004019FD   SUB EBX, 822
00401A03   XOR EAX, EAX
00401A05   INC EAX
00401A06   SHL EAX, 1D
00401A09   OR DWORD PTR DS:[EBX], EAX
00401A0B   JMP SHORT Uedit32.00401A61


... si verifica che la combinazione di primo e quarto match sia pari ( nella word più bassa ) alla versione del programma ( 15 in questo caso ). Inoltre:

00401A0D   ROR EAX, 18                     ;  << deve essere uguale anche altro
00401A10   ROR EBX, 18
00401A13   SUB BL, AL                      ;  << byte + bassi = ?
00401A15   JE SHORT Uedit32.00401A61       ;  - lo faccio saltare
        :

ci deve essere una corrispondenza anche tra le "parti alte" delle dword.


Infine si scomoda la FPU per l'ultimo check:

00401A61   MOV EBX, DWORD PTR SS:[EBP-4]
00401A64   SUB EBX, 8B4                    ;  << importante_401004
00401A6A   WAIT
00401A6B   FINIT
00401A6D   FILD QWORD PTR DS:[EBX]         ;   << 00 C0 69 2A | C9 00 00 00
00401A6F   FILD QWORD PTR SS:[EBP-10]      ;  << secondo e terzo match assieme come qword
                                              ;  << (secondoMatch nella dword + bassa )
00401A72   MOV EBX, DWORD PTR SS:[EBP-4]
00401A75   SUB EBX, 8A8
00401A7B   FILD QWORD PTR DS:[EBX]         ;  << currSysTime in FileTime
00401A7D   FSUB ST(0), ST(1)               ;  << sottrae quest'ultima con secondo_terzo
00401A7F   FDIV ST(0), ST(2)               ;  << divide per la prima (dovrebbe essere costante)
00401A81   FISTP DWORD PTR SS:[EBP-24]     ;  << salva il risultato (DWORD)
00401A84   MOV EAX, DWORD PTR SS:[EBP-24]  ;  << recupera questo valore
00401A87   TEST EAX, 80000000              ;  << e lo controlla
00401A8C   JE SHORT Uedit32.00401A90
00401A8E   NEG EAX
00401A90   CMP EAX, 17C                    ;  << EAX deve essere <= 17C
00401A95   JBE SHORT Uedit32.00401AAC      ;  DEVE SALTARE
00401A97   MOV EBX, DWORD PTR SS:[EBP-4]
00401A9A   SUB EBX, 822
00401AA0   MOV EAX, 1FF
00401AA5   SHL EAX, 10
00401AA8   OR DWORD PTR DS:[EBX], EAX      ;  
00401AAA   JMP SHORT Uedit32.00401ABA
00401AAC   ROL EAX, 10                     ;  << ROL EAX,10 posizioni
00401AAF   MOV EBX, DWORD PTR SS:[EBP-4]
00401AB2   SUB EBX, 822                    ;  << dword di controllo
00401AB8   OR DWORD PTR DS:[EBX], EAX      ;  << salva il valore nell'indicatore
00401ABA   MOV EAX, DWORD PTR SS:[EBP-18]  ;  << valore in posizione quartoMatch
00401ABD   AND EAX, 0F                     ;  << ultima cifra esadecimale
00401AC0   TEST EAX, 8
00401AC5   JNZ SHORT Uedit32.00401AE6      ;  << ** NON deve saltare
00401AC7   AND EAX, 7
00401ACA   OR AL, 0
00401ACC   JE SHORT Uedit32.00401AF7       ;  << questo *deve* saltare
00401ACE   OR EAX, 20
00401AD1   SHL EAX, 7
00401AD4   AND EAX, 1F80
00401AD9   MOV EBX, DWORD PTR SS:[EBP-4]
00401ADC   SUB EBX, 822
00401AE2   OR DWORD PTR DS:[EBX], EAX      ;  << male
00401AE4   JMP SHORT Uedit32.00401AF7
00401AE6   MOV EBX, DWORD PTR SS:[EBP-4]
00401AE9   SUB EBX, 822
00401AEF   XOR EAX, EAX
00401AF1   INC EAX
00401AF2   SHL EAX, 6
00401AF5   OR DWORD PTR DS:[EBX], EAX
00401AF7   POP EDI                         ;  
00401AF8   POP ESI                         ;  
00401AF9   POP EDX                         ;  
00401AFA   POP ECX                         ;  
00401AFB   POP EBX                         ;  
00401AFC   POP EAX                         ;  
00401AFD   ADD ESP, 24
00401B00   MOV EBP, DWORD PTR SS:[ESP]     ;  
00401B03   ADD ESP, 8
00401B06   JMP DWORD PTR SS:[ESP-4]        ;  << **** torna al chiamante


La sottrazione della data corrente con la combinazione di secondo e terzo match, e la successiva divisione per una costante, deve fornire un risultato minore di 17Ch. Questo valore contribuirà anche al valore dell'indicatore (dword di controllo [401096]), quindi va scelto con cura.

Si noti che in questa routine si modifica il valore della dword di controllo ([401096]) in diversi punti. Alcuni di essi sono codici di errore che saranno testati all'uscita, nella parte conclusiva della routine principale.

Routine di Registrazione – Parte 3: Siamo registrati ?.

Una volta tornati alla routine principale si analizza il valore della dword di controllo (il quale, se ben ricordiamo, è memorizzato in EBX) per verificare che tutto sia regolare. I test eseguiti sono illustrati di seguito.

Sostanzialmente si verifica che la cifra esadecimale più bassa (dopo lo shift right) della dword di controllo sia pari ad uno e che il valore non corrisponda ad uno dei codici di errore.

00440F1D   ADD ESP, 28                                          
00440F20   CALL <Uedit32.divisione_qword>                       ;  << seconda divisione quadword
                                                                        (non ci interessa)
00440F25   MOV ESI, EBX                                         ;  << la call precedente NON modifica EBX
00440F27   SHR ESI, 10                                          
00440F2A   MOV ECX, EBX                                         ;  << nuova copia di EBX
00440F2C   AND CL, 0F                                           ;  << ultimo char esadecimale
00440F2F   AND ESI, 1FF                                         ;  << and esi, 1FF
00440F35   CMP CL, 1                                            ;  << CL deve essere = 1
00440F38   JE SHORT Uedit32.00440F3F                            
00440F3A   MOV BYTE PTR SS:[ESP+17], 0                          ;  << :(
00440F3F   TEST EBX, 4000                                       ;  << test
00440F45   JE SHORT Uedit32.00440F51                            
00440F47   TEST BL, 40                                          
00440F4A   JNZ SHORT Uedit32.00440F51                          
00440F4C   MOV BYTE PTR SS:[ESP+17], 0                          ;  << :(
00440F51   TEST EBX, 20000000                                   ;  << test EBX 20.000.000
00440F57   JE SHORT Uedit32.00440F5E                            
00440F59   MOV BYTE PTR SS:[ESP+17], 0                          ;  << :(
00440F5E   PUSH ECX                                       ;  << push valore 'iniziale' della dword di controllo
                :
                :
00440FAC   CMP BYTE PTR SS:[ESP+17], 0                          ;  << byte di registrazione
00440FB1   MOVZX EDX, AL                                        
00440FB4   MOV DWORD PTR DS:[D05598], EDX                      
00440FBA   JE SHORT Uedit32.00440FF2                            
00440FBC   CMP ESI, 17C                                     ;  << *** DEVE SALTARE PER NON MOSTRARE LA NAG
                                                                    ;  (dipende dal valore di "controllo")
00440FC2   JB SHORT Uedit32.00440FF2                           ;  v
00440FC4   TEST BL, 40                                          
00440FC7   JNZ SHORT Uedit32.00440FF2                          
00440FC9   MOV EAX, DWORD PTR SS:[ESP+18]                      
00440FCD   MOV ECX, DWORD PTR SS:[ESP+24]                      
00440FD1   MOV EDX, DWORD PTR SS:[ESP+28]                      
00440FD5   MOV DWORD PTR DS:[EAX], 1                            
00440FDB   MOV EAX, DWORD PTR SS:[ESP+1C]                      
00440FDF   MOV DWORD PTR DS:[EAX], ECX                          
00440FE1   MOV ECX, DWORD PTR SS:[ESP+2C]                      
00440FE5   MOV DWORD PTR DS:[EAX+4], EDX                        
00440FE8   MOV EDX, DWORD PTR SS:[ESP+30]                      
00440FEC   MOV DWORD PTR DS:[EAX+8], ECX                        
00440FEF   MOV DWORD PTR DS:[EAX+C], EDX                        
00440FF2   CMP DWORD PTR SS:[ESP+6C], 10                        ;  << lungh. del seriale == 10h ?
00440FF7   JB SHORT Uedit32.00441006                            
00440FF9   MOV EAX, DWORD PTR SS:[ESP+58]                       ;  << stringa estratta da seriale
00440FFD   PUSH EAX                                             ;  << push stringa estratta
00440FFE   CALL <Uedit32.FreeString>                            
00441003   ADD ESP, 4                                          
00441006   MOV AL, BYTE PTR SS:[ESP+17]                         ;  << stato di registrazione
0044100A   JMP SHORT Uedit32.0044100E                          
0044100C   XOR AL, AL                                          
0044100E   MOV ECX, DWORD PTR SS:[ESP+2B0]                      
                :
                :
00441035   RETN


Si noti che lo stato di registrazione ( il byte in posizione [ESP+17] ) è di default pari ad 1 e viene posto a 0 se qualche test non è andato a buon fine. In linea 00441006 lo stato di registrazione viene memorizzato in AL, concludendo così la routine e, con essa, la nostra analisi.

Reversiamo il reversabile.

Per concludere, riassumo rapidamente l'idea risolutiva per ottenere un seriale corretto, rimandando al codice allegato tutti i dettagli del caso. La parte più complessa è stata la costruzione di match che potessero superare tutti i controlli. In pratica, ho scelto il primo match in maniera fissa ed ho calcolato gli altri a partire dalla data corrente e invertendo le operazioni:

void UEditKeyGenerator::codificaMatch()
{

        int periodo2 = 0x10;                                    // POSSO SCEGLIERE: 0 <= periodo2 <= 17C
        int valore_minore_17C = periodo2;

        // (data_corrente - secondo_terzo) / intervallo = valore_minore_17C
        // -> l'unica incognita è secondo_terzo. ==>
        //    secondo_terzo = ( data_corrente - valore_minore_17C * intervallo )

        __int64 intervallo = 0x000000C92A69C000;                                        // costante
        __int64 data_corrente = DataCorrenteInFileTime();      
        __int64 intermedio = ( intervallo * valore_minore_17C );

        // e' una QWORD
        // ( data_corrente - secondo_terzo ) \ intervallo = valore_minore_17C;
        __int64 secondo_terzo = data_corrente - intermedio;
       
        int match3_temp = secondo_terzo / 0x100000000;          // Recupera DWORD alta
        int match2_temp = secondo_terzo & 0xFFFFFFFF;           // Recupera DWORD bassa


        // Per la v.15
        // 150000 = ( match4 XOR (match1 ROR 0D) ) XOR match1;  
        // -> SE SCELGO match1 = valore random in "X6A3KVMOQ8D2HCPI" ==> i.e match1 = 14232417 ==> 6KA30-AK6O0
        //        POSSO OTTENERE match4 (invertendo questa formula).
        this->match1 = 0x14232417;

        int x1 = ( match1 ^ 0x10150000 );               // Uso 10150000 invece di 00150000 perchè
                                                        // la cifra esadecimale + alta viene portata
                                                        // a diventare la cifra + bassa e viene posta
                                                        // in OR con [00401096] in linea 004019DD.
                                                        // In questo modo mi assicuro di
                                                        // avere ( CL = 1 ) all'uscita dal controllo
                                                        // - e quindi - ottenere il prog. registrato :)

        int x2 = match1;
        __asm ROR x2, 0x0D;

        // Ricavo dunque match4
        this->match4 = x1 ^ x2; // XOR

        // Posti
        int m2 = 0; // BSWAP(match2_temp);
        int m3 = 0; // BSWAP(match3_temp);

/*     
         // Viene eseguito (con match2_temp e match3_temp che ora sono noti, per cui sono noti anche m2 ed m3):
         // --------------------------------------
         match2_temp = BSWAP( match2 XOR match3 )          ==> match2 XOR match3          = BSWAP(match2_temp);        
         match3_temp = BSWAP( match3 XOR match2_temp ) ==> match3 XOR match2_temp = BSWAP(match3_temp);

         // Sistema :
        1) match2 XOR match3 = m2;                              ==> match3 = m2 XOR match2;
        2) match3 XOR match2_temp = m3;

        2) ( m2 XOR match2 ) XOR match2_temp = m3; ==> m2 XOR match2 = m3 XOR match2_temp;
       
        // Risultato ( ^ = XOR )
        //----------------------------------------
        match2 = ( m3 ^ match2_temp) ^ m2;
        match3 = m2 ^ match2;

  */



        // Modo sporco, ma veloce, per eseguire i BSWAP di match2_temp e match3_temp
        __asm
        {
                PUSH EAX                // Salva registro EAX

                // Esegue primo BSWAP
                MOV EAX, match2_temp   
                BSWAP EAX
                MOV m2, EAX

                // Esegue secondo BSWAP
                MOV EAX, match3_temp
                BSWAP EAX
                MOV m3,EAX

                POP EAX
        }
       
         // Ottiene i due match mancanti
         this->match2 = ( m3 ^ match2_temp) ^ m2;      
         this->match3 = m2 ^ match2;                           

}

Una volta in possesso dei match, ho ricavato, simulando i BSWAP, gli indici nella stringa "X6A3KVMOQ8D2HCPI", e dunque i corrispondenti caratteri.

void UEditKeyGenerator::calcolaIndici()
{
   // Estrae gli indici da utilizzare per l'estrazione del seriale.
   // Gli indici sono le posizioni 0-based nella stringa 'X6A3KVMOQ8D2HCPI'.
   // Probabilmente si poteva semplificare la gestione dei BSWAP ... ma non ho trovato rapidamente il modo ;)

        int i = 0, j = 0;
        int indici[8];

        // Recupera gli indici da match1 (ultimi 6)
        getHexDigits( indici, this->match1, 2, 7 );

        // Copia nell'array interno
        for( j = 2; j < 8; j ++ )
                this->indici[i++] = indici[j];

        // Recupera gli indici da match2
        getHexDigits( indici, this->match2, 0, 7 );

        // Copia nell'array interno
        for( j = 0; j < 8; j ++ )
                this->indici[i++] = indici[j];

        // Recupera gli indici da match3
        getHexDigits( indici, this->match3, 0, 7 );

        // Copia nell'array interno
        for( j = 0; j < 8; j ++ )
                this->indici[i++] = indici[j];

        // Recupera gli indici da match4
        getHexDigits( indici, this->match4, 0, 7 );

        // Copia nell'array interno
        for( j = 0; j < 8; j ++ )
                this->indici[i++] = indici[j];

        // Recupera gli indici da match1 ( primi 2 )
        getHexDigits( indici, this->match1, 0, 1 );

        // Copia nell'array interno
        for( j = 0; j < 2; j ++ )
                this->indici[i++] = indici[j];

}


In seguito ho riprodotto la generazione del vettore di valori sulla base del nome utente, con il conseguente recupero dei 32 valori di lookup (memorizzati nel vettore di interi 'codici'); ho eseguito la somma del resto e la moltiplicazione per 10 ... in modo da ottenere così le singole lettere del seriale. Ho, per concludere, aggiunto un carattere qualsiasi come quinto elemento di ogni sottogruppo.

void UEditKeyGenerator::calcolaSeriale( char* serialBuffer )
{
    int idx = 0;
    int idx2 = 0;

        int sz = 32;    // lunghezza degli indici
        int eax = 0;

    for ( int i = 0; i < sz; i++ )
    {    

        // Recupera i-esimo valore del vettore calcolato da [hash] userName
        eax = this->codici[i];
               
        // Chiama la routine per il calcolo del carattere 'successivo' del seriale:
        char char_seriale = getCharSeriale( eax, idx2 );
                 
        // Memorizza risultato
        serialBuffer[idx++] = char_seriale;
        idx2++;
       
         // Aggiunge un trattino ogni 4 ( con un carattere qualunque ... per ora infatti
          // non e' controllato dall'algoritmo di verifica del programma).
         if ( idx2 % 4 == 0 )
         {
              serialBuffer[idx++] = 'A';
               serialBuffer[idx++] = '-';
         }                  
                         
    } // fine for
   
    // Conclude la stringa
    serialBuffer[idx - 1] = '\0';

}


Una volta ottenuto il nostro bel seriale, possiamo verificare se abbiamo fatto un buon lavoro. Beh, ... la finestra di about sembra darci ragione :D

Image:UditRegistered.PNG

Una volta verificato che tutto funzioni a dovere, non dimentichiamo di cancellare il file uedit32.reg dalla cartella %userprofile%\Application Data\IDMComp\UltraEdit" per ripristinare lo stato trial del programma.


Note Finali

Mammamia. Non credevo che scrivere un tutorial potesse essere così complesso. Per fortuna sono giunto vivo alla fine e, spero, che lo siate anche voi.

Da tempo frequento la UIC ma non ho mai avuto il coraggio di contribuire dal momento che, spesso, i miei successi si limitavano a soluzioni banalissime per i più. Sono convinto invece, questa volta, di aver realizzato qualcosa degna di nota e, perchè no, in grado di dare una mano a qualche niubbo come me.

Mi aspetto commenti, critiche, segnalazioni su imprecisioni o cazzate ... insomma tutto ciò che possa migliorare queste pagine :D.


Ringraziamenti

Voglio ringraziare innanzitutto la UIC per essere un posto unico per imparare l'arte del reversing e per condividere qualunque tipo di emozione. Ringrazio yami per avermi aiutato a rendere leggibile questo documento, albesp77 per avermi dato la sua "opinione professionale" al riguardo, e tutti voi che leggerete questo tutorial.

Un ringraziamento speciale va alla mia prima e straordinaria insegnante di RCE, ovvero lena151 grazie alla quale (i suoi video tutorial sono eccezionali) sono entrato in questo mondo splendido e complicatissimo (almeno per quanto mi riguarda).

Ringrazio ancora tutti i grandi della scena che, con i loro innumerevoli tutorial, articoli e consigli, consentono anche a noi niubbi di fare dei passi avanti.

Pre-Disclaimer

L'autore di queste pagine declina ogni responsabilità relativa al loro contenuto e all'impiego delle informazioni in esso riportate. Il lettore si assume piena responsabilità per un loro eventuale uso inappropriato e/o per qualunque utilizzo intenda farne. Il tutorial è rilasciato "così com'è" senza garanzie di alcun tipo. Tutte le informazioni sono qui riportate solo per fini educativi e non vanno assolutamente adoperate per scopi illegali.


Disclaimer

I documenti qui pubblicati sono da considerarsi pubblici e liberamente distribuibili, a patto che se ne citi la fonte di provenienza. Tutti i documenti presenti su queste pagine sono stati scritti esclusivamente a scopo di ricerca, nessuna di queste analisi è stata fatta per fini commerciali, o dietro alcun tipo di compenso. I documenti pubblicati presentano delle analisi puramente teoriche della struttura di un programma, in nessun caso il software è stato realmente disassemblato o modificato; ogni corrispondenza presente tra i documenti pubblicati e le istruzioni del software oggetto dell'analisi, è da ritenersi puramente casuale. Tutti i documenti vengono inviati in forma anonima ed automaticamente pubblicati, i diritti di tali opere appartengono esclusivamente al firmatario del documento (se presente), in nessun caso il gestore di questo sito, o del server su cui risiede, può essere ritenuto responsabile dei contenuti qui presenti, oltretutto il gestore del sito non è in grado di risalire all'identità del mittente dei documenti. Tutti i documenti ed i file di questo sito non presentano alcun tipo di garanzia, pertanto ne è sconsigliata a tutti la lettura o l'esecuzione, lo staff non si assume alcuna responsabilità per quanto riguarda l'uso improprio di tali documenti e/o file, è doveroso aggiungere che ogni riferimento a fatti cose o persone è da considerarsi PURAMENTE casuale. Tutti coloro che potrebbero ritenersi moralmente offesi dai contenuti di queste pagine, sono tenuti ad uscire immediatamente da questo sito.

Vogliamo inoltre ricordare che il Reverse Engineering è uno strumento tecnologico di grande potenza ed importanza, senza di esso non sarebbe possibile creare antivirus, scoprire funzioni malevoli e non dichiarate all'interno di un programma di pubblico utilizzo. Non sarebbe possibile scoprire, in assenza di un sistema sicuro per il controllo dell'integrità, se il "tal" programma è realmente quello che l'utente ha scelto di installare ed eseguire, né sarebbe possibile continuare lo sviluppo di quei programmi (o l'utilizzo di quelle periferiche) ritenuti obsoleti e non più supportati dalle fonti ufficiali.