January 1999
"ACDSee 32 2.4"
W32 PROGRAM Code Reversing
by   N i X e 
Code Reversing For Beginners 

Program Details
Program Name: acdc3224.exe
Program Type: Image viewer
Program Location: Here
Program Size: 1899 Kb

Tools Used:
Softice - Win'95 Debugger
W32Dasm - Win'95 Disassembler
Rating  Easy ( X )  Medium ( )  Hard ( )  Pro ( ) 
Solving the puzzle

The fastest and easiest-to-use image viewer available for Windows 95, Windows 98, and Windows NT! ACDSee is several tools in one.
A full-featured image viewer quickly displays your images in high quality.
The image browser lets you efficiently find and organize your images.
ACDSee also provides several image manipulation functions.
About this protection system

This shareware will end up registered if you enter your "Full Name:" and the correct "Registration Code:". But as you know by now, there are other ways to get registered;-)

The following entries are created in the registry:

  • HKEY_LOCAL_MACHINE\Software\ACD Systems\ACS See32\Evaluation
  • HKEY_LOCAL_MACHINE\Software\ACD Systems\ACS See32\RegCode
  • HKEY_LOCAL_MACHINE\Software\ACD Systems\ACS See32\RegName
  • Note: Use Regmon to find out what is written to/read from the Windows Registry.
    The Essay

    First run ACDsee a few times and enter whatever in the registration screen. You will probably get the same message I got: ''Your name and registration code do not match'. Write the string on a piece of paper and disassemble ACDSee32.exe with W32Dasm. Look for the string that we get when entering an invalid registration code: 'Your name and registration code do not match'.

    Here is the W32Dasm listing from "..code do not match." and some lines up (we have a lot of jumps here):

    :00406DC3 6882000000              push 00000082
    :00406DC8 57                      push edi
    :00406DC9 FFD6                    call esi
    :00406DCB 33DB                    xor ebx, ebx
    :00406DCD C70540004E0000000000    mov dword ptr [004E0040], 00000000
    :00406DD7 8A44241C                mov al, byte ptr [esp+1C]
    :00406DDB 8D74241C                lea esi, dword ptr [esp+1C]
    :00406DDF 84C0                    test al, al
    :00406DE1 0F844C010000            je 00406F33
    * Referenced by a (U)nconditional or (C)onditional Jump at Address: 00406DFE(C)
    :00406DE7 0FBE16                  movsx edx, byte ptr [esi]
    :00406DEA 52                      push edx
    :00406DEB E87E840B00              call 004BF26E
    :00406DF0 83C404                  add esp, 00000004
    :00406DF3 85C0                    test eax, eax
    :00406DF5 7401                    je 00406DF8
    :00406DF7 43                      inc ebx                       ; no of chars in name
    * Referenced by a (U)nconditional or (C)onditional Jump at Address :00406DF5(C)
    :00406DF8 8A4601                  mov al, byte ptr [esi+01]
    :00406DFB 46                      inc esi
    :00406DFC 84C0                    test al, al                   ; is next char zero?
    :00406DFE 75E7                    jne 00406DE7
    :00406E00 6683FB05                cmp bx, 0005                  ; is no of chars 5
    :00406E04 0F8C29010000            jl 00406F33                   ; if less than 5 jump to
    :00406E0A 8D44247C                lea eax, dword ptr [esp+7C]   ; our serial
    :00406E0E 6A00                    push 00000000
    :00406E10 8D4C2420                lea ecx, dword ptr [esp+20]   ; our name
    :00406E14 50                      push eax                      ; save serial on stack
    :00406E15 51                      push ecx                      ; save name on stack
    * Possible StringData Ref from Data Obj ->"-314159265"
    :00406E16 6888034E00              push 004E0388                 ; push -314159265
    :00406E1B E8701C0500              call 00458A90                 ; the magic call!?!?!
    :00406E20 83C410                  add esp, 00000010
    :00406E23 F7D8                    neg eax
    :00406E25 1BC0                    sbb eax, eax
    :00406E27 F7D8                    neg eax
    :00406E29 85C0                    test eax, eax
    :00406E2B A340004E00              mov dword ptr [004E0040], eax
    :00406E30 0F8EFD000000            jle 00406F33
    * Referenced by a (U)nconditional or (C)onditional Jump at Addresses: 00406DE1(C), :00406E04(C), :00406E30(C)
    :00406F33 8A44241C                mov al, byte ptr [esp+1C]
    :00406F37 33DB                    xor ebx, ebx
    :00406F39 84C0                    test al, al
    :00406F3B 8D74241C                lea esi, dword ptr [esp+1C]
    :00406F3F 745B                    je 00406F9C
    * Referenced by a (U)nconditional or (C)onditional Jump at Addresses: 00406F3F(C), :00406F5E(C), :00406F7B(C)
    :00406F9C 6AFA                    push FFFFFFFA
    :00406F9E 57                      push edi
    * Reference To: USER32.GetWindowLongA, Ord:0156h
    :00406F9F FF1514C64C00            Call dword ptr [004CC614]
    * Possible StringData Ref from Data Obj ->"ACDSee 32"
    :00406FA5 8B15F0014E00            mov edx, dword ptr [004E01F0]
    :00406FAB 6A00                    push 00000000
    :00406FAD 52                      push edx
    * Possible Reference to String Resource ID=00566: "Your name and registration code do not match.
    Please check y"

    After some debugging in SoftIce you can see that the call to 00458A90 returns eax=0 if invalid reg code!
    Well, that sounds like the usual 'is valid password' function. We better take at look at it instead of just reversing the jump after - the 'is valid password' function could be called from several places!

    * Referenced by a CALL at Addresses: 00406772, :004068A9, :00406E1B, :00406F71, :0040734A
    :00458A90 8B4C2408                mov ecx, dword ptr [esp+08] ; mov eax,406f9c
    :00458A94 81EC84000000            sub esp, 00000084
    :00458A9A 8D442400                lea eax, dword ptr [esp]
    :00458A9E 56                      push esi
    :00458A9F 57                      push edi
    :00458AA0 50                      push eax
    :00458AA1 51                      push ecx
    :00458B18 C3                      ret

    Yes, the 'is valid password' function is called from 5 different locations. Let's us have a closer look...
    After debugging this function for the better of an hour whithout finding the correct reg code, I decided to go for the brute force crack and make the 'is serial valid' function return with eax=1 no matter what you enter as name and reg code. Here is what I did:

    :00458A90 8B4C2408                mov ecx, dword ptr [esp+08]   ; before the fix
    :00458A94 81EC84000000            sub esp, 00000084
    :00458A9A 8D442400                lea eax, dword ptr [esp]
    :00458A90 B801000000              mov eax,1                     ; After the fix
    :00458A95 C3                      ret                           ; Set eax to 1 and return to
    :00458A96 90                      nop                           ; caller while we are ahead
    :00458A97 90                      nop
    :00458A98 90                      nop
    :00458A99 90                      nop
    :00458A9A 8D442400                lea eax, dword ptr [esp]

    He he. We are now registered. When restarting the program you will get a message saying that it is an old reg code but if you click "Don't show this message again' - it will go away forever.
    Final Notes

    Maybe you could also have sniffed the password. I'm still not good enough... maybe you are?
    But okay, enter anything you like as "Full Name:" (at least 5 letters) and any "Registration Code:" and it is accepted!

    This 'is serial valid' function which returns 0 if not vaild and '1 or ?' if valid was also used in mIRC. Look out for that.

    Greetings/thanks to The Sandman, Razzia, Volatility, Eternal Bliss, and all other tutorial writers!
    Ob Duh

    I wont even bother explaining you that you should BUY this target program if you intend to use it for a longer period than the allowed one.