- (Link now to a download for v8.0), the authors homepage appears
to be dead.
Welcome once again. In previous serial # tutorials I've shown you how to quickly crack programs by finding the error message and then backtracing through the disassembly to reverse the necessary (yet all to often) single jump. In some cases its been a requirement to trace the protection_routine CALL and patch that, but in either case the idea has been simple and any casual cracker with a modicum of intelligence would easily beat 60% of today's software.
Although I've done it in many tutorials, patching some instructions to circumvent a serial # protection is not "real" reverse engineering. I've chosen APP LAUNCHER because it looks as if patching would be a fairly cumbersome process (a lot of jumps to reverse), so without further ado lets whip out W32Dasm and hunt down the following code.
:00411A4B CALL 00427050 <-- Call great protection
:00411A50 TEST EAX,EAX <-- Test EAX.
:00411A52 JZ 00411AC9 <-- Jump_bad_registration_code.
Simple reversing of this JZ or an equivalent patch (say NOP's) ensures that any code returns the good_guy message box, but there is a snag, the program uses a registry key to store the good code and on subsequent restarting verification fails. The result is that 00427050 will have to be traced/patched regardless. You should see that EAX must be returned !=0 and as we've seen countless times, bad guy i.e. EAX=0 will probably be achieved using XOR EAX,EAX.
As I indicated earlier, patching this function would be a painful and unprofessional process, so I'll highlight the important pieces of code instead (note that we wont be needing to SoftICE).
:0042709E CMP DWORD PTR [EAX-08], 15 <-- Check length
of [EAX-08] for 15h.
:004270A2 JZ 004270C9 <-- Jump_good_else_XOR_EAX_@004270B5.
This length check ought to be elementary, 15h = 21 decimal. So it seems our good code must be of length 21 or we'll get thrown out. Lets follow the code further.
:004270C9 CMP BYTE PTR [EAX], 41 <-- Check 1st digit.
:004270CC JZ 004270F3 <-- Jump_good_else_XOR_EAX_@004270DF.
:004270F3 CMP BYTE PTR [EAX+01], 4C <-- Check 2nd digit.
:004270F7 JZ 0042711E <-- Jump_good_else_XOR_EAX_@0042710A.
:0042711E CMP BYTE PTR [EAX+0C], 21 <-- Check 13th digit.
:00427122 JZ 00427149 <-- Jump_good_else_XOR_EAX_@00427135.
:00427149 MOV DL, BYTE PTR [EAX+02] <-- DL moved to 3rd digit.
:0042714C MOV CL, 2D <-- CL moved to 2D (hyphen).
:0042714E CMP DL,CL <-- Compare.
:00427150 JZ 00427177 <-- Jump_good_else_XOR_EAX_@00427163.
:00427177 CMP BYTE PTR [EAX+0B], CL <-- Check 12th digit.
:0042717A JZ 004271A1 <-- Jump_good_else_XOR_EAX_@0042718D
:004271A1 CMP BYTE PTR [EAX+10], CL <-- Check 17th digit.
:004271A4 JZ 004271CB <-- Jump_good_else_XOR_EAX_@004271B7.
:004271CB CMP BYTE PTR [EAX+05], 78 <-- Check 6th digit.
:004271CF JZ 004271F6 <-- Jump_good_else_XOR_EAX_@004271E2.
O.K, lets take a breather, as you can see there are 7 checks here (in addition to the length check we saw earlier), although fairly trivial checks a crack which patched some instructions and then said insert serial # blah to pass these checks would indeed be frowned upon. So from this code lets build up an initial code matrix that would pass these checks.
AL-zzxzzzzz-!zzz-zzzz - where z is currently unknown.
Lets continue onwards.
:004271F6 MOV ESI, 03 <-- ESI will now be used as
a loop control variable.
:00427201 MOV AL, BYTE PTR [ESI+EAX] <-- AL points at 4rth digit.
:00427204 PUSH EAX <-- Stack it.
:00427205 CALL 0042C220 <-- Your going to see this call a few times.
:0042720D TEST EAX,EAX <-- Test EAX.
:0042720F JZ 0042734C <-- Bad_jump.
:00427215 INC ESI <-- Increase loop.
:00427216 CMP ESI, 04 <-- Check ESI for 4.
:00427219 JLE 004271FD <-- Loop again.
The first loop in our protection will execute twice (ESI has to reach 5), it checks obviously digits 4 and 5 of our code, the magic being worked beneath CALL 0042C220 which must return EAX non-zero. You need only trace this call once to work out the pass criteria (in fact you may even be able to see it from the disassembly String Reference *smile*), remember that I'm offering 2 grade 'A's to anyone working this section out. Be sure also to remember 0042C220 as you are going to see it again for sure.
We move further on into the verification code, once again look how ESI is used to access various positions of our code.
:0042721B MOV ESI, 06 <-- 7th digit now.
:00427224 MOV DL, BYTE PTR [ESI+ECX] <-- DL now holds 7th digit.
:00427228 CALL 0042C220 <-- Needless to say we've seen this before.
:00427230 TEST EAX,EAX <-- Ditto and this.
:00427232 JZ 0042734C <-- Ditto and this as well.
:00427238 ADD ESI, 04 <-- Add 4 to ESI i.e. shift along the code.
:0042723B CMP ESI, 0A <-- Check ESI for 0Ah 10dec.
:0042723E JLE 00427220 <-- Loop.
This loop executes twice and requires gentle analysis, the first pass checks the 7th digit where as the 2nd pass (after ADD ESI, 04) will check the 11th - you already know what 0042C220 desires so fixing your input should be fairly easy. Rather than totally lead you through the code, 1 more section (virtually identical) to the 2 you've already seen checks 2 more positions of the code.
The next snippet now checks position 8 of the code, again using ESI as the pointer.
:00427265 MOV ESI, 07 <-- Prepare to point at 8th
:0042726E MOV AL, BYTE PTR [ESI+EDX] <-- Use AL.
:00427272 CALL 0042C390 <-- Another magic function.
:0042727A TEST EAX,EAX <-- Which must return EAX!=0.
:00427283 CMP ESI, 09 <-- So we are going to check positions 8,9 & 10.
:00427286 JLE 0042734C <-- Loop_a_bit_more.
This code style ought to be fairly familiar by now, however 0042C390 works some slightly different magic, yet unfortunately the String Reference would seem to give away the surprise. Remember this function as you step because the program checks other positions with exactly the same criteria. At the end of this there are yet more checks.
:004272E4 MOV DL, BYTE PTR [EAX+03] <-- 4rth digit.
:004272E7 MOV CL, BYTE PTR [EAX+0D] <-- 14th digit.
:004272EA CMP DL, CL <-- Compare.
:004272EC JZ 00427313 <-- Jump_good.
In similar fashion, the 8th & 15th digits must be the same as do the 11th & 16th. The final check involves the last 4 digits which should be the only ones which are now unknown. This is the relevant code.
:00427398 CALL 004356B0 <-- Here's_where_the_magic's_done.
:004273AC CMP EDI, 0F <-- Check *something* against 0Fh.
:004273BB JZ 004273D6 <-- Jump_and_MOV_EAX_1_good_guy.
This last function and check which you ought to trace is trickier, the real magic is done a level or so below this call again, but the simpler answer is that the last 4 numbers ASCII values -30h must add up to F (or 15 dec). So 2346 would work as a combination (i.e. 2 + 3 + 4 + 6 = 0F). Thus our final result looks as follows.
My verdict, well this isn't a particularly bad scheme and I can't see any warez cracker wanting to patch it, I fear unfortunately though that the lack of any algorithm will result in a generic serial # appearing on one of those lists I so detest.