003: Clean Cracking ‘Where in Time is Carmen Sandiego?’

Introduction

One of the early forms of copy protection was a “doc check”. It was a prompt to enter in a certain word or page number from the game manual. Photocopier-proof sheets and even code wheels were sometimes packaged with games. Not only was this copy protection annoying, since it had to be entered on each playthrough, but preserving the needed reference materials can be a pain decades later. Using a modern emulator, QEMU, and MacsBug, we are going to completely bypass the copy protection (a “clean” crack) for Where in Time is Carmen Sandiego?.

Tools

This project uses MacsBug 6.6.3 and Resorcerer 1.2.5. For more details on their installation and usage, check out Tutorial 001. We will also use QEMU — refer to its documentation for setting up OS 9. Be sure to enable the QEMU Monitor console with -monitor stdio.

Save The State

Once we get to the end of the first level, just after catching the crook — maybe during the arrest animation, save the state in QEMU with savevm carmen1 (carmen1 can be any name you like). We will restore back to this state throughout this tutorial.

Investigation

After a few clicks, the copy protection screen should appear. In previous tutorials, we tried to figure out the Toolbox trap used in a dialog box for a serial number. Some doc checks use a standard dialog box and this would be a good strategy for those instances, but here we definitely are not going to be using GetDialogItemText. We could try StringToNum or GetKeys, but notice that after entering in five wrong answers, the game quits.

The copy protection screen. Who has an encyclopedia?

Reverse Engineering in Reverse

Since the game quits right from the copy protection screen, let’s drop into MacsBug using nmi in QEMU. Once in, atb ExitToShell. After entering a wrong guess five times, the game should try and quit and break into MacsBug. From there, ip to disassemble around the current instruction:

Disassembling from 0E25771A
'CODE 0005 00B2'
 +00CDA 0E25771A   DC.W       $F632                      ; ????       | F632
 +00CDC 0E25771C   RTS                                                | 4E75
 +00CDE 0E25771E   MOVEQ      #$01,D0                                 | 7001
 +00CE0 0E257720   MOVE.B     D0,-$09CF(A5)                           | 1B40 F631
 +00CE4 0E257724   JSR        'CODE 0005 00B2'+00CD2     ; 0E257712   | 4EBA FFEC
 +00CE8 0E257728   MOVE.W     #$FFFF,-(A7)                            | 3F3C FFFF
 +00CEC 0E25772C   CLR.W      -(A7)                                   | 4267
 +00CEE 0E25772E   JSR        'CODE 0009 00B2'+01B0E                  | 4EAD 0952
 +00CF2 0E257732  *_ExitToShell                          ; 0031B22E   | A9F4
 +00CF4 0E257734   RTS                                                | 4E75
 +00CF6 0E257736   CLR.L      -(A7)                                   | 42A7
 +00CF8 0E257738   _FrontWindow                          ; 00959EA4   | A924
 +00CFA 0E25773A   MOVEA.L    (A7)+,A0                                | 205F
 +00CFC 0E25773C   CMPA.L     -$0A8C(A5),A0                           | B1ED F574
 +00D00 0E257740   SEQ        D0                                      | 57C0
 +00D02 0E257742   NEG.B      D0                                      | 4400
 +00D04 0E257744   EXT.W      D0                                      | 4880
 +00D06 0E257746   RTS                                                | 4E75
 +00D08 0E257748   LINK       A6,#$FFF8                               | 4E56 FFF8
 +00D0C 0E25774C   CLR.L      -(A7)                                   | 42A7

The current subroutine is quite short and begins at +00CDE. We can tell this because there is an RTS right above at +00CDC. What we want to do is figure out the place in memory that called this Exit routine. We can do this by noting the memory address of the first instruction: 0E25771E. This number will likely change if the game is reopened, but because we saved the QEMU state, this memory location remains the same if we were to restore the state.

Restore the state in QEMU using loadvm carmen1. We are back just before the copy protection screen, but now drop into MacsBug and bp 0E25771E. After five wrong guesses we should now be at the entry point a few lines above ExitToShell. If this is the case, the return address should be the first address on the stack. We can verify this with dm A7 and ip @A7:

