==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,
<curious@progsoc.org>
--[ 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 - <http://www.tidbits.com/tb-issues/
TidBITS-731.html#lnk4> ). 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
<nimportequoi>.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 <dyld_stub_objc_msgSend>
# r3 = [ Preferences sharedInstance ];
# (gdb) po $r3
# <Preferences: 0x10d6c0>
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 <dyld_stub_objc_msgSend>
# 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 <dyld_stub_objc_msgSend>
# 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 <dyld_stub_objc_msgSend>
# 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 <dyld_stub_objc_msgSend>
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 <dyld_stub_objc_msgSend>
0x00051094 <-[StateController validState]+196>: lwz r4,1504(r30)
0x00051098 <-[StateController validState]+200>: lwz r3,5924(r28)
0x0005109c <-[StateController validState]+204>:
bl 0x739d0 <dyld_stub_objc_msgSend>
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 <dyld_stub_objc_msgSend>
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 <dyld_stub_objc_msgSend>
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 <dyld_stub_objc_msgSend>
# 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 <dyld_stub_objc_msgSend>
# 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 <dyld_stub_objc_msgSend>
# 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 <dyld_stub_objc_msgSend>
# 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 <main>: stw r31,-20(r1)
| 0x1df0 <main+4>: lwz r31,44(r3)
| 0x1df4 <main+8>: stw r31,40(r3)
| 0x1df8 <main+12>: lwz r31,-20(r1)
| 0x1dfc <main+16>: xor r3,r3,r3
| 0x1e00 <main+20>: blr
| 0x1e04 <dyld_stub_exit>: mflr r0
Nous voulons voir le code machine pour 24 instructions après le <main>.
| (gdb) x/24xb main
| 0x1dec <main>:
| 0x93 0xe1 0xff 0xec 0x83 0xe3 0x00 0x2c
| 0x1df4 <main+8>:
| 0x93 0xe3 0x00 0x28 0x83 0xe1 0xff 0xec
| 0x1dfc <main+16>:
| 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 <executable name>
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
<dyld_stub_objc_msgSend>. 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
<http://blogs.23.nu/c0re/stories/7873/>). 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( <address>, <length>, 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 <filename>
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 ]=--------------------------------------------------------------=|