Défi Main Rouge

Teleport Pro 1.29 (build 1422)

By Christal

 Teleport Pro ne pose aucun problème pour se laisser aller à toutes les confidences...

1- informations à remplir dans trois champs (Name/Compagny/ Registration Code)
2- BPX Hmemcpy -> et on appuis 8 fois sur F12
3- Appel du contenu des champs par GetWindowTextA, un classique
4- Traçage F10 jusqu'à l'apparition de l'écran "code invalide" en 0042604F
5- On remonte de quelques lignes pour regarder ce que l'on trouve

:0042604F call 0044A0D1 >
call " code invalide "

"You haven't entered a valid username. Your username must be"

en remontant de quelques lignes :

* Referenced by a (U)ncond or (C)ond Jump at Address: 00425FF9(C)

On met la main sur le branchement Pas Glop !

:00425FF9 jne 0042602C >
Pas Glop !

Mais l'inverser ne donne pas le résultat attendu :

"You must enter your username in the Name field, exactly as y"

Alors on remonte encorte un peu (Le Dead Listing ca a du bon aussi...)

:00425F65  call 00426900                 > génération du code
:00425F6A  cmp dword ptr [ebp-18], eax   > comparaison good serial/bad serial
:00425F6D  pop ecx                       
:00425F6E  jne 00425FAA                  > goto bad boy

Avec quelques lignes plus bas un push 00001B74 qui donne :

"Thank you! Your copy of Teleport Pro is now registered "

Good.
Here you are

Aurions nous trouvé ?

Clic droit/display sur [ebp-18]

:006FF468 77 77 07 00 B4 24 F5 BF-1A 5F 42 00 A4 F5 6F 00  ww...$..._B...o. 

077777 est la traduction hexadécimale du serial que j'ai entré, soit 489335
Et dans EAX (52E2E4B4), je vous le donne en mille, le bon serial en hexa (bien sur !)

Petite visite du call "génération code". A l'entrée de cette procédure, EAX vaut 077777 (my serial entered)

:00426900  PUSH      EDI            > sauvegarde du registre EDI 
:00426901  MOV       EDI,[ESP+08]   > name entré
:00426905  TEST      EDI,EDI        > présence caractère ?
:00426907  JZ        00426912       > non -> end
:00426909  PUSH      EDI            > pousse name sur pile
:0042690A  CALL      0042C020       > calcul longueur
:0042690F  POP       ECX         
:00426910  JMP       00426914       > on évite la mise à 0 de EAX
:00426912  XOR       EAX,EAX        > no name        
:00426914  CMP       EAX,05         > 6 caractères mini
:00426917  JAE       0042691D       > Glop glop!
:00426919  XOR       EAX,EAX        > name trop court
:0042691B  POP       EDI            > restaure registre
:0042691C  RET
:0042691D  PUSH      EBX            > sauvegarde registres   
:0042691E  PUSH      ESI            > EBX et ESI
:0042691F  MOV       ESI,5DFEE4A4   > clé de cryptage
:00426924  XOR       EBX,EBX        > EBX=0 compteur
:00426926  TEST      EDI,EDI        > présence caractère?
:00426928  JZ        00426933       > goto suite!
:0042692A  PUSH      EDI            > pousse name
:0042692B  CALL      0042C020       > contrôle validité
:00426930  POP       ECX            
:00426931  JMP       00426935       > saute le Xor eax,eax
:00426933  XOR       EAX,EAX        > plus de caractères
:00426935  ADD       EAX,-04        > eax=eax-4
:00426938  CMP       EBX,EAX        > fin du name?       
:0042693A  JAE       00426948       > goto end
:0042693C  XOR       ESI,[EDI+EBX]  > crypte 

le cryptage va se faire sur un Dword (par exemple irhc pour christal). Si votre Nom comprend moins de 8 caractères, le serial sera généré deux fois sur le même DWORD (irhc placé dans EDI qui pointe sur la string correspondant au Name que vous aurez entré) sans addition du résultat du premier calcul avec le second. Si vote Nom comprend plus de 9 caractères il sera effectué sur la totalité du nom par DWORD (par exemple "niam" et "uor " pour main rouge), et ce jusqu'à concurrence de 40h caractères.
A chaque fois le compteur EBX va servir conjointement à pointer sur le caractère suivant (qui n'est pris en compte qu'au-delà de 4 caractères -> un DWORD), et à bidouiller le cryptage :

