Title : PowerPC Cracking on OSX with GDB
Author : curious
==Phrack Inc.==
Volume 0x0b, Issue 0x3f, Phile #0x10 of 0x14
|=-----=[ Reverse engineering - PowerPC Cracking on OSX with GDB ]=------=|
|=-----------------------------------------------------------------------=|
|=--------------------------=[ curious ]=--------------------------------=|
|=-----------------------------------------------------------------------=|
--[ Contents
1.0 - Introduction
2.0 - The Target
3.0 - Attack Transcript
4.0 - Solutions and Patching
A - GDB, OSX, PPC & Cocoa - Some observations.
B - Why can't we just patch with GDB?
--[ 1.0 - Introduction
This article is a guide to taking apart OSX applications and
reprogramming their inner structures to behave differently to their
original designs. This will be explored while uncrippling a
shareware program. While the topic will be tackled step by step,
I encourage you to go out and try these things for yourself, on your
own programs, instead of just slavishly repeating what you read here.
This technique has other important applications, including writing
patches for closed source software where the company has gone out of
business or is not interested, malware analysis and fixing incorrectly
compiled programs.
It is assumed you have a little rudimentary knowledge in this area
already - perhaps you have some assembly programming or you have some
cracking experience on Windows or Linux. Hopefully you'll at least
know a little bit about assembly language - what it is, and how it
basically works ( what a register is, what a relative jump is, etc. )
If you've never worked with PowerPC assembly on OSX before, you might
want to have a look at appendix A before we set off. If you have some
basic familiarity with GDB, it will also be very useful.
This tutorial uses the following tools and resources - the XCode
Cocoa Documentation, which is included with the OSX developer tools,
a PowerPC assembly reference ( I recommend IBM's "PowerPC Microprocessor
Family: The Programming Environments for 32-Bit Microprocessors" - you
can get it off their website ), gcc, an editor and a hexeditor ( I use
bvi ). You'll also be using either XCode/Interface Builder or Steve
Nygard's "class-dump" and Apple's "otool".
I'm no expert on this subject - my knowledge is cobbled
together from time spent working in this area with Windows, then Linux and
now OSX. I'm sure there's lots in this article that could be done more
correctly / efficiently / easily, and if you know, please write to me and
discuss it! Already this article is seriously indebted to the excellent
suggestions and hard work of Christian Klein of Teenage Mutant Hero Coders.
I had a very hard time deciding whether or not to publish this article
anonymously. Recently, my country has enacted ( or threatened to enact )
DMCA style laws that represent a substantial threat to the kinds of
exploration and research that this document represents - exploration and
research which have important academic and corporate applications. I
believe that I have not broken any laws in authoring this document,
but the justice system can paint with a broad brush sometimes.
Thanks for reading,
<[email protected]>
--[ 2.0 - The Target
The target is a shareware client for SFTP and FTP, which I was first
exposed to after the automatic ftp execution controversy a few years ago
( see - <http://www.tidbits.com/tb-issues/TidBITS-731.html#lnk4> ). Out
of respect for the authors, I'm not going to name it explicitly, and
the version analysed is now deprecated.
--[ 3.0 - Attack Transcript
The first step is to prompt the program to display the undesirable
behavior we wish to alter, so we know what to look out for and change.
From reading the documentation, I know that I have fifteen days of usage
before the program will start to assert it's shareware status - after
that time period, I will be unable to use the Favourites menu, and
sessions will be time limited.
As I didn't want to wait around fifteen days, I deleted the program
preferences in ~/Library/Application Support/, and set the clock back
one year. I ran the software, quit, and then returned the clock to
normal. Now, when I attempt to run the software, I receive the expired
message, and the sanctions mentioned above take effect.
Now we need to decide where we are to make the initial incision
In the program. Starting at main() or even NSApplicationMain() (
which is where Cocoa programs 'begin' ) is not always feasible in the
large, object based and event driven programs that have become the norm
in Cocoa development, so here's what I've come up with after a few false
starts.
One approach is to attack it from the Interface. If you have a look
inside the application bundle ( the .app file - really a folder ), you'll
most likely find a collection of nib files that specify the user interface.
I found a nib file for the registration dialog, and opened it in Interface
Builder.
Inspecting the actions referred to there we find a promising sounding
IBAction "validateRegistration:" attached to a class
"RegistrationController". This sounds like a good place to start, but if
the developers are anything like me, they won't have really dragged their
classes into IB, and the real class names may be very different.
If you didn't have any luck finding a useful nib file, don't despair.
If you have class-dump handy, run it on the actual mach-o executable
( usually in <whatever>.app/Contents/MacOS/ ), and it will attempt to
form class declarations for the program. Have a look around there for
a likely candidate function.
Now that we have some ideas of where to start, let's fire up GDB
and look a bit closer. Start GDB on the mach-o executable. Once loaded,
let's search for the function name we discovered. If you still don't
have a function name to work with ( due to no nib files and no
class-dump ), you can just run "info fun" to get a list of functions
GDB can index in the program.
| (gdb) info fun validateRegistration
| All functions matching regular expression "validateRegistration":
| Non-debugging symbols:
| 0x00051830 -[StateController validateRegistration:]
"StateController" would appear to be the internal name for that
registration controlling object referred to earlier. Let's see
what methods are registered against it:
| (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", having no arguments ( no trailing ':' ) sounds very
promising. Placing a breakpoint on it and running the program shows
it's called twice on startup, and twice when attempting to possibly change
registration state - this seems logical, as there are two possible
sanctions for expired copies as discussed earlier. Let's dig a bit
deeper with this function.
Here's a commented partial disassembly - I've tried to bring it down
to something readable on 75 columns, but your mileage may vary. I'm
mainly providing this for those unfamiliar with PPC assembly, and it's
summarized at the end.
(gdb) disass 0x50fd0
Dump of assembler code for function -[StateController validState]:
0x00050fd0 <-[StateController validState]+0>: mflr r0
# Copy the link register to r0.
0x00050fd4 <-[StateController validState]+4>: stmw r27,-20(r1)
# Store r27, r28, r29, r30 and r31 in five consecutive words
# starting at r1 - 20 ( 0xbfffe2bc ).
0x00050fd8 <-[StateController validState]+8>: addis r4,r12,4
# r4 = r12 + 4 || 16(0)
#
# || = "concatenated", in this case with sixteen zeroes.
# this has the effect of shifting the "four" ( 100B )
# into the high sixteen of the register.
0x00050fdc <-[StateController validState]+12>: stw r0,8(r1)
# Write r0 to r1 + 8.
0x00050fe0 <-[StateController validState]+16>: mr r29,r3
# Copy r3 to r29. At the moment, this would contain
# the address of the object we're being invoked on
# ( a StateController instance ).
0x00050fe4 <-[StateController validState]+20>: addis r3,r12,4
# As 0x50fd8, but into r3.
0x00050fe8 <-[StateController validState]+24>: stwu r1,-96(r1)
# Store Word With Update:
# "address" = r1 - 96
# store r1 to "address"
# r1 = "address"
0x00050fec <-[StateController validState]+28>: mr r31,r12
# Copy r12 to r31.
0x00050ff0 <-[StateController validState]+32>: lwz r4,1620(r4)
# Load r4 with contents of memory address r4 + 1620 ( 0x91624 ).
# r4 now contains 0x908980CC = c string, "sharedInstance".
0x00050ff4 <-[StateController validState]+36>: lwz r3,5944(r3)
# Load r3 with contents of memory address r3 + 5944 ( 0x92708 ).
# r3 now contains 0x92b20 = objc object, describes itself as
# "Preferences".
#
# This seems to be an instance of the undocumented preferences
# api used by mail and 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)
# Load r29 + 40 into r0. As you may recall, r29 was set
# at 0x50fe0 to be the StateController instance. Hence
# this offset refers to some kind of instance variable.
#
# In this case, it's value is nil. Guess it hasn't been
# assigned yet. My theory is that this function will be
# invoked several times on the same object and this, the
# first run through, will do initialization.
0x00051000 <-[StateController validState]+48>: mr r27,r3
# Copy the shared instance ( herein reffered to as prefObject )
# returned in 0x50ff8 to r27.
0x00051004 <-[StateController validState]+52>: cmpwi cr7,r0,0
# Compare r0 ( the first instance variable ( herein SC:1 ) )
# with nil, store the result.
#
# (gdb) print /t $cr
# $19 = 100100000000000100001001000010
#
# cr7 occupies offset 21-24, 001B ( "equal" ).
# The CR's can contain 100B ( "higher" ), 010B ( "lower" )
# or 001B ( "equal" ).
0x00051008 <-[StateController validState]+56>:
bne+ cr7,0x51030 <-[StateController validState]+96>
# Jump to +96 if the equal bit of cr7 is not set.
# It is, so we just continue on.
0x0005100c <-[StateController validState]+60>: addis r4,r31,4
# As 0x50fd8, but into r4. Note that r31 is the new address
# of the r12 address used in both of those instances. I would
# say r31 contains the start of the table listing the
# message names available in this program.
0x00051010 <-[StateController validState]+64>: lwz r4,5168(r4)
# Load r4 + 5168 into r4. This turns out to be a c string,
# "firstLaunch".
0x00051014 <-[StateController validState]+68>:
bl 0x739d0 <dyld_stub_objc_msgSend>
# r3 = [ prefObject firstLaunch ];
# This turns out to be an NSDate object, in this case
# 2003-09-19 23:30:10 +1000. We'll refer to this as
# firstLaunchDate.
0x00051018 <-[StateController validState]+72>: cmpwi cr7,r3,0
# Compare firstLaunchDate with nil, results to cr7.
0x0005101c <-[StateController validState]+76>: stw r3,40(r29)
# Store r3 ( firstLaunchDate ) to r29 + 40 - you'll recall
# this as being the StateController local variable referred
# to 0x50ffc, SC:1.
0x00051020 <-[StateController validState]+80>:
beq+ cr7,0x51030 <-[StateController validState]+96>
# If the equal bit is set, jump to +96 - same location as
# at 0x51008 for successful loads. Not what I was expecting.
0x00051024 <-[StateController validState]+84>: addis r4,r31,4
0x00051028 <-[StateController validState]+88>: lwz r4,2472(r4)
# As we did manage to load successfully, we fall through to
# here - load the message table and the string "retain".
0x0005102c <-[StateController validState]+92>:
bl 0x739d0 <dyld_stub_objc_msgSend>
# firstLaunchDate = [ firstLaunchDate retain ];
0x00051030 <-[StateController validState]+96>: lwz r3,40(r29)
# Here's where the divergent paths rejoin - load r3 with
# the SC:1.
0x00051034 <-[StateController validState]+100>: cmpwi cr7,r3,0
0x00051038 <-[StateController validState]+104>:
beq+ cr7,0x51070 <-[StateController validState]+160>
# Check to see if it's nil, and if so, jump out to +160.
# This would catch the case where we jumped from 0x51020 -
# would have seemed to make more sense to jump directly.
0x0005103c <-[StateController validState]+108>: addis r4,r31,4
0x00051040 <-[StateController validState]+112>: lwz r4,4976(r4)
# Load the message table and the string "timeIntervalSinceNow".
0x00051044 <-[StateController validState]+116>:
bl 0x739d0 <dyld_stub_objc_msgSend>
# r3 = [ firstLaunchDate timeIntervalSinceNow ];
#
# This message returns as an NSTimeInterval, which is a double.
# As a result, the function returns to f1 instead of the usual
# r3. The result in my case is:
# (gdb) print $f1
# $21 = -31790371.620961875
# (gdb) print $f1/60/60/24
# $22 = -367.944115983355
#
# This seems as expected from what we did at the beginning.
0x00051048 <-[StateController validState]+120>: addis r2,r31,3
# Not sure what's at r31 + 3 || 0x0000. It's not the message
# symbol table, and r2 is usually reserved for RTOC.
0x0005104c <-[StateController validState]+124>: lfd f0,26880(r2)
# Load double at r2 + 26880 into f0. Perhaps r2 is a constants
# table. It ends up being a big fat zero.
0x00051050 <-[StateController validState]+128>: fcmpu cr7,f1,f0
0x00051054 <-[StateController validState]+132>:
ble+ cr7,0x51070 <-[StateController validState]+160>
# Compare the time between first invocation and now with zero,
# if it's less ( and it should be, unless the first invocation
# was in the future! ) we jump to +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)
# Load our ever present SC:1 into r0.
0x00051074 <-[StateController validState]+164>: addis r2,r31,4
0x00051078 <-[StateController validState]+168>: addis r28,r31,4
# Load the message symbols into both r2 and r28.
0x0005107c <-[StateController validState]+172>: lwz r3,44(r29)
# Load another instance variable on the StateController - this
# one is 4 more along, at +44. We'll tag it as SC:2.
#
# It turns out to be another NSDate, this one is
# "2004-09-21 21:55:27 +1000", the time I started the current
# gdb session.
0x00051080 <-[StateController validState]+176>: addis r30,r31,4
# Load the message symbols into r30.
0x00051084 <-[StateController validState]+180>: cmpwi cr7,r0,0
0x00051088 <-[StateController validState]+184>:
bne- cr7,0x510cc <-[StateController validState]+252>
# Compare SC:1 with 0, if it's not equal, jump to +252.
# Which we do.
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)
# Load r4 with r2 + 5172. r2 still has the message symbol
# table from 0x51074. The string is "timeIntervalSince1970".
0x000510d0 <-[StateController validState]+256>:
bl 0x739d0 <dyld_stub_objc_msgSend>
# r3 still contains SC:2 from 0x5107c, the time this instance was
# launched.
#
# f1 = [ SC:2 timeIntervalSince1970 ];
# f1 = 1095767727.4292047
# f1/60/60/24/365 = 34.746566699302541
0x000510d4 <-[StateController validState]+260>: lwz r4,1504(r30)
# r30 still has the message symbol table. r4 gets
# "dateWithTimeIntervalSince1970:"
0x000510d8 <-[StateController validState]+264>: lwz r3,5924(r28)
# Last I saw of r28, it had the message symbol table in it
# as well, but +5924 seems to contain the NSDate class object.
0x000510dc <-[StateController validState]+268>:
bl 0x739d0 <dyld_stub_objc_msgSend>
# r3 = [ NSDate dateWithTimeIntervalSince1970: $f1 ]
# Since the first argument is a float, it will draw from f1 -
# which still has the seconds since 1970 to current invocation
# from 0x510d0.
# We end up with an exact copy of SC:2. We'll call it
# thisLaunchDate.
0x000510e0 <-[StateController validState]+272>: addis r4,r31,4
# Load the message symbol table into r4.
0x000510e4 <-[StateController validState]+276>: mr r29,r3
# Copy r3 to r29.
0x000510e8 <-[StateController validState]+280>: mr r3,r27
# Copy r27 to r3. When last sighted at 0x51000, this
# held the prefs shared object.
0x000510ec <-[StateController validState]+284>: lwz r4,5168(r4)
# Load string "firstLaunch" to r4.
0x000510f0 <-[StateController validState]+288>:
bl 0x739d0 <dyld_stub_objc_msgSend>
# r3 = [ prefObject firstLaunch ];
# As seen at 0x51014, the value returned from here was later
# stored in SC:1.
0x000510f4 <-[StateController validState]+292>: addis r4,r31,4
# Load the message symbol table to r4.
0x000510f8 <-[StateController validState]+296>: mr r5,r3
# Move the NSDate just returned from prefObject to
# r5 ( second argument ).
0x000510fc <-[StateController validState]+300>: mr r3,r29
# Copy r29 to r3 - r29 had the reconstituted NSDate
# 'thisLaunchDate' from 0x510dc.
0x00051100 <-[StateController validState]+304>: lwz r4,3456(r4)
# Load "isEqualToDate:" into r4.
0x00051104 <-[StateController validState]+308>:
bl 0x739d0 <dyld_stub_objc_msgSend>
# r3 = [ thisLaunchDate isEqualToDate: firstLaunchDate ];
# That's going to be a big zero unless it's the first time
# you're running.
0x00051108 <-[StateController validState]+312>: addic r2,r3,-1
# r2 = r3 - 1 with carry flag.
# r2 will be set to max now.
# 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
# Move r0 to r3 - the function result.
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
# Various housekeeping and then return. For the most
# part, we reload those words we pushed into memory and
# the link register we stored in the opening moves.
End of assembler dump.
Ok, in summary, it seems validState does something different to what
it's name might indicate - it checks if it's the first time you've run
the program, initializes some data structures, etc. If it returns one,
a dialog box asking you to join the company email list is displayed.
So it's not what we thought, but it's not a waste of time - we've
uncovered two useful pieces of information - the location of the date of
first invocation ( StateController + 40 ) and the location of the date of
current invocation ( StateController + 44 ). These should all be set
correctly anytime after the first invocation of this function. These
two pieces of information are key to determining whether the software
has expired or not.
We have a couple of options here. Knowing the offset information of
this data, we can attempt to find the code that checks to see if the
trial is over, or we can attempt to intercept the initialization
process and manipulate the data loading to ensure that the user is
always within the trial window. As this would be perfectly sufficient,
we'll try that - a discussion of other avenues might make for interesting
homework or a future article.
--[ 4.0 - Solutions and Patching
A possible method will be to overwrite the contents of
StateController + 40 with StateController + 44 ( setting the
date the program was first run to the current date ) and then return
zero, leaving alone the code that deals with the preferences api. Due to
the object oriented methodology of Cocoa development, the chances of
some other function going crazy and performing a jump into the other parts
of the function are slim to nil, and so we can leave it as is.
A Proposed replacement function:
Obtain a register for us to use. Load the contents of StateController
+44 into it, write that register to StateController +40, release the
register, zero r3, return. The write is done like this as you cannot
write directly to memory from memory in PPC assembler.
+-----
| stw r31, -20(r1)
| lwz r31, 44(r3)
| stw r31, 40(r3)
| lwz r31, -20(r1)
| xor r3, r3, r3
| blr
+-----
Instead of consulting with the instruction reference to assemble it by
hand, I'm going to be cheap and use GCC. Paste the code into a file as
follows:
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
-----
Compile it as follows: `gcc newfunc.s -o temp`, and load it into 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
We want to see the machine code for 24 instructions post <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
Now that we have our assembled bytecode, we need to paste it into
our executable. GDB is ( in theory ) capable of patching the file
directly, but it's a bit more complicated than it might appear (
see Appendix B for details ).
The good news is, finding the correct offset for patching the file
itself is not difficult. First, note the offset of the code you wish
to replace, as it appears in GDB. ( In this case, that's 0x50fd0. ) Now,
do the following:
| (gdb) info sym 0x50fd0
| [StateController validState] in section LC_SEGMENT.__TEXT.__text
| of <executable name>
Armed with this knowledge of what segment the code falls in
( __TEXT.__text ), we can proceed. Run "otool -l" on your binary,
and search for something like this ( taken from a different executable,
unfortunately ):
| 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
The offset to your code in the file is equal to the address of the
code in memory, minus the "addr" entry, plus the "offset" entry. Keep
in mind that "addr" is in hex and offset is not! Now you can just
over-write the code as appropriate in your hex editor.
Save and then try and run the program. It worked for me first time!
--[ A - GDB, OSX, PPC & Cocoa - Some Observations.
Calling Convention:
When handling calls, registers 0, 1 and 2 store important housekeeping
information. They are not to be fucked with unless you carefully restore
their values post haste. Arguments to functions commence at r3, and
return values are stored at r3 as well. Except for stuff like floats,
which you might find coming back in f1, etc.
One of the things that makes OSX applications such a joy to crack is
the heavy reliance on neatly defined object oriented interfaces, and the
corresponding heavy use of messaging. Often in disassemblies you will
come across branches to <dyld_stub_objc_msgSend>. This is a reformulation
of the typical calling convention:
| [ anObject aMessage: anArgument andA: notherArgument ];
Into something like this:
| objc_msgSend( anObject, "aMessage:andA:", anArgument, notherArgument );
Hence, the receiving object will occupy r3, the selector will be a
plain string at r4, and subsequent arguments will occupy r5 onwards. As
r4 will contain a string, interrogate it with "x/s $r4", as the receiver
will be an object, "po $r3", and for the types of subsequent arguments, I
recommend you consult the xcode documentation where available. "po" is
shorthand for invoking the description methods on the receiving object.
GDB Integration:
Due to the excellent Objective C support in GDB, not only can we
breakpoint functions using their [] message nomenclature, but also
perform direct invocations of methods as such: if r5 contained a pointer
to an NSString object, the following is quite reasonable:
| (gdb) print ( char * ) [ $r5 cString ]
| $3 = 0x833c8 " \t\r\n"
Very useful. Don't forget that it's available if you want to test
how certain functions react to certain inputs.
-- [ B - Why can't we just patch with GDB?
As some of you probably know, GDB can, in principle, write changes
out to core and executable files. This is not really practical in
the scenario we're dealing with here, and I'll explain why.
First, Mach-O binaries have memory protection. If you're going to
overwrite parts of the __TEXT.__text segment, you're going to have
to reset it's permissions. Christian Klein has written a program to
do this ( see <http://blogs.23.nu/c0re/stories/7873/>. ) You can
also, once the program is running and has an execution space, do
things like:
| (gdb) print (int)mprotect( <address>, <length>, 0x1|0x2|0x4 )
However, even when this is done, this only lets you write to the
process in memory. To actually make changes to the disk copy, you
need to either invoke GDB as 'gdb --write', or execute:
| (gdb) set write on
| (gdb) exec-file <filename>
The problem is, OSX uses demand paging for executables.
What this means is that the entire program isn't loaded into memory
straight away - it's lifted off disk as needed. As a result, you're
not allowed to execute a file which is open for writing.
The upshot is, if you try and do it, as soon as you run the program
in the debugger, it crashes out with "Text file is busy".
|=[ EOF ]=---------------------------------------------------------------=|