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... "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 ! "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. :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 :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. 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 : :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... 0007B05C E046 4100 A665 4100 4E69 4200 8569 4200 A569 4200 FA..eA.NiB..iB..iB. Aussitôt dit, aussitôt fait... This program has been altered, possibly by a virus; program execution will stop now On a un petit Checksum sur le programme... :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, " 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... Ca donne un peu d'air... :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...
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 :
: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à... :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 :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é. 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. 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.... * 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... 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... ;======================================================================= ; 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 :
: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]. :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 : 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]. :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 |
Bonne Journée