Displaying memory from A7
0E3D4EE4  0E25 41CA 4EF8 0E3D  4EF8 0E25 20C8 0004
Disassembling from @A7
'CODE 0006 00B2'
 +032A2 0E2541B2   BEQ.S      'CODE 0006 00B2'+032BA     ; 0E2541CA   | 6716
 +032A4 0E2541B4   JSR        'CODE 0002 00B2'+002CC                  | 4EAD 0492
 +032A8 0E2541B8   TST.B      D0                                      | 4A00
 +032AA 0E2541BA   BNE.S      'CODE 0006 00B2'+032BA     ; 0E2541CA   | 660E
 +032AC 0E2541BC   JSR        'CODE 0006 00B2'+026F2     ; 0E253602   | 4EBA F444
 +032B0 0E2541C0   CMPI.W     #$FFFF,D0                               | 0C40 FFFF
 +032B4 0E2541C4   BNE.S      'CODE 0006 00B2'+032BA     ; 0E2541CA   | 6604
 +032B6 0E2541C6   JSR        'CODE 0005 00B2'+00CDE                  | 4EAD 0742
 +032BA 0E2541CA   JSR        'CODE 0006 00B2'+02B54     ; 0E253A64   | 4EBA F898
 +032BE 0E2541CE   JSR        'CODE 0006 00B2'+02AB2     ; 0E2539C2   | 4EBA F7F2
 +032C2 0E2541D2   TST.W      D0                                      | 4A40
 +032C4 0E2541D4   BEQ.S      'CODE 0006 00B2'+032CC     ; 0E2541DC   | 6706
 +032C6 0E2541D6   JSR        'CODE 0006 00B2'+0303C     ; 0E253F4C   | 4EBA FD74
 +032CA 0E2541DA   BRA.S      'CODE 0006 00B2'+032D6     ; 0E2541E6   | 600A
 +032CC 0E2541DC   JSR        'CODE 0006 00B2'+03086     ; 0E253F96   | 4EBA FDB8
 +032D0 0E2541E0   BRA.S      'CODE 0006 00B2'+032D6     ; 0E2541E6   | 6004
 +032D2 0E2541E2   JSR        'CODE 0006 00B2'+0581C     ; 0E25672C   | 4EBA 2548
 +032D6 0E2541E6   JSR        'CODE 0006 00B2'+02AB2     ; 0E2539C2   | 4EBA F7DA
 +032DA 0E2541EA   TST.W      D0                                      | 4A40
 +032DC 0E2541EC   BEQ.S      'CODE 0006 00B2'+032E6     ; 0E2541F6   | 6708

