Let's learn something about the innards of new Borland's programming
tools.
This knowledge will allow us to speed up cracking sessions, as will
teach shareware programmers who use Delphi to be more careful and not
to
happily expose their "secrets" to +curious eyes B)
I'm serious about this. If you are a Delphi programmer you'll find
the way to prevent your apps from being cracked (using this method)
at the bottom of this text.
VCL stands for "visual component library", a library used by recent Borland visual languages as Delphi and BC++ Builder.
These environments use a proprietary resource format, that appear as "RCDATA" when listed by Resource Workshop.
This resources contain "forms". In Delphi jargon, forms are the
windows
of the program. All the info about their design is stored there.
When a typical Delphi app is starting, the initialisation code creates
the forms, loading the required information from the resources.
Sometimes this loading is deferred - forms that aren't used very often
are created and destroyed as needed.
This system is the best and the worst of Delphi.
It allows a very fast way of programming but, for full-length apps,
it can slow down the loading.
The really interesting part of this information is that the address of the routines -called in response to user interactions with the elements of the form- are bound at run time by name. So knowing these names we can find the appropriate addresses.
If you have cracked any Delphi apps, you have surely experienced the
long
chain of calls inside the library, from the breakpoints on the API
calls to
the "do something" code.
I hoped that these addresses could help in pinpointing the relevant
code.
+ReZiDeNt suggested me to test this method on a "real world" program:
Flexed, an HTML editor written in Delphi 2.
You can download it from its
home page.
About the program: I haven't used it. Like Fravia+, I prefer to edit
html
in plain text, but now you'll will be able to evaluate for as long as
you
need to.
Remember that if you find it handy, you should pay for it.
These guys are not M$, just a small company, and probably need
your support to get their bills paid.
I installed it, without looking for registry or file changes O:)
Later, I saw that it creates a "C:" key under HKCU and a "flini" file
in the Windows directory.
It must also put another mark on the disk, as it's not
possible to reinstall it after just deleting the files/key mentioned
above.
You can use RegMon and FileMon and find out what happens. I'm not
interested
since I cracked it completely, as you are about to see :=)
The weeks passed and I hadn't had the time to work on it... when I started it, I found a nasty "Your evaluation period has expired" message :-(
Hands on! You'll need at least SoftIce for W95, an hexadecimal editor
and
a new tool: frmspy.
You can use RWS instead, or you could even just browse the exe with
your
favourite tool, but using frmspy is quicker and clearer.
Using WDAsm is helpful too.
The first step is to gather the information about the target exe with frmspy. You will see the list of forms, and, after double-clicking on them, a reverse engineered text representation of them.
You may be tempted to investigate TVALIDATORDLG, the form where the
user name and registration key is input. But all you'll find is a mere
dialog.
The real work is accomplished from its caller: TSPLASHFORM.
This is the nag window that appears at the beginning of the program,
as
well as when it's shutting down and from the help->about menu.
You can select TSplashForm and look at the text representation of it. A lot of information about the buttons and labels will appear. Let's concentrate on the following part, near the end.
object RegButton: TButton Left = 200 Top = 176 Width = 97 Height = 25 Caption = 'Register' TabOrder = 1 OnClick = RegButtonClick end
What's that? This is the button with the caption "Register". You can see its size, position... and something with a suggestive name: "OnClick". "OnClick" tell us the name of the routine invoked when the user presses this button. Once we have the name (yes, "nomen est omen" :) we can search for the address of this routine. This is because the routine is bound to the button at run time by name.
Using Hex Workshop, I looked for "RegButtonClick" and I found it twice. The second occurrence is the resource itself, the first is within an address table:
000A4990 ____ ____ ____ BC57 4A00 0E52 6567 4275 ______.WJ..RegBu 000A49A0 7474 6F6E 436C 6963 6B__ ____ ____ ____ ttonClick_______
Now look at the magic numbers before the name. There is a byte ('0E') indicating the length of "RegButtonClick" (14 characters) and before that an address: 004ABC57.
WDasm seems to think that file is too long and it doesn't disassemble this portion of the exe correctly - however, with Softice we can bpx on this and... right! It stops at the point just when we push the button.
A couple of instructions forward you'll find a CALL. Tracing into it you'll find a "standard stack frame" in 44ECC8:
0044ECC8 55 push ebp 0044ECC9 8BEC mov ebp, esp ...
This is the kind of thing expected at the beginning of a high level routine, made by the application programmer. We have avoided the whole chain of library calls through the VCL from Windows notifications, and landed in the right place!
From this point, there are some calls you can easily test by setting breakpoints on them - you'll find that their purpose is to show the dialog asking for the user name and registration key. Then, the key is calculated from the user name and compared with the one the user entered.
You can enter the name you choose, and anything as the key, after BPXing 44ED69. Here, a call to a routine compares two strings. D EDX will show the fake key you entered and D EAX will show the correct calculated key. Easy, isn't it? A ten minute crack by a beginner!!
Err... I'm just learning to use SoftIce so I was tempted to stop here. But, no!! let's drink one of my special whisky cocktails. I don't know whether it's more similar to vodka-martini or Wafna's dry kerosene... but it worked :*)
The call to the encryption algorithm is a little before, in 44ED58.
I think the following listing is auto-commented enough. You shouldn't have any problem understanding it, as I'll explain the meaning of each library call from it.
It begins by aligning the stack, making room for local variables.
:44E714 push ebp ... ... :44E72D mov dword ptr [ebp-4], eax32 bit Delphi has a powerful and complex string handling system. It doesn't makes copies of the string if it's unnecessary. The following call increments the reference counter for the string with the user name.
:44E730 mov eax, dword ptr [ebp-8] :44E733 call 4039A6 ; Inc ref. counterI'm not sure what's the purpose of this bit is. I suspect it's related to the structured handling of exceptions (try-except or try-finally blocks) as it pushes an alternative address for RET.
:44E738 xor eax, eax :44E73A push ebp :44E73B push 44E858 :44E740 push dword ptr fs:[eax] :44E743 mov dword ptr fs:[eax], espThis checks that the user name is not empty. If it is, it goes away.
:44E746 cmp dword ptr [ebp-8],0 :44E74A je 44E826 :44E750 lea eax, dword ptr [ebp-10] :44E753 call 403680 ; Clears this string :44E758 mov eax, dword ptr [ebp-8] :44E75B call 4037F4 ; Length of user name? :44E760 mov ebx, eax ; Store it in ebxTesting length again?
:44E762 test bl, bl :44E764 jbe 44E7AEStrings indexes begin with 1
:44E766 mov [ebp-9],1This loop filters the string, eliminating all characters except letters and digits.
Load a char and convert it to uppercaseLOOP:
:44E76A movzx esi, byte ptr [ebp-9] :44E76E mov eax, dword ptr [ebp-8] :44E771 mov al, byte ptr [eax+esi-1] :44E775 call 402A20 ; To uppercaseFilters the character, leaving only letters and digits
:44E77A add al, D0 :44E77C sub al, A :44E77E jb 44E786 :44E780 add al, F9 :44E782 sub al, 1A :44E784 jnb 44E7A7Redundant, if the character is "valid", adds it to a new string.
:44E786 mov eax, dword ptr [ebp-8] :44E789 mov al, byte ptr [eax+esi-1] :44E78D call 402A20 ; To uppercase :44E792 mov edx, eax :44E794 lea eax, dword ptr [ebp-1C] :44E797 call 403790 ; Convert char to string :44E79C mov edx, dword ptr [ebp-1C] :44E79F lea eax, dword ptr [ebp-10] :44E7A2 call 4037FC ; Adds to the end. :44E7A7 inc [ebp-9] :44E7AA dec bl :44E7AC jne 44E76A ;more?Now it starts the pseudo-random number generator. As it needs a consistent result every time it's called, the "seed" is set directly to a fixed number, taken from the address pointed by [ebp-4]. The value is 0EEEEH.END LOOP
:44E7AE mov eax, dword ptr [ebp-4] :44E7B1 mov dword ptr [4AE02C], eax :44E7B6 lea eax, dword ptr [ebp-14] :44E7B9 call 403680 ; Clears a stringGet the length of filtered string
:44E7BE mov eax, dword ptr [ebp-10] :44E7C1 call 4037F4 ; Length :44E7C6 mov byte ptr [ebp-15], alPrepare the new loop. Result key must be 12 chars length.
:44E7C9 mov [ebp-A], 0 :44E7CD mov [ebp-9], CRandomly select one of the characters of the filtered string. EAX selects on entry the range of result.
- LOOP:
:44E7D1 xor eax, eax :44E7D3 mov al, byte ptr [ebp-15] :44E7D6 call 402B94 ; Random numberFrom high level, strings indexes begin with 1
:44E7DB inc eax :44E7DC and eax, FFNow use this character as an inner loop counter to call random routine again.
:44E7E1 mov edx, dword ptr [ebp-10] :44E7E4 mov bl, byte ptr [edx+eax-1] :44E7E8 test bl, bl :44E7EA jbe 44E7FDRange in EAX. 0AH as it's meant to be a digit.
- LOOP:
:44E7EC mov eax,A :44E7F1 call 402B94 ; Random number :44E7F6 mov byte ptr [ebp-A], al :44E7F9 dec bl :44E7FB jne 44E7ECTake the last generated random number. Convert it to string.END LOOP.
:44E7FD lea edx, dword ptr [ebp-1C] :44E800 xor eax, eax :44E802 mov al, byte ptr [ebp-A] :44E805 call 406354 ; To stringConcatenate the strings.
:44E80A mov edx, dword ptr [ebp-1C] :44E80D lea eax, dword ptr [ebp-14] :44E810 call 4037FC ; Concatenation :44E815 dec [ebp-9] :44E818 jne 44E7D1 ; more?Copy string with the keyEND LOOP
:44E81A mov eax, edi :44E81C mov edx, dword ptr [ebp-14] :44E81F call 403714 ; Copy string ...The rest is just some cleanup code.
Well, once we know how the key is calculated from the asm code, how
about
trying to figure out what it looks like in Delphi?
This is a key generator written in Delphi itself.
If you want to translate it to other language, take a look at 402B94, the source of random number generator. Remember the "seed" must be initialised to 0EEEEH.
function GuessKey( UserName: string ): string; var n: Integer; IterateRandom: Integer; Calculated: Integer; Selected: Integer; Filtered: string; begin UserName := UpperCase( UserName ); Filtered := ''; for n := 1 to Length( UserName ) do if UserName[ n ] in ['0'..'9','A'..'Z'] then Filtered := Filtered + UserName[ n ]; Result := ''; if Filtered = '' then Exit; RandSeed := $EEEE; for n := 1 to 12 do begin Selected := Ord( Filtered[ Succ( Random( Length( Filtered ) ) ) ] ); for IterateRandom := 1 to Selected do Calculated := Random( 10 ); Result := Result + IntToStr( Calculated ); end; end;
How this way of cracking can be avoided?
Easy: don't use automatic methods created by double clicking on the button or the object inspector. Write your code somewhere else in your program, preferably in another module, and bind it to the button using code such as:
RegButton.OnClick := RegButtonClick;
Of course you'll need to enter this code after the form is created and before it's called. Best if it's rounded by a lot of unrelated stuff.
This won't necessarily prevent your program from being cracked of course, but things will not be as easy as you have seen above O:)
Thanks to +ReZiDeNt for his help and encouragement ;)
...and of course +ORC and Fravia+ :)
(c) +trurl All rights reversed