---=== PalmPilot reversing Tutorial
===---
By AY ;)
Preface:
Yesterday, a few friends of mine asked me how did I cracked a game they were playing with on their PalmPilot PDA. I tried to explain in general the steps needed in order to crack a program, but my explanations sounded too vague. Since it's late at night and I'm board, I decided to write a small tutorial explaining the general process of reversing a program, and give example on a specific target. This tutorial is targeted to people with no experience with reversing at all, or for people who are not familiar with PalmPilot reversing.
I assume that the reader is somehow familiar with how computers
works :)
You don't have to fully understand the structure of the Palm OS and the hardware
architecture of the PalmPilot, but familiarization with a basic CPU (did anyone
mentioned PDP11 ? :) and memory access can help.
General steps for cracking a program:
There are 2 main approaches for code reversing: dead-list and live. Live approach
means that we use a debugger to debug
the program while it's running (similar to what we do with high-level languages
like C++), and dead-list approach means
that we try to understand what the program is doing by looking at its assembler
code.
When dealing with Windows cracking, most of the time we use both a debugger
and a dead-list, but in the Palm environment, most of the time a debugger won't
be necessary.
Here we will deal only with dead-list approach, working with PalmDebugger requires
another tut :)
A note about assembler: when dealing with reverse code engineering, it is assumed
that we don't have the source code of our target (otherwise we would simply
change it and recompile). Since the only thing that we have is the executable
(a prc file in the case of PalmOS), all we can do is disassemble it and get
the program code in assembler. In our case, we deal with Motorola 68K assembler
(unlike Intel's x86 assembler), since the Palmpilot CPU is based on Motorola's
Draggonball series CPU (a variant of the 68K family).
We don't need to know by heart all the CPU instructions (although it helps),
and later I'll describe the most important.
Here are the general steps we follow when we want to understand the protection
mechanism and finally patch the program (change it's code in order to change
its behavior):
1. Run the program on the emulator to understand
its behavior and protection mechanism (function limitations, serial number,
expire date etc...). Try to force the program to expire by altering the system
date or by start it on and of several times.
At the end of step 1 you should know if the program is limited in any way, if
it is, what are the limitations and if there any way to remove those limitation
through the user interface (entering some serial number, installing additional
files etc...).
There is a small chance that the program is only a DEMO and not a TRIAL, meaning
that it is a crippled version (the full version is actually bigger, means it
has additional code not included in the demo version). In this case, we have
nothing to do with it, since we are not going to write the missing code by our
self.
2. Write down the resource IDs that interest us
(Alert or Forms).
3. Disassemble the program.
4. Try to locate the interesting part in the code
(dead-list) usually by searching for the "interesting" IDs or by searching
for suspected strings.
5. When you found the relevant code part - try
to understand what it does (here we finally need to use our brain).
6. Patch the file where you think it will fix what
we wanna fix (not before making a backup of the original file).
7. Test the patched file on the emulator (never
on the real device!)
8. If successful - write down what you did and
install the program to the real device
9. If not successful - restore the original file
and go back to step 5.
Structure of PalmOS executable (prc):
Before we go into details about each step, I'll shortly describe how a palm
program works.
Palm files are divided into resources. Those recourses can be Forms, Alert,
Bitmaps, Icons, Buttons and actually everything you see on the Palm screen.
Of course there is also the code section within the file as well as other data
sections.
The most common resources are:
Form - a "window". for example, whenever
you start a program, a new form is opened. A form contains form objects like
buttons, bitmap, labels, fields (text or numeric) etc... each object has an
ID.
Alert - a "Message". looks like a popup
window that appear in response to some action you did, usually notice you about
some error or any other info and allows you to press "OK" button.
Menu Bar - All the menu tree is kept in its own
resource.
Bitmaps - all bitmaps (b/w and color and icons)
are kept in a resource.
Strings - string can be kept in a resource and
be referred from the code using a unique ID.
there may be some other resources, but those are the most common. Usually what interest us are Alerts.
To see what we are talking about, let's take a look at a screenshot of PalmdeMON (a resource editor and disassembler that will be discussed later):
The resource list on the upper left part.
On the lower left part we see a preview of an Alert that is currently highlighted.
On the right window we see the disassembly.
Tools we gonna need:
Before we start the real work, we gonna need some tools, find them on the internet
(google is best place to start):
Palm Emulator - also known as POSE
- Palm OS Emulator
hex editor - I use UltraEdit
disassembler - palmdeMON
by carpathia and Pilotdis (pilotdis.exe)
resource editor - palmdeMON or PRCedit
debugger - Palmdebugger (not necessary at the moment since it won't be discussed
in this tut)
paper and pen - most important :)
To the work:
The target we are dealing is a Monopoly game. You can get the original file
here.
Step 1: getting
familiar with the program
Load the file to POSE and run it. After the opening screen, you will see a form
titled "New Game" with the buttons PLAY and CANCEL.
Press PLAY. Another window appear (note that this window might be a Form or
a special alert called Custom Alert) with a title saying "Welcome".
This window has 2 buttons: "Try the Demo" and "Enter Serial":
Press "Enter Serial":
A new form appears titled "Enter Serial Number". When you try to enter
some number and press the OK button, you get an Alert saying "You have
entered an invalid serial number".
Ok, at this point we have enough information to suspect that this program is
protected by a serial number, and if we will enter the correct serial number,
not only that it will not ask as for a serial number each time we start a new
game, but probably other limitations, that we currently do not know what they
are, are gonna disappear.
To finish this step, we will try to make the program expire. Goto the palm preferences
panel and change the date to one year later from now (not the PC date, but in
the emulator, goto Preferences from the main palm application browser and do
it from there).
Try to run the program again - nothing has changed. So, this program must use
some other kind of mechanism for expiration.
You don't have to try this, but let me tell you that if you will start 10 new
games the program will expire. After 10 new games, the "Welcome" form/alert
is changed to a "Demo Period Expired" Alert:
We can see that now we don't have any choice but enter a valid serial or exit
the program. It won't be a wild guess to think that there is some counter that
is being advanced every time we start a new game.
How are we going to deal with the protection ?
There are several things we can do in order to "fix" the program.
For example, we can try to force it accept any random serial number we enter
and by that register it, or, we can try to make the program not expire after
10 games without entering a serial number. In the last case, we will also want
to make the "Welcome" form not coming up every time we start a new
game.
Here I'll demonstrate the second option. At the end of it we will have a pre-registered
program (since the Welcome form will never come up, and the user will never
be asked to enter a serial number). In order to do that we will deal separately
with the Expiration after 10 games, and with the Welcome form. Disabling the
Welcome form only, will not necessarily make the program stop counting the allowed
10 games.
Step 2: digging
out some resources
Run palmdeMON and open the prc file in it. Start browsing the Alerts looking
for familiar stuff. Note that the status bar at the button of palmdeMON window
shows the resource ID and the file offset (later on that). Numbers that begins
with the $ sign or that looks like 0x123 are Hex numbers.
You can find the "Demo Period Expired" Alert easily in the alerts
list. Note that the preview window looks a little bit different than the alert
we got earlier (there are ^1 and ^2 signs). Those signs are simply text placeholders
(something like string pointers) that are addressed during runtime. Write down
this alert resource ID: Resource ID: 7904 (0x1EE0) File Offset 222090 (0x3638A).
6 Alerts bellow we see Alert named "Thank You!". Highlight it and
look at the preview: "Thank you for purchasing ^1 ^2" with OK button.
Hmm... this is probably something we gonna need (you can guess that this is
the alert we should get if a valid serial is entered). Write down its resource
ID: 7903 (0x1EDF) File Offset 223786 (0x36A2A).
A note about terminology: in reversers jargon, this is what we called "GoodGuy"
and "BadGuy" - u can figure out which is which...
Look at the "Welcome" Alert, the preview says: "This limited
demo of ^1 ^2 ^3". This looks just like our Welcome form. A quick look
at the Forms list shows that there is no other form looking like this. This
is a special case of what's called Custom Alert (an alert that has more design
options than the standard alerts). It looks similiar to a Form, but in fact
its Alert resource.
To conclude what we know so far:
"Welcome - This limited version..." Alert ID =
7901 ($1EDD hex)
"Serial number failed..." (BadGuy) Alert ID =
7902 ($1EDE hex)
"Thanks for purchasing..." (GoodGuy) Alert ID = 7903 ($1EDF
hex)
"Program Expired" Alert ID =
7904 (@1EE0 hex)
As mentioned before, we will first take care of the expiration "problem",
and later disable the "Welcome" Alert.
Step 3: Disassembling
At this point we gonna disassemble the program and start looking at the code.
As palmdeMON can disassemble the file, I personally prefer the disassembly generated
by Pilotdis (a command line utility). You can look at the disassembly in palmdeMON
by highlighting a code part in the code section (on the resources list) and
dbl-click it. You may notice that the code is divided to several parts - I will
not go into the reasons for that here.
In order to generate the disassembly with Pilotdis, simply open a command window
and type Pilotdis <name of file.prc>. You will get "name of file.prc.s"
file. It is very convenient to associate .s files with your favorite text editor,
since it's pure ASCII file.
Now that you have the disassembly, also known as "Dead-List", you
can start exploring it.
Step 4: Find
the relevant code section
There is no need (and no chance) to fully understand the whole dead-list. Actually,
there is no point in trying to understand how the program works only by looking
at the dead-list, since you can simply play with it on the emulator. The first
thing to do, what mostly takes the longest time, is locating the relevant part
in the code that interests us.
We will start looking for the "Program Expire" Alert. A good way to
start looking for it is by searching for its resource ID.
Note: in the Pilotdis dead-list, numbers that starts with the # sign are Decimal,
while number that starts with the $ sign are Hex.
A search for "$1EE0" shows several occurrences. The first occurrence
is at address 12aa (this is the file offset that was mentioned earlier - not
the real memory address that is used during runtime):
000012aa 3f3c1ee0 MOVE.W
#7904!$1ee0,-(A7)
000012ae 2f2f001a MOVE.L
26(A7),-(A7)
000012b2 4e4fa180 TRAP
#15
000012b6 DC.W
sysTrapFrmGetObjectIndex
On the left most part we see the address (file offset), next comes a hex number
which is the opcode. This hex number is the binary interpretation of the English
commands we see next.
The line MOVE.W #7904!$1ee0,-(A7) stands for Move Word (there is also move.b
- byte, move.d - double, move.l - long etc...) valued 7904 (decimal) to data
register A7 (one of the address registers).
The next line does similar thing (move.long a data found at 26 bytes offset
of current A7 value and store it in A7).
Trap 15 is like Interrupt in PC. It commands the operating system to perform
a build-in API (this API is built into the operating system and the program
coder doesn't have to worry about its execution). From the Trap name, we learn
that the above 4 code lines get some Object Index (something similar to Object
ID) from an object whose Object ID is #7904 (Object ID and Object Index are
different things).
If you scroll down a bit, you will see more traps like "sysTrapFldGetTextLength".
For some reason, it doesn't "smell" right... what does text length
has to do with our "Program Expired" Alert ? We can guess that this
occurrence of "$1EE0" doesn't represent our Alert. This could simply
be an object (most likely a text field) inside some form, which happens to have
the same ID as out Alert. Note that there are no 2 Alerts with same ID, but
it is possible to have same IDs for different kind of resources.
A Note about CPU registers:
The Motorola 68328 Draggonball CPU has 8 Data registers, numbered D0 through
D7 and 8 Address registers numbered A0 through A7. There is also the PC (Program
Counter) register which is same as the IP register in Intel based CPUs, the
SR (Status Register) which is same as the Flags register in Intel's CPUs. There
are 2 more registers which keeps data on the stack (USP and SSP).
In most of the cases you'll see that A7 is used for immediate instructions,
while the rest of the address register usually keep pointers to relevant data.
In the Data registers, D0 and D1 is most commonly in use.
If you are already familiar with x86 processors (Intel), you might know that
x86 assembler is written in reverse, hence MOV AX,DX will move copy the data
in register DX to register AX. In 68K assembler this is note the case. Operators
appear the same way you read them (from left to right), hence MOVE.W D3,D0 will
copy the data in register D3 to register D0.
For more information about the Palm CPU architecture, see this link.
Keep searching for out string ("$1EE0"). The next occurrence looks similar to the first. Keep searching until you reach the last occurrence of the string. Here are a few lines of code just before our ID (I manually added remarks to make the code more clear), note the Trap at the last line:
00001b72 0c430002 L165 CMPI.W
#2,D3 //
check if the data in Data Register D3 is the decimal number "2".
00001b76 6632 BNE
L168 //
jump to Label 168 if D3 =! 2
00001b78 48780000 PEA
$0000.W //
push the data pointed by address 0000 (hex, size of a Word) to the stack.
00001b7c 486f0008 PEA
8(A7) //
push the data pointed by the address stored at register A7+8 bytes (if the address
is 0000, then push the data at 0008) to the stack.
00001b80 2f0a MOVE.L
A2,-(A7) //
copy the data pointed by Adress Register A2 to register A7.
00001b82 3f3c1ee0 MOVE.W
#7904!$1ee0,-(A7) //
move the decimal number 7904 (the "Program Expire" resource ID) to
register A7.
00001b86 4e4fa194 TRAP
#15
00001b8a DC.W
sysTrapFrmCustomAlert //
execute the system command "Form Custom Alert". This will display
"Program Expired" Alert.
What we see here is that #7904 is pushed into register A7, and then the FrmCustomAlert
API is called. Now this sound much closer to what we are looking for. Write
down this address (1b82).
Step 5: Analyzing
the code
Now lets take a look on the line before the Alert pops. On address 1b72 we see
that the value in register D3 is compared with "2". Next line is conditional
branch: BNE = Branch if Not Equal (same as JNZ in Intel CPU). The jump is to
L168 which stands for Label 168. The label number is marked at the beginning
of each code block (you can thing of each label as a start of a new function).
Pilotdis marks those labels for us.
After the BNE we see 2 lines with PEA instructions. PEA = Push Effective Address.
Remember those text placeholders (^1 and ^2) from the Alert as we saw it in
palmdeMON ? Here they are filled with data.
We can suspect the line at address 1b76 to be our place to patch the program
(change the branch condition). Before we do that, let's go to L168 to see what's
there:
00001baa 7001 L168
MOVEQ #1,D0
00001bac 4fef016c LEA
364(A7),A7
00001bb0 4cdf5c18 MOVEM.L
(A7)+,D3/D4/A2-A4/A6
00001bb4 4e75 RTS
instructions summery:
MOVEQ = Move Quick (move direct data to register)
LEA = Load
Effective Address
MOVEM.L = Move Memory Long
RTS = Return
from Sub-Routine (return to the place where this sub-routine/function was called)
We can see that very little happens here, "1" is moved to register
D0. Registers data are restored to initial values (as they were before calling
this function), and the program returns to the calling function (with the value
"1" in Data Register D0).
You can see that if we will make the program perform the jump at address 1b76
in any case, we will never see the "Program Expired" alert again.
But, will this thing actually remove the program limitations, or only remove
the alert and keep the limitations (program will not run after 10 new games)
?
In order to check that, we will patch the program at address 1b76, run it and
check what happens.
Before patching, load the original prc to POSE and start a new game 10 times
until the "Demo Expired" Alert pops.
Step 6: Patching
What we are going to do is make the program jump at address 1b76 meaning that
we are going to change the conditional jump to an un-conditional jump.
The instruction at 1b76 is BNE, which stands for Branch if Not-Equal. We can
change it to BEQ (Branch if Equal), but better way will be to change it to BRA
(Branch - no conditions). The opcode for the branch command is as follows:
BRA = 60
BEQ = 67
BNE = 66
In our case, at address 1b76 we see the opcode "6632". This opcode
stands for "BNE L168", where "66" is the
BNE instruction and "32" is the bytes offset from the END of the current
instruction location (our instruction start at 1b76 and it's 2 bytes long, hence
offset is from 1b78), meaning 1B78+32=1BAA. And indeed, if you look at address
1BAA you'll that Label 168 starts there.
In order to change the code from BNE to BRA, we simply need to change the opcode
from "6632" to "6032" (the offset doesn't change).
Before patching, make a backup of the original file.
Open the .prc file with UltraEdit and press CTRL+G (Hex goto). At the address
type "0x1b76". this will put you in the right place in the .prc file.
Now change to byte "66" to "60" and save the file.
Step 7: test
the patched file on the emulator
Load the patched file into POSE and make it expire (start a new game 10 times).
What happens after 10 games ? The "Demo Expired" Alert is gone, but,
something is still wrong: Pressing the Play button does nothing... So, we can
guess that this was NOT the right place to patch...
Motivation Pauses: RCE involves a lot of trial and error. We find many things
by simply trying lots of combinations. As soon as you'll get more experience,
you'll see things more clearly, and the code will come up between the lines
without having to try so often.
Back to business:
The patch at 1b76 didn't work, so we will restore the original file and continue
from the place we were. Let's try to figure out where does the program return
to after the RTS in address 1bb4, and by that try to figure out what happens
when we press the Play button (since we already forced the program to bypass
the "Demo Expired" Alert, and reach that place).
How do we know where the program returns to ? Look at the line just before L165
starts, at address 1b70 you'll see also RTS. It means that there is no way the
program simply continues from 1b70 to 1b72. So, 1b72 (L165) must have been called
from somewhere. Search for "L165" and you'll find it here (I added
my own comments):
00001a88 4a43 L152 TST.W
D3 // test
data in Data Register D3
00001a8a 660000e6 BNE
L165
//
jump to Label 165 if D3 != 0
00001a8e 4a14 TST.B
(A4) // test
data in Address Register A4
00001a90 6650 BNE
L156
//
jump to Label 156 if A4 does not contain zeros
00001a92 2017 MOVE.L
(A7),D0 //
move the value pointed by the Address Register A7 to Data Register D0
00001a94 223c00015180 MOVE.L
#86400!$15180,D1 //
Put 86400 (decimal) in Data Register D1
00001a9a 4ebaed3e JSR
L9 // perform
sub-routine at Label 9
00001a9e 2600 MOVE.L
D0,D3
// move data in D0 to register D3
00001aa0 6706 BEQ
L153 //
jump to label L153 if ZERO was moved from D0 to D3.
00001aa2 5380 SUBQ.L
#1,D0 //
subtract 1 from the value in D0 and store the result in D0.
00001aa4 6714 BEQ
L154 //
jump to Label 154 if D0 = 0.
00001aa6 6024 BRA
L155 //
Jump to Label 155.
instructions summery:
TST.W = Test Word
BNE = Branch if
Not Equal
JSR =
Jump to Sub-Routine
SUBQ.L = Quick Subtraction of Long
BRA = Branch
(jump anyway with no conditions).
Look on address 1a8a: there's a conditional branch directly to L165, which
by now, we already know that cause the "Demo Expired" Alert to show.
What will happen if we cancel that conditional branch, and let the program continue
anyway without jumping ? Let's try this out:
In order to cancel an instruction we use a special command named NOP (no operation).
The opcode of NOP in 68k assembler is "4E71" (note it takes 2 bytes
- unlike x86 where NOP's opcode is "90").
As you can see, the opcode at 1a8a is 8 bytes long, hence we will put to NOPs
one after another. It is very important not to leave any "half-instructions"
by patching half instruction. This will cause a crash when the program runs,
because the hex numbers left there doesn't represent any reasonable code.
Repeat what we did earlier and replace the opcode in 1a8a with "4E71 4E71"
(do that on the restored file, not what we patched earlier).
Load the file to POSE and start a new game 10 times, This is what happens when
you try to start a new game for the 11 or 12 time:
You can see that the program keep counting the games (into negative range), but it doesn't expire ! This is good news. All we have to do now, is make that "Welcome" Form (actually a Custom Alert) never show up and nag us.
Remember the resource IDs we wrote down earlier ? The Resource ID of the "Welcome"
Alert is "7901". Search in the dead-list for "#7901" (or
for "$1edd").
You'll find it in address 1b34:
00001b26 7601 L160
MOVEQ
#1,D3
00001b28 6032 BRA
L163
00001b2a 486f0004 L161 PEA
4(A7)
00001b2e 486f009e PEA
158(A7)
00001b32 2f0a MOVE.L
A2,-(A7)
00001b34 3f3c1edd MOVE.W
#7901!$1edd,-(A7)
// "Welcome
to this limited demo..." Alert
00001b38 4e4fa194 TRAP
#15
00001b3c DC.W
sysTrapFrmCustomAlert //execute
a system API: Form Custom Alert
00001b3c 5340 SUBQ.W
#1,D0
00001b3e 4fef000e LEA
14(A7),A7
00001b42 660c BNE
L162
00001b44 7000 MOVEQ
#0,D0
00001b46 4fef016c LEA
364(A7),A7
00001b4a 4cdf5c18 MOVEM.L
(A7)+,D3/D4/A2-A4/A6 //restore
registers data before returning to calling function
00001b4e 4e75 RTS //
return to calling function
00001b50 486f00fe L162
PEA
254(A7)
00001b54 4ebafa4e JSR
L126
//
perform function at Label 126
00001b58 3600 MOVE.W
D0,D3
00001b5a 584f ADDQ.W
#4,A7
00001b5c 3803 L163
MOVE.W D3,D4
00001b5e 6706 BEQ
L164
//
jump if D4=0 - don't patch here
00001b60 0c440002 CMPI.W
#2,D4
00001b64 66c4 BNE
L161
///
if branch - show the Welcome form (enter serial/try the demo)
00001b66 3003 L164
MOVE.W
D3,D0
00001b68 4fef016c LEA
364(A7),A7
00001b6c 4cdf5c18 MOVEM.L
(A7)+,D3/D4/A2-A4/A6 //restore
registers data before returning to calling function
00001b70 4e75 RTS
//
return to calling function
Look at Label 161: on the line before it, we see BRA (on address 1b28). That means that whenever the program gets to address 1b28, it jumps directly to L163 without showing our "Welcome" Alert. Since we know that the Alert appears (and let you even enter a new serial number, since we already patched the expiration), we must conclude that L161 is called from somewhere. A search for "L161" leads us to address 1b64. On 1b64 there is a conditional branch, and the most natural thing to do is patch it. I'll spare the time here, but this does not give us the solution (you can try patch it by yourself and see what happens).
Before we continue to guess, let's try to understand what's going on here:
on address 1b5c, the data inside D3 is copied to D4. If that data was zero,
the program jumps to L164 and return to the calling function on address 1b70.
If D4 doesn't contain zero, then it is compared with "2", and if not
equal it branches to L161 and we get the "Welcome" Alert.
Let's try to see where D3 gets its value: on address 1b54 we see that some function
is executed and right after it, the value at D0 (which is probably something
returned from the function) is copied to D3. On the other hand, we see, on address
1b26 (just before a branch to L163), that the value "1" is copied
to D3. We know now that if D3=0 we will jump directly to L164 without displaying
the "Welcome" Alert, and id D3=!0 - we will get that alert. This is
why the "flag" on 1b26 should suspect this. We don't know what the
function on L126 (called at 1b54) is doing, but we sure know that value of "1"
inside D3 is not good.
In order to fix the "flag" at 1b26, and make sure that D3=0, we will
copy "0" instead of "1" to D3, and see what happens.
The opcode for the flag at 1b26 is "7601". 76 represents MOVE.Q (quick
move data to a register), while 01 represent the value to be moved to the register.
If we change the opcode to "7600" it'll put zero instead of "1"
inside D3, so instead of
MOVEQ
#1,D3
we will get: MOVEQ #0,D3
Patch the file with the hex editor the same way we did it before and load to
the emulator.
Now, when you start a new game, the nagging "Welcome" Alert doesn't
appear, and the game simply starts. Also we are not limited by any number of
games.
As you can see, by changing 9 bytes on the .prc files we made a pre-registered version of the game. In many other programs, only one byte is needed to be changed in order to bypass the protection. As there is always more than way to crack a file, we could also try and look for the function that calculates the serial number, and change it in a way that instead of accepting only valid serial numbers, it will accept only non-valid serial numbers (the odds the we randomly type an invalid serial numbers are near 100% if you think about it...)
Patch summery:
1A8A 660000e6 BNE
L165
------> NOP (opcode
= 4E71 4E71)
0001b26 7601 L160
MOVEQ #1,D3
-----> MOVEQ #0,D3 (opcode = 7600)
And that's really all I have to say about it...
AY ;)
p.s. for any comments/corrections - mail me.