IDA - The
Interactive Disassembler - part 2 Dans la première partie nous avons vu les bases de IDA, nous allons maintenant l'illustrer par l'analyse de A à Z en dead-listing d'un crackme très simple. Nous partirons de la source pure tout juste désassemblé pour finir par une source complétement modifiée et adaptée à notre compréhension, je vais y allez doucement en rappelant quelques fondement sur l'ASM et j'essairai de commenter chaque manoeuvre et de détailler l'utilisation des commandes de IDA, en espérant ne rien oublier. Pour la suite il est nécéssaire d'avoir quelques bases en assembleur et quelques notions de la programmation system en Win32, et surtout d'être muni d'une référence des fonctions Windows comme Win32.hlp ou le MSDN afin de pouvoir se référer aux prototypes des fonctions, comprendre la fonction en elle même et les argument dont elle se sert. Vous verrez que ces documents sont indispensables. Les blocs d'exemples seront de 2 couleurs, bleu pour original, rouge une fois modifié.Voici le programmeOn l'ouvre sous IDA, on laisse les options de désassemblage par défaut. Une fois le désassemblage fini, on adpate les options personnelles et au saute directement à l'entry point par le menu jump / jump to entry point ou (ctrl-E) CODE:00401000 ; ¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ S U B R O U T I N E ¦¦¦¦¦¦s¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦Nous voici donc là où le programme commence. Cet endroit est defini par le label start comme dans une source assembleur. IDA a ici interprété ce bloc comme une procedure à l'image de la fonction main() ou winmain() dans un programme en C/C++, puique ces fonctions représentent le point d'entrée d'un programme dans une source en C (à savoir que IDA analyse le code comme si il venait d'une source en C/C++). Le 1er call que l'on rencontre est GetModuleHandleA cette fonction sert à recupérer l'Hinstance handle du programme, qui est en fait un numero d'identification du programme qui est souvent utilisé pour appeler d'autres fonctions, on rencontre cette fonction vers l'entry point sur la quasi totalité des programmes. Si l'on regarde en amont d'un call on verra ses argument pushé dans la pile. D'après le prototype de la fonction GetModuleHandle celle-ci necessite 1 seul argument, que l'on retrouve ici par push 0. On voit que IDA a ajouté un commentaire qui identifie l'argument de la fonction avec son nom tel que déclaré dans son prototype : lpModuleName. Pour comprendre pourquoi sa valeur est 0 il suffit de lire le prototype qui nous dit que si ce paramètre est NULL (0), GetModuleHandle retourne un handle représentant le programme. Suit une instruction qui place dans une variable (nommé automatiquement par IDA) la valeur de retour de GetModuleHandle (le retour d'une fonction se faisant par le registre eax, j'espere que je ne vous apprend rien) soit l'Hinstance handle. Si on double clique sur le nom de cette variable (ds:hInstance) on se retrouve dans la partie .data ou elle est declaré comme suit : DATA:00402000 ; HINSTANCE hInstanceOn voit que cette variable est de type double-word ( dd : declare double-word ) et qu'elle est référencée deux fois (note: le nombre maximum de XREF affiché est par defaut de 2, si il y a plus de référence vous pourrez voir ... à la fin de la ligne, a moins que vous n'ayer modifié le nbr de XREF à afficher dans les options). Le nom par défaut de cette variable est tres bien représentatif nous ne la renommerons donc pas. On fait 'echap' pour revenir en arriere et continuer notre analyse. Commence ensuite une serie de push avant l'appel de DialogBoxParamA. Pareil on regarde dans le manuel à quoi sert cette fonction : elle sert à créer une boite de dialogue à partir d'un fichier ressource. Et on analyse les arguments que l'on vient de lui passer par la pile. (n'oubliez pas que les argument sont pushés en ordre inverse par rapport au prototype) On voit ainsi qu'on lui passe l'Hinstance tout juste recupéré précedement, et parmi les autres arguments un particulièrement interressant est l'adresse d'une procedure qui va servir à gérer les évenements qui se produisent sur cette boite de dialog. Cet argument est lpDialogFunc // dialog box procedure. Nous reviendrons sur ce point dans quelques instants, finissons par le dernier call que l'on rencontre ensuite qui est ExitProcess et qui va se servir du retour de la fonction precedente (eax) comme unique argument. Cette fonction sert à terminer un processus en l'ocurrence notre programme. Nous voyons bien ensuite la fin de la procedure start par le label start endp qui pourrait représenter la fin de la fonction main() en C/C++. Mais que devient le programme ? En realité il ne va pas tout de suite sur le call ExitProcess, lors de l'appel de la fonction DialogBoxParamA celle-ci va creer et afficher une boite de dialog qui entrera dans une sorte de boucle sans fin. Le dialog sera alors gérer par une procedure et le seul moyen de quitter le dialog et de revenir après l'appel de DialogBoxParamA sera d'envoyer un message à ce dialogue que l'on recuperera via sa procedure de gestion des messages et en specifiant de mettre fin à ce dialog et le detruire. On reviens donc sur l'argument de DialogBoxParamA qui specifie l'adresse de cette procedure de gestion des message: CODE:0040100E push offset sub_40102B ; lpDialogFuncOn double-click sur sub_40102B et l'on se retouve au debut de cette procedure: CODE:0040102B ; ¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ S U B R O U T I N E ¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦Si on regarde dans notre manuel pour DialogBoxParamA et le parametre lpDialogFunc. On nous dit de nous référer, pour plus d'informations sur la procedure de la dialog-box, à la déclaration DialogProc dont voici le prototype : BOOL CALLBACK DialogProc( Si nous comparons avec celui qu'IDA nous donne on voit
qu'il n'est pas tout à fait bon, ou plutôt qu'il n'a mentionné en
argument que ceux que le code qui suit exploite. De plus mis à part hWnd
qui est bien nommé les argument suivants n'ont pas de noms. Comme nous
sommes surs à 100% que cette procédure est bien la procédure
de DialogBoxParam nous allons réctifier le prototype ( nom
de la fonction, et argument ). Pour cela il nous faut placer notre curseur
sur sub_40102B proc near
et cliquer droit pour choisir
'edit fuction' à partir du menu contextuel. BOOL DialogProc (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); CODE:0040102B ; ¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ S U B R O U T I N E ¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦Tous les arguments ont été renommés et l'un a même été ajouté (lParam). Il n'était pas présent avant car il n'etait sans doute pas utilisé par le code. Faisons un petit saut en arriere histoire de voir que le re-nommage s'est effectué dans toutes les parties référencées, pour l'entry-point nous obtenons désormais ca: CODE:00401000 ; ¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ S U B R O U T I N E ¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ CODE:00401000 CODE:00401000 CODE:00401000 public start CODE:00401000 start proc near CODE:00401000 push 0 ; lpModuleName CODE:00401002 call GetModuleHandleA CODE:00401007 mov ds:hInstance, eax CODE:0040100C push 0 ; dwInitParam CODE:0040100E push offset DialogProc ; lpDialogFunc CODE:00401013 push 0 ; hWndParent CODE:00401015 push offset aMydialog ; lpTemplateName CODE:0040101A push ds:hInstance ; hInstance CODE:00401020 call DialogBoxParamA CODE:00401025 push eax ; uExitCode CODE:00401026 call ExitProcess CODE:00401026 start endp On voit que l'argument lpDialogFunc est push offset DialogProc et on comprend clairement que l'on push l'adress de la procedure DialogProc plutot qu'un simple push offset sub_40102B. Continuons dans cette procédure qui est le moteur de notre boite de dialogue. CODE:0040102B ; BOOL DialogProc(HWND hWnd,UINT uMsg,WPARAM wParam,LPARAM lParam) CODE:0040102B ; Attributes: bp-based frame CODE:0040102B CODE:0040102B DialogProc proc near ; DATA XREF: start+Eo CODE:0040102B CODE:0040102B hWnd = dword ptr 8 CODE:0040102B uMsg = dword ptr 0Ch CODE:0040102B wParam = dword ptr 10h CODE:0040102B lParam = dword ptr 14h CODE:0040102B CODE:0040102B enter 0, 0 CODE:0040102F cmp [ebp+uMsg], 10h CODE:00401033 jz short loc_40104E CODE:00401035 cmp [ebp+uMsg], 110h CODE:0040103C jz short loc_40105F CODE:0040103E cmp [ebp+uMsg], 111h CODE:00401045 jz short loc_401079 CODE:00401047 mov eax, 0 CODE:0040104C jmp short locret_4010A5 CODE:0040104E ; --------------------------------------------------------------------------- Analysons cette premiere partie très importante : enter 0, 0 va permettre de préparer la pile, pour un accès conventionnel, cette instruction revient à :
CODE:0040102F cmp [ebp+uMsg], 10hLe programme compare [ebp+uMsg] qui correspond donc au 2eme argument de cette procédure (soit uMsg). Regardons à quoi correspond cet argument. Il représente le message que la procedure est chargé d'intercepter et de lier à un evenement si l'on souhaite le traiter. En gros, tout sous Windows est lié à la gestion de message, chaque evenement (clic sur un bouton...) declenche un message, et si l'on souhaite traiter cet evenement on va intercepter ce message et le lier à une procedure. Là nous voyons donc que nous comparons uMsg avec la valeur 10h. Cette valeur represente un message, chaque message correspond à un nombre entier representé par un nom pour faciliter la vie des programmeurs. Quel est le message qui à la valeur 10h ? WM_CLOSE. IDA va encore nous aider ici car il possede toute la liste des messages avec leur valeur numerique et pour faire la correspondance entre la valeur numerique et le nom du message il suffit de clicker droit et du menu chosir 'use standart symbol content'. Une liste assez immense apparaît et indique tous les noms symboliques qui representent la valeur 10h. En se situant dans le context on sait que l'on cherche un message, ceux-ci commence par WM_, alors il suffit de commencer à taper au clavier WM pour placer la recherche sur ce que nous voulons. On se trouve sur WM_CLOSE et on clique OK. IDA a remplacé 10h par le nom du message qu'il symbolise. Faites pareil pour les deux autres comparaisons avec uMsg. Suivi du cmp [ebp+uMsg], WM_CLOSE on trouve un saut conditinnel qui saute vers un label si la comparaison est égale. C'est la que sera defini le code à executer si notre dialog a bien recu le message WM_CLOSE. On peut donc renommer les 3 labels suivant les comparaisons avec des noms plus explicites et en rapport avec les messages qu'ils traitent et pourquoi pas mettre quelques commentaires, tel que mon exemple : CODE:0040102B enter 0, 0 CODE:0040102F cmp [ebp+uMsg], WM_CLOSE CODE:00401033 jz short _CloseDlg ; Ferme le dialog CODE:00401035 cmp [ebp+uMsg], WM_INITDIALOG CODE:0040103C jz short _InitDlg ; Initialisation du dialog CODE:0040103E cmp [ebp+uMsg], WM_COMMAND CODE:00401045 jz short _Command ; Commande actionné sur le dialog CODE:00401047 mov eax, 0 CODE:0040104C jmp short locret_4010A5 CODE:0040104E ; --------------------------------------------------------------------------- Apres la comparaison des messages on trouve mov eax, 0 et jmp short locret_4010A5, si on double-clique sur le jmp on voit que l'on se retrouve sur la fin de la procédure symbolisée par DialogProc endp. Toujours dans le manuel, il est dit que la procedure DialogProc doit retourner non-zero si elle a repondu à un message et zero dans le cas contraire, une exception se faisant sur le message WM_INITDIALOG si dans le code qui lui correspond on appel la fonction SetFocus auquel cas nous devrions retourner zero aussi. Ici on voit que l'on respecte bien cette condition et on mettra 0 dans eax, si aucun message n'a été traité. Avant de sauter sur un label qui nous envois vers la fin de la procedure, renommons ce label en _LeaveProc. CODE:004010A5 _LeaveProc: ; CODE XREF: DialogProc+21j On voit ici l'instruction leave qui va permettre de rétablir la pile qui a été modifié lors de enter 0,0. Suivi de retn 10h qui marque le retour de la fonction et qui precise en specifiant à 10h de désempiler les 4 argument de DialogProc. Pourquoi 10h ? DialogProc se sert de 4 arguments sur la pile tous sont des DWORD ( DWORD = 4 BYTES ) 4 bytes * 4 argument = 16 soit 10 en hexa. Analysons les routines des 2 premiers messages traités : WM_CLOSE et WM_INITDIALOG CODE:0040104E ; --------------------------------------------------------------------------- Pour le code executé si l'on recoit le message WM_CLOSE (message qui est declenché lors de l'appuie sur la croix de fermeture de la fenetre) on voit que l'on appel l'API EndDialog en lui passant l'handle de notre boite boite de dialog que l'on met eax à 1 pour etre conforme à la valeur de retour de DialogProc puisqu'on à traité le message et que l'on saute vers le label defini il y a quelques instants vers la fin de la procedure. Pour WM_INITDIALOG (message qui est déclenché une seule fois au moment de la creation de la fenetre) on appel GetDlgItem, cette fonction permet de recuperer l'handle d'un control, pour ensuite appeler la fonction SetFocus en lui passant cet handle. SetFocus sert à 'donner la main' à un controle ou encore sur un edit-box de placer le curseur tout de suite sur ce controle permettant de pouvoir écrire directement dans celui-ci sans avoir besoin de le selectionner avant. Si on lance le programme on s'apercoit que c'est le champ 'Name' qui va recevoir le focus puisque le curseur est placé dans celui-ci au démarrage. On peux en conclure du n° d'identification du controle qui est celui presicé par dans le 1er argument pushé de GetDlgItem correspond au controle Edit-box 'Name'. On va placer cette info en commentaire. Pareil la suite se conforme à la condition de retour de DialogProc laquelle precise l'exeption de retourné 0 si le message WM_INITDIALOG est traité mais que celui-ci appel dans sa routine fera appel à SetFocus. Passons au traitement de WM_COMMAND CODE:00401079 _Command: ; CODE XREF: DialogProc+1Aj On commence par placer dans eax le 3eme argument wParam on compare ensuite seulement la valeur contenu dans ax - explication : Ce message est declenché quand on actionne un boutton, ou un element d'un menu, d'apres le prototype de WM_COMMAND on voit que wParam contient alors 2 infos : wNotifyCode = HIWORD(wParam); // notification code wID = LOWORD(wParam); // item, control, or accelerator identifier wParam etant un DWORD on peux le diviser en 2 WORD, le word de partie haute contient le notification code, et le word de la partie basse l'identifiant du controle, en asm pour separer les parties on va se servir d'un registre 32 bits comme eax, si l'on place wParam dans eax, ax de capacité 16 bit ( WORD ) contiendra naturellement le WORD de la partie basse, et si l'on souhaite acceder au WORD de la partie haute il suffit de decaler les bits dans eax afin de faire passer ceux qui se trouvait en partie haute dans la partie basse avce l'instruction shr eax,16, qui decale de 16 bits (1 WORD) vers la partie basse. Ici donc on compare l'identificateur d'un controle avce la valeur 3EAh, si vous cliqker droit sur cette valeur vous pourrez voir la valeur exprmié sous d'autre base numerique comme décimal, octal et binaire. Si l'identifiant n'est pas egal à cette valeur on saute sur loc_40108C. Ou la on retrouve le meme shema de comparaison de l'identifiant avce la valeur 3EBh cette fois. Si l'identificateur n'est toujours pas egal à cette nouvelle valeur on saute sur un nouveau label qui place 1 dans eax et qui se poursuit par la fin de la procédure. On peux renommer les 2 labels que nous venons de voir le premier nous faisant sauter sur la comparaison avce une autre valeur (qui represente un autre boutton) et le 2eme qui nous envoie vers la fin de la procedure. on renomme le 1er en _TestNextId ( pour test next identifiant ) et le 2eme en _LeaveProc2 (un label _LeaveProc existant deja). Analysons maintenant le cas ou l'identificateur correspond au valeur commencons par la 2eme ( ouias c pas chronologiquement logique mais c plus simple pour le tut enfin je pense) CODE:0040108C _TestNextId: ; CODE XREF: DialogProc+55j Si l'identificateur du controle qui à été actionné correspond à 3EBh, on va appeler la fonction SendMessageA. Cette fonction sert à envoyer un windows message vers une fenetre, en analysant les argument pushé on peux definir que le message est destiné à cette mem boite de dialog (hWnd) et que le message est WM_CLOSE toujours en cliquant droit pour acceder aux 'use standard symbol constant' et en selectionnant un WM_. Donc la tout deviens clair, si l'identificateur du boutton est 3EBh a va envoyer le Message WM_CLOSE sur notre propre boite de dialog ce qui aura pour effet de la fermer. Si on lance le programme on voit qu'il y un boutton qui permet de fermer le programme, on comprend dès lors que le code que nous venons de voir est celui qui est rataché à ce boutton. Comme sur cette fenetre il n'y a que 2 bouttons on en deduis que l'autre identifiant represente le boutton qui reste : le boutton 'Check', on peux arranger cette partie du code sous IDA en precisant en commentaire les bouttons qui sont actionnés. Si on reviens sur le controle de boutton check on voit qu'on va appeler une nouvelle fonction nommé par defaut sub_4010A9 la on peux en déduire directement que c'est dans cette nouvelle procedure que va se passer la verification du serial, puisque nous avons reussis à analyser tout les traitement des messages et leur evenements, on peux donc renommer aussi cette nouvelle procedure en _CheckProc ce qui au final nous donne : CODE:00401079 _Command: ; CODE XREF: DialogProc+1Aj On peux passer à présent à l'analyse de la procedure de check du serial: Voici la 1ere partie : CODE:004010A9 ; int __cdecl CheckProc(HWND hDlg) Pareil ici on retrouve enter 0, 0 qui va nous permettra d'acceder aux argument de cette fonction via le registre ebp+argument. On trouve aussi une serie de 3 push de registre, généralement on push des registres en debut de procedure lorque celle-ci est succeptible de les modifier alors en les sauvgardant dans la pile on pourra les restituer en fin de procedure en les rappelant par des pop. Ensuite on entamme les push de la fonction GetDlgItemTextA. (attention de ne pas confondre des simple push avce des push passage d'argument d'une fonction, pour cela il suffit de regarder le prototype et de compter le nombre d'argument qu'elle prend et de re-compter en remontant a partir du call pour determiner quel est le premier push de la fonction), GetDlgItemText permet de recuperer le text d'une fenetre ou d'un controle vers un buffer (variable de type string). La on apprend pleins de chose en examinant ses arguments, nMaxCount specifie le nbr max de Byte à copier dans le buffer, lpString pointe vers l'adresse de notre buffer qui recevra le text du controle, et 3E8h determine la valeur le controle en lui meme. Si on se rappelle plus tot on s'est servi de SetFocus en specifiant cette meme valeur on en deduit que c'est donc le meme controle càd l'edit-box 'Name', on arrange ca à notre sauce : CODE:004010A9 enter 0, 0 La fonction GetDlgItemTextA retourne le nbr de char copié dans le buffer. Suite du code : CODE:004010C4 cmp eax, 5 On compare donc le nbr de char copié dans le buffer avce 5, si c'est moins au saute vers un label ou l'on va appeler la fonction SetDlgItemTextA et si on regarde dans les argument on voit que l'on pousse une string declaré comme suit : aLeNomDoitCompo db 'LE NOM DOIT COMPORTER 5 LETTRES MINIMUM',0 On renomme cette variable en 'Mess_5char' et le label qui nous envoie vers cette routine '_5charMin' ce qui donne: CODE:004010C4 cmp eax, 5 ; cmp nbr de char copié, 5 On continue la procedure Check: CODE:004010CF loc_4010CF: ; CODE XREF: _CheckProc+32j APres analyse de ce bloc on comprend que l'on place dans dl , le byte qui se touve à l'adresse de esi qui est NameBuffer, donc le 1er caractere du Nom on test si ce byte egal 0 soit la valeur de fin d'une strings , c'est pas le cas on continue en ajoutant 2 à ce byte et on remplace l'ancien byte par le nouveau dans la variable NameBuffer on incremente esi qui pointe alors sur le caractere suivant et on jump au debut de cette boucle tant que la chaine n'est pas fini, On commente tout ca : CODE:004010CF _Boucle1: ; CODE XREF: _CheckProc+32j Et on continue avce le reste de l'algo maintenant que vous avez compris (j'espere) la technique d'analyse et de renommage petit à petit des label et des variables: CODE:004010DD _endBoucle1: ; CODE XREF: _CheckProc+2Aj Une fois travaillé cette partie on obtient : CODE:004010DD _endBoucle1: ; CODE XREF: _CheckProc+2Aj Et voila l'analyse complete de ce crackme, l'algo du serial est on ne peux plus simple il ajoute tout simplement 2 à la valeur de chaque caractere du Nom et le serial doit correspondre à ce Nom modifié. Pour ceux que ca interresse ou qui veulent comparer ce
listing de IDA avce la vrai source voici la source complete du crackme
en asm - Download me |