==Phrack Inc.== Volume 0x0b, Édition 0x3f, Article #0x10 sur 0x14 |=-----=[ Reverse engineering - PowerPC Cracking on OSX with GDB ]=------=| |=-----------------------------------------------------------------------=| |=--------------------------=[ curious ]=--------------------------------=| |=-----------------------------------------------------------------------=| |=------------=[ Traduit par TboWan pour arsouyes.org ]=-----------------=| --[ Contents 1.0 - Introduction 2.0 - La cible 3.0 - Copie de l'attaque 4.0 - Solutions et Patche A - GDB, OSX, PPC & Cocoa - Quelques observations. B - Pourquoi ne pouvons-nous par patcher directement avec GDB ? --[ 1.0 - Introduction Cet article est un guide pour prendre des applications OSX et de reprogrammer leur structure intrinsèque pour qu'ils se comportent différement par rapport à leur conception originale. Ceci sera exploré tout en crackant un shareware. Pendant qu'on s'attaquera au sujet pas à pas, je vous encourage à tester ces méthodes vous-même de votre côté, sur vos propres programmes, au lieu de simplement répeter fidèlement ce que vous lisez ici. Cette technique a une autre application importante, incluant l'écriture de patches pour des logiciel propriétaires quand la compagnie à fait faillite ou n'en est plus intéressée, l'analyse de malwares et la correction de programmes compilés incorrectement. Vous êtes sensé avoir un savoir rudimentaire dans ce domaine - vous avez peut-être une expérience de programmation en assembleur ou de cracking sous Windows ou Linux. Heureusement, vous devrez au moins connaître un minimum de langage assembleur - ce que c'est et comment ça fonctionne en gros ( qu'est-ce qu'un registre, qu'est-ce qu'un saut relatif, etc). Si vous n'avez jamais travaillé avec l'assembleur PowerPC sur OSX, vous voudrez sans doute jeter un coup d'oeuil à l'Annexe A avant qu'on ne commence. Si vous êtes un minimum familié avec GDB, ça vous aidrea beaucoup. Ce tuto utilise les outils et ressources suivantes - la XCode Cocoa Documentation, qui est inclue avec tous les outils de développement OSX, un ouvrage de référence sur l'assembleur PoxerPC (je recommande le "PowerPC Microprocessor Family: The Programming Environments for 32-Bit Microprocessors" de IBM - vous pouvez l'avoir à partir de leur site web), gcc, un éditeur normal et aussi en hexadécimal (j'utilise bvi). Vous utiliserez aussi XCode/Interface Builder ou "class-dump" de Steve Nygard et le "otool" de Apple. Je ne suis pas un expert du sujet - ma connaissance est un agréga de temps passés à travaillé dans ce domaine sous Windows, puis Linux et maintenant OSX. Je suis sûr qu'il y a plein de choses dans cet article qui pourraient être faites plus correctement / efficacement / facilement, et si vous savez lesquelles et comment, écrivez moi qu'on puisse en discuter ! Cet article est aussi fortement redevable des excellentes suggestion et le très bon travail de Christian Klein de chez les Teenage Mutant Hero Coders. J'ai eu un très mauvais moment à passer à me demander si je publiais cet article anonymement ou pas. Récement, mon pays a mis en vigeur (ou menacé de le faire) des lois du style DMCA qui représente un risque substanciel aux style d'explorations et de recherches que ce document représente - l'exploration et la recherche qui ont des applications académiques et privées. Je pense que je n'ai enfreind aucune loi en créant ce document, mais le système juridique n'y va pas toujours par le dos de la cuillère. Merci de votre intéret, --[ 2.0 - La cible La cible est un client shareware pour SFTP et FTP, que j'ai découvert la première fois après la controverse sur l'exécution automatique sur ftp il y a quelques années (voir - ). Par respect envers les auteurs, je ne donnerai pas son nom explicite, et la version analysée ici est obsolette. --[ 3.0 - Copie de l'attaque La première étape est de pousser le programme à nous montrer le comportement indésirable que nous voulons altérer, pour savoir vers quoi regarder et quoi changer. D'arpès la documentation, j'ai vu que j'avais quinzes jours d'utilisation avant que le programme ne commence à me montre son statu de shareware - après cette période, je ne pourrai plus utiliser le menu Favrites et les sessions seront limitées en temps. Comme je ne voulais pas attendre deux semaines, j'ai supprimé les préférences du programme dans ~/Library/Application Support/, et configuré l'horloge un an plus tôt. J'ai lancé le logiciel, quitté et remis l'horloge normalement. Maintenant, quand j'essaye de lancer le programme, j'ai un message d'expiration, et les sanctions mentionnées plus haut prennent effet. Maintenant, nous devons décider de l'endroit où mettre l'incision initiale dans le programme. Commencer au main() ou sur NSApplicationMain() (où les programment Cocoa "commencent") n'est pas toujours faisable dans la majorité des programmes basés objects et dirigés par événements qui sont devenu la norme dans le développement Cocoa, donc, voici où j'en suis parvenu après quelques faut départs. Une approche est de l'attaquer depuis l'Interface. Si vous jetez un coup d'oeuil dans le paquetage de l'application (le fichier .app - en fait, un répertoire), vous devriez trouver un paquet de fichiers nib qui spécifient l'interface utilisateur. J'ai trouvé un fichier nib pour la boite de dialogue d'enregistrement, je l'ai ouvert dans Interface Builder. En regardant les actions référencant là bas, nous trouvont quelque chose comme un IBAction "validateRegistration:" attaché à l'objet "RegistrationController". Ça semble être un bon endroit pour commencé, mais si les développeurs sont dans mon genre, ils ne laisseront pas leur classes en IB [NDT : Interface Builder], et le nom réel de la classe serait différent. Si vous avez eu la malchance de ne pas trouvé de fichier nib utile, ne désespérez pas. Si vous avez un copieur de classes sous la main, lancez-le sur l'exécutable mach-o actuel (souvent, dans .app/Contents/MacOS/), et il va tenter de créer la déclaration des classes du programme. Regardez de ce côté pour un bon candidat. Maintenant que nous avons des idées d'où commencer, lançons GDB et regardons-y de plus près. Lancez GDB sur l'exécutable mach-o. Une fois chargé, cherchons après le nom de fonction que nous avons découvert. Si vous n'avez toujours pas trouvé de nom de fonction intéressant (parce qu'il n'y a pas de fichier nib ou de copieur de classes), vous pouvez simplement lancer "info fun" pour avoir la liste des fonctions que GDB peut indexer dans le programme. | (gdb) info fun validateRegistration | All functions matching regular expression "validateRegistration": | Non-debugging symbols: | 0x00051830 -[StateController validateRegistration:] "StateController" pourrait apparître être le nom interne de l'objet de contrôle d'enregistrement mentionné plus tôt. Voyons quelles méthodes y sont rattachées : | (gdb) info fun StateController | All functions matching regular expression "StateController": | Non-debugging symbols: | 0x0005090c -[StateController init] | 0x00050970 +[StateController sharedInstance] | 0x000509f8 -[StateController appDidLaunch] | 0x00050e48 -[StateController cancelRegistration:] | 0x00050e8c -[StateController findLostNumber:] | 0x00050efc -[StateController state] | 0x00050fd0 -[StateController validState] | 0x00051128 -[StateController saveState:] | 0x000512e0 -[StateController appendState:] | 0x00051600 -[StateController initState] | 0x0005165c -[StateController stateDidChange:] | 0x00051830 -[StateController validateRegistration:] | 0x00051bd8 -[StateController windowDidLoad] "validState", n'ayant pas d'arguments (pas de ':' à la fin) semble très prometeur. Y mettre un point d'arret et lancerle programme nous montre qu'elle est appellée deux fois au démarrage, et deux fois quand on essaye de changer l'état d'enregistrement - ce qui semble logique puisqu'il y a deux sanctions possibles pour les copies expirées comme expliqué plus haut. Creusons un peut plus profond dans cette fonction. Voici un code assembleur partiellement commenté - j'ai essayé de rendre tout ça lisible sur 75 colonnes, mais qui peut savoir ;). Je fourni ceci principalement pour ceux qui ne sont pas familier de l'assembleur PPC, et c'est résumé à la fin. (gdb) disass 0x50fd0 Dump of assembler code for function -[StateController validState]: 0x00050fd0 <-[StateController validState]+0>: mflr r0 # Copie le link register vers r0. 0x00050fd4 <-[StateController validState]+4>: stmw r27,-20(r1) # Stocke r27, r28, r29, r30 et r31 dans cinq mots consécutifs # commencant à r1 - 20 ( 0xbfffe2bc ). 0x00050fd8 <-[StateController validState]+8>: addis r4,r12,4 # r4 = r12 + 4 || 16(0) # # || = "concaténé", dans notre cas avec seize zéros. # ça a l'effet de déplacer le "quatre" ( 100B ) # vers les seizes hauts bits du registre. 0x00050fdc <-[StateController validState]+12>: stw r0,8(r1) # Écrit r0 vers r1 + 8. 0x00050fe0 <-[StateController validState]+16>: mr r29,r3 # Copie r3 vers r29. À ce moment, il devrait contenir # l'adresse de l'objet qu'on va invoquer # ( une instance de StateController ). 0x00050fe4 <-[StateController validState]+20>: addis r3,r12,4 # comme 0x50fd8, mais vers r3. 0x00050fe8 <-[StateController validState]+24>: stwu r1,-96(r1) # Store Word With Update: # "address" = r1 - 96 # stocke r1 vers "address" # r1 = "address" 0x00050fec <-[StateController validState]+28>: mr r31,r12 # Copie r12 vers r31. 0x00050ff0 <-[StateController validState]+32>: lwz r4,1620(r4) # Charge dans r4 le contenu de l'adresse r4 + 1620 (0x91624) # r4 contient maintenant 0x908980C= chaine c, "sharedInstance". 0x00050ff4 <-[StateController validState]+36>: lwz r3,5944(r3) # Charge dans r3, le contenu de l'adresse r3 + 5944 ( 0x92708 ). # r3 contient maintenant 0x92b20 = objet obj, se décrivant # lui-même comme "Preferences". # # Il semble que c'est une instance de l'api preference # noncodumentée utilisée par mail et safari. tut tut. 0x00050ff8 <-[StateController validState]+40>: bl 0x739d0 # r3 = [ Preferences sharedInstance ]; # (gdb) po $r3 # 0x00050ffc <-[StateController validState]+44>: lwz r0,40(r29) # charge r29 + 40 dans r0. Comme vous vous rappelez, r29 # a été mis à 0x50fe0 pour être l'instance de StateController. # Donc, cet offset réfèreà certaine variable de l'instance. # # Dans notre cas, sa valeur est null. Je suppose que sa valeur # n'a pas encore été assignée. Ma théroie est que cette fonction # va être invoquée plusieurs fois sur le même objet et que ceci, # la première fois qu'on y passe, fera l'initialisation. 0x00051000 <-[StateController validState]+48>: mr r27,r3 # Copie l'instance partagée (ici référée en tant que prefObject) # retournée à 0x50ff8 vers r27. 0x00051004 <-[StateController validState]+52>: cmpwi cr7,r0,0 # Compare r0 (la première variable d'instance (ici SC:1)) # avec null, stocke le résultat. # # (gdb) print /t $cr # = 100100000000000100001001000010 # # cr7 occupe l'offset 21-24, 001B ("equal"). # Les CR peuvent contenir 100B ("plus grand"), 010B # ("plus petit") ou 001B ("agal"). 0x00051008 <-[StateController validState]+56>: bne+ cr7,0x51030 <-[StateController validState]+96> # Saute vers +96 si le bits d'égalité de cr7 n'est pas placé. # Il l'est donc, on continue. 0x0005100c <-[StateController validState]+60>: addis r4,r31,4 # Comme 0x50fd8, mais vers r4. Notez que r31 est la nouvelle # adresse de l'adresse r12 utilisée dans les deux instances. # Je dirait bien que r31 contien le début de la table qui # qui liste les noms de messages disponibles dans ce programme. 0x00051010 <-[StateController validState]+64>: lwz r4,5168(r4) # Charge r4+5168 vers r4. Ça apparaît être une chaine c, # "firstLaunch". 0x00051014 <-[StateController validState]+68>: bl 0x739d0 # r3 = [ prefObject firstLaunch ]; # Il apparaît que c'est un objet NSDate, dans notre cas # 2003-09-19 23:30:10 +1000. Nous en parleront en tant que # firstLaunchDate. 0x00051018 <-[StateController validState]+72>: cmpwi cr7,r3,0 # Compare firstLaunchDate avec null, le résultat vers cr7. 0x0005101c <-[StateController validState]+76>: stw r3,40(r29) # Enregistre r3 (firstLaunchDate) vers r29+40 - vous vous en # souviendrez comme étant le début de la variable locale de # StateController référée à 0x50ffc, SC:1. 0x00051020 <-[StateController validState]+80>: beq+ cr7,0x51030 <-[StateController validState]+96> # Si le bits d'égalité est mis, saut à +96 - même endroit # que pour 0x51008 pour les chargements réussis. # Ce n'est pas ce que j'avais prévu. 0x00051024 <-[StateController validState]+84>: addis r4,r31,4 0x00051028 <-[StateController validState]+88>: lwz r4,2472(r4) # Comme nous avons tout fait pour chargé sans problème, # nous tombons ici - charger la table des messages # et la chaine "retain". 0x0005102c <-[StateController validState]+92>: bl 0x739d0 # firstLaunchDate = [ firstLaunchDate retain ]; 0x00051030 <-[StateController validState]+96>: lwz r3,40(r29) # C'est ici que les chemins divergents se rejoignent - # charger r3 avec le SC:1. 0x00051034 <-[StateController validState]+100>: cmpwi cr7,r3,0 0x00051038 <-[StateController validState]+104>: beq+ cr7,0x51070 <-[StateController validState]+160> # vérification pour voir que ce n'est pas null, et si c'est le # cas, saute à +160. Ceci prend en compte le cas où # nous sautions depuis 0x51020 - ça aurait eu plus de sens # si ça avait été fait directement. 0x0005103c <-[StateController validState]+108>: addis r4,r31,4 0x00051040 <-[StateController validState]+112>: lwz r4,4976(r4) # Charge la table des messages et la chaine # "timeIntervalSinceNow". 0x00051044 <-[StateController validState]+116>: bl 0x739d0 # r3 = [ firstLaunchDate timeIntervalSinceNow ]; # # Ce message retourne quelque chose du genre NSTimeInterval, # qui est un double. Donc, la fonction retourne dans r1 # au lieu de l'usuel r3. Dans mon cas, le résultat est : # (gdb) print $f1 # = -31790371.620961875 # (gdb) print $f1/60/60/24 # = -367.944115983355 # # Ceci concorde avec ce qu'on a prévu au début. 0x00051048 <-[StateController validState]+120>: addis r2,r31,3 # Je ne suis pas sûr de ce qu'il y a à r31 + 3 || 0x0000. # Ce n'est pas la table des messages, et r2 et d'habitude # réservé pour RTOC. 0x0005104c <-[StateController validState]+124>: lfd f0,26880(r2) # Charge le double de r2 + 26880 vers f0. r2 est peut-être # une ctable de onstantes. Il est apparu que c'était # un bon gros zéro. 0x00051050 <-[StateController validState]+128>: fcmpu cr7,f1,f0 0x00051054 <-[StateController validState]+132>: ble+ cr7,0x51070 <-[StateController validState]+160> # Compare le temps entre la première invocation avec zéro, # et s'il est plus petit (ça le devrais, à moins que la première # invocation soit dans le future). nous sautons à +160. 0x00051058 <-[StateController validState]+136>: addis r4,r31,4 0x0005105c <-[StateController validState]+140>: lwz r3,40(r29) 0x00051060 <-[StateController validState]+144>: lwz r4,1836(r4) 0x00051064 <-[StateController validState]+148>: bl 0x739d0 0x00051068 <-[StateController validState]+152>: li r0,0 0x0005106c <-[StateController validState]+156>: stw r0,40(r29) 0x00051070 <-[StateController validState]+160>: lwz r0,40(r29) # Charge notre SC:1, jamais absent, dans r0. 0x00051074 <-[StateController validState]+164>: addis r2,r31,4 0x00051078 <-[StateController validState]+168>: addis r28,r31,4 # Charge les symboles de message dans r2 et r28. 0x0005107c <-[StateController validState]+172>: lwz r3,44(r29) # Charge une autre variable d'instance du StateController - # celui-ci est 4 plus loins, à +44. Nousl'appeleront SC:2. # # Il est apparu que c'est un autre NSDate, celui-ci est # "2004-09-21 21:55:27 +1000", le moment où j'ai chargé la session # gdb courante. 0x00051080 <-[StateController validState]+176>: addis r30,r31,4 # Charge les symboles de message dans r30. 0x00051084 <-[StateController validState]+180>: cmpwi cr7,r0,0 0x00051088 <-[StateController validState]+184>: bne- cr7,0x510cc <-[StateController validState]+252> # Compare SC:1 à 0, si ce n'est pas égal, saute à +252. # Ce que nous faisons. 0x0005108c <-[StateController validState]+188>: lwz r4,5172(r2) 0x00051090 <-[StateController validState]+192>: bl 0x739d0 0x00051094 <-[StateController validState]+196>: lwz r4,1504(r30) 0x00051098 <-[StateController validState]+200>: lwz r3,5924(r28) 0x0005109c <-[StateController validState]+204>: bl 0x739d0 0x000510a0 <-[StateController validState]+208>: stw r3,40(r29) 0x000510a4 <-[StateController validState]+212>: addis r4,r31,4 0x000510a8 <-[StateController validState]+216>: lwz r4,2472(r4) 0x000510ac <-[StateController validState]+220>: bl 0x739d0 0x000510b0 <-[StateController validState]+224>: lwz r5,40(r29) 0x000510b4 <-[StateController validState]+228>: mr r3,r27 0x000510b8 <-[StateController validState]+232>: addis r4,r31,4 0x000510bc <-[StateController validState]+236>: lwz r4,5176(r4) 0x000510c0 <-[StateController validState]+240>: bl 0x739d0 0x000510c4 <-[StateController validState]+244>: li r3,1 0x000510c8 <-[StateController validState]+248>: b 0x51114 <-[StateController validState]+324> 0x000510cc <-[StateController validState]+252>: lwz r4,5172(r2) # Charge r4 avec r2 + 5272. r2 a toujours la table de symboles # de 0x51074. La chaine est "timeIntervalSince1970". 0x000510d0 <-[StateController validState]+256>: bl 0x739d0 # r3 contient toujours SC:2 depuis 0x5107c, quand l'instance # a été lancée. # # f1 = [ SC:2 timeIntervalSince1970 ]; # f1 = 1095767727.4292047 # f1/60/60/24/365 = 34.746566699302541 0x000510d4 <-[StateController validState]+260>: lwz r4,1504(r30) # r30 contient toujours la table des symboles. # r4 récupère "dateWithTimeIntervalSince1970:" 0x000510d8 <-[StateController validState]+264>: lwz r3,5924(r28) # La dernière fois que j'ai vu r28, il contenait la table # des messages; mais +5924 semble contenis l'objet de classe # NSDate. 0x000510dc <-[StateController validState]+268>: bl 0x739d0 # r3 = [ NSDate dateWithTimeIntervalSince1970: $f1 ] # Puisque le premier argument est un flottant, qu'il a récupéré # de f1 - qui a toujours le nombre de secondes depuis 1970 # à l'invocation en 0x510d0. # On fini avec une copie exacte de SC:2. Nous l'appeleront # thisLaunchDate. 0x000510e0 <-[StateController validState]+272>: addis r4,r31,4 # Charge la table de messages dans r4. 0x000510e4 <-[StateController validState]+276>: mr r29,r3 # Copie r3 vers r29. 0x000510e8 <-[StateController validState]+280>: mr r3,r27 # Copie r27 vers r3. La dernière fois, à 0x51000, il # contenait l'objet partagé prefs. 0x000510ec <-[StateController validState]+284>: lwz r4,5168(r4) # Charge la chaine "firstLaunch" dans r4. 0x000510f0 <-[StateController validState]+288>: bl 0x739d0 # r3 = [ prefObject firstLaunch ]; # Comme vu en 0x51014, La valeur sortie ici est stockée plus tard # dans SC:1. 0x000510f4 <-[StateController validState]+292>: addis r4,r31,4 # Charge la table de messages dans r4. 0x000510f8 <-[StateController validState]+296>: mr r5,r3 # Déplace le NSDate retourné de prefObject vers r8 # (deuxième argument). 0x000510fc <-[StateController validState]+300>: mr r3,r29 # Copie r29 vers r3 - r29 a le NSDate reconstitué # 'thisLaunchDate' de 0x510dc. 0x00051100 <-[StateController validState]+304>: lwz r4,3456(r4) # Charge "isEqualToDate:" dans r4. 0x00051104 <-[StateController validState]+308>: bl 0x739d0 # r3 = [ thisLaunchDate isEqualToDate: firstLaunchDate ]; # Ça va être un bon gros zéro à moins que ça ne soit la première # fois que vous lancez le programme. 0x00051108 <-[StateController validState]+312>: addic r2,r3,-1 # r2 = r3 - 1 avec les drapeaux. # r2 sera mis au maximum. # XER = 100B. 0x0005110c <-[StateController validState]+316>: subfe r0,r2,r3 # subfe r0, r2, r3 = !( r2 + r3 + XER[ carry bit ] ) # = !( max + 0 + 0 ) # = !( max ) # = 0 0x00051110 <-[StateController validState]+320>: mr r3,r0 # Déplace r0 vers r3 - Le résultat de la fonction. 0x00051114 <-[StateController validState]+324>: lwz r0,104(r1) 0x00051118 <-[StateController validState]+328>: addi r1,r1,96 0x0005111c <-[StateController validState]+332>: lwz r27,-20(r1) 0x00051120 <-[StateController validState]+336>: mtlr r0 0x00051124 <-[StateController validState]+340>: blr # Fait un peu de ménage et retourne. Pour la plus grande # part, nous rechargeons ces mots et les mettons en mémoire # et dans le link register que nous avons stocké dans le prélude. Fin de la copie d'assembleur. Ok, en bref, il semble que validState fait des choses différentes que son nom l'indiquait - il vérifie si c'est la première fois qu'on lance le logiciel, initialise certaines structures, etc. S'il retourne un, une boite de dialogue vous demandant de rejoindre la mailing list de la compagnie est affiché. Donc, ce n'est pas ce que nous avions prévu, mais ce n'est pas une perte de temps - nous avons découvert deux morceaux d'informations très utiles - l'endroit où est la date de premier lancement (StateController + 40) et l'endroit de la date courante (StateController + 44). Ceci devrait être correctement mis après la première invocation à cette fonction. Ces deux informations sont la clef pour déterminer si le soft a expiré ou pas. Ici, nous avons une paire de solution. Connaissant l'offset pour ces données, nous pouvont essayer de trouver le code qui les vérifie pour voir si le délai est dépassé, ou nous pouvons essayer d'interpreter le processus d'initialisation et de manipuler les données chargée pour permettre à l'utilisateur de toujours être dans la version d'essai. Comme ça serait parfaitement suffisent, nous allons l'essayer - une discussion d'autres voies possibles pourrait avoir lieu pour des travaux intéressant ou un futur article. --[ 4.0 - Solutions et Patche Une méthode possible serait d'écraser le contenu de StateController + 40 aveccelui de StateController + 44 (mettant la date de premier lancement à celle du lancement courant) et et d'ensuite, retourner zéro, laissant seul le code qui s'occupe de l'api des preferences. À cause de la méthodologie orienté objet du développement Cocoa, les chances que d'autres fonctions deviennent folles et qu'elles sautent vers d'autres endroits du programme sont proches de zéro, et nous pouvons laisser tel quel. Une fonction de remplacement proposée : Obtenir un registre que nous pourions utiliser. Charge le StateController +44 dedans, écrire ce registre vers StateController +40, libérer le registre, mettre r3 à zéro, retourner. L'écriture est faite comme ceci parce qu'on ne peut pas écrire depuis la mémoire vers la mémoire en assembleur PPC. +----- | stw r31, -20(r1) | lwz r31, 44(r3) | stw r31, 40(r3) | lwz r31, -20(r1) | xor r3, r3, r3 | blr +----- Au lieu de consulter les références aux instructions de l'instruction à la main, je vais faire facile et utiliser GCC. Coller le code dans un fichier comme suit : newfunc.s: ----- .text .globl _main _main: stw r31, -20(r1) lwz r31, 44(r3) stw r31, 40(r3) lwz r31, -20(r1) xor r3, r3, r3 blr ----- Le compiler comme suit : `gcc newfunc.s -o temp`, et le charger dans gdb : | (gdb) x/15i main | 0x1dec
: stw r31,-20(r1) | 0x1df0 : lwz r31,44(r3) | 0x1df4 : stw r31,40(r3) | 0x1df8 : lwz r31,-20(r1) | 0x1dfc : xor r3,r3,r3 | 0x1e00 : blr | 0x1e04 : mflr r0 Nous voulons voir le code machine pour 24 instructions après le
. | (gdb) x/24xb main | 0x1dec
: | 0x93 0xe1 0xff 0xec 0x83 0xe3 0x00 0x2c | 0x1df4 : | 0x93 0xe3 0x00 0x28 0x83 0xe1 0xff 0xec | 0x1dfc : | 0x7c 0x63 0x1a 0x78 0x4e 0x80 0x00 0x20 Maintenannt que nous avons le code machine, nous devons le coller dans notre exécutable. GDB est (en théorie) capable de patcher les fichier directement, mais c'est un peut plus difficile qu'il n'y parait. (voir Annexe B pour les détails). La bonne nouvelle est que, trouver le bon offset pour patcher le fichier lui-même n'est pas difficile. D'abord, noter l'offset du code qu'on veut rempacer, tel qu'il apparait dans GDB. (Dans notre cas, 0x50fd0.) Maintenant, faites ce qui suit : | (gdb) info sym 0x50fd0 | [StateController validState] in section LC_SEGMENT.__TEXT.__text | of Un fois qu'on sait dans quel segment tombe le code (__TEXT.__text), on peut poursuivre. Lancez "otool -l" sur votre binaire et cherchez quelque chose comme ceci (pris d'un exécutable différent malheureusement) : | Section | sectname __text | segname __TEXT | addr 0x0000236c | size 0x000009a8 | offset 4972 | align 2^2 (4) | reloff 0 | nreloc 0 | flags 0x80000400 | reserved1 0 | reserved2 0 L'offset de votre code dans le fichier est égal à l'adresse du code en mémoire, moins l'entrée "addr", plus l'entrée "offset". Gardez à l'esprit que "addr" est en hexa et que l'offset ne l'est pas ! Maintenant, vous pouvez juste écraser le code dans un éditeur hexa. Suavegardez et lancez le programme. Ça a marché du premier coup chez moi ! --[ A - GDB, OSX, PPC & Cocoa - Quelques observations. Conventions d'appel : Quand on gère un appel, les registres 0, 1 et 2 stockent des informations importantes de nettoyage. Il ne faut pas les bidouiller à moins que vous ne restauriez leurs valeurs après la bidouille. Les arguments de fonctions commencent à r3 et la valeur de retour est dans r3 aussi. Sauf pour des trucs come les flottants que vous devriez trouver dans r1, etc. Une des choses qui font que cracker les applications OSX est si chouette est l'énorme confiance dans les interfaces élégement orientées objet, et l'utilisation correspondante énorme des messages. Souvent, en désassemblant, vous tomberez sur des branches vers . C'est une reformulation d'une conventiond'appel typiquye : | [ anObject aMessage: anArgument andA: notherArgument ]; Vers quelque chose comme ceci : | objc_msgSend( anObject, "aMessage:andA:", anArgument, notherArgument ); Donc, l'objet qui recoit va occuper r3, le sélecteur sera un chaine de caractère en r4 et les arguments occuperont l'avant de r5. Comme r4 contiendra une chaine, interroger par "x/s $r4", si c'est un objet, "po $r3", et pour le type des arguments, je recommande de consulter la documetation xcode quand elle est disponible. "po" est le racourci pour invoquer les méthodes de description sur un ojbet cible. Integration GDB : Grace à l'excellent suppor d'Objective C dans GDB, nous pouvons non seulement mettre des points d'arret sur les fonctions en utilisant leur tableau de nom, mais aussi d'invoquer directemetnles péthodes comme suit : si r5 contient un pointeur vers un objet NSString, la suite est assez juste : | (gdb) print ( char * ) [ $r5 cString ] | = 0x833c8 " trn" Très utile. N'oubliez pas que c'est disponible si vous voulez tester le comportement de certaines fonctions sur certaines entrées. --[ B - Pourquoi ne pouvons-nous par patcher directement avec GDB ? Comme certains d'entre vous le savent sûrement, GDb peut, e nprincipe, écrire des changement dans le coeur du fichier exécutable. Ce n'est en fait pas très partique dans notre scénario et je vais vous expliquer pourquoi. D'abord, les binaires Mach-O ont une protection mémoire. Si vous voulez écraser une partie du segment __TEXT.__text, vous allez devoir remetre ses permissions. Christian Klein a écrit un programme pour le faire (voir ). Vous pouvez aussi, une fois que le programme est lancé, et a un espace d'exécution, faire la chose suivante : | (gdb) print (int)mprotect(
, , 0x1|0x2|0x4 ) Cependant, quand c'est fait, ça vous autorise seulement à écrire dans le processus en mémoire. Pour faire vraiment les changements sur le disque, vous devez soit invoquer GDB par 'gdb --write', ou exécuter : | (gdb) set write on | (gdb) exec-file Le problème est que OSX utilise les pages sur demande pour les exécutables. Ce que ça veut dire est que le programme entier n'est pas chargé en mémoire d'un coup - il est pris du disque quand c'est nécessaire. Comme résultat, vous n'allez pas pouvoir exécuter un fichier qui est ouvert en écriture. Le résultat est, si vous essayez de le faire, que si vous lancez le programme dans le débogueur, il crashe avec le message "Text file is busy". |=[ EOF ]=--------------------------------------------------------------=|