
Donc par exemple on veut changer un jne en un je, on va
ouvrir le programme avec ollydbg et on recherche l'instruction que vous voulez changer. Une fois celle-ci trouvée, vous devriez avoir un truc du genre:JNE
|
|
ADD AX,CX (si AX=10 et CX=20 alors nous aurons AX=30 et CX=20)
ADD EAX,EBX ADD AX,-123 (l'assembleur autorise les nombres négatifs) SUB DI,12 Quelques exemples impossibles : ADD AX,CL (comme pour MOV, un 16 bits avec un 8 bits sont incompatibles) SUB ES:[DI],DS:[SI] ADD ES,13 (impossible car c'est un registre de segment) |
À présent, nous abordons l'instruction PUSH qui signifie Pousser. Cette instruction permet de
placer une valeur au sommet de la pile. PUSH doit être accompagné d'une valeur de
16 ou 32 bits (souvent un registre) ou d'une valeur immédiate.
Exemple 1:
PUSH AX
PUSH BX
En premier lieu, AX est placé en haut de la pile et ensuite BX est placé au sommet,
AX est repoussé au deuxième rang.
Exemple 2:
PUSH 1230
PUSH AX
Nous plaçons dans la pile, la valeur 1230 (qui aurait pu être aussi un 32 bits)
et ensuite AX, ce qui place 1230 au deuxième rang.
Passons maintenant à l'instruction de "récupération" qui se nomme POP (sortir-tirer).
Cette instruction demande comme PUSH, une valeur de 16 ou 32 bits (seulement un registre).
Elle prend la première valeur de la pile et la place dans le registre qui suit l'instruction.
Exemple 1:
Nous avions PUSH AX et PUSH BX et maintenant nous allons les sortir de la pile en utilisant
POP BX
POP AX
Nous retrouvons les valeurs initiales de AX et BX mais nous aurions eu aussi la
possibilité de faire d'abord POP AX et POP BX, ce qui aurait placé dans AX, la valeur
BX (1er dans la pile) et dans BX, la valeur AX (2ème).
Exemple 2:
La première partie était PUSH 1230 et PUSH AX. Nous allons placer dans CX, la valeur
de AX (1er de pile) et dans DX, le nombre 1230 (2ème puisque "pilé" après)
POP CX
POP DX
Dans CX, nous avons la valeur de AX et dans DX, nous avons 1230.
La pile est très utile pour garder la valeur d'un registre que l'on va utiliser afin de le
retrouver intact un peu plus loin. Souvent dans des routines, le programmeur doit utilise tout
les registres mais leur nombre est limité (utilisation des registres au maximum = gain de vitesse). Le programmeur va donc utiliser les fonctions de la pile pour pouvoir utiliser les registres et garder leur
valeur actuelle pour une utilisation ultérieure. Malheureusement les instructions de la pile
ralentissent le programme, la pile étant de toute façon plus lente que les registres qui sont
au coeur du CPU.
Cette liste non-exhaustive vient de Alligator27
********************************************************************************
| LES INSTRUCTIONS ASSEMBLEUR |
| PAR |
| ALLIGATOR427 | 09/96
*********************************************************************************
AAA ajustement ascii de apres une addition. Format BCD
AAD ajustement ascii de avant une division BCD
AAM ajustement ascii de apres une multiplication. Format BCD
AAS ajustement ascii de apres une soustraction.
ADC addition avec retenue
ADD addition sans retenue
AND operation logique ET
ARPL ajuste le niveau du privilege (mode protégée)
BOUND teste un nombre par rapport a des limites
BSF cherche 1 bit de droite a gauche
BSR " " " " " gauche a droite
BSWAP conversion d'un registre en un format INTEL
BT charge 1 bit en CF
BTC " " " " " " et complement
BTR " " " " " " " " " puis le met a 0 dans la source
BTS " " " " " " " " " " " " " " " " "1 " " " " "" " "
CALL appel de sous programme
CBW convertit l'octet signé de en un mot dans
CDQ convertit le mot signé de EAX en un quadruple mot dans
CLC mets l'indicateur a 0 dans CF
CLD " " " " " " " " " " " " " DF
CLI " " " " " " " " " " " " " IF
CLTS mets a 0 l'indicateur de changement de contexte Task-Switch-Flag
mode protégée.
CMC complemente l'indicateur CF
CMP comparaison logique
CMPS[b][w] compare des chaines de caracteres
CMPSD compare des chaines de caractere 80386()
CMPXCHG compare l'accumulateur avec AL, AX, ou EAX
CWD convertit le contenu signe de AX en un double mots DX:AX
CWDE " " " " " "" " "" " " " " " " " "" " " " " " " " dans EAX
DAA ajustement decimal de AL apres une addition BCD
DAS " " " " " " " " " " " " " " " " " soustraction BCD
DEC decrementation
DIV division non signee
ENTER construit un cadre de pile pour une procedure de haut niveau.
ESC acces pour le coprocesseur
HLT arret du processeur en attente d'un evenement externe
IBTS insere une chaine de bits (mode protegee)
IDIV division signée
IMUL multiplication signée
IN lit un octet ou mot sur un port peripherique
INC incrementation
INS[b][w] lit une chaine sur un port
INT interruption logiciel
INTO active l'interruption 4 si l'indicateur OF est armé
INBD efface le contenu de la mem cache du 486
INVLPG exploitation du 486 en mode virtuel. multiprocesseur 8088
IRET retour d'interruption
IRETD " " " " " " " " " "" depuis un segment de 32bits
JA branchement si superieur
JAE "" " " " "" " " "" " ou egal
JB branchement si inferieur
JBE " " " " " " " " " " " " " ou egal
JC branchement si CF est a 1
JNC " " " " "" " "" " "" 0
JCXZ " " " " " " " CX " " " "
JECXZ branchement si le registre ECX est a 0
JE branchement en cas d'egalité
JG branchement si arithmetiquement superieur ou egale
JMP branchement a l'adresse indiquée
JNA branchement si non superieur
JNAE branchement si non superieur ou egal
JNB branchement si non inferieur
JNBE "" " " " "" " " " "" " " " ou egal
JNE branchement en cas de non egalité
JNG branchement si arithmetiquement non superieur
JNGE " " " " "" " " "" " " " " "" " " "" "" " ou egal
JNL branchement si non inferieur arithmetiquement
JNLE branchaement si arithmetiquement non inferieur ou egale
JNO branchement si l'indicateur OF est a 0
JNP branchement si parité impaire (indicateur PF a 0)
JNS branchement si positif
JNZ branchement si different
JO branchement si OF est a 1
JP branchement si parité pair. indicateur PF est a 1
JPE " " " " " " " " " " " " " " " " " " " " " " " " "
JPO " " " "" " " " " " " "impair. " " " " " "" " " 0
JS branchement si negatif
JZ branchement en cas d'egalité
LAHF charge en AH la partie basse du registre des indicateurs
LAR charge le drout d'acces (mode protegée)
LDS charge une adresse physique en DS: registre
LEA " " " " " " "" " "effective
LEAVE libere le cadre de pile, installer par entrée
LES charge une adresse physique en ES: registre
LFS " " " "" " "" " " " "" " " " FS: " '" "
LGDT charge le registre de la table des descripteurs globaux (mode protégée)
LGS charge une adresse physique en GS: registre
LIDT charge le registre de la table des descripteurs d'interruption (MODE P)
LLDT charge le registre de la table des descripteurs locaux
LMSW " " " le mot d'etat de la machine (mode protégée)
LOCK verrouille le bus
LODS[b][w] charge AL/AX avec le contenu de DS:SI
LODSD charge EAX avec le contenu de DS:SI
LOOP branchament tant que CX #0
LOOPE " " " "" " " " "" " " " "et ZF =1
LOOPNZ " " " " " " "" "" " " "" " ""=0
LOOPZ " " " " " " " " " " "" "" " " =1
LSL charge une limite de segment (mode protegée)
LSS charge une adresse physique en SS: registre
LTR charge le registre de tache (mode protégée)
MOV tranfere une donnée
MOVS transfere une chaine de caractere octet par octet ou mot pour mot de
DS:SI en ES:DI
MOVSD transfere une chaine de caracteres double mot par double mot
MOVSX tranfert avec extension de signe
MOVZX tranfert avec une extension de 0
MUL multiplication non signée
NEG negation par complement de 2
NOP pas d'operation, voir utilisation de samsom pour ex:
NOT operation logique NON complement a 1
OR operation logique OU inclusif
OUT transmets un octet ou mot a un periph
OUTS[b][w] transmets une chaine a un port
OUTSD transmets un double mot a un port
POP depile un mot
POPA depile les registres
POPAD depile tous les registres 32 bits
POPF depile un mot et le tranfere vers le registre des indicateurs
POPFD " """ " DOUBLE MOT "" " " " " "" " " "" " " " " "" " " " "" " "" "
sur 32bits
PUSH: empile une valeur
PUSHA empile tous les registres
PUSHAD " " " " " "" " " "" "" 32 bits
PUSHF empile le registre des indicateurs
PUSHFD empile le registre des indicateurs a 32bits
RCL rotation a gauche a travers CF
RCR rotation a droite a travers CF
REP[z][nz] prefixes de repetition
REP[e][ne] pour traiter les chaines de caractere en association avec CX et
les indicateurs
RET[n][f] retour de sous programme
ROL rotation a gauche
ROR rotation a droite
SAHF copie AH dans la partie basse du registre des indicateurs.
SAL decalage a gauche avec introduction de 0
SAR " " " " " droite avec signe
SBB soustraction non signée avec prise en compte de CF
SCAS[b][w] compare une chaine octet par octet ou mot par mot avec le contenu de
AL/AX
SCASD compare une chaine double mot par double mot avec le contenu EAX
SETA initialisation a 1 si CF et ZF sont a 0, sinon initialisation a 0
SETAE " " " " " " " " " " "" est a 0, sinon init. a 0
SETB " " " " " " " " " " " " " " " 1, " " " " " " " "
SETBE " " " " " " " " " " " CF ou ZF est a 1, sinon initialisation a 0
SETE " " " " " " " " " " " ZF est a 1, sinon init. a 0
SETG " " " " " " " " " " " " " " " " 0 et SF=OF, sinon init. a 0
SETGE " " " " " " " " " " " SF=OF, sinon init. a 0
SETL " " " " " " " " " " " SF#OF, sinon init. a 0
SETLE " " " " " " " " " " " ZF est a 1 et SF#OF, sino init a 0
SETNA " " " " " " " " " " " CF ou ZF est a 1, init a 0
SETNAE " " " " " " " " " " " CF est a 1, sinon init. a 0
SETNB " " "" " " "" "" "" CF est a 0, sinon init a 0
SETNBE " " " " " " " "" " " "CF et ZF sont a 0, sinon, init. a 0
SETNE " " " " " " " " " " " ZF est a 0, sino init a 0
SETNG " " " " " " " " " " " " " " " " 1 ou SF#OF, sinon init. a 0
SETNGE " " " " " " "" " " " "SF#OF, sinon init. a 0
SETNL " " " " " " " " " " " "SF et OF sont egaux, sinon init. a 0
SETNLE " " " " " " " " " " " "ZF est a 0 et SF=OF, sinon init a 0
SETNO " " " " " " " " " " " OF est a 0, sinon init a 0
SETNP " " " " " " " " " " " "PF est a 0, " " " "" " " "
SETNS " " " " " " "" " "" " SF " " " " " " "" " " " ""
SETNZ " " "" " " " "" " " " "ZF " " " "" " "" " " " "
SETO " " " " " " " " "" " OF est a 1, "" " "" " " ""
SETP " " " " " " " " " " " PF " " " " " " " " " "" "
SETPE " " " " "" " " " " " " " " " " " " "" "" " "" "
SETPO " " " " " " " " " " " " " " " " 0, " " " " " " "" "
SETS " " " " " " " " " " " " SF est a 1, sinon " " " " "
SETZ " " " " " " " " "" " " ZF " " " " " " " " "" "" "
SGDT sauvegarde le registre de la table des descripteurs globaux(mode prot.)
SHL voir SAL
SHLD decalage double a gauche
SHR decalage a droite avec introduction de 0
SHRD decalage double a droite
SIDT sauvegarde le registre de la table des interruptions. (mode protégée)
SLDT " " " " " " " " " " " " " " " " " " "" descripteurs locaux (mode P)
SMSW sauvegarde le mot d'etat de la machine (mode P)
STC mets a 1 l'indicateur CF
STD " " " " "" "" " " " DF
STI " " " " " " " "" " "IF
STIOS[b][w] transfert octet par octet, mot par mot le contenu de AL en ES:DI
STOSD transfert double mot par double mot le contenu de EAX en ES:DI
STR sauvegarde le registre de tache (mode Protégée)
SUB soustraction non signée
TEST test si un bit est a 1
VERR test l'autorisation de lecture d'un segment (mode prot‚g‚e)
VERW test l'autorisation d'ecriture dans un segment (mode prot‚g‚e)
XADD addition signée
WAIT attends que la ligne BUSY ne soit plus actif
XBINVD efface le contenu de la memoire cache du 486
XBTS prends une chaine de bits (mode protégée)
XCHG echange les contenus de 2 registres
XLAT charge en AL l'octet de la table DS:BX+AL
XOR operation logique ou exclusive
Nous abordons une partie qui est nécessaire lors de la création d'un programme. Souvent, le programme doit faire une action selon la valeur d'un résultat. Les instructions conditionnelles comme leur nom l'indique, sont des instructions qui font une action selon un résultat. Elles se basent sur les flags pour faire leur choix. Vous vous souvenez de l'instruction JMP, il s'agissait d'un simple saut vers une autre partie du programme. D'autres instructions comme JMP font des sauts mais selon certains critères, on les appelle des sauts conditionnels. Voici la liste des ces instructions avec la valeur de l'indicateur nécessaire à l'exécution.
|
JB - JNAE - JC Below - Not Above or Equal - Carry CF = 1 JAE - JNB - JNC Above or Equal - Not Below - Not Carry CF=0 JE - JZ Equal - Zero ZF=1 JNE - JNZ Not Equal - Not Zero ZF=0 JO - JNO Overflow - Not Overflow OF=1 - OF=0 JP - JPE Parity - Parity Even PF=1 JNP - JPO No Parity - Parity Odd PF=0 JS - JNS Signed - Not Signed SF=1 - SF=0 JA - JNBE Above - Not Below or Equal CF=0 et ZF=0 JBE - JNA Below or Equal - Not Above CF=1 ou ZF=1 JG - JNLE Greater - Not Less or Equal ZF=0 et SF=OF JGE - JNL Greater or Equal - Not Less SF=OF JL - JNGE Less - Not Greater or Equal SF (signé)=OF JLE - JNG Less or Equal - Not Greater ZF=1 ou SF (signé)=OF |
Pour tester tout cela, nous avons besoin d'une instruction, c'est CMP qui nous aidera
à le faire.
L'instruction CMP
Cette instruction va nous servir à tester différentes valeurs et modifier les flags en
fonction du résultat. CMP est un SUB qui ne change ni la source ni la destination, seulement
les flags. Un CMP BX,CX sera comme un SUB BX,CX à l'excéption près que BX ne sera pas modifié.
Si BX=CX alors BX-CX=0 donc le flag ZF sera égal à 1. Si nous voulons faire un saut
avec "égal à" (JNE ou JZ qui demande ZF=1), nous avons ZF=1 et comme
JZ demande que ZF=1 pour faire le saut, nous avons donc le saut. Souvenez-vous simplement
que la plupart du temps, il s'agit de comparaisons du genre :
effectue le saut : plus grand que (nombres non-signés) ---> JA plus petit que (nombres non-signés) ---> JB plus grand que (nombres signés) -------> JG plus petit que (nombres signés) -------> JL égal à (signé et non-signé) -----------> JE ou parfois JZ
il suffit de rajouter un 'n' après le 'j' pour avoir la même instruction mais exprimée
de facon négative
ne saute pas si :
plus grand que (nombres non-signés) ---> JNA (jump if _not above_)
...et ainsi de suite.
Vous vous souvenez de eax, c'est un
des registres. Les registres sont des sortes de variables, mais au niveau du processeur, ils
servent principalement à utiliser et à gérer la mémoire (ram ou rom). Les registres sont au
nombre de 16, 8 registres généraux, 6 registres de segment, un registre IP et un registre de
Flags. Chacun ayant une fonction particulière. Ils servent à manipuler des données, à transférer
des paramètres lors de l'appel de fonctions et à stocker des résultats intermédiaires. Voici une
petite doc tirée de "Résumer et Théorie sur l'Assembler par TeeJi - Release 24/07/99 - ":
Les Registres Généraux.
Il y a 8 Registres Généraux. Chacun ayant une fonction particulière. Ils servent à manipuler
des données, à transférer des paramètres lors de l'appel de fonctions Dos/Win et à stocker des
résultats intermédiaires.
| EAX | 32 Bits Accumulateur
--------| AX | 16 Bits
| AH| AL| 8 Bits
=================
| EBX | 32 Bits Base
--------| BX | 16 Bits
| BH| BL| 8 Bits
=================
| ECX | 32 Bits Compteur
--------| CX | 16 Bits
| CH| CL| 8 Bits
=================
| EDX | 32 Bits Données
--------| DX | 16 Bits
| DH| DL| 8 Bits
=================
| ESI | 32 Bits Index de Source
--------| SI | 16 Bits
=================
| EDI | 32 Bits Index de Destination
--------| DI | 16 Bits
=================
| ESP | 32 Bits Pointeur de Pile
--------| SP | 16 Bits
=================
| EBP | 32 Bits Pointeur de Base
--------| BP | 16 Bits
Une petite explication s'impose, si nous prenons EAX, c'est un registre de 32 Bits (Extended AX).
AX étant les 16 bits de poids faible et AX est lui même divisé en 2 partie, les 8 Bits de poids
fort étant AH ( H pour High ) et les 8 Bits de poids faible AL ( L pour Low ). Si vous modifiez
une partie de EAX ( AX ou AL ou AH ) EAX est modifié. Si vous modifiez une partie de AX ( AH ou
AL ), AX est modifié et si vous modifiez AL, AH reste le même et inversément. Donc, seul AH et AL
sont indépendant !
Les Registres de Segments
Ils sont utilisé pour stocker l'adresse de début d'un segment. Il peut s'agir de l'adresse du
début des instructions d'un programme, du début des données ou du début de la pile.
|
CS : Segment de Code ( Code Segment ) Ce registre indique l'adresse du début des instructions d'un programme ou d'une sous-routine. DS : Segment de Donnée ( Data Segment ) Ce registre contient l'adresse du début des données de vos programmes. Si votre programme utilise plusieurs segments de donnée, cette valeur devra être modifiée durant son exécution. ES : Extra Segment Ce registre est utilisé, par défaut, par certaines instructions de copie de bloc. En dehors de ces instructions, le programmeur est libre de l'utiliser comme il l'entend. SS : Segment de Pile ( Stack Segment ) Il pointe sur une zone appelée la pile. FS : Segment supplémentaire GS : Segment supplémentaire Ces deux registres ont un rôle fort similaire à celui du segment ES. |
Le Registre IP ( Instruction Pointer )
C'est ce registre qui contient le déplacement à effectuer par rapport au segment CS pour se
positionner sur la prochaine instruction à éxécuter. Il ne faut jamais se soucier de ce registre !
Il est entièrement géré par le processeur. Remarque, la plupart ont été doté d'une extension à
32 Bits et sont donc précédé aujourd'hui d'un E ( AX --> EAX / IP --> EIP / etc.. )
Le Registre de Flag
Le registre de flag est un registre de 16 Bits, dont chacun des bits est un indicateur qui est
positionné donc, soit sur 1 soit sur 0 ! Chaque Bits à un nom. Voici une liste en commencant par
le bit de poids le plus faible :
|
CF : Carry Flag ( retenue ) PF : Parity Flag ( parité ) AF : Auxiliary Flag ( retenue auxiliaire ) ZF : Zero Flag ( zéro ) SF : Sign Flag ( signe ) TF : Trop Flag ( exécution pas à pas ) IF : Interrupt Flag ( interruption ) DF : Direction Flag ( direction ) OF : Overflow Flag ( débordement ) IOPL : Inpuut/Output Privilege Level ( il prend 2 bits ! ) NT : Nested Task Flag 0 NT IOPL OP DF IF TF SF ZF 0 AF 0 PF 1 CF |
Les Flags d'état ( CF, PF, AF, ZF, SF et OF ) sont modifiés par les instructions arithmétiques, logique et des instructions de comparaison. Les instructions de Jump Conditionnel par exemple, testent l'état des ces Flags en vue d'effectuer ou non leur Jump. Les Flags de contrôle ( TF, IF, DF ) donnent des indications au processeur concernant l'exécution du programme. Ils peuvent être activés ou désactivés par le programme en cours.
Voici à présent Code.exe : Programme très simple permettant d'apprendre à cracker.
A la base, quand on tape 1, le programme écrit "Bon code" et pour tout le reste, il écrit "Mauvais code".
Amusons nous avec un debugger, dans cet exemple, j'utilise Turbo Debugger pour DOS.
Je repère, assez rapidement dans le programme une comparaison :
cs:005B 9A91028560 call 6085:0291 cs:0060 803E520001 cmp byte ptr [0052],01 /// ici, comparaison cs:0065 751E jne 0085 ////ici, saut si faux cs:0067 BF5401 mov di,0154 cs:006A 1E push ds
Jne : cette instruction sert à aller à un emplacement quand la valeur est fausse, son code hexadécimal est 75
Je : cette instruction sert à aller à un emplacement quand la valeur est correcte, son code hexadécimal est 74.
Jmp : cette instruction est à aller à un emplacement dans tous les cas, son code hexadécimal est EB.
Il faut donc remplacer 75 (Jne) par 74(Je), ce qui donnera :
cs:005B 9A91028560 call 6085:0291 cs:0060 803E520001 cmp byte ptr [0052],01 cs:0065 741E je 0085 cs:0067 BF5401 mov di,0154 cs:006A 1E push ds
avec un éditeur hexadécimal, on cherchera donc :
80 3E 52 00 01 75 1E BF 54 01 et on remplace par
-- -- -- -- -- 74 -- -- -- --
Maintenant, quand on tape 2, le programme ‚écrira Bon code et quand on tape 1, le programme écrit : Mauvais code.
Approfondissons un peu, le programme doit écrire "Bon code", que l'on tape 1 ou n'importe quel autre nombre.
Je vais vous montrer une erreur à éviter mais qui nous en apprend :
"Si on remplace jne 0085 par jmp 0085, comme ça, il saute tout de suite sur 0085"
cs:005B 9A91028560 call 6085:0291 cs:0060 803E520001 cmp byte ptr [0052],01 cs:0065 EB1E jmp 0085 ////---- ici cs:0067 BF5401 mov di,0154 cs:006A 1E push ds
avec un éditeur hexadécimal, on cherchera donc : 80 3E 52 00 01 74 1E BF 54 01 et on remplace par -- -- -- -- -- EB -- -- -- --
mais maintenant, tout est mauvais, quoi que l'on tape, eh ben, on réfléchit :
"Si le code est faux (Jne), il va à l'emplacement 0085, ça veux dire que si il est juste, il ne lit pas cette instruction et continue à lire la suite du programme"
On dira alors au programme de sauter à l'instruction juste après : 0067
cs:005B 9A91028560 call 6085:0291 cs:0060 803E520001 cmp byte ptr [0052],01 cs:0065 EB00 jmp 0067 cs:0067 BF5401 mov di,0154 cs:006A 1E push ds
avec un éditeur hexadécimal, on cherchera donc :
80 3E 52 00 01 EB 1E BF 54 01 et on remplace par
-- -- -- -- -- -- 00 -- -- --
Eh voilà le travail, maintenant, le programme écrit "Bon code", quoi
qu'il arrive. Amusez vous bien.