1er  cryptage  irhc  -> ESI= 348C8CC7
2ème cryptage  irhd  -> ESI= 47E5FEAF
3ème cryptage  irhe  -> ESI= 339697DD
4ème cryptage  irhf  -> ESI= 52E2E4B4

et ainsi de suite si votre nom contient plus de 9 caractères...

:0042693F  TEST      BL,40          > 40h caractères atteint ?
:00426942  JZ        00426945       > alors, il est temps de finir
:00426944  INC       EBX            > incrémente 2 fois le compteur
:00426945  INC       EBX
:00426946  JMP       00426926       > loop
:00426948  MOV       EAX,ESI        > place bon serial dans EAX
:0042694A  POP       ESI            > restaure registres
:0042694B  POP       EBX
:0042694C  POP       EDI
:0042694D  RET

Rien de plus facile, maintenant que de bidouiller un rapide Key-Gen :

C'est assurément la meilleure des solutions possibles, que de disposer du bon numéro d'enregistrement.
Il est aussi possible de forcer les choses, en plaçant le bon serial à la place du notre, au moment du test " is good serial number ? "

:00425F6A cmp dword ptr [ebp-18], eax > comparaison good serial/bad serial

Une modification éventuelle peut se faire ici :

:00426948  MOV       EAX,ESI        > place bon serial dans EAX
:0042694A  POP       ESI            > restaure registres
:0042694B  POP       EBX
:0042694C  POP       EDI
:0042694D  RET
:0042694E  jmp       00426953

* Referenced by a (U)nconditional or (C)onditiona1 Jump at Address:
|:0042694E(U)
* Possible StringData Ref from Data 0bj ->" "

:00426953  push 0047B824

Si vous regarder bien le jmp 00426953, il ne sert visiblement qu'à sauter à la ligne suivante. Aucun appel direct ne semble être fait sur l'adresse 0042694E. Méfiade, méfiade...
En posant un BPM X à cette adresse, on trouve un call EAX en 0042D475 qui va brancher dessus...

Une petite recherche dans un éditeur hexadécimal va permettre de trouver une entrée sur cette adresse, et comme j'ai besoin de la place occupée par ce jmp , je vais tout simplement modifier 4E 69 42 00 en 53 69 42 00

0007B05C E046 4100 A665 4100 4E69 4200 8569 4200 A569 4200 FA..eA.NiB..iB..iB.

Aussitôt dit, aussitôt fait...
Ouch !

This program has been altered, possibly by a virus; program execution will stop now

On a un petit Checksum sur le programme...
Possibilité d'un CRC32 ? Peut être, mais facile à contourner :

:0040B482     mov eax, dword ptr [0047B6EC] -> EAX = good value
:0040B487     cmp esi, dword ptr [eax]      -> ESI = Value réelle calculée
:0040B489     je 0040B49E                   -> bad boy, don't touch my program
:0040B48B     push ebx
:0040B48C     push ebx

* Possible StringData Ref from Data 0bj ->"This program has been altered, "
->"possibly by a virus; program execution will stop now."

qu'il est facile de shunter en faisant un

:0040B482  MOV       ESI,[0047B6EC]  -> good value in ESI
:0040B488  NOP
:0040B489  JMP       0040B49E        -> et on saute le message Pas Glop ! 
Mais il y a une manière plus élégante de se débarrasser du Checksum :

:0040B482     mov eax, dword ptr [0047B6EC] -> EAX = good value
:0040B487     cmp esi, dword ptr [eax]      -> ESI = Value réelle calculée

La bonne valeur du Checksum est placée dans [0047B6EC]. Une rapide recherche dans le Dead Listing semble montrer qu'il n'y aurait pas de mov [0047B6EC], et que par conséquent il est fort probable que la valeur attendue se trouve dans les codes du programme en clair. Une interrogation D *EAX va donner E01928E3, valeur que l'on va retrouver facilement avec un éditeur hexa :

0007B6EC F0B6 4700 E019 28E3 F0B6 0700 0000 0000 5468 6973 ..G... .........This
0007B700 2070 726F 6772 616D 2068 6173 2062 6565 6E20 616C  program has been al

