You are on page 1of 11

Copyright 2001 KGL All Rights Reserved

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

Read the essay “Simple RE Techniques”.

-1-
Copyright 2001 KGL All Rights Reserved

Tools We Will Use

SoftICE
IDA
RegMon

Know Your Enemy

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.

00411570 loc_411570: ; CODE XREF: sub_411230+31B↑j


00411570 push ebx
00411571 push ebx
00411572 push offset aYouHaveEntered ; "You have entered an invalid
code. Pleas"...

Another cross reference. Double-click it so we could see why we have landed here.

00411539 mov eax, [edi]


0041153B lea edx, [esp+328h+var_300]
0041153F push edx
00411540 push eax
00411541 call __mbsicmp
00411546 add esp, 10h
00411549 test eax, eax
0041154B jnz short loc_411570

-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:

_wcsicmp and _mbsicmp are wide-character and multibyte-character versions


of _stricmp. The arguments and return value of _wcsicmp are wide-character
strings; those of _mbsicmp are multibyte-character strings. _mbsicmp
recognizes multibyte-character sequences according to the current multibyte code
page and returns _NLSCMPERROR on an error. (For more information, see Code
Pages.) These three functions behave identically otherwise.

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:

0041152B mov eax, [ebp+0]


0041152E lea ecx, [esp+320h+strRealCode]
00411532 push ecx
00411533 push eax
00411534 call sub_404E10

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:

0041152B mov eax, [ebp+0] ; Serial number


0041152E lea ecx, [esp+320h+strRealCode] ; Destination address
00411532 push ecx
00411533 push eax
00411534 call sub_404E10 ; Generate real unlock code
00411539 mov eax, [edi] ; Our unlock code
0041153B lea edx, [esp+328h+strRealCode] ; Real unlock code
0041153F push edx
00411540 push eax
00411541 call __mbsicmp ; Compare the codes
00411546 add esp, 10h
00411549 test eax, eax ; Are they equal?
0041154B jnz short loc_411570 ; If not, show an error message

-5-
Copyright 2001 KGL All Rights Reserved

And its abstraction:

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:

00404E10 push ebp


00404E11 mov ebp, esp
00404E13 sub esp, 10h
00404E16 push 8
00404E18 mov eax, [ebp+arg_0]
00404E1B add eax, 1
00404E1E push eax
00404E1F lea ecx, [ebp+var_10]
00404E22 push ecx
00404E23 call _strncpy
00404E41 add esp, 0Ch

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).

00404E10 var_10 = byte ptr -10h


00404E10 var_8 = byte ptr 8
00404E10 var_4 = dword ptr -4

Let’s move on to the second part of this function, and see what happens there:

00404E2B mov [ebp+var_8], 0


00404E2F lea edx, [ebp+var_4]
00404E32 push edx

-6-
Copyright 2001 KGL All Rights Reserved

00404E33 push offset a08x ; "%08x"


00404E38 lea eax, [ebp+var_10]
00404E3B push eax
00404E3C call _sscanf
00404E41 add esp, 0Ch

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:

00404E44 mov ecx, [ebp+var_4]


00404E47 xor ecx, 715A98C1h
00404E4D mov [ebp+var_4], ecx
00404E50 mov edx, [ebp+var_4]
00404E53 shr edx, 5
00404E56 mov eax, [ebp+var_4]
00404E59 shl eax, 1Bh
00404E5C add edx, eax
00404E5E mov [ebp+var_4], edx

This part equals the following piece of pseudo-code:

var_4 = var_4 xor 0x715A98C1;


var_4 = (var_4 shr 5) + (var_4 shl 0x1B);

Let’s move on to the last part of the function:

00404E61 mov ecx, [ebp+var_4]


00404E64 push ecx
00404E65 push offset a04x ; "%04x"
00404E6A mov edx, [ebp+arg_4]
00404E6D push edx
00404E6E call _sprintf
00404E73 add esp, 0Ch
00404E76 xor eax, eax
00404E78 mov esp, ebp
00404E7A pop ebp
00404E7B retn

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

var_4 to a string and puts it in strRealCode (remember that arg_4 is actually


strRealCode).
We now know all we need in order to transform a serial number into an unlock code,
and make our own unlock code generator.
Just to make the database more complete, choose View->Functions, right-click on the
function sub_404E10, choose Edit… and change the function’s name to CodeGen.

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:

00479014 aUnlockCode db 'Unlock Code',0 ; DATA XREF: sub_411230+29↑o


00479014 ; sub_411230+320↑o

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:

00411256 xor ebx, ebx ; EBX is constantly 0


00411258 push ebx
00411259 push offset aUnlockCode ; "Unlock Code"
0041125E push offset aRegistration ; "Registration"
00411263 lea eax, [esp+32Ch+var_2EC]

-8-
Copyright 2001 KGL All Rights Reserved

00411267 push eax


00411268 mov ecx, esi
0041126A call sub_461DE0 ; These three calls
0041126F lea edi, [esi+110h]
00411275 push eax
00411276 mov ecx, edi
00411278 mov [esp+324h+var_4], ebx
0041127F call sub_447124 ; should get the unlock code
00411284 lea ecx, [esp+320h+var_2EC]
00411288 mov [esp+320h+var_4], 0FFFFFFFFh
00411293 call sub_447037 ; from the registry
00411298 mov ecx, [edi]
0041129A mov ebp, 1 ; EBP is constantly 1
0041129F mov [esi+114h], ebp ; Set a boolean flag
004112A5 cmp [ecx-8], ebx
004112A8 jz short loc_4112D5
004112AA mov eax, [esi+10Ch] ; Serial number
004112B0 lea edx, [esp+320h+strRealCode] ; Destination address
004112B4 push edx
004112B5 push eax
004112B6 call CodeGen ; Generate the real unlock code
004112BB mov eax, [edi] ; Unlock code from registry
004112BD lea ecx, [esp+328h+strRealCode] ; Real unlock code
004112C1 push ecx
004112C2 push eax
004112C3 call __mbsicmp ; Compare the codes
004112C8 add esp, 10h
004112CB test eax, eax ; Do they match?
004112CD jnz short loc_4112D5 ; If not, don't clear the flag
004112CF mov [esi+114h], ebx ; Clear the flag
004112D5
004112D5 loc_4112D5: ; CODE XREF: sub_411230+78↑j
004112D5 ; sub_411230+9D↑j
004112D5 cmp [esi+114h], ebx ; Is the flag cleared?
004112DB jnz short loc_4112E4 ; No, continue with the other checks
004112DD mov eax, ebp ; Set return value to 1 (TRUE)
004112DF jmp loc_4116C2 ; Go to the function's end

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

Find out how the serial number is generated.

- 11 -

You might also like