Reverse engineering Advanced Disk Catalog v. 1.1

by Aesculapius
(Redundant instructions: the undiscovered treasure
and a couple of lessons for shareware programmers)

(21 July 1997, slightly edited by Fravia+)


Courtesy of Fravia's page of reverse engineering

Well, Aesculapius show us a whole new direction and cracks a couple of useful tools without even noticing it much... what should I say? An outstanding essay from an outstanding +cracker!

                        Advanced Disk Catalog v. 1.12
          ("Redundant instructions: the undiscovered treasure")
                              By Aesculapius


	There was a time when CPU's were turtles and hard drives smaller than
your RAM capacity is today. Considering this panorama, it's understandable
why assembly coding was so necessary to enhance applications performance;
things have changed though, hardware does not limit today's programmers and it's
possible and desirable to use a more sophisticated high level programming
language instead of pure assembly. These facts have obvious consequences: 
most programs include unnecessary instructions and such thing could represent
an open door for a very well shielded protection scheme. We'll deal in this
essay with debugging and patching unfriendly application. 
ADC 1.12 code is very sensitive to patching procedures. 
Slight changes, for instance JNE to JE, will hang the whole thing, or simply, 
although logic suggests that some change should crack the scheme, you can hold 
your breath and count to one thousand because nothing will happen. 
This is especially true if the changes are carried out in code locations "close" 
to the protection scheme. 
The main validation code resides in a *.DLL file named VALIDATE.DLL; to
produce a more robust anti-crackers strategy, there's a checksum in the main
executable (ADC.EXE) to validate the integrity of VALIDATE.DLL (one
validation over another, interesting!). The scheme code itself inside
VALIDATE.DLL will be executed many times. As you can see, paranoia in
it's maximum expression!
	
	To figure out that the protection scheme was inside VALIDATE.DLL,
Win-eXpose-I/O 95 v. 2.00 (WXI) probed to be a formidable tool. You can
locate it at http://www.sheltef.com/wxi95-20.zip, 1.1 Mb. You should also
get Win-eXpose-Registry 95 v. 1.00 at http://www.sheltef.com/wxr95-10.zip,
1.1 Mb. Both of these applications are excellent additions for our trade.
All Sheltef Solutions products are distributed as a 30 days free trial limit
shareware. This is the snippet of the protection scheme for all products:

:0040619D FF15247B6D00 Call dword ptr [006D7B24]    ; Calculates the valid
                                                    ; password 
:004061A3 83C40C       add esp, 0000000C
:004061A6 8D85CCFEFFFF lea eax, dword ptr [ebp+FECC]; Moves to EAX the
                                                      memory offset
                                                      where the password
                                                      you typed is 
                                                      located
:004061AC 8D8DCCFDFFFF lea ecx, dword ptr [ebp+FDCC]; Moves to ECX the
                                                      memory offset
                                                      where the valid
                                                      password is located

Referenced by a Jump at Address:004061CC(C) ; The following code compares
                                            ; both numbers one byte at a time
:004061B2 8A10                    mov dl, byte ptr [eax]; 
:004061B4 3A11                    cmp dl, byte ptr [ecx]
:004061B6 751A                    jne 004061D2
:004061B8 0AD2                    or dl, dl
:004061BA 7412                    je 004061CE
:004061BC 8A5001                  mov dl, byte ptr [eax+01]
:004061BF 3A5101                  cmp dl, byte ptr [ecx+01]
:004061C2 750E                    jne 004061D2
:004061C4 83C002                  add eax, 00000002
:004061C7 83C102                  add ecx, 00000002
:004061CA 0AD2                    or dl, dl
:004061CC 75E4                    jne 004061B2

	Using Winice, set a breakpoint at CS:4061CC (BPX CS:4061CC), and
execute the following instruction: "D ECX" after winice pops up, the memory
location where the valid password is stored will be revealed. If you're too
lazy, then use this data, Serial Number: AESCULAPIUS, Password: f3e39742.
	Sheltef Solutions programmers should be strong candidates to win the
