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.

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.

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:
- A standalone serial number that conforms to some validation algorithm.
- A serial number generated from a particular dataset, like a name and/or organization.
- 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.

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.

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.

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.

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.