mirror of
https://github.com/fdiskyou/Zines.git
synced 2025-03-09 00:00:00 +01:00
726 lines
28 KiB
Text
726 lines
28 KiB
Text
![]() |
==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,
|
||
|
<curious@progsoc.org>
|
||
|
|
||
|
--[ 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 ]=---------------------------------------------------------------=|
|
||
|
|