Et placée juste avant le message d'erreur...

Après les différentes modifications apportées au programme, il n'y aurait plus qu'à poser un BPM X sur 0040B482, relever la valeur contenue dans ESI, et aller l'inscrire à la place de l'autre ?
Et bien oui !
Ca marche, la valeur du Checksum n'étant pas Hard Coded, le programme va maintenant vérifier l'intégrité de ses codes en utilisant la nouvelle valeur mise en place. Glop ! Glop !

Ca donne un peu d'air...
Et surtout, ca permet de gagner les 5 octets du JMP 00426953, pour y implanter notre petite bidouille, en se rappelant que notre serial entré se trouve placé dans EBP-18 :

:00426948  8975E8     MOV       [EBP-18],ESI -> place bon serial dans EBP-18
:0042694B  8BC6       MOV       EAX,ESI      -> le place dans EAX
:0042694D  5E         POP       ESI          -> restaure les registres
:0042694E  5B         POP       EBX
:0042694F  5F         POP       EDI
:00426950  C3         RET                    -> va comparer serial entré et good serial
:00426951  90         NOP                    -> équilibre les deux octets
:00426952  90         NOP                    -> non utilisé
:00426953  6824B84700 PUSH      0047B824     -> programme normal

Cette routine va également être utilisée au lancement de l'application qui va récupérer les informations dans la base de registres :

[HKEY_CURRENT_USER\Software\Tennyson Maxwell\Teleport Pro\User]
"Registration"  =dword:52e2e4b4
"Name"          ="christal"
"Company"       =""
"Bytes"         =dword:00000000
"Explored"      =dword:00000000
"Retrieved"     =dword:00000000
"LastMSIE"      =dword:00000001
"FreeSpace"     =dword:00000018
"LastProject"   =""

Ou l'on voit que notre " forcing " a payé. Le bon serial est écrit dans la base de registre...
A partir de maintenant le programme va se comporter comme une honnête Full Version.

Mais revenons un instant au Key Generator.
Dans le cadre du défi de la Main Rouge, il fallait aussi proposer un option REVERSE ENGINEERING.

Le problème, c'est que pour faire mumuse avec le code, il faut de bien meilleures bases en assembleur que les miennes et que pour un simple Call/Test/Jump à modifier.

Mon idée, très simple, a été de vouloir insérer un Key Generator à l'intérieur des codes de Teleport Pro pour obtenir autant de serials que désiré, et de se faire enregistrer avec chacun d'eux.

Pour cela, il a fallu :

- Créer de l'espace supplémentaire pour y loger les modifications envisagées
- Trouver et modifier la DialogBox d'enregistrement
- Permettre des enregistrements multiples
- Détourner le programme à un endroit à définir
- Afficher le bon serial si le Key generator est activé
- Réafficher le bon serial à la demande

Créer de l'espace supplémentaire pour y loger les modifications envisagées

Pour créer de l'espace, il existe pas mal de méthodes. On peut le faire manuellement dans un éditeur hexadécimal, en utilisant un outil comme ProcDump couplé avec HexWorkShop, ou utiliser un petit tool bien pratique comme TOPO qui va le faire à votre place, en créant un section supplémentaire :

Sections d'origine:
 .text     00063029  00001000  00064000  00001000  60000020
.rdata     0001502E  00065000  00016000  00065000  40000040
.rsrc      00046FB0  00085000  00047000  00080000  40000040
Sections Modifiées :
.text      00063029  00001000  00064000  00001000  60000020
.rdata     0001502E  00065000  00016000  00065000  40000040
.rsrc      00046FB0  00085000  00047000  00080000  40000040
.topo0     00000FA0  000CC000  00001000  000C7000  E0000020

Maintenant que nous disposons de place pour nos modifications, il va falloir :

Trouver et modifier la DialogBox d'enregistrement

Cette fois ci c'est Borland Ressources WorkShop qui s'y colle.
La Dialog box qui nous intéresse est la 154 (ID :009A).
Pour que le programme ne plante pas au moment de l'affichage de notre DialogBox refondue, il va falloir faire attention à ne pas supprimer sauvagement tout et n'importe quoi, à moins de devoir aussi tripatouiller le code de Teleport Pro pour virer certaine contraintes d'affichage comme l'image " thank You for registering... ", ou le champ du serial à entrer.
Or comme il se trouve que justement je ne veux plus de ce champ, j'ai vu au moins deux solutions : le cacher derrière l'image, ou le réduire à 1 pixel.
Au passage on peut relever les ID des différents champs, mais dans mon cas, je n'en ai pas eu besoin...