The address that called the routine should be exactly four bytes less than the return address. Sure enough, the calling line is: +032B6 0E2541C6 JSR 'CODE 0005 00B2'+00CDE. Note that 'CODE 0005 00B2'+00CDE matches the entry point of the Exit routine. Around the line, there are a few branches to the return address, 0E2541CA. Looking at the closest branch at +032B4, if D0 is not equal to FFFF (CMPI.W #$FFFF,D0), branch to the return address, bypassing the Exit routine.

Let’s note the address of the comparison for the branch, 0E2541C0, restore the state with loadvm carmen1, and set a breakpoint on it with bp 0E2541C0. After entering five guesses, MacsBug should open and D0 should contain FFFF. This value will cause the branch to fall through, running the Exit routine. Set D0=1 and g to resume execution.

It works! The game does not quit and you can proceed to the next level. This would be a working crack, but not a clean one. Not only does the doc check screen come up, but it also requires you to enter five wrong guesses. We can do better.

A Clean Crack

Recall our disassembly around the call to the Exit routine at 0E2541CA in the previous listing. The branch above our breakpoint at 0E2541C0 was at +032A8. There, if D0 is non-zero (TST.B D0 / BNE.S), bypass the Exit routine. What about above that at +032A2? Let’s ip 0E2541B2:

Disassembling from 0E254194
'CODE 0006 00B2'
 +03284 0E254194   ADDQ.L     #$2,A7                                  | 548F
 +03286 0E254196   CMPI.W     #$0004,$0008(A6)                        | 0C6E 0004 0008
 +0328C 0E25419C   BNE.S      'CODE 0006 00B2'+032D2     ; 0E2541E2   | 6644
 +0328E 0E25419E   JSR        'CODE 0002 00B2'+0030C                  | 4EAD 04E2
 +03292 0E2541A2   TST.B      D0                                      | 4A00
 +03294 0E2541A4   BNE.S      'CODE 0006 00B2'+032D6     ; 0E2541E6   | 6640
 +03296 0E2541A6   JSR        'CODE 0006 00B2'+02AC2     ; 0E2539D2   | 4EBA F82A
 +0329A 0E2541AA   MOVEA.L    -$3C92(A5),A0                           | 206D C36E
 +0329E 0E2541AE   TST.B      $000A(A0)                               | 4A28 000A
 +032A2 0E2541B2   BEQ.S      'CODE 0006 00B2'+032BA     ; 0E2541CA   | 6716
 +032A4 0E2541B4   JSR        'CODE 0002 00B2'+002CC                  | 4EAD 0492
 +032A8 0E2541B8   TST.B      D0                                      | 4A00
 +032AA 0E2541BA   BNE.S      'CODE 0006 00B2'+032BA     ; 0E2541CA   | 660E
 +032AC 0E2541BC   JSR        'CODE 0006 00B2'+026F2     ; 0E253602   | 4EBA F444
 +032B0 0E2541C0   CMPI.W     #$FFFF,D0                               | 0C40 FFFF
 +032B4 0E2541C4   BNE.S      'CODE 0006 00B2'+032BA     ; 0E2541CA   | 6604
 +032B6 0E2541C6   JSR        'CODE 0005 00B2'+00CDE                  | 4EAD 0742
 +032BA 0E2541CA   JSR        'CODE 0006 00B2'+02B54     ; 0E253A64   | 4EBA F898
 +032BE 0E2541CE   JSR        'CODE 0006 00B2'+02AB2     ; 0E2539C2   | 4EBA F7F2
 +032C2 0E2541D2   TST.W      D0                                      | 4A40

At +0329E there is a check if $000A(A0) is zero (TST.B $000A(A0)) and a branch if it is (BEQ.S). Since there are no other branches to the return address 0E2541CA nearby, this seems like another good place to set a breakpoint with bp 0E2541AE. Upon restoring the state with loadvm carmen1 and proceeding to the copy protection screen, it drops into MacsBug instead. Use t to step to the branch, but simulate taking the branch with pc=0E2541CA. This sets the program counter, the active memory location, as if we had taken the branch. After g to resume, the game moves on to the next level with no copy protection in sight!

Creating a Cracked Application

Now the clean crack is clear, we have to set a branch always (BRA) at CODE 6 +032A2. To do this, open the Carmen Time application in Resorcerer. Open the CODE 6 resource then Code > Goto/Find/Replace…. Type in the offset 32A2:

Going to an offset.

Now highlight the 67 in 6716 and change it to 60. The branch instruction should change to BRA accordingly:

We have to BRA instead of BNE.

Save your work and test it out by playing through the first level.

Conclusion

When reverse engineering, working backwards can be faster and easier than working forwards. Instead of catching ExitToShell, sometimes you’ll want to catch an alert with Alert, NoteAlert, CautionAlert, or StopAlert and work back from there. These traps are useful when creating NoCDs since there is likely a prompt to insert a CD. QEMU save states are another tool we can use to crack applications because memory addresses are preserved. With fixed memory addresses, setting breakpoints at key places is trivial, and are able to quickly give clarity to complex code. A clean crack makes gameplay seamless and that’s always the ultimate goal when cracking.

002: REALbasic Traps and ‘Clean Desk’

Introduction

REALbasic was to the Macintosh as VisualBasic was to Windows. It lowered the barrier to entry substantially and more software flooded the Mac scene with thousands upon thousands of niche utilities trying to cash in on the shareware craze. Since many REALbasic applications were written by junior programmers, copy protection was not particularly robust in many cases. In this project we will look at a common way these programs were exploited and reversed by looking at Clean Desk.

Tools

This project uses MacsBug 6.6.3. For more details on its installation and usage, check out Tutorial 001.

Investigation

We can check if the application is 68k or PPC as in Tutorial 001, but it’s just as easy to assume it’s PPC based on the application release year of 2000, which was fairly late in the Classic Mac OS’s life. From the application’s about box we can determine that it was written in REALbasic.

Made with REALbasic.

We can also look at the style of windows and dialogs and see that they don’t completely conform to Apple standards. Notice the background texture and text size. This is usually indicative of PowerPlant or REALbasic applications.

A non-standard dialog box — another clue it might be a REALbasic application.

If we suspect a REALbasic application, we can drop into MacsBug and create a TVector Break (TVB) on a known REALbasic export. In this project, we will eventually use the export RuntimeStringCompare.

Serial Number Schemes

There are three common types of serial number schemes:

  1. A standalone serial number that conforms to some validation algorithm.
  2. A serial number generated from a particular dataset, like a name and/or organization.
  3. A serial number generated from a uniquely generated number for that particular installation.

Type 1 was the scheme examined in Tutorial 001. A good validation routine requires math skill to limit the number of valid serial numbers such that one could not be easily guessed at random, but allows for a range that would not set a hard limit on sales unless you reuse serial numbers. These serial numbers are easily shared in serial number lists.

Type 2 could be incredibly complex and has the added benefit that some applications can ban prolific cracker’s names like Buck Rogers and MoonDark. These serial numbers are still shared in serial number lists. In terms of complexity, a good algorithm decodes data from the entered serial number and compares that data to the entered data. The serial number is essentially an encrypted form of the user’s name, for example. If the programmer doesn’t use even trivial encoding/decoding, it opens up a significant flaw that makes reversing a serial number far easier than Type 1 or Type 3.

Type 3 eliminates the possibility of sharing the serial number, but programs like Buck’s Serial Number Generator (BSNG) were created to circumvent this and allow users to generate their own serial numbers. This type of scheme has drawbacks though, since a user needs to receive a new serial number if they want to transfer the software to a different computer.

Clean Desk is a Type 2 algorithm and you can bet that we are going to exploit the significant flaw present in a vulnerable implementation of this scheme.

Flawed Code

Consider the following code:

 1.  name = getNameText();
 2.  enteredSN = getSerialNumberText();

 3.
 4.  calculatedSN = calculateSN(name);
 5.
 6.  if (enteredSN == calculatedSN) {
 7.      goodSerialNumber();
 8.  } else {
 9.      alert("Bad Serial Number!");
10.  }

On first glance you might think we will have to reverse line 4’s calculateSN() function. This would give us the algorithm that we can then use to calculate the serial number. But after the function runs, it returns the calculated serial number such that we already have the calculated serial number in calculatedSN. All we have to do is peak at calculatedSN in memory and we should see our valid serial number!

Exploiting the Flaw

We don’t even have to break before line 4 and step over the calculateSN() function. REALbasic has a couple internal functions (exports) for line 6: StringStrCompare and RuntimeStringCompare. Both of these functions take two String objects as parameters much like C’s strcmp(). Using MacsBug, we first set a break with tvb RuntimeStringCompare.

Adding a break on RuntimeStringCompare.

Note that the break was created without error. Since RuntimeStringCompare is unique to REALbasic, we can assume this is a REALbasic application. Type g to resume execution. Let’s enter some random data and press OK.

Random serial number.

In PPC, any arguments passed to a function are located in R3 through R10. If we dm r3, there is an address four bytes in.

Four bytes into R3 is 0DBF 6F50.

Let’s peak at that address either by dm <addr> or dm @(r3+4).

0DBF 6F50 contains our random serial number.

That address contains our entered serial number, 12345. If RuntimeStringCompare has two parameters, with the first one being enteredSN, then R4 should be calculatedSN. We can confirm this with dm @(r4+4).

@(r4+4)[828-5198-247] sure looks like a serial number.

Sure enough, 828-5198-247 looks like a serial number, so enter it back into the application and let’s see what happens.

Bingo.

Conclusion

Although there might be some clever math behind the serial number generation function, if the application is vulnerable to this exploit, we never even have to step through it. In practice, this whole exploit takes mere seconds. A more robust implementation would look something similar to:

 1.  name = getNameText();
 2.  sn = getSerialNumberText();
 3.
 4.  nameHash = calculateHash(name);
 5.  decodedHash = decodeSerialNumber(sn);
 6.
 7.  if (nameHash == decodedHash) {
 8.      goodSerialNumber();
 9.  } else {
10.      alert("Bad Serial Number!");
11.  }

In this design, the serial number contains information about the name. Here it is a hash, but it could be an encrypted form of the name itself. The key thing here is that peeking at the comparison nameHash == decodedHash on line 7 does not tell you anything about the entered serial number. In order to get a valid serial number, the cracker is forced to reverse the entire decodeSerialNumber() function on line 5.

Some serial number algorithms decrypt a serial number hundreds of bytes long to verify an entire street address (among other data). Sometimes the encryption code is mistakenly present in the application, but more often we have to perform the difficult process of reversing the decryption function to figure out the encryption function, and that’s a tutorial for another time.

001: Reversing ‘Grateful Dead: D2S2’

Introduction

In this project, we will be looking at an easy algorithm to reverse in Grateful Dead: D2S2. I will also be going over some basic setup since this is the first in my tutorial series. At the end of this project, you will be able to identify a 68k vs PPC application, set a Toolbox breakpoint, manually bypass the authentication algorithm, and then fully reverse the algorithm to generate a valid serial number. A passing familiarity with 68k assembly will help, but it’s not necessary.

Tools

We will be using MacsBug 6.6.3 and Resorcerer 1.2.5. To install MacsBug, simply drop it in your System Folder and reboot. MacsBug is a low level debugger that displays the assembly language instructions sent to the CPU. Resorcerer is used to view and edit the binary data of the application.

“Debugger Installed”. Let the fun begin!

Investigation

MacsBug uses different commands to break on Macintosh Toolbox calls depending on the architecture (more on that later). The first thing to do is determine if the application is 68k or PPC. While there are applications designed for this purpose, I find it just as easy to open the application in Resorcerer and peek at the Data Fork <DF> resource.

The installer application.
The Data Fork <DF> resource.

As a rule of thumb, PPC applications will have the string Joy!peffpwpc at the very beginning of the Data Fork. Since the Data Fork here does not contain the string, we can safely assume it is a 68k application.

Next, we need to carefully look at the registration process. Let’s fire up the installer for D2S2 – The Digital Dead. A dialog appears and asks for a serial number. Let’s try 123456. We have to click OK to proceed.

What happens when we enter a bad serial?
We get an alert.

After clicking OK, an alert appears complaining about an invalid serial number. From this simple investigation we can conclude:

  1. This is a 68k application.
  2. The serial number entry looks like a standard textbox within a standard dialog.
  3. The error alert looks like a standard alert.

What I mean here by standard is that the dialog and textbox looks like every other normal dialog and textbox in the OS. We can dive further and verify this by looking at the DITL and DLOG resources within Resorcerer, but I look for things like custom borders, custom fonts, or fancier controls. Compare the dialogs above with the one below. Notice the different fonts on the buttons and the different textbox size and outline. These observations are needed to figure out how to stop program execution with MacsBug at a precise point since standard dialogs and controls will usually use standard Macintosh Toolbox calls (again, more on that later).

A non-standard dialog box.

Strategy

If we can stop program execution immediately after we click the OK button at the serial number prompt, we should land in MacsBug just before the validation routine. At a very high level, we expect the validation logic to look something like this pseudocode:

 1.  sn = getSerialNumberText();
 2.  validSN = isValidSerialNumber(sn);
 3.
 4.  if (validSN) {
 5.      goodSerialNumber();
 6.  } else {
 7.      alert("Bad Serial Number!");
 8.  }

We want to break into the debugger right at line 1 so we can step through isValidSerialNumber(sn) and see what constitutes a valid serial number.

This strategy is in fact how all cracking works regardless of the system you are using or the age of the application. For example, cracking DOS applications would require breaking into the debugger when the Enter key is pressed. Cracking modern games involving online activation requires breaking into the debugger to inspect the memory and packets being sent to the server just prior to and after a validation check.

There are two tough parts:

  1. Figuring out what to break on.
  2. Figuring out the validation algorithm.

For the first issue, we are going to use Toolbox calls. Number two requires a bit more brain power.

The Toolbox

The Macintosh Toolbox is a set of routines that interact with the Macintosh operating system. There are hundreds of different routines. You can review the complete documentation here. Luckily, we don’t need to know more than a small subset.

From our investigation, remember it appears we are using a standard textbox within a standard dialog. Our first attempt will use the Toolbox call GetDialogItemText. This is the call used to get textbox contents from a standard dialog box. As I write more tutorials in this series, we will look at using different Toolbox calls since developers of more sophisticated copy protection schemes will rarely use something as obvious as GetDialogItemText.

Recall that what looks like a standard alert is shown when we enter an invalid serial number; we can also be looking for the Toolbox call Alert around our validation code.

Setting a Toolbox Breakpoint

With a logical guess of the Toolbox call GetDialogItemText, we can use MacsBug to stop program execution immediately before it is called. Run the installer for D2S2 – The Digital Dead again and get to the serial number prompt. Now we have to get into MacsBug. This can be done a number of ways:

  1. Pressing the Programmer’s Key on an older Macintosh.
  2. Pressing Command-Power.
  3. Using the CS Debugger Control Strip.
  4. Using my own Debugger! utility.
  5. The nmi (Non-Maskable Interrupt) command from the QEMU monitor.
  6. Ctrl-I (Interrupt) in Mini vMac.

Once we are in MacsBug, we set a breakpoint on a Toolbox call. We can do this with atb GetDialogItemText. We are using atb because from our investigation we found the application to be 68k. If we wanted a PPC breakpoint, the command would be tvb GetDialogItemText. It’s good practice to write your Toolbox breakpoints case-sensitive.

Setting an A-Trap Break with atb GetDialogItemText.

Once the break is set, g will resume program execution. Again, let’s use 123456 as the serial number and click OK. We should now automatically be back in MacsBug. The Toolbox breakpoint was a success!

The MacsBug screen after pressing OK.

Deeper into MacsBug

Now that we are looking at the disassembly of our application in MacsBug, we can trudge through the code step by step. The MacsBug commands we will be using now are:

  • s: Step to the next instruction. This will also enter any subroutine calls.
  • so / t: Step Over or Trace to the next instruction. This will not enter any subroutine calls.
  • il: disassemble from current instruction (Instruction List).
  • ip: disassemble around current instruction (Instruction list Page).
  • dm <address>: Display Memory. Used to show the memory contents at the specified address.

The first time I break into MacsBugs, I like to ip to get my bearings. Press Enter again to disassemble a few more lines. The asterisk in the display denotes the current pc (Program Counter) or the instruction about to be executed on the CPU. You can see at +000C6 it is GetDialogItemText, which is the breakpoint we set.

Using ip to disassemble around the current instruction.

A call to GetDialogItemText will put the textbox contents into the 32-bit (4 byte) memory address on the stack. The stack is referenced by address register A7, and we can view the contents of the stack by using dm a7. Addresses will usually change each time the application is run, but in this case the first 4 bytes are 0E1F F8C0. In other words, after we trace over GetDialogItemText, address 0E1FF8C0 will contain the textbox string.

Let’s do dm @a7, which is a shortcut to display the memory of the 32-bit address at A7. You should see the memory at location 0E1FF8C0. There is just some garbage data there, but now lets t to step over GetDialogItemText. We can then use dm ., which will display the memory at the last address returned by MacsBug.

The contents of the textbox are now in memory after GetDialogItemText.

The ASCII contents of 0E1FF8C0 should be 123456 — our entered serial number. Notice that the first byte is 06. This is because GetDialogItemText will return a Pascal String (pstring), which has the length of the string in the first byte.

Looking ahead a few lines, there is a DisposeDialog call and then after a few more lines there is an RTS (ReTurn from Subroutine) instruction. Let’s t to trace over everything including the RTS instruction. We should now be at a PEA (Push Effective Address) instruction. The full instruction is PEA -$0100(A6) and we can translate this as “push the address of A6-0x100 onto the stack”. What is at this location? Let’s dm a6-100 to take a look… it’s our entered serial number! Note also that the memory address is the same as before: 0E1FF8C0. Let’s ip again to get our bearings.

An ip after displaying A6-100.

Just before the pc (Program Counter / asterisk), there is a JSR instruction at +00030. This was the subroutine that we just exited. Remember that it was getting the entered serial number via GetDialogItemText. After pc, there is another JSR instruction at +00038. A few more instructions down at +0004C we see an Alert. Thinking back to our pseudocode, it’s more than likely that the subroutine at +00038 is isValidSerialNumber(sn), where sn is passed via the stack, and the Alert is displayed if we have entered an invalid serial number.

A Quick Crack

Now that we have likely narrowed down where the validation check occurs, it’s time to do some actual reverse engineering! Let’s s to step into the subroutine referenced by the JSR at +00038. We can il a few times to disassemble from the current pc.

Using il to Instruction List the subroutine.

This might look intimidating, but we will go through it step by step. The first interesting block is:

+000D8    MOVEA.L    $0008(A6),A3                        | 266E 0008
+000DC    CMPI.B     #$0D,(A3)                           | 0C13 000D
+000E0    BNE        'IBeg 0080 012E DDSS'+00194         | 6600 00B2

The instruction, MOVEA.L $0008(A6),A3 moves the address at A6+8 into A3. Let’s s over that instruction then dm a3 to see what is stored there. It’s the pstring of our serial number! The next instruction, CMPI.B #$0D,(A3) compares the byte (denoted by the .B) 0x0D and the byte at the address in A3. The brackets around A3 mean we treat the A3 register value as an address and look there, without the brackets we look at the register value itself — think of brackets like dereferencing pointers (*) in C. What’s the byte at A3? Remember that for pstrings, the length of the string is stored in the first byte, so the byte at A3 will be the length of our serial number: 6. These two instructions compare the length of our serial number to 0x0D, which is 13 in decimal. The next instruction, BNE 'IBeg 0080 01E DDSS'+00194, will Branch to offset +00194 if the comparison of the two bytes is Not Equal (technically if the subtraction of the two is non-zero, which is why BNE is sometimes denoted BNZ).

Looking further down our listing for offset +00194:

+00194    MOVEQ      #$00,D0                             | 7000
+00196    BRA.S      'IBeg 0080 012E DDSS'+0019A         | 6002
+00198    MOVEQ      #$01,D0                             | 7001
+0019A    MOVEA.L    (A7)+,A3                            | 265F
+0019C    UNLK       A6                                  | 4E5E
+0019E    RTS                                            | 4E75

If the length of our serial number is not 13, it will set D0 to all zeros (MOVEQ #$00,D0) then branch (BRA) to 'IBeg 0080 012E DDSS' +0019A, which cleans up and returns from the subroutine. We can s all the way through this to confirm that the branches do in fact happen. Once we step passed RTS and back to the previous subroutine, these two instructions are next:

+0003C    MOVE.B     D0,D6                               | 1C00
+0003E    TST.B      D6                                  | 4A06

This block will move D0 into D6 (MOVE.B D0,D6) and then test if D6 is zero (TST.B D6). Remember that just before in the previous subroutine, D0 was set to all zeros (+00194) when our serial number length was not 13. Thinking back to our pseudocode, it seems reasonable to suspect that D0 is in fact the validSN variable and that if D0 is non-zero, we have a valid serial number. If we can force validSN to be 1, then the application will think we entered a valid serial number. How can we test this in MacsBug?

Before we step passed the two instructions starting at +0003C, let’s do d0=1. Notice how the D0 register is now red and set to 00000001.

Manually setting D0 to 1.

Let’s g to resume program execution. We should be at the install screen… We just cracked the program!

Success!

At this point, we could use Resorcerer to modify the application and make the crack permanent. We could do this a number of different ways: changing branches, always setting D0 to 1, or by bypassing the serial number prompt altogether. I will be going into more detail about this in a future tutorial, but for now, let’s do the hard work and figure out the validation algorithm.

The Algorithm

Everything that we have done up to this point can be generalized across all software: there’s a prompt, user input, a validation check, and a result. The validation check is unique to each application and can span dozens of lines to thousands of lines.

This is a listing of the current validation subroutine:

'IBeg 0080 012E DDSS'
    +000D2   *LINK       A6,#$0000                           | 4E56 0000
    +000D6    MOVE.L     A3,-(A7)                            | 2F0B
    +000D8    MOVEA.L    $0008(A6),A3                        | 266E 0008
    +000DC    CMPI.B     #$0D,(A3)                           | 0C13 000D
    +000E0    BNE        'IBeg 0080 012E DDSS'+00194         | 6600 00B2
    +000E4    ADDQ.L     #$1,A3                              | 528B
    +000E6    MOVE.B     (A3),-(A7)                          | 1F13
    +000E8    JSR        'IBeg 0080 012E DDSS'+001A0         | 4EBA 00B6
    +000EC    TST.B      D0                                  | 4A00
    +000EE    ADDQ.L     #$2,A7                              | 548F
    +000F0    BEQ        'IBeg 0080 012E DDSS'+00194         | 6700 00A2
    +000F4    ADDQ.L     #$1,A3                              | 528B
    +000F6    MOVE.B     (A3),-(A7)                          | 1F13
    +000F8    JSR        'IBeg 0080 012E DDSS'+001A0         | 4EBA 00A6
    +000FC    TST.B      D0                                  | 4A00
    +000FE    ADDQ.L     #$2,A7                              | 548F
    +00100    BEQ        'IBeg 0080 012E DDSS'+00194         | 6700 0092
    +00104    ADDQ.L     #$1,A3                              | 528B
    +00106    MOVE.B     (A3),-(A7)                          | 1F13
    +00108    JSR        'IBeg 0080 012E DDSS'+001CE         | 4EBA 00C4
    +0010C    TST.B      D0                                  | 4A00
    +0010E    ADDQ.L     #$2,A7                              | 548F
    +00110    BEQ        'IBeg 0080 012E DDSS'+00194         | 6700 0082
    +00114    ADDQ.L     #$1,A3                              | 528B
    +00116    MOVE.B     (A3),-(A7)                          | 1F13
    +00118    JSR        'IBeg 0080 012E DDSS'+001CE         | 4EBA 00B4
    +0011C    TST.B      D0                                  | 4A00
    +0011E    ADDQ.L     #$2,A7                              | 548F
    +00120    BEQ.S      'IBeg 0080 012E DDSS'+00194         | 6772
    +00122    ADDQ.L     #$1,A3                              | 528B
    +00124    CMPI.B     #$35,(A3)                  ; '5'    | 0C13 0035
    +00128    BNE.S      'IBeg 0080 012E DDSS'+00194         | 666A
    +0012A    ADDQ.L     #$1,A3                              | 528B
    +0012C    MOVE.B     (A3),-(A7)                          | 1F13
    +0012E    JSR        'IBeg 0080 012E DDSS'+001CE         | 4EBA 009E
    +00132    TST.B      D0                                  | 4A00
    +00134    ADDQ.L     #$2,A7                              | 548F
    +00136    BEQ.S      'IBeg 0080 012E DDSS'+00194         | 675C
    +00138    ADDQ.L     #$1,A3                              | 528B
    +0013A    MOVE.B     (A3),-(A7)                          | 1F13
    +0013C    JSR        'IBeg 0080 012E DDSS'+001CE         | 4EBA 0090
    +00140    TST.B      D0                                  | 4A00
    +00142    ADDQ.L     #$2,A7                              | 548F
    +00144    BEQ.S      'IBeg 0080 012E DDSS'+00194         | 674E
    +00146    ADDQ.L     #$1,A3                              | 528B
    +00148    MOVE.B     (A3),-(A7)                          | 1F13
    +0014A    JSR        'IBeg 0080 012E DDSS'+001A0         | 4EBA 0054
    +0014E    TST.B      D0                                  | 4A00
    +00150    ADDQ.L     #$2,A7                              | 548F
    +00152    BEQ.S      'IBeg 0080 012E DDSS'+00194         | 6740
    +00154    ADDQ.L     #$1,A3                              | 528B
    +00156    MOVE.B     (A3),-(A7)                          | 1F13
    +00158    JSR        'IBeg 0080 012E DDSS'+001CE         | 4EBA 0074
    +0015C    TST.B      D0                                  | 4A00
    +0015E    ADDQ.L     #$2,A7                              | 548F
    +00160    BEQ.S      'IBeg 0080 012E DDSS'+00194         | 6732
    +00162    ADDQ.L     #$1,A3                              | 528B
    +00164    MOVE.B     (A3),-(A7)                          | 1F13
    +00166    JSR        'IBeg 0080 012E DDSS'+001CE         | 4EBA 0066
    +0016A    TST.B      D0                                  | 4A00
    +0016C    ADDQ.L     #$2,A7                              | 548F
    +0016E    BEQ.S      'IBeg 0080 012E DDSS'+00194         | 6724
    +00170    ADDQ.L     #$1,A3                              | 528B
    +00172    MOVE.B     (A3),-(A7)                          | 1F13
    +00174    JSR        'IBeg 0080 012E DDSS'+001CE         | 4EBA 0058
    +00178    TST.B      D0                                  | 4A00
    +0017A    ADDQ.L     #$2,A7                              | 548F
    +0017C    BEQ.S      'IBeg 0080 012E DDSS'+00194         | 6716
    +0017E    ADDQ.L     #$1,A3                              | 528B
    +00180    MOVE.B     (A3),-(A7)                          | 1F13
    +00182    JSR        'IBeg 0080 012E DDSS'+001CE         | 4EBA 004A
    +00186    TST.B      D0                                  | 4A00
    +00188    ADDQ.L     #$2,A7                              | 548F
    +0018A    BEQ.S      'IBeg 0080 012E DDSS'+00194         | 6708
    +0018C    ADDQ.L     #$1,A3                              | 528B
    +0018E    CMPI.B     #$4C,(A3)                  ; 'L'    | 0C13 004C
    +00192    BEQ.S      'IBeg 0080 012E DDSS'+00198         | 6704
    +00194    MOVEQ      #$00,D0                             | 7000
    +00196    BRA.S      'IBeg 0080 012E DDSS'+0019A         | 6002
    +00198    MOVEQ      #$01,D0                             | 7001
    +0019A    MOVEA.L    (A7)+,A3                            | 265F
    +0019C    UNLK       A6                                  | 4E5E
    +0019E    RTS                                            | 4E75

We already figured out that +000D8 to +000E0 was testing that the length of the serial number was equal to 13. Let’s carry on at +000E4. Notice there is a repeating pattern of: ADDQ, MOVE, JSR, TST, ADDQ, BEQ. What is interesting here is that there are 11 JSR instructions followed by BEQs, one comparison to ‘5’ at +00124, and one comparison to ‘L’ at +0018E. That’s a total of 13 comparisons — the same as our serial number length. Those 11 JSR instructions are only going to two subroutines: 'IBeg 0080 012E DDSS'+001A0 and 'IBeg 0080 012E DDSS'+001CE. After returning from those two subroutines, it will test D0 (TST.B D0) and then branch if equal (BEQ) to 'IBeg 0080 012E DDSS'+00194. This means that if the subroutine returns 0 in D0, it will branch to the same cleanup and return section of the current subroutine as it did when the length of the serial number was not equal to 13. So, we know that the two subroutines must always return a non-zero result to have a valid serial number.

The first routine looks like this:

'IBeg 0080 012E DDSS'
    +001A0    LINK       A6,#$0000                           | 4E56 0000
    +001A4    MOVE.L     D7,-(A7)                            | 2F07
    +001A6    MOVE.B     $0008(A6),D7                        | 1E2E 0008
    +001AA    CMPI.B     #$41,D7                    ; 'A'    | 0C07 0041
    +001AE    BCS.S      'IBeg 0080 012E DDSS'+001B6         | 6506
    +001B0    CMPI.B     #$5A,D7                    ; 'Z'    | 0C07 005A
    +001B4    BLS.S      'IBeg 0080 012E DDSS'+001C6         | 6310
    +001B6    CMPI.B     #$61,D7                    ; 'a'    | 0C07 0061
    +001BA    BCS.S      'IBeg 0080 012E DDSS'+001C2         | 6506
    +001BC    CMPI.B     #$7A,D7                    ; 'z'    | 0C07 007A
    +001C0    BLS.S      'IBeg 0080 012E DDSS'+001C6         | 6304
    +001C2    MOVEQ      #$00,D0                             | 7000
    +001C4    BRA.S      'IBeg 0080 012E DDSS'+001C8         | 6002
    +001C6    MOVEQ      #$01,D0                             | 7001
    +001C8    MOVE.L     (A7)+,D7                            | 2E1F
    +001CA    UNLK       A6                                  | 4E5E
    +001CC    RTS                                            | 4E75

Can you guess just by glancing at it what it is doing? The comparisons to ‘A’, ‘Z’, ‘a’, and ‘z’ should give it away. It’s simply checking to see if the byte at D7 is between ‘A’ and ‘Z’ or ‘a’ and ‘z’.

The byte at D7 happens to be the next character in the serial number. How it winds up in D7 isn’t terribly important, as it has to do with linking (LINK) the stack and local variables. The quick and dirty way to see what D7 is to just type d7. If we look at +001A6, MOVE.B $0008(A6),D7 is moving the byte at A6+8 into D7. We can dm a6+8 to see what’s there, and it’s the next byte of our serial number.

The individual BCS and BLS instructions in the routine can be translated as Branch on LOwer (BCS is equivalent to BLO) and Branch on Lower or Same. So, if the byte is not in the range of ‘A’ to ‘Z’, check if it’s in the range of ‘a’ to ‘z’. If the byte is in either range, +001C6 will set D0 to 1 (MOVEQ #$01,D0) and return, otherwise, +001C2 will set D0 to 0 (MOVEQ #$00,D0) and then return. Put simply, this first routine is checking if the input byte is a letter.

The second routine looks like this:

'IBeg 0080 012E DDSS'     
    +001CE    LINK       A6,#$0000                           | 4E56 0000
    +001D2    CMPI.B     #$30,$0008(A6)             ; '0'    | 0C2E 0030 0008
    +001D8    BCS.S      'IBeg 0080 012E DDSS'+001E2         | 6508
    +001DA    CMPI.B     #$39,$0008(A6)             ; '9'    | 0C2E 0039 0008
    +001E0    BLS.S      'IBeg 0080 012E DDSS'+001E6         | 6304
    +001E2    MOVEQ      #$00,D0                             | 7000
    +001E4    BRA.S      'IBeg 0080 012E DDSS'+001E8         | 6002
    +001E6    MOVEQ      #$01,D0                             | 7001
    +001E8    UNLK       A6                                  | 4E5E
    +001EA    RTS                                            | 4E75

In much the same way as the first routine, this routine is checking to see if the input byte is a number (if A6+8 is between ‘0’ and ‘9’). If subroutine +001A0 checks for letters and subroutine +001CE checks for numbers, we can deduce structure of a valid serial number given the validation routine at +000D2 (a = alpha; n = number):

a   a   n   n   5   n   n   a   n   n   n   n   L
01  02  03  04  05  06  07  08  09  10  11  12  13

Following this format, let’s restart the application and try serial number: AB12512A1234L. Success!

Conclusion

That’s a basic, but detailed introduction into what it takes to reverse engineer software. After the setup and learning the various MacsBug commands, it’s really just a matter of finding the validation routine and going through it one line at a time. The algorithm here is fairly straightforward, but even in its simplicity, it still requires careful consideration. Did you notice that the trailing ‘L’ must be uppercase (+0018E)? Future tutorials in this series will not only look at much more complicated routines, but also some of the clever ways that they are hidden.