[ News ] [ Paper Feed ] [ Issues ] [ Authors ] [ Archives ] [ Contact ]


..[ Phrack Magazine ]..
.:: PowerPC Cracking on OSX with GDB ::.

Issues: [ 1 ] [ 2 ] [ 3 ] [ 4 ] [ 5 ] [ 6 ] [ 7 ] [ 8 ] [ 9 ] [ 10 ] [ 11 ] [ 12 ] [ 13 ] [ 14 ] [ 15 ] [ 16 ] [ 17 ] [ 18 ] [ 19 ] [ 20 ] [ 21 ] [ 22 ] [ 23 ] [ 24 ] [ 25 ] [ 26 ] [ 27 ] [ 28 ] [ 29 ] [ 30 ] [ 31 ] [ 32 ] [ 33 ] [ 34 ] [ 35 ] [ 36 ] [ 37 ] [ 38 ] [ 39 ] [ 40 ] [ 41 ] [ 42 ] [ 43 ] [ 44 ] [ 45 ] [ 46 ] [ 47 ] [ 48 ] [ 49 ] [ 50 ] [ 51 ] [ 52 ] [ 53 ] [ 54 ] [ 55 ] [ 56 ] [ 57 ] [ 58 ] [ 59 ] [ 60 ] [ 61 ] [ 62 ] [ 63 ] [ 64 ] [ 65 ] [ 66 ] [ 67 ] [ 68 ] [ 69 ] [ 70 ] [ 71 ]
Current issue : #63 | Release date : 2005-01-08 | Editor : Phrack Staff
IntroductionPhrack Staff
LoopbackPhrack Staff
LinenoisePhrack Staff
Phrack Prophile on TiagoPhrack Staff
OSX heap exploitation techniquesNemo
Hacking Windows CE (pocketpcs & others)San
Games with kernel Memory...FreeBSD Stylejkong
Raising The Bar For Windows Rootkit Detectionsherri sparks & jamie butler
Embedded ELF DebuggingELFsh crew
Hacking Grub for Fun & Profitcoolq
Advanced antiforensics : SELFripe & pluf
Process Dump and Binary Reconstructionilo
Next-Gen. Runtime Binary Encryptionzvrba
Shifting the Stack Pointerandrewg
NT Shellcode Prevention Demystifiedpiotr
PowerPC Cracking on OSX with GDBcurious
Hacking with Embedded Systemscawan
Process Hiding & The Linux Schedulerubra
Breaking Through a Firewallkotkrye
Phrack World NewsPhrack Staff
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 ]=---------------------------------------------------------------=|

[ News ] [ Paper Feed ] [ Issues ] [ Authors ] [ Archives ] [ Contact ]
© Copyleft 1985-2024, Phrack Magazine.