1. Un éditeur de texte
2. TASM 5.0, complet avec libs, etc.
3. Une référence API-Win32 (Win32.HLP)
Je suppose que avez une connaissance fondamentale de l'assembleur,
sinon la plupart des
outils sont facile à obtenir.
Nous voulons coder un patcher generique aussi connu comme un "Search-and-Destroy
Patcher".
Beaucoup de gens ne savent pas quoi attendre d'un Patcher
Générique, je vais l'expliquer brièvement. C'est un
patcher, qui est non seulement capable de patcher une certaine version
d'un programme, mais les versions futures aussi, s'ils sont presque identiques.
Cela se fait en cherchant un certain modèle d'octet (et en
écrivant un nouveau) et non en patchant une adresse précise,
ce qui fait cette sorte de patcher plus universel à employer.
Comme généralement le schéma de protection
n'a pas été changé par le coder, les octets de cette
routine peuvent avoir une autre adresse dans la nouvelle (ou ancienne)
version, mais les octets restent les mêmes.
Voila l'astuce =).
OK, premierement nous penserons à la structure principale
de notre programme, puis nous
penseront aux fonctions que nous emploierons, et "last but not
least" nous écrirons le programme.
1. Intro
- Petite intro, présentée dans un MessageBox
2. Open File
- Assigner un handle pour le fichier.
Si fichier n'existe pas -> MessageBox
3. Get Filesize
- Obtenir la taille du fichier
4. Allocate Memory - Allouer une
zone mémoire égale a la taille du fichier.
Si erreur -> MessageBox
5. Read File
- copier le fichier complet dans la mémoire allouée
6. Search Byte
- Détermination de l'adresse (NdT : offset) du modèle
d'octets.
Si erreurs -> MessageBox
7. Set File Pointer to offset
8. Overwrite File - Patch du
fichier. SUCCES -> MessageBox
with new bytes
9. Close File
- Nettoyage!
Deallocate Mem
Quit
- Tous les messages seront présentés par un messagebox, ex: 'MessageBoxA'.
- Pour ouvrir le fichier nous emploierons la fonction 'CreateFileA', qui est plus complexe que 'OpenFile', mais qui est plus flexible à employer.
- Pour fermer le fichier nous emploierons 'CloseHandle'.
- La taille du fichier (NdT : filesize) sera obtenue par 'GetFileSize'
- Nous affecterons la mémoire à l'aide de 'GlobalAlloc'; nous la libèrerons avec 'GlobalFre'
- Logiquement nous lirons le fichier avec 'ReadFile' et l'écrirons avec 'WriteFile'
- Le pointeur de fichier (NdT : Filepointer) peut être établi avec 'SetFilePointer'
- Pour quitter nous emploierons 'ExitProcess'
C'est le coeur de notre patcher. A l'aide de cette petite routine,
un modèle d'octet est cherché dans le fichier cible, qui
sera changé ultérieurement. Je vous l'expliquerai brièvement,
parce que la plus grande partie que vous pourrez obtenir est dans le code.
OK, en premier lieu nous chargeons la taille du fichier (pour l'allocation
mémoire) en ECX afin d'établir une valeur pour la commande
REPNZ; le premier octet du modèle de la recherche est écrit
en AL et ESI est placè à l'adresse des valeurs originales.
Avec 'REPNZ SCASB' la valeur de AL est comparé avec la valeur
de l'adresse mémoire pointée par EDI (EDI est incrementé
de 1à chaque fois). La commande 'REPNZ' répète le
'SCASB' jusqu'a ce que ECX=0 ou que les valeurs comparées soient
égales (FLZ=1).
Si les valeurs sont égales, ECX est chargé avec la
longueur du patch, EDI est décrementé de 1, car 'SCASB' compte
déjà un octet de plus.
la commande 'REPZ CMPSB' répète 'CMPSB' (comparaison
de l'adresse [ESI] avec [EDI]) jusqu'a ce que ECX=0 ou que la valeur diffère.
Maintenant, jetons rapidement un coup d'oeil sur la routine de patch
en elle-même.
Premièrement, l'offset est calculé en incrementant
ECX (le compteur) par 1, et en soustrayant cette valeur à la taille
du fichier (NdT : Filesize) :
- - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - -
(FILESIZE) - (OCTETS JUSQU'A LA FIN DU FICHIER) = OFFSET REEL
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - -
Cette valeur est mise sur la pile, ainsi que l'handle du fichier
pour ultérieurement
APPELER la fonction 'SetFilePointer' afin d'établir le pointeur
du fichier (NdT : Filepointer) à notre décalage.
Ensuite, le buffer des octets à écrire (bwrite),
la longueur du patch, le décalage des nouveaux octets et l'handle
du fichier sont POUSSES sur la pile et la fonction API 'WriteFile' est
APPELEE.
Peut-être un peu complexe, mais je pense encore facilement
comprendre =) ...
(NdT : la traduction du code n'a pas été faite
dans un soucis de compréhension, honnêtement le code est très
facile à comprendre !!!)
;------------------- ----------- -===START===-------- -------------------
; établir un couple d'options pour l'assembleur
.386P
Locals
jmps
.Model Flat ,StdCall
mb_ok
equ 0
hWnd
equ 0
FILE_ATTRIBUTE_NORMAL equ 080h
OPEN_EXISTING
equ 3
GENERIC_READ
equ 80000000h
GENERIC_WRITE
equ 40000000h
; --==declaration de toutes les fonctions à employer==--
extrn ExitProcess
: PROC ;la procédure pour terminer le programme
extrn MessageBoxA
: PROC ;la procédure pour afficher un MessageBox
extrn CreateFileA
: PROC ; ... pour ouvrir
un fichier
extrn ReadFile
: PROC ;lire un bloc dans un fichier
extrn WriteFile
: PROC ;écrire un bloc dans un fichier
extrn CloseHandle
: PROC ;fermer le fichier
extrn GetFileSize
: PROC ;obtenir la taille du fichier
extrn GlobalAlloc
: PROC ;allouer la mémoire
extrn GlobalFree
: PROC ;libérer la mémoire
extrn SetFilePointer : PROC
;déplacer le pointeur de fichier
; --==Ici commence nos données==--
.Data
caption db "_masta_'s essayer sur Win32-ASM-Coding, partie
1,0"
;captionstring, 0-terminated
text db "Hi, nice to CU again",13,10
db "This tut will describe
you how to make",13,10
db "Win32-ASM Search and
Destroy patchers",0
;introtext, 0-terminated
err_cap db "ERREUR",0 ;caption for errormessage
openerr db "Erreur d'ouverture de fichier",0
;errortext opening file
memerr db "Erreur d'allocation mémoire",0
;errortext alloc. memory
byterr db "Le fichier est présent, mais pas
les octets originaux",0 ;error while bytesearch
readycap db "Ready",0 ;caption for 'done'
readytxt db "Ok, le fichier est patché",0 ;text for 'done'
file db "make.old",0
;what file we want to patch?
org_val db "Xmas'97"
;original values
new_val db "_masta_"
;new values
len equ $-new_val
;how many values (length)
;org_val and new_val must be equal
fhandle dd ?
;variable for the filehandle
fsize dd ?
;variable for the filesize
memptr dd ?
;pointer to allocated memory
bread dd ?
;number of read bytes
bwrite dd ?
;number of written bytes
;--==et ici nous commencons notre code==--
.Code
Main:
push mb_ok
;PUSH value for 'uType'
push offset caption
;PUSH pointer to caption
push offset text
;PUSH pointer to Text
push hWnd
;PUSH Masterhandle
call MessageBoxA
;CALL MessageBoxA
push 0
;for Win95 always 0
push FILE_ATTRIBUTE_NORMAL
;standard Fileattributes
push OPEN_EXISTING
;open existing file
push 0
;no Security-attributes
push 0
;disable Share-Mode
push GENERIC_READ +
GENERIC_WRITE ;read- and writeaccess
push offset file
;offset of the filename
Call CreateFileA
;open file
mov fhandle,eax
;save filehandle
cmp eax,0FFFFFFFFh
;if eax=FFFFFFFF then
error
jnz file_is_here
push mb_ok
push offset err_cap
push offset openerr
push hWnd
call MessageBoxA
; showerrormessage
jmp end_
; jump to end
file_is_here: ;file is there, so go on
push 0
;can be 0, if the filesize is less then 4,3 GB :)
push fhandle
;PUSH filehandle
Call GetFileSize
;get the filesize
mov fsize,eax
;save the filesize
push fsize
;PUSH filesize=size of the buffer
push 0
;0=GMEM_FIXED -> fixed memory-area
Call GlobalAlloc
;allocate as much as memory as filesize
mov memptr,eax
;save pointer to memory-area
cmp eax,0
;if eax=0, then there were errors
jnz mem_ok
push mb_ok
push offset err_cap
push offset memerr
push hWnd
call MessageBoxA
jmp end_kill_handle
;end program, close file b4
mem_ok: ;memory is allocated -> next step
push 0
;set to 0 in most cases
push offset bread
;pointer to number of read bytes
push fsize
;read how many bytes?, fsize=whole file
push memptr
;save where? ->allocated memory
push fhandle
;filehandle
Call ReadFile
;read file!
read_ok:
mov edi,memptr
;set EDI to memory-area
mov ecx,fsize
;set ECX (for repnz) to filesize
mov esi,offset
org_val ;set ESI to the string to find
mov al, byte ptr
[esi] ;load AL with the first byte
loop_:
repnz scasb
;repeat until ECX=0 or AL equals
;the value of the byte [EDI], EDI is
;incremented by 1 every run
cmp ecx,0
;If ECX=0, nothing is found
jz not_found
here_is_something: ;found matching byte
push ecx
;save register
push edi
push esi
dec edi
;EDI-1, cos REPNZ SCASB is one step too far
mov ecx,len
;ECX=length of the patch
repz cmpsb
;repeat until the values in the memory of
;[EDI] and [ESI] are different,
;or ecx=0
cmp ecx,0
;If ecx=0, then the org_val is in memory
jz patch
;->jump to patcher
not_that: ;it is not yet here
pop esi
;POP ESI
pop edi
pop ecx
jmp loop_
;search next byte
patch:
;start of the patcher
pop esi
;POP registers
pop edi
pop ecx
dec edi
;EDI-1
inc ecx
;ECX+1
mov eax,fsize
sub eax,ecx
;compute Offset
push 0
;offset from the beginning of the file
push 0
;is 0, if file < 4,3GB
push eax
;offset
push fhandle
;filehandle
call SetFilePointer
;set FilePointer
push 0
;normally 0
push offset bwrite
;how many bytes where written?
push len
;length of the bytes to write
push offset new_val
;offset to new values
push fhandle
;filehandle
Call WriteFile
;write block to file
push mb_ok
push offset readycap
push offset readytxt
push hwnd
call MessageBoxA
;OK, patch is done!
jmp end_kill_all ;END! Cleanup!
not_found:
push mb_ok
push offset err_cap
push offset byterr
push hWnd
call MessageBoxA
;the bytes where not in the file
end_kill_all:
push memptr
;pointer to Memoryarea
call GlobalFree
;enable (free) memory
end_kill_handle:
push fhandle
;PUSH filehandle
call CloseHandle
;CloseHandle
end_:
CALL
ExitProcess ;Quit program
End Main
;end of code, JUMP-spot (main)
;------------------- ----==END OF SOURCE==----------- -----------------
OK, c'est fini !
Pour le compiler, lancer simplement 'MAKE.BAT'
OK, je pense que cette fois nous avons fait quelque chose de réellement
utile, pas seulement un MessageBox comme dans mon premier cours, mais un
véritable outil-de-tous-les-jours d'un cracker.
Le source peut être employé librement et peut-être
qu'il y a certaines choses que vous
voudriez optimiser, surtout concernant la routine de recherche
(Hi Fongus ;)), mais
Je pense que pour un rôle d'apprentissage il est OK.
Voici un petit défi:
--> Vous pourriez chercher les 4 premiers octets du début
OK, j'espère que ma boîte aux lettres ( >masta_t@USA.NET
) explosera bientôt
(LES CRITIQUES SONT BIENVENUES) et que je vous verrai tout prochainement
... =)
Sur cette lancée, j'essaie d'établir un channel IRC
sur le sujet ...
--> #win32asm
J'espère juste qu'il y a assez de gens intéressés à ce travail, qu'ils donnent leur connaissance à d'autres.