Permettre des enregistrements multiples

Lors de l'ouverture de la dialogBox, le champ 'Registration Code' sera affiché si aucun Glop! Glop! serial n'est trouvé dans la base de registre. Dans le cas contraire le texte REGISTERED sera affiché.

Comme j'ai masqué ce champ, l'utilisateur n'aura plus le moyen de se rendre dans la routine de test du serial dont j'ai pourtant besoin.
Pas de problème, on va supprimer le saut_qui_va_bien :

:00425F43   mov dword ptr [ebp-18], eax        > code entré dans EAX
:00425F46   cmp byte ptr [ecx+000004DB], bl    > 1ere vérification
:00425F4C   je 00426164                        > jump si Pas registered
:00425F52   cmp eax, ebx                       > nouvelle comparaison
:00425F54   mov esi, 0047C9B8                  
:00425F59   je 00426065                        > jump si serial absent

Pour trouver ces lignes rien n'a été plus facile : une recherche sur [ebp-18], dont on savait déjà qu'il contenait le code entré, et un petit coup d'œil sur les codes qui précédaient la génération du bon serial, et voilà...

Il suffit d'inverser le JE 00426065, pour que le programme ne détecte plus l'absence d'un serial entré et se rende dans la routine de contrôle située trois lignes plus bas :

:00425F5F     push dword ptr [edi+000000D5]
:00425F65     call 00426900                   > routine de génération
:00425F6A     cmp dword ptr [ebp-18], eax     > comparaison Good serial/serial entered

Détourner le programme à un endroit à définir

Pour ne pas me compliquer la vie, je vais détourner le programme juste après la génération du serial, avec un petit inconvénient auquel il faudra remédier, ainsi que vous le verrez un peu plus tard:

:00426948  MOV       EAX,ESI        > place bon serial dans EAX
:0042694A  POP       ESI            > restaure registres
:0042694B  POP       EBX
:0042694C  POP       EDI
:0042694D  RET
:0042694E  jmp       00426953

Comme tout a l'heure, je vais utiliser la place du JMP 00426953, pour y glisser une dérivation vers la routine que je vais implanter dans ma nouvelle section:

:00426948  MOV       EAX,ESI        > place bon serial dans EAX
:0042694A  POP       ESI            > restaure registres
:0042694B  POP       EBX
:0042694C  POP       EDI
:0042694D  jmp       004CC000       > adresse complaisamment donnée par TOPO  
:00426951  ret

La routine vers lequel le programme va être dérouté devra s'occuper de placer le bon serial dans [ebp-18], à la place de celui sensé s'y trouver et correspondant au serial entré.

Afficher le bon serial si le Key generator est activé

Reste maintenant à utiliser le bon serial, dans ESI, pour qu'il s'affiche en décimal dans une boite de messages.

      invoke LoadLibraryA, ADDR lzUSER32
      invoke GetProcAddress, eax, ADDR lzWSPRINTFA
      push   esi
      push   offset conversion
      push   offset buffer2
      call   eax

Ainsi que vos redoutables dons d'observation ont pu le capter, il y a deux autres API's (en plus de MessageBoxA) dont je vais avoir besoin.
La méthode la plus laborieuse consiste à tout écrire à la main grâce à l'assembleur de SoftIce, puis de se retaper le boulot dans un éditeur hexa, sans oublier de trouver dans Wdasm les adresses des API's pour faire des Call Dword ptr [adresse trouvée], afin d'éviter que le programme ne plante sur un Kernel32 différent du mien.

Pas Glop ! Pas Glop !

Une autre solution consiste à utiliser le principe des codes relogeables :

MessageBoxA_relogeable proc

TextAbout db 'Key generator pour Teleport Proe',0  > titre de la messagebox
call Delta                        > appel la ligne suivante
Delta:
pop eax                           > récupération de l'adresse de retour
sub eax,offset Delta              > calcul de la différence entre offsets
push 0                            > bouton OK de la message box (MB_OK)
lea ebx,[eax+offset TextAbout]    > charge la string de textAbout
push ebx                          > et la pousse sur la pile
push esi                          > good serial qu'il faudra convertir en décimal
push 0                            > non associée à une fenêtre particulière
call [MessageBoxA]                > pointe directement dans le Kernel32
ret 
MessageBoxA_relogeable endp 