stupidity award, because all their products use not only the same protection
scheme, but also the same code generation sequence! So this data will
unprotect Win-eXpose-Registry too... you wont believe it: the same password
works over and over for every single program, this is too much!

	Now, in regard to the subject of this essay, ADC 1.12 can be located
at http://www.ecsc.mipt.ru/Elcom/, 1.21 MB, dated June 25 1997.

	To begin this session, load the file VALIDATE.DLL in wdsm85, hit the
search button looking for: "thank you for registering". We land here:

* Referenced by a Jump at Address:1C001354(C)
|
:1C00123F C68500FFFFFF00          mov byte ptr [ebp+FFFFFF00], 00
:1C001246 6800010000              push 00000100
:1C00124B 8D8500FFFFFF            lea eax, dword ptr [ebp+FF00]
:1C001251 50                      push eax

* Possible StringData Ref from Data Obj ->"Enter code:"
                                  |
:1C001252 684060001C              push 1C006040

* Possible StringData Ref from Data Obj ->"Registration"
                                  |
:1C001257 684C60001C              push 1C00604C

* Reference To: USER32.GetFocus, Ord:00F7h
                                  |
:1C00125C FF152C92001C            Call dword ptr [1C00922C]
:1C001262 50                      push eax
:1C001263 E8AA010000              call 1C001412
:1C001268 83F801                  cmp eax, 00000001
:1C00126B 0F85B8000000            jne 1C001329
:1C001271 8D8500FFFFFF            lea eax, dword ptr [ebp+FF00]
:1C001277 50                      push eax
:1C001278 E881020000              call 1C0014FE
:1C00127D 85C0                    test eax, eax
:1C00127F 0F8514000000            jne 1C001299
:1C001285 8D8500FFFFFF            lea eax, dword ptr [ebp+FF00]
:1C00128B 50                      push eax
:1C00128C E847060000              call 1C0018D8 ; Code validation
                                                ; Sequence
:1C001291 85C0                    test eax, eax ; EAX=0 Unreg.
                                                ; EAX=1 Reg.
:1C001293 0F8477000000            je 1C001310   ; Don't change this jump, 
                                                  it won't work.

* Referenced by a Jump at Address:1C00127F(C)
| Possible StringData Ref from Data Obj ->"Registration" ; We want to 
                                                         ; Execute this code
:1C001299 685C60001C              push 1C00605C
:1C00129E E81D090000              call 1C001BC0
:1C0012A3 8985FCFEFFFF            mov dword ptr [ebp+FEFC], eax
:1C0012A9 8D8500FFFFFF            lea eax, dword ptr [ebp+FF00]
:1C0012AF 50                      push eax
:1C0012B0 E8BB0D0000              call 1C002070
:1C0012B5 83C404                  add esp, 00000004
:1C0012B8 50                      push eax
:1C0012B9 6A01                    push 00000001
:1C0012BB 8D8500FFFFFF            lea eax, dword ptr [ebp+FF00]
:1C0012C1 50                      push eax

* Possible StringData Ref from Data Obj ->"Code"
                                  |
:1C0012C2 686C60001C              push 1C00606C
:1C0012C7 8B85FCFEFFFF            mov eax, dword ptr [ebp+FEFC]
:1C0012CD 50                      push eax
:1C0012CE E872090000              call 1C001C45
:1C0012D3 8B85FCFEFFFF            mov eax, dword ptr [ebp+FEFC]
:1C0012D9 50                      push eax
:1C0012DA E84F090000              call 1C001C2E
:1C0012DF 6A40                    push 00000040

* Possible StringData Ref from Data Obj ->"ADC"
                                  |
:1C0012E1 687460001C              push 1C006074

* Possible StringData Ref from Data Obj ->"Thank you for registering!"
                                  |
:1C0012E6 687860001C              push 1C006078

The validation sequence begins at :1C00128C. Lets search for the code 
where this CALL points to: Hit the search button at wdsm85 (or text 
editor) looking for :1C0018D8. We land here:

* Referenced by a CALL at Addresses:1C00128C, :1C001A4D             
                                     ; The validation sequence is activated 
									 ; from two different code locations   

