Professional Documents
Culture Documents
Serial Protection
Disclaimer
This essay, as all of KGL’s essays, is for informational purposes only. The KGL and
its members are not responsible for any action (and its result) that will be taken as a
result of reading this essay (the responsibility thus falls on the reader alone), and
provides no warranty as to the accuracy or completeness of the information presented
herein.
Neither the KGL, nor any of its members endorse illegal activities, and the readers are
not permitted to use any of the information contained in this essay for illegal activities
(directly or indirectly). Consequently, every identifying detail was stripped from the
program’s code whenever possible (although the essay is written as though you have
the program). You should buy every commercial program that you use.
You may freely distribute this essay as long as you leave it intact. You may reproduce
portions of it only with explicit permission from the KGL.
By continuing on reading you implicitly agree to comply with this disclaimer.
Introduction
In this essay we’ll show how very talented programmers (and they are) fail to protect
their software satisfactorily (it seems as though they only protected it because they
were ordered to do so). During this process, we’ll start exploring IDA, and see for
ourselves some of the features that make it so powerful. We’ll conclude by listing
some of the mistakes made by the programmers.
Prerequisites
-1-
Copyright 2001 KGL All Rights Reserved
SoftICE
IDA
RegMon
Upon running the program, we are greeted with a splash screen that also indicates the
number of days left to use the program, and contains three buttons: Trial, Register and
Help.
Clicking the Trial button sends us right to the program’s main window, where we can
freely use the software, except for one function, which shows a message box telling us
we should register the program.
Clicking the Help button shows us how we can register, and tells us something about
the limitations we’ll encounter while using the trial version. This info may help us, so
let’s read it closely. During the trial period, we’re entitled for 15 days of using the
software, with only one disabled function. The program itself should provide us with a
serial number, and by sending it to the company (and paying the appropriate fees)
we’ll get the program’s unlock code. The serial number is said to be unique for every
computer on which the program is installed.
All we have left now is to click the Register button. Indeed, we get a dialog box with
a given serial number, and a place to enter our unlock code. Enter any number in the
text box, and press the OK button. If you weren’t very lucky, you got a message
saying “You have entered an invalid code. Please try again.”, and got back to the code
entering dialog (which is not such a clever idea, since it’s highly traceable in the
program’s code). Write this error message down.
Let’s check just one more thing – close the program and launch RegMon, then start
the program again. Sure enough, we can see that the program searches for the key
HKCU\Software\CompanyName\ProgramName\Registration\Unlock Code and
doesn’t find it.
-2-
Copyright 2001 KGL All Rights Reserved
Serial Killing
Load the program into IDA, choose to analyze it as a PE file, and wait for the analysis
to complete. Remember to save the program’s database each time you make some
modifications to it, since even a simple modification can be a result of hours of work.
As we learned before, the error message might prove to be very useful. Let’s search
for it. Choose View->Names and in the names window, start typing aYouHave (all
string references in IDA begin with the letter ‘a’). The highlighted line should now
contain “aYouHaveEntered” as the name, and 004790C4 as the address. Double-click
the highlighted line to get to the actual string in the file:
004790C4 aYouHaveEntered db 'You have entered an invalid code. Please try again.',0
004790C4 ; DATA XREF: sub_411230+342↑o
Just above and below this string’s definition, we can see other interesting strings in
the file (telling us that we’ve successfully registered the product, or that the trial
period has expired etc.), but we’ll leave them alone (other approaches might use them
though).
We can tell that this string is being referenced only in one place, 342 bytes into the
subroutine, which begins at address 0x411230 (which sums up to address 0x411572).
In order to quickly jump to this code location, double-click the text sub_411230+342.
Another cross reference. Double-click it so we could see why we have landed here.
-3-
Copyright 2001 KGL All Rights Reserved
See how IDA’s FLIRT signatures come in handy now. Instead of having a bogus call
at address 0x00411541, we can see the real function name (it’s from the C Run-Time
Library – notice that the caller is responsible for restoring the stack after the local
variables are allocated). Here’s an excerpt from the MSDN library:
So what we have here is a simple string comparison, and if the results don’t match, we
get an error message. This probably means that the arguments for this function are the
unlock code that we’ve entered, and the real unlock code (or perhaps a hash of each of
them).
We know that the function gets two strings as arguments, and the code above tells us
that the pointers to these strings lie in EDX and EAX just before the call. It’s time to
launch SoftICE and test our assumption. Load the program into SoftICE and after
setting the appropriate address context, write BPX 41153F and let the program run.
When you get the splash screen, hit the Register button. Write down your serial
number in case we stumble onto it in the code, and enter 19375 as your unlock code.
Press the OK button, and land into SoftICE at address 0x0041153F. It’s time to check
the registers to see what strings they point at. After writing DB EDX and DB EAX we
find out that EDX points to the real unlock code and EAX points to our unlock code
(we know that EDX points to the real unlock code and not to a hash of it because it is
being compared to our unlock code as we’ve entered it).
Basically, this was all we needed if we were only interested in the unlock code. Since
we are not, let’s dig in a bit further. It’s time to return to IDA and mark our findings.
Go to address 0x00411539 and hit the (semi)colon key to add a (repeatable-)comment
to this line (this comment should reflect the fact that EAX now holds the unlock code
that we’ve entered). Similarly, add a comment to the next line too. Notice that the line
at address 0x0041153B refers to a local variable (var_300) of the function we’re in
(sub_411230). As we’ve already seen, this variable holds the real unlock code, as a
string, so let’s change the name of the variable to reflect this. Double-click the text
var_300 and a window titled “Stack of sub_411230” should open. Since you’ve
double-clicked var_300, it should appear at the window’s top. Right click it, and
-4-
Copyright 2001 KGL All Rights Reserved
choose Rename. Rename it to strRealCode and close the stack window. Observe the
change in code, as IDA now refers to this variable as “strRealCode”.
Having changed the variable’s name, a few code lines just above the lines we’ve just
analyzed draw our attention:
We see that a function is called with two arguments, one of them is the variable that
should hold the real unlock code, and the other one is unknown at this time. The code
to compare the real unlock code and our unlock code comes directly after the call to
this function.
Let’s see what this function does. Move to SoftICE, clear the previously set
breakpoint, and set a breakpoint on address 0x00411532 (don’t forget to match the
address context first). Run the program, write 19375 as the unlock code, and press the
OK button. SoftICE breaks at address 0x00411532, and we want to check the
function’s arguments. Looking at EAX in the registers window makes us speculate
that it holds a pointer to somewhere, so off we go with a DB EAX, and indeed, we see
that it points to the serial number that the program gives us. Writing DB ECX
however, doesn’t produce anything interesting. It seems as though the string hasn’t
been initialized yet to hold the real unlock code. Summarizing the last few findings,
we get the following code:
-5-
Copyright 2001 KGL All Rights Reserved
strRealCode = garbage;
sub_404E10(strSerial, strRealCode);
if (_mbsicmp(strRealCode, strOurCode) != 0)
show_error_msg();
We can only draw one conclusion from this – sub_404E10 generates the real unlock
code using the given serial number. Let’s analyze it in parts:
The first three lines comprise the function’s prolog. Right after them we see that some
parameters are pushed onto the stack, and the function strncpy is called. The last line
cleans the local variables of strncpy. Remembering that arg_0 is actually strSerial, we
get the command strncpy(strSerial + 1, var_10, 8), which copies the last eight
characters (since the serial number only had nine characters) of the serial number to
the location pointed to by var_10. A look at the function’s stack confirms that var_10
is an eight-byte array (no place for a null byte, so all the functions that reference it
must use some form of length limitation).
Let’s move on to the second part of this function, and see what happens there:
-6-
Copyright 2001 KGL All Rights Reserved
The function initializes var_8 to zero (here’s our terminating null), and then issues the
command sscanf(var_10, “%08x”, var_4), which effectively translates the string at
var_10 to a number and stores it in var_4.
Here’s the next part of this function:
The last five lines are the function’s epilog, and the lines that precede them execute
the command sprintf(strRealCode, “%04x”, var_4), which transforms the number in
-7-
Copyright 2001 KGL All Rights Reserved
Patching
Let’s see what can be done without using the unlock code. Obviously, just patching
the conditional jump at address 0x0041154B won’t help much, since the program
checks the registry key “Unlock Code” on startup. Perhaps if we can find this check
and bypass it, we will fool the protection scheme (evidently the protection scheme is
very weak, so perhaps there’s no more than one check).
In IDA, go to View->Names->aUnlockCode to get the following:
As we can see, there are only two references to this string, and both of them originate
from the same function (and it’s the same function that we’ve partly analyzed in the
previous section). The second reference is at address 0x00411550 (411230 + 320),
and it comes right after the program compares our unlock code with the correct
unlock code (if you didn’t jump to the error message). Since the program reaches this
line of code only when the unlock code we’ve entered is correct (hence only after
we’ve entered it), this is not the check to see if the program is already registered, but
rather some code to write the unlock code we’ve entered, into the registry.
Since you should know what you’re doing by now, here are the interesting
(commented) code lines from the first reference:
-8-
Copyright 2001 KGL All Rights Reserved
As we can see, the program uses an internal flag to indicate whether it has been
registered or not. When this flag is cleared, the program is considered to be registered
(this is backed up by several other references to this flag throughout the function) and
the function returns TRUE (a value of 1).
By tracking the code’s optional execution paths we can conclude that clearing the flag
in the first place (instead of setting it) will trick the program into believing it’s already
been registered. This can easily be done by replacing EBP with EBX at address
0x0041129F (since EBX is constantly zero). Note that changing EBP at address
-9-
Copyright 2001 KGL All Rights Reserved
0x0041129A will change the function’s return value, thus it may not lead to the
wanted results.
Conclusions
We’ve witnessed a program with a very poor protection scheme. Let’s list the
programmer’s mistakes:
1. Showing the error message immediately after the comparison. The error
message is the most obvious mark to start searching from. Finding the
validation routine shouldn’t be so easy.
2. Returning to the registration dialog box after showing the error message.
Although we haven’t used it here, this is a clear split of the execution paths
(for a valid/invalid unlock code), which can be traced easily.
3. Saving the error message as a simple string reference. This makes the job of
finding the error message in code much easier. The string should have been
scrambled when saved to the file, and unscrambled again only when showing
the message box.
4. Comparing the strings in plaintext. This gives you the correct unlock code
without much effort on your behalf. A better solution would be to save a hash
of the real unlock code, hash the user’s unlock code, and compare the results.
5. Using a very simple algorithm to generate the unlock code (in addition to
using common library functions). This makes the job of making a code
generator much simpler.
6. Using a simple flag to determine if the program is registered or not. Only one
byte of code needs to be patched, and that says it all.
- 10 -
Copyright 2001 KGL All Rights Reserved
Extra Credit
- 11 -