Le call [MessageBoxA] marche car on considère qu'on appel ici directement la fonction dans le Kernel32.... En d'autre terme, MessageBoxA n'est pas l'adresse de MessageBoxA dans l'Import Table mais celle dans le Kernel....
Bien que je pense encore que ce ne soit vrai que sur un Kernel identique au mien...

Le principe est simple :
On provoque un call sur la ligne suivante. Le processeur va placer sur la pile l'adresse de retour, celle de Delta, et le POP EAX va récupérer cette adresse dans EAX.
Lors de la compilation de cette procédure, le programme se voit attribuer l'adresse d'origine de Delta (en général 0040106, soit l'Entry Point 00401000 classique plus les 5 octets utilisés par le call, plus un octet pour l'adresse de retour).
Un sub EAX, 00401006, ou EAX contient l'EIP de retour, va placer la différence entre les offsets de la compilation d'origine et ceux de la nouvelle implantation au sein d'un programme hôte. Ainsi, un lea ebx,[eax+offset TextAbout] va " corriger " la différence entre l'adresse où se trouvait la string TextAbout, et celle ou elle se trouvera après un coupé/collé dans la cible.

Reste le problème de l'appel des API's.
On peut toujours truander le principe en faisant un call dword ptr (adresse de l'api) où cette adresse est celle relevée dans le dead Listing :

* Reference To: USER32.MessageBoxA, Ord:01BEh
                                  |
:0044A09C FF1594554600            Call dword ptr [00465594]

cette adresse étant calculé lors de la compilation du programme et permet (normalement) de ne pas avoir à tenir compte de tel ou tel kernel...

Mouais...

On peut faire encore autrement, et s'occuper soi même de récupérer les adresses des API's dont on va avoir besoin. Bien évidement, le programme qui doit juste afficher une messagebox, prend des allures d'usine à gaz, mais une fois que la routine est écrite, elle pourra servir pour d'autres occasions...

La première chose va être de déclarer nos variables et nos constantes :
Pour trouver l'adresse de l'API MessageBoxA, nous aurons besoins de Kernel32.dll, User32.dll, FreeLibraryA et LoadLibraryA.

Entry_Point        dd 0                 > retour du programme
Delta_Number       dd 0                 > stockage de la différence entre adresse
Kernel32Base       dd 0                 > stockage adresse Kernel32.DLL

LoadLibrary        db "LoadLibraryA",0  > pour recherche @ LoadLibrary A dans DLL
_LoadLibrary       dd 0                 > stockage adresse
FreeLibrary        db "FreeLibrary",0
_FreeLibraryA      dd 0
GetProcAddress     db 'GetProcAddress',0
_GetProcAdresse    dd 0

ModuleHandle       dd 0

API_USER32         db 'user32.dll',0   > recherche de la DLL User32.dll
MessageBox         db 'MessageBoxA',0  > recherche de l'API dans la DLL
_MessageBoxA       dd 0                > stockage de l'adresse trouvée

TextAbout          db "Your serial Number is:",0

Il y aura autant de variables que de DLL et d'API à trouver...

pushad                                        ; sauve les registres
call Get_Delta 
Get_Delta:
pop ebp                                       ; ebp = offset courant
sub ebp, offset Get_Delta                     ; ebp = différence des offsets
mov dword ptr [ebp+offset Delta_Number], ebp  ; qu'on sauve
mov eax, dword ptr [esp+(8*4)]                ; eax pointe dans kernel32
and eax, 0FFFF0000h                           ; - 4 derniers chiffres de l'offset
Base_Loop:
cmp word ptr [eax], 'ZM'                      ; recherche le début de kernel32
je Kernel32_Base                              ; offset de kernel32 trouvé
sub eax, 10000h
jmp Base_Loop
Kernel32_Base:                                ; on continue ...
;=======================================================================
;             Recherche des adresses des API LoadLibraryA, GetProcAddress et FreeLibraryA
;=======================================================================
        mov ebx, eax                                  > @ de Kernel32.dll dans EDX
        mov dword ptr [ebp+offset Kernel32Base], eax  > stock l'adresse de Kernel32
        
        lea edi, dword ptr [ebp+offset LoadLibrary]   > recherche de LoadLibraryA
        call VgImport
        mov dword ptr [ebp + _LoadLibraryA], eax      > stockage de l'adresse
        
        lea edi, dword ptr [ebp+offset GetProcAddress]
        call VgImport
        mov dword ptr [ebp + _GetProcAddress], eax
        
        lea edi, dword ptr [ebp+offset FreeLibrary]
        call VgImport
        mov dword ptr [ebp + _FreeLibraryA], eax

Le call VgInport est le fer de lance de notre recherche des adresses, nous y reviendrons tout à l'heure...
Pour un programme plus complexe, Vous aurez autant de blocs de recherche que d'adresses à trouver dans Kernel32.

;=======================================================================
;                       Recherche de l'API MessageBoxA via LoadLibraryA et GetProcAddress
;=======================================================================
mov ebx, dword ptr [ebp+offset _LoadLibraryA]   ; récupère @ de la fonction dans EBX 
mov esi, offset API_USER32                      ; prépare la recherche de User32.dll
add esi, ebp                                    ; rajoute la différence entre offsets
push esi                                        ; pousse le nom de la DLL sur la pile
call ebx ; call LoadLibraryA                    ; appel LoadLibraryA
mov dword ptr [ebp+offset ModuleHandle], eax    ; stock 

mov ebx, dword ptr [ebp+offset _GetProcAddress] ; récupère adresse de getprocAddress
mov esi, offset MessageBox                      ; prépare recherche de l'API
add esi, ebp                                    ; + différence entre offsets
push esi                                        ; pousse l'API à trouver sur la pile
push eax                                        : pousse ModuleHandle 
call ebx                                        ; call GetProcAddress

Pour d'autres API's à trouver dans User32.dll, vous avez juste à " automatiser " ,sous la forme de procédures, la recherche de l'adresse convoitée.

call catch_1
      mov edx, offset RegisterClassExA          ; recherche de l'API
      add edx, ebp                              ; différence entre offsets
call catch_api 
      mov [ebp+egisterClassExA], eax            ; stockage

A ce stade, vous êtes en mesure de faire un call MessageBoxA, ou du moins sont équivalent :

        push 0                        ; MB_OK
        mov esi, offset TextAbout     ; Adresse de la string Caption
        add esi, ebp                  ; + différence entre offsets
        push esi                      ; pousse @ sur la pile
        push edi                      ; pousse le serial à afficher dans ESI
        push 0                        ; pas de fenêtre propriétaire
        mov eax, [ebp+ _MessageBoxA]  ; place adresse de MessageBox dans EAX
        call eax                      ; call MessageBoxA

Il ne reste plus qu'à retourner à l'adresse qui a branché sur cette " dérivation " :

;======================================================================
;                                                             retour au programme
;======================================================================
        Popad                       ; restaure les registres
        call The_End                ; même technique, on récupère l'adresse
        The_End:                    ; en cours
        pop eax                     ; que l'on place dans EAX
        sub eax, offset The_End     ; moins la différence entre offsets
        mov eax, dword ptr [eax+offset Entry_Point]   ; on rajoute à EAX
        add eax, 5             ; les 5 octets du jmp qui a branché sur notre routine
        jmp eax                     ; et on saute à l'adresse de retour

Reste le gros morceau, la routine de Virogen que Nody avait déjà présenté dans l'un de ses textes

;======================================================================
;                                                           Importation des API's 
;======================================================================
VgImport proc

; ENTREE : EDI = pAPIName
; EBX = ModuleBase
; ECX = Ordinal
; 
; ModuleBase=Base of the module you want to import the API from. 
; pAPIName =pointer to the ASCIIz name of the API, or NULL if you
; are importing by ordinal. (on utilise cette fonction)
; Ordinal =Ordinal number if you wish to import by ordinal, use
; 0 if pAPIName is supplied. (on utilise pas)
;
; SORTIE : EAX = offset de l'API
; EAX = 0 si erreur

mov edx,ebx                 ; edx=base
cmp word ptr [ebx],'ZM'
jnz ret error
movzx eax,word ptr [ebx+3ch]
add ebx,eax                 ; ebx->PE Header
cmp word ptr [ebx],'EP'
jnz ret error
mov ebx,[ebx+120]           ; ebx->RVA of Export Table
add ebx,edx                 ; ebx->Export table
or edi,edi                  ; was pAPIName supplied?
jz ImportByOrdinal
mov esi,[ebx+32]            ; esi->RVA of Address Name Table
add esi,edx                 ; esi->Address Name Table
mov ecx,[ebx+24]            ; ecx=number of API Names
push ebx
xor ebx,ebx                 ; use ebx as counter
  
SearchLoop:
lodsd                       ; eax->RVA of API Name
add eax,edx                 ; eax->API Name
push esi edi
xchg edi,esi                ; esi->pAPINamse
xchg edi,eax                ; edi->API Name 

strcmpi:
lodsb
or al,al                    ; end of string?
jnz not zero
cmp byte ptr [edi],0
jz found api                ; if so make sure other string ends
jmp didnt find api

not zero:
cmp byte ptr [edi],al
jnz didnt find api
inc edi
didnt find api: 
mov al,1                   ; 1==continue search to next api

found api:
pop edi esi
or al,al                   ; if 0 then we found the api
jz EndLoop
inc ebx
loop SearchLoop

EndLoop:
xchg eax,ebx               ; eax=index
pop ebx                    ; ebx->Export Table
mov esi,[ebx+36]           ; esi->RVA of Ordinal Table
add esi,edx                ; esi->Ordinal Table
inc eax
shl eax, 1
add esi,eax                ; esi->Ordinal number
movzx ecx,word ptr [esi]   ; ecx=Ordinal number
                           ; entry:
                           ; ecx=ordinal

ImportByOrdinal:
sub ecx,dword ptr [ebx+16] ; subtract ordinal base
mov esi,[ebx+28]           ; esi->RVA of Address table
add esi,edx                ; esi->Address Table
shl eax, 2
add esi,eax                ; esi->Address
mov eax,dword ptr [esi]    ; eax->RVA of API
add eax,edx                ; eax->API
mov ebx, edx
ret

ret error:
xor eax,eax
mov ebx, edx
ret

VgImport endp

end start

Documents joints :


Code asm du Key Generator utilisant le principe de la recherche d'adresse. Seule une partie des codes sont relogeables. La procédure WndProc n'a pas été "traitée" pour être recasée à volonté.

Code asm de l'ensemble de la procédure relogeable, convertissant le serial hexadécimal (placé dans ESI) en décimal, et affichant la MessageBox.
Attention le lancement de l'exécutable provoquera une erreur en sortie dans la mesure ou le JMP EAX ne correspond à rien tant que cet exécutable n'est pas inséré dans un autre programme.

Ré-afficher le bon serial à la demande

Comme le programme doit permettre l'obtention de serials multiples, il faut continuer à pouvoir y entrer nos nom et compagnie (bien que ce champ ne serve pas dans la génération du serial), et/ou pouvoir retrouver le précédent serial obtenu sans passer par la MessageBox et la touche [Generate].

:00425F43   mov dword ptr [ebp-18], eax        > code entré dans EAX
:00425F46   cmp byte ptr [ecx+000004DB], bl    > 1ere vérification
:00425F4C   je 00426164                        > jump si Pas registered

Le noppage pur et simple du JE 00426164 (le CRC est désactivé) va permettre au programme de passer par la routine " génération du serial ", nous reviendrons dans un instant sur l'éventuel intérêt de [ecx+000004DB].

Parce que le programme, via [ecx+000004DB] reconnaît l'utilisateur comme valide, la boite d'enregistrement laisse apparaître REGISTERED à la place (normale) du champ de saisie du serial.

Morceau de chance (bien que Borland Ressources nous aurait "corrigé" un défaut potentiel), REGISTERED comprend autant de lettres que le serial doit contenir de caractères. Je pense que vous voyez déjà ou je veux en venir...

:004BA67A 52 00 45 00 47 00 49 00-53 00 54 00 45 00 52 00  R.E.G.I.S.T.E.R.
:004BA68A 45 00 44 00 00 00 00 00-00 00 00 00 02 50 00 00  E.D..........P..

Il va suffire de rajouter à notre routine d'affichage du bon serial par la MessageBox, quelques lignes supplémentaires qui vont écrire ce numéro de série à la place de la Chaîne REGISTERED ! sachant que :

ESI pointe sur l'adresse qui contient le bon serial en décimal
La chaîne à modifier est UNICODE (Wide Char Format)

Loop2 :
Xor   ebx, ebx                     ; mise à zéro du compteur
Movsx eax, byte ptr [esi]          ; charge 1er caractère du serial
Mov   byte ptr [ebx+004BA67A], al  ; remplace un caractère de REGISTERED
Inc   esi                          ; caractère suivant
Add   ebx,02                       ; saute un octet pour respecter le format
                                   ; et pointe sur l'octet suivant à remplacer
Cmp   edi , 14h                    ; le compteur est il à 20 ?
                                   ; (10 caractères + les espaces = 20)
Jne   Loop2                        ; non -> continue

Ainsi, à l'ouverture de la DialogBox, le Nom récupéré dans la base de registre et la compagnie vont s'afficher, suivi de l'ancien REGISTERED remplacé par le serial correspondant. Le tout, bien sur, si vous avez déjà entré au moins une fois un Nom dans la boite d'enregistrement et validé celui ci par [GENERATE].

Maintenant, pourquoi REGISTERED s'affiche alors que les informations Name et Serial n'ont pas encore été vérifiée ?

Grâce à un Flag (celui dont nous avons parlé il y a un instant) :
[ecx+000004DB]

C'est ce Flag qui va déterminer votre statut " Is user registered ".
Au lancement de l'application, la valeur TRUE va automatiquement être placée à cette adresse :

:00414A9C MOV       BYTE PTR [ESI+000004DB],01 > 01 -> Not registered

Un BPM judicieux va permettre de suivre les différentes tribulations de ce Flag, et permettre de trouver rapidement la routine suivante :

:0041514F  CMP       [ESI+51F],EBX           is serial présent? -> EBX
:00415155  JZ        00415181                (JUMP if not registered)
:00415157  PUSH      DWORD PTR [ESI+517]     pousse le contenu de 'Registration'
:0041515D  LEA       EDI,[ESI+517]           le place dans EDI
:00415163  CALL      00426900                génération du serial
:00415168  TEST      EAX,EAX                 no serial found ?
:0041516A  POP       ECX
:0041516B  JZ        00415181                bad boy go away
:0041516D  PUSH      DWORD PTR [EDI]         Name dans EDI
:0041516F  CALL      00426900                héhé, notre routine de génération !
:00415174  CMP       [ESI+51F],EAX           is good serial ? -> EAX
:0041517A  POP       ECX
:0041517B  JNZ       00415181               (JUMP if not registered)
:0041517D  XOR       EAX,EAX                 EAX = 0
:0041517F  JMP       00415184                Evite la mise à 1 du flag
:00415181  PUSH      01
:00415183  POP       EAX                     EAX = 1
:00415184  MOV       [ESI+4DB],AL            Valeur de EAX placée dans Flag

En cherchant à remonter la piste on voit [ESI+51F] qui a reçu le serial trouvé dans la base de registre, puis la vérification de la présence effective d'un serial, avant de le vérifier en réutilisant la même procédure de génération à partir du Name trouvé. Comme le programme repasse par notre routine rajoutée, le résultat est l'apparition sauvage, au lancement de l'application, de la MessageBox affichant le serial qui va nous obliger à rajouter un compteur au début de notre routine en 004CC000 :

pushad
Cmp byte ptr [004CCXXX], 01       > 004CCXXX adresse de stockage
Je  affiche_serial                > si 01 alors le programme est déjà passé par là
Mov byte ptr [004CCXXX], 01       > sinon place 01 en mémoire
Jmp retour_prog -> call The End   > et quitte la routine

Ainsi, une solution simple pour patcher le programme, après modification du Checksum, serait de remplacer le JZ 00415181 par un JMP 0041517D, avec pour seul inconvénient d'avoir à inscrire son nom à la main dans le champ Ad Hoc de la base de registre à moins de ne rien voir dans la case NAME de la boite d'enregistrement

Greetz to : Nody, Pulasr, Papaow et Virogen à qui j'ai emprunté des extraits de leurs textes

Bonne Journée

Christal