:1C0018D8 55              push ebp
:1C0018D9 8BEC            mov ebp, esp
:1C0018DB 83EC30          sub esp, 00000030
:1C0018DE 53              push ebx
:1C0018DF 56              push esi
:1C0018E0 57              push edi
:1C0018E1 C745D01E000000  mov [ebp-30], 0000001E     ; fixed value
:1C0018E8 C745FC06000000  mov [ebp-04], 00000006     ; fixed value
:1C0018EF 837D0800        cmp dword ptr [ebp+08], 00 ; Is the typed code
                                                       equal to 00? then
                                                       EAX=0 Unreg.
									  
:1C0018F3 0F8507000000    jne 1C001900
:1C0018F9 33C0            xor eax, eax               ; Erase EAX, EAX=0 Unreg.
:1C0018FB E900010000      jmp 1C001A00               ; Return to the CALLer

* Referenced by a Jump at Address:1C0018F3(C)
|
:1C001900 8B4508                  mov eax, dword ptr [ebp+08]
:1C001903 50                      push eax

? Reference To: KERNEL32.lstrlenA, Ord:028Dh ; Get Number of digits of the
                                             ; typed serial number
                                  |
:1C001904 FF15A891001C            Call dword ptr [1C0091A8]
:1C00190A 83F808                  cmp eax, 00000008 ; Is the number you
                                                      typed more or less
                                                      than 8 digits, then
                                                      EAX=0 Unreg.
:1C00190D 0F8407000000            je 1C00191A
:1C001913 33C0                    xor eax, eax      ; Erase EAX
:1C001915 E9E6000000              jmp 1C001A00      ; Return to the CALLer

* Referenced by a Jump at Address:1C00190D(C)
|
:1C00191A 8B45D0                  mov eax, dword ptr [ebp-30]
:1C00191D 50                      push eax

* Possible StringData Ref from Data Obj ->"%02d"
                                  |
:1C00191E 68D460001C  push 1C0060D4
:1C001923 8D45D4      lea eax, dword ptr [ebp-2C]
:1C001926 50          push eax
:1C001927 E8B4080000  call 1C0021E0
:1C00192C 83C40C      add esp, 0000000C
:1C00192F 8B4508      mov eax, dword ptr [ebp+08] ; Moves the code offset
                                                    to EAX
:1C001932 0FBE00      movsx eax, byte ptr [eax];    Moves the first byte
                                                    of the code to EAX
:1C001935 83E830      sub eax, 00000030;            Converts the first
                                                    digit to decimal
:1C001938 8945F8      mov dword ptr [ebp-08], eax;  Moves the decimal 
                                                    value to [ebp-08]
:1C00193B 8B4508      mov eax, dword ptr [ebp+08];  Moves the code offset
                                                    to EAX
:1C00193E 0FBE4001    movsx eax, byte ptr [eax+01]; Moves the second byte
                                                    of the code to EAX
:1C001942 83E830      sub eax, 00000030;            Converts second digit
                                                    to decimal
:1C001945 8945F4      mov dword ptr [ebp-0C], eax;  Moves the decimal
                                                    value of the second
                                                    digit to [ebp-0c] and
                                                    so on with the rest
                                                    of the 8 digits
:1C001948 8B4508      mov eax, dword ptr [ebp+08]
:1C00194B 0FBE4002    movsx eax, byte ptr [eax+02]
:1C00194F 83E830      sub eax, 00000030
:1C001952 8945F0      mov dword ptr [ebp-10], eax;  third decimal value
                                                 ;  in [ebp-10]
:1C001955 8B4508      mov eax, dword ptr [ebp+08]
:1C001958 0FBE4003    movsx eax, byte ptr [eax+03]
:1C00195C 83E830      sub eax, 00000030
:1C00195F 8945EC      mov dword ptr [ebp-14], eax;  fourth in [ebp-14]
:1C001962 8B4508      mov eax, dword ptr [ebp+08]
:1C001965 0FBE4004    movsx eax, byte ptr [eax+04]
:1C001969 83E830      sub eax, 00000030
:1C00196C 8945E4      mov dword ptr [ebp-1C], eax;  fifth in [ebp-14]
:1C00196F 8B4508      mov eax, dword ptr [ebp+08]
:1C001972 0FBE4005    movsx eax, byte ptr [eax+05]
:1C001976 83E830      sub eax, 00000030
:1C001979 8945E0      mov dword ptr [ebp-20], eax;  sixth in [ebp-20]
:1C00197C 8B4508      mov eax, dword ptr [ebp+08]
:1C00197F 0FBE4006    movsx eax, byte ptr [eax+06]
:1C001983 83E830      sub eax, 00000030
:1C001986 8945DC      mov dword ptr [ebp-24], eax;  seventh in [ebp-24]
:1C001989 8B4508      mov eax, dword ptr [ebp+08]
:1C00198C 0FBE4007    movsx eax, byte ptr [eax+07]
:1C001990 83E830      sub eax, 00000030
:1C001993 8945D8      mov dword ptr [ebp-28], eax;  eighth in [ebp-28]
:1C001996 8B45F8      mov eax, dword ptr [ebp-08];  Moves first digit to
                                                    EAX
:1C001999 2B45D8      sub eax, dword ptr [ebp-28];  first-eighth
:1C00199C 8B4DF4      mov ecx, dword ptr [ebp-0C];  Moves second digit to
                                                    ECX
:1C00199F 2B4DDC      sub ecx, dword ptr [ebp-24];  second-seventh
:1C0019A2 0FAFC1      imul eax, ecx         ; (first-eighth)*(second-seventh)
:1C0019A5 8945E8      mov dword ptr [ebp-18], eax;  Result stored in 
                                                    [ebp-18]
:1C0019A8 8B45E8      mov eax, dword ptr [ebp-18] ; Moves result to EAX
:1C0019AB 99          cdq               ; Moves high order bits of EAX to EDX
:1C0019AC 33C2        xor eax, edx                ; Exclusive OR
:1C0019AE 2BC2        sub eax, edx                ; subtract EAX-EDX
:1C0019B0 8945E8      mov dword ptr [ebp-18], eax ; result in [ebp-18]
:1C0019B3 8B45E8      mov eax, dword ptr [ebp-18] ; result in EAX
:1C0019B6 3945D0      cmp dword ptr [ebp-30], eax ; EAX must be equal
                                                  ; to value at [ebp-30]
                                                  ; [ebp-30]=1Eh=30Dec at
                                                  ; :1C0018E1 (look back) 
:1C0019B9 0F8407000000je 1C0019C6
:1C0019BF 33C8        xor ecx, eax ; if EAX<>1E (30 decimal) then XOR EAX
                                   ; and EAX=0 Unreg.
:1C0019C1 E93A000000  jmp 1C001A00                ; Returns to the CALLer

At this point we could easily crack this schemes by changing instruction 
:1C0019BF from XOR EAX,EAX to XOR ECX,EAX, this would erase ECX 
leaving EAX intact, if EAX<>0 the program is Registered, and the 
protection scheme would be disabled. Off course, we would have to deal 
with the checksum (read the validate.dll checksum annihilation add on, at 
the end of this essay). 

However, we want to go for the gold, lets calculate the valid serial 
number: we know that the validation code is calculated according to the 
following equation: (first digit - eighth digit) * (second digit - 
seventh digit)= 1Eh = 30 Decimal. The high order bits of EAX should be 
"0" because this value will be stored in EDX and finally EDX is 
subtracted from EAX.

        So lets begin with this tentative 8 digits number:

        88776623 ; Applying the equation: (8 - 3) * (8 - 2) => (5) * (6) = 
30 Decimal = 1E Hexadecimal.

	(Don't forget to check the SUB REGISTER, 30 instruction ADD ON, at 
the end of this document)

	This attempt will pass the first validation test and leads the 
execution to the following piece of code:
	
* Referenced by a Jump at Address:1C0019B9(C)
|
:1C0019C6 8B45F0        mov eax, dword ptr [ebp-10]; Moves third digit to EAX 
:1C0019C9 2B45E0        sub eax, dword ptr [ebp-20]; Subtract third-sixth 
:1C0019CC 8B4DEC        mov ecx, dword ptr [ebp-14]; Moves Fourth digit to ECX
:1C0019CF 2B4DE4        sub ecx, dword ptr [ebp-1C]; Subtr. Fourht - fifth
:1C0019D2 0FAFC1        imul eax, ecx ;       (third-sixth) * (fourth-fifth)
:1C0019D5 8945E8        mov dword ptr [ebp-18], eax; Moves result to
                                                   ; [ebp-18]
:1C0019D8 8B45E8        mov eax, dword ptr [ebp-18]; Moves result to EAX
:1C0019DB 99            cdq            ; Moves high order bits of EAX to EDX
:1C0019DC 33C2          xor eax, edx               ; Exclusive OR
:1C0019DE 2BC2          sub eax, edx               ; Subtract EAX-EDX
:1C0019E0 8945E8        mov dword ptr [ebp-18], eax; Moves result to
                                                   ; [ebp-18]
:1C0019E3 8B45E8        mov eax, dword ptr [ebp-18]; Moves result to EAX
:1C0019E6 3945FC        cmp dword ptr [ebp-04], eax; Compares EAX with
                                                   ; [ebp-04] which is
                                                   ; equal to 06h (look
                                                   ; back at :1C0018E8)
:1C0019E9 0F8407000000  je 1C0019F6          ; If equal return to the CALLer
:1C0019EF 33C0          xor eax, eax         ; If not equal EAX=0 Unreg.
:1C0019F1 E90A000000    jmp 1C001A00         ; Return to the CALLer with EAX=0

* Referenced by a Jump at Address:1C0019E9(C)
|
:1C0019F6 B801000000  mov eax, 00000001 ; The serial number has passed
                                        ; all tests, therefore,
                                        ; EAX=01 Registered
:1C0019FB E900000000  jmp 1C001A00      ; Return to the CALLer
                                        ; with EAX=1 Registered

* Referenced by a Jump at Addresses:1C0018FB(U), :1C001915(U), :1C0019C1(U), 
                                   :1C0019F1(U), :1C0019FB(U)
|
:1C001A00 5F                      pop edi
:1C001A01 5E                      pop esi
:1C001A02 5B                      pop ebx
:1C001A03 C9                      leave
:1C001A04 C20400                  ret 0004 

	The equation looks like this: (third digit - sixth digit) * (fourth
        digit - fifth digit) = 06h = 6 Decimal.

        Lets modify our tentative number according to this:

       Valid Number: 88552323, where, (5 - 3) * (5 - 2) =>
       (2) * (3) = 6 Decimal = 06hex.

        This number seems to pass all tests.

	Now try to register ADC 1.12 with the Serial Number: 88552323 and
        it works!


                       VALIDATE.DLL ANNIHILATION ADD ON

	Once we've cracked the file validate.dll with our favorite hex
editor, everything seems to be fine, but when we double click on the main
executable (ADC.EXE), another window pops up!!! VALIDATE.DLL missing or
invalid. No!!!, there's a checksum to verify the integrity of the .dll file,
Damn!!!
	We're sitting in our workbench once again. If you try to execute
ADC.EXE with WXI in memory, it'll inform you that the checksum resides in
the main executable (ADC.EXE) itself. So lets disassemble this file with
wdsm85 and hit the search button looking for this sequence: "VALIDATE.DLL is
missing or invalid". We land here:

* Referenced by a Jump at Address:00409239(U)
|
:00409266 83C4FC                  add esp, FFFFFFFC
:00409269 A350F44800              mov [0048F450], eax
:0040926E C60548F3480000          mov byte ptr [0048F348],00; Redundant 
                                                            ; Instruction
					
        Several instructions eliminated to save some space!

* Referenced by a Jump at Address:004092DC(U)
|
:0040930D A33CF34800              mov [0048F33C], eax
:00409312 E8A9A70100              call 00423AC0      ; Checksum
:00409317 A3FCF74800              mov [0048F7FC], eax; Moves the checksum
                                                     ; of the modified
                                                     ; file to [48F7FC]

:0040931C 8B15E8154800  mov edx, dword ptr [004815E8]; Moves the valid
                                                       checksum of the
                                                       original file
                                                       validate.dll to
                                                       [4815E8]
:00409322 3BC2          cmp eax, edx                 ; Compares both values
:00409324 0F8576000000  jne 004093A0                 ; Jumps if the .dll
                                                     ; file is invalid
                                                     ; to 4093A0

          Several instructions eliminated to save space!

? Referenced by a Jump at Addresses:00409324(C), :0040937A(C) 
                                      ; The checksum is activated 
                                      ; from two different sections
                                      ; of the code.
* Reference To: USER32.GetFocus, Ord:0000h
                                  |
:004093A0 E800D50600              Call 004768A5 ; This is the part we
                                                ; don't want to see
                                                ; in our screen!
:004093A5 6A00                    push 00000000
:004093A7 6A10                    push 00000010

* Possible StringData Ref from Data Obj ->"Fatal"
                                  |
:004093A9 689B7B4700              push 00477B9B

* StringData Ref from Data Obj ->"VALIDATE.DLL is missing or invalid."
                                  |
:004093AE 68777B4700              push 00477B77
:004093B3 50                      push eax

Pay attention to this explanation: The checksum is calculated at :409319,
then, the checksum of the modified file is stored at memory location
[48F7FC], the valid checksum of the original file is stored at EDX from
memory location [4815E8]. At instruction :409322 both values are compared
and the decision is taken at instruction :409324 which will jump if the
checksums values are different. Changing the JNE to JE won't crack this
scheme, because the invalid checksum has already been stored at a memory
location where it'll be checked by other locations of the code. The
programmer was very intelligent because he moved the invalid checksum from a
register (EAX) to the memory, and the valid checksum from memory to a
register (EDX); this particular configuration will prevent us to move the
right checksum to the invalid checksum memory location, because assembly can
not move data directly from one memory location to another (except by the
use of rep movs instructions). We can't move the right cheksum to EAX at
409317 (MOV EAX, [4815E8]) because the program will crash. This piece of code
is not as easy to crack as it seems.

	Now load ADC.EXE with WINICE press ctrl-d, set a breakpoint at
cs:409322 to see what's the difference between both checksums. Start ADC and
WINICE will pop up. Execute: ? EAX and ? EDX to check both values. You get
this:
		
			EAX: F4E9C8A1
			EDX: F4E9C8A9

	The difference resides in the last digit; you also know that EAX
value is stored backwards at [48F7FC], this means that A1 will be
stored at the first byte of that memory location. All we have to do
to crack this mess is search for a redundant instruction to change the byte
at [48F7FC] from A1h to A9h. Searching backwards in the code we find the
perfect instruction (mov byte ptr) at :40926E. To be sure that it is in fact
redundant, set a breakpoint at CS:40926E and when WINICE pops up, execute:
d [48F348]. Just what I thought, this byte is already zero before the
instruction stores 00 at it, therefore, it's redundant and not necessary. 

	Finally, to crack the checksum, change this:

:0040926E C60548F3480000 mov byte ptr [0048F348],00 

to

:0040926E C605E8154800A9 mov byte ptr [0048F7FC],A9 

        The checksum has been defeated!

        Don't forget this: redundant instructions are frequently located at
push & pop sequences of the beginning and ending of almost every CALL. You
may use this spare bytes at your will!

                    SUB REGISTER, 30 INSTRUCTION ADD ON
                               By Aesculapius

	Many programs use the instruction: SUB REGISTER, 30 to convert a
hexadecimal number to its decimal equivalent. Locating instructions like
this one, for instance: SUB EAX, 30 several times in the same piece of code
will warn you about two things:

1. A valid code calculation sequence could be there.
2. This instruction is used only with numbers (not with letters). So there
are as many numbers in the serial code as SUB REGISTER, 30 instructions in
the asm code.


                                     Aesculapius - July 97
                                     Email: aesculapius@cyberjunkie.com
                                     HomePage: http://aesculapius.home.ml.org
  


You are deep inside Fravia's page of reverse engineering, choose your way out:

homepage links red anonymity +ORC students' essays tools cocktails
search_forms corporate mailFravia
Is reverse engineering legal?