|  | CD-Cops Another ready-made protection annihilated
 |  Project 4
 | 
| 20 January 1999 | by 
McLallo |  | 
|  | Courtesy of Fravia's page of
reverse engineering |  | 
| fra_00xx 990120
 McLallo
 0010
 P4
 PC
 
 | "The CD-Cops software which recognizes and either accepts or rejects the CD is protected
by Link's Code Security, a system which has been in use since 1984 and is known throughout
the world as being virtually unbreakable. Link's Code Security is a legend in the Middle
East where piracy is a serious problem" Yep, sure... sort of.
 
 A very able reverser: welcome McLallo!
 |  Our Protections
 
 
  Advanced
 | 
|  | There is a crack, a crack in everything
That's how the light gets in |  | 
| Rating | ( )Beginner ( )Intermediate (x)Advanced ( )Expert |  | 
Commercial ready-made copy protections are always interesting. Here is another one,
used to protect CD-Roms. It's a nice one, timers making sure the program will crash if
something takes more time than it should (as if someone would put a breakpoint somewhere
and fool around), a couple of checksums to make sure no one's been changing anything in
the code, and - of course - some self-modifying code decrypting itself just when it needs
to be used. It even claims to be able to separate perfect CD copies from the original CD!
CD-Cops
   
   
Another ready-made protection annihilated
   
Written by
McLallo
"The CD-Cops software which recognizes and either accepts or rejects the CD is protected
by Link's Code Security, a system which has been in use since 1984 and is known throughout
the world as being virtually unbreakable. Link's Code Security is a legend in the Middle
East where piracy is a serious problem."
       / Quote from the CD-Cops homepage, www.scandiplan.com/UKCDCOPS.htm
WDasm32 8.93
Borland's Turbo Debugger 5.0 (Yep!)
HEdit 2.1.11 (Or any other hexeditor)
Hey! No SoftIce? Sorry guys, not this time.
www.scandiplan.com -- the creators of CD-Cop's.
You won't be able to download any test versions here, I'm sorry. (They're friendly enough
to offer you to buy one for only $60, though) And the application I'm using is 6 CDs, so
I doubt you'll find it on the net. (But you never know what those crasy wankers out there
are trading today!)
There are a couple of different versions of this protection:
CD-Cops, Single version 16 bit
CD-Cops, Single version 32 bit (this one is our target, in version 1.46)
CD-Cops, Network version
DVD-Cops, Single version 16 bit
DVD-Cops, Single version 32 bit
DVD-Cops, Single version 16 and 32 bit
PREFACE
  I found this CD protection on a program called Nationalencyklopedin (swedish for
  'The National Encyclopedia'). The essay will only cover how to remove the CD-Cops
  protection envelope, and not how to crack the entire application, as there is an
  extra bonus protection in the original exe-file as well, just in case. However,
  that one isn't interesting to us, and will be mentioned no more.
START: WHAT'VE WE GOT?
  For a start, let's look what we got to play with. There are 4 files that concern us:
  NE.EXE      98 kB  16 bit application
  NE.QZ_      63 kB  Renamed 32 bit application
  NE.W_X     730 kB  Renamed 32 bit application
  CDCOPS.DLL  22 kB  A .dll for the protection
  Those renamed applications seem fun. I tried to rename and run them, but they both
  crashed. Hardly suprising, but you'll never know!
  Ok, so start up NE.exe and look what's it all about. First time run, it wants a code.
  If you give it a false code, a MessageBox will pop up explaining your mistake to you.
  So, to begin I went into SoftIce and put a breakpoint at MessageBox, to catch that
  'Bad code!' message. Went back to the OS and run. Bam! The whole system goes down.
  Restart the computer and throw SoftIce away. It just wont work with this protection.
  Rather than adjusting the target for the tool, I chose to pick another tool,
  Borland's good old Turbo Debugger. But first, let's try WDasm.
DISASSEMBLING NE.EXE
  Looking around in the disassembled code for a while, I stubled into calls to encrypted
  code. Whatever you do, follow the code from the start, or just looking around, you
  won't miss them. Guess there's no use looking for that 'Bad code!' message. If there
  is sections of encrypted code, any interesting parts most certain will be in there.
  So, I followed the program flow from start to the encrypted section, and this is how
  it begins:
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:0001.0D3E(C)
|
:0001.0DCC 9AFFFF4F00             call CDCOPS.Ord{0003h}
:0001.0DD1 D8A3BA70               fsub dword ptr [bp+di+70BA]
:0001.0DD5 FA                     cli
:0001.0DD6 E9F3B3                 jmp C1CC
  First a call to our CDCOPS.dll, and then a large block of encrypted code! My (correct)
  guess was that the CDCops call will decrypt the code underneath, and then execute it as
  it returns. Ok, how do I decrypt it? Just setting a breakpoint at 1.0dd1, or somewhere
  else in the encrypted area, and look what's beeing runned wont do. Turbo Debugger's
  breakpoints change the code where they're put, so a breakpoint in the encrypted area
  would alter the decrypted data. Keep this problem in mind, as we'll see more of this
  in this protection.
  Decrypting the block
  What I did was to run the whole program, without any breakpoints, and when it's
  all finished, the encrypted code is still there, lying waiting for somebody to
  steal it.
  Fine! Let's save this decrypted block then! We know where it starts, but how big
  is it? I actually don't really know how the size of these blocks are determined,
  but it's really easy to see where they start and where they end in the disassembly.
  Correcting overwritten calls
  But the first time I was tricked, and the program wouldn't run at all after I had
  pasted my code over the encrypted. After a closer look I found out that the last bytes
  of the decrypted area were writing over a library call to CDCops.dll. This made the
  reallocations all screwed up. So we got to fill in this call ourselves. Actually, we
  only have to fill in the first byte, as the rest of the instruction can't be encrypted.
  because the OS needs it intact when it tries to reallocate it.
  This is how the decrypted section looks in memory, after complete run:
:0001.1C90 2E803E9D1CC3           cmp byte ptr cs:[1C9D], C3
:0001.1C96 75F8                   jne 1C90
:0001.1C98 90                     nop
:0001.1C99 90                     nop
:0001.1C9A 90                     nop
:0001.1C9B 90                     nop
:0001.1C9C 90                     nop
:0001.1C9D C3                     ret
  Here's how my corrected version looks like:
:0001.1C90 2E803E9D1CC3           cmp byte ptr cs:[1C9D], C3
:0001.1C96 75F8                   jne 1C90
:0001.1C98 9AFFFF0000             call CDCOPS.Ord{0005h}  // Here, 9A is the missing byte
:0001.1C9D C3                     ret
  What more have we got to keep in mind? The call att 1.0dcc to CDCOPS.3 is NOPed by
  itself while it's decrypting the block, but we can't do that, as the OS will try to
  reallocate this call as well. We'll have to leave it alone, but then we've got to
  make sure it isn't called, as it then would try to decrypt the already decrypted
  data and destory it.
  Save down the decrypted code
  Just one more thing before we can get to work; saving memory is a bit tricky in Turbo
  Debugger. I don't know if I've misunderstood something fataly, or if the Save Block
  feature just didn't work, but the only way I managed to save memory was by first to
  create a file filled with zeros, then in Turbo Debugger choose View->File... and paste
  my copied blocks there. Now, this is how I did:
      1. Create a file filled with zeros to paste the block in, at least 0xec6 bytes
      2. Load the application in Turbo Debugger
      3. Make sure there are no breakpoints (Breakpoints->Delete All)
      4. Run the program, and wait for it to finish
      5. Mark and copy from cs:0dd1 to cs:1c97
      6. View->File... and choose the file you've prepared
      7. Paste, and close the window
  Now exit and paste the saved file into NE.exe at offset 0x0fb1. Then fill in the
  missing byte (0x9a) at 0x1e78. To patch away that call to CDCOPS.3 in the beginning,
  it's time to switch over to WDasm and check for all references to 1.0dcc. Just
  one this time: 1.0d3e.
:0001.0D3E 0F848A00               je 0DCC
  I patched this one to jump 5 bytes further. That makes 0F848F00. A 0x8f at offset
  0xf20, then.
  Now it's time to check if we made it.
  Did we?
  No, we did not. It died somewhere inside CDCops.dll. Ok, maybe that last CDCOPS call
  wasn't ment to be run after all. Perhaps it was some kind of ending routine for the
  CDCOPS.3 call? That part only seems to make sure that the decrypted code is not only
  in some cache, and as we're not modifying code while running any longer, it may not
  be needed at all. It WAS nopped when we found it, so I guess it's ok for us just to
  kill it, without really asking it's name. I changed at 0x1e70 to 0xc3 (ret). Tried
  again, and this time it runs smooth!
  First encrypted section done
  Ok, so any interesting parts in the new area? Of course there are, or they wouldn't
  have been encrypted!
:0001.0E7C  call 1067                   // This one checks the registry for a code,
:0001.0E7F  jnb 0E98                    // and if the code is good, jump to 1.e98!
:0001.0E81  mov byte ptr [0DA1], 59
:0001.0E86  push word ptr [344E]
:0001.0E8A  push 0001
:0001.0E8C  call far word ptr [0742]    // This is a call to USER.ShowWindow()
:0001.0E90  mov word ptr [0032], 06B5
:0001.0E96  jmp 0EFE                    // Else if bad or no code at all, jump to 1.efe.
                                        // 1.efe eventually does a 'jmp word ptr [0032]'
                                        // so this is actually a jmp 1.6b5.
:0001.0E98  push word ptr [344E]
:0001.0E9C  push 0002
:0001.0E9E  call far word ptr [0742]    // call User.ShowWindow()
:0001.0EA2  mov word ptr [0032], 094B
:0001.0EA8  push word ptr [35CE]
:0001.0EAC  call far word ptr [0726]    // call User.SetCursor()
:0001.0EB0  call far word ptr [0772]    // call KRNL386.Yield()
:0001.0EB4  call 10E7                   // Let's check this one out! Probably sets [35cd]
:0001.0EB7  push word ptr [35D0]
:0001.0EBB  call far word ptr [0726]    // call User.SetCursor()
:0001.0EBF  cmp byte ptr [35CD], 59     // ('Y')
:0001.0EC4  je 1C9E
:0001.0EC8  cmp byte ptr [35CD], 52     // ('R')  If [35cd] is Y or R, then 1.1c9e
:0001.0ECD  je 1C9E
:0001.0ED1  cmp byte ptr [35CD], 44     // ('D')  If [35cd] is D, then 1.83e
:0001.0ED6  je 0EDA
:0001.0ED8  jmp 0EE0
:0001.0EDA  mov word ptr [0032], 083E
:0001.0EE0  cmp byte ptr [35CD], 56     // ('V')  If [35cd] is V, then 1.7ce
:0001.0EE5  je 0EE9
:0001.0EE7  jmp 0EEF
:0001.0EE9  mov word ptr [0032], 07CE
:0001.0EEF  cmp byte ptr [35CD], 4E     // ('N')  If [35cd] is N, then 1.735
:0001.0EF4  je 0EF8
:0001.0EF6  jmp 0EFE
:0001.0EF8  mov word ptr [0032], 0735
:0001.0EFE
  Here we've got a call to the routine that checks the code, and we got some kind of
  main selector, choosing what to do. I started looking what these different letters
  in [35cd] ment:
  N (1.735) has a string reference to "Code: ". Boring, I don't wanna enter codes.
  V (1.7ce) has a reference to "Insert the correct CD". Don't wanna do that either.
  D (1.83e) refers to "    ". I tried this one, it's some kind of demonstaion mode.
  Y/R (1.1c9e) is directing us to a new call CDCops.3! Definitely the best alternative.
  Second encrypted section
  Ok, so this new section starts at 1.1c9e (or 1.1ca3, if we exclude the CDCops-call),
  and scrolling down. It wasn't that obvious this time, there's a new encrypted block
  starting precisely after ours, but it looks like our last byte at is 1.1fd1.
  If we just run the program, we will never come to this 1.1c9e call. We've got to
  give it some help. So I started it in Turbo Debugger, went to 1.e7f (good code
  code entered?) and forced it to always jump, and then at 1.ec4 (jump to encrypted
  section) I forced that one to always jump too. Now, let's just run the program. It
  will most certain crash, as the code probably is used some way, and perhaps some
  other initializing is skipped with those jumps, but that doesn't destrub me. I just
  want that section decrypted. And remember to turn faults off in SoftIce, if you're
  running it, or SoftIce will pop in and your computer will crash.
  So I did like last time, and merged the new decrypted code into ne.exe. Just for fun
  I filled in the missing byte in the call to CDCops.5 (0x21ac = 0x9a), and then I put
  that ending routine out of action by a RET at 1.1fc4. Now we have to alter all the
  references to make them skip the introducing CDCops.3 call. There are two references,
  1.ec4 and 1.ecd correct them by make them jump 5 bytes further, and we're done!
  Second encrypted section done -- back to WDasm
  Ah, here's interesting stuff! It opens "NE.QZ_", reads until if finds offset 0xc544,
  where it reads 80 bytes. And this is what's happening next:
:0001.1DC2  mov cx, 0040    // 40 words to do (80 bytes)
:0001.1DC5  xor dx, dx
:0001.1DC7  cld
:0001.1DC8  lodsw           // read a word
:0001.1DC9  xor dx, ax      // xor it into the checksum
:0001.1DCB  add dx, ax      // and add it the the checksum
:0001.1DCD  loop 1DC8
:0001.1DCF  xor [0329], dx  // xor the checksum to [0329]
:0001.1DD3  je 1DE9         // jump if [0329] is now zero
:0001.1DD5  mov cx, 000A
:0001.1DD8  push bx
:0001.1DD9  push cx
:0001.1DDA  mov ax, 0E07
:0001.1DDD  int 10          // Display char 0x07 on screen == beep
:0001.1DDF  xor cx, cx
:0001.1DE1  loop 1DE1       // Wait a little while
:0001.1DE3  pop cx
:0001.1DE4  loop 1DD9       // Do this 10 times
:0001.1DE6  pop bx
:0001.1DE7  jmp 1DD5        // And when done 10 times, do it again, forever
  It's creating a sort of checksum of these 80 bytes, xors it into [0329]. If [0329]
  doesn't become 0 after the xor, the program will enter the ugly routine you can
  see at 1.1dd5. An endless loop with beeping. We don't want to go there, do we?
  Nice, we've found the checksum check for NE.QZ_! Will be usefull if we'll want to
  change anything in it. (And if they put an checksum on it, we probably will!)
  Data stored in the registry
  Anyway, we go on, and look what's happening if the checksum is correct, at 1.1de9
  It will read a bit, it calls GetTickCount and stores the result, and then there is
  this new checksum routine:
:0001.1E19  lodsb
:0001.1E1A  stosb
:0001.1E1B  add dx, ax
:0001.1E1D  or ax, ax
:0001.1E1F  jne 1E19
  It does a simple checksum on the string "BRABOKER_NE1100898". "BRABOKER" is the
  publishers of the program, and "NE1" is the label of the original CD, "100898" is the
  date of the NE.EXE (and a lot of other files), in the format ddmmyy. The string
  "NE1100898" is hardcoded into NE.EXE. You'll see quite a lot of it, it will work as
  some kind of product code for the protected application. Anyway, after this, it adds
  ".CRC" to the string, and then a timer again...
:0001.1E2E  call far word ptr [075A]  // call MMSYSTEM.TimeGetTime
:0001.1E32  pop bx                    // Checksum of "NE1100898"
:0001.1E33  xor dx, bx
:0001.1E35  mov bx, 048E
:0001.1E38  call 01F5                 // Creates an ascii string...
  I haven't got any documentations about this function, but I have this strange
  feeling that it returns the current time. I know it returns a 32 bit value, though.
  The high word in dx and the low one in ax. What's returned in dx is xor:ed with
  the checksum of "BRABOKER_NE1100898", and then 1.1f5 is called. This function creates a
  ascii string of the 32 bit value dx and ax do together. Let me explain:
    "3DFE1204"  << This is the created ascii string (a 32 bit hex number)
        "1204"  << The 16 lowest bits are what TimeGetTime returned in ax
    "3DFE"      << The 16 highest bits are what's returned in dx xor the checksum
  Then Shell.RegSetValue is called, and this string is stored in a key called
  "HKEY_CLASSES_ROOT\BRABOKER_NE1100898.CRC".
  Checksum for "NE.QZ_"
  Ok, follow the code. Lot's of interesting stuff here, we break for this one:
:0001.1E53  xor ax, [0329]
:0001.1E57  add dx, [0329]
:0001.1E5B  add dh, [35FE]
  Look, [0329]. That one should be zero if "NE.QZ_" has got the correct checksum.
  Ax and dx here are the result from that GetTickCount. As [0329] should be zero, the
  first two lines does nothing. Wonder what [35fe] is. I searched for it, and I
  only found one single spot, where it was cleared. Untouched, it's hardcoded to 0x37.
  I can't see directly what's happening in the area where it is cleared, but as we only
  got to alternatives, we can simply try both and earn a lot of time. However, my guess
  I that we leave both of the registers alone. (And as I actually how the story ends,
  you should soon see that my guesses are often very, very good.)
  NE.QZ_ called with data as argument
  Ok, I went on, and the next interesting part I found was this one:
:0001.1E61  mov di, 02EB    // pointer to "NE1100898"
:0001.1E64  mov cx, 0040
:0001.1E67  mov bx, FFF9    // = -7
:0001.1E6A  mov si, di
:0001.1E6C  xor eax, eax
:0001.1E6F  cld
:0001.1E70  repnz
:0001.1E71  scasb           // scan the string for zero
:0001.1E72  sub di, si
:0001.1E74  lea cx, [bx+di] // cx = di - 7
  This one doesn't look important at all, but what it leaves in cx will be used right
  below, so I better present this as well. As you can see, the value in di will be one
  more than the actual string length, so the result in cx is length("NE1100898")+1-7=3.
  (Other products will of course have other strings here, making this number vary a bit)
:0001.1E76  xor ebx, ebx
:0001.1E79  lodsb           // read a byte from "NE1100898"
(..Uppercase routine...)    // make it uppercase (censored)
:0001.1E84  add ebx, eax    // add it
:0001.1E87  rol ebx, cl     // rotate with our magic number
:0001.1E8A  loop 1E79
:0001.1E8C  mov ax, 3773    // 3773 is a kind of product number for the protected app
:0001.1E8F  add ax, bx
:0001.1E91  ror ebx, 10
:0001.1E95  sub ax, bx
:0001.1E97  xor dx, ax
  Dx at this point holds the high word of the result from the GetTickCount call, and
  is now xored with a checksum of "NE1100898".
:0001.1E99  pop si
:0001.1E9A  pop ax          // pops the low word from GetTickCount back into ax
:0001.1E9B  push dx         // high word from GetTickCount
:0001.1E9C  push ax         // low word from GetTickCount
:0001.1E9D  pop ebx         // observe that this is a 32 bit register
(...)
:0001.1EBA  mov cx, 0008
:0001.1EBD  rol ebx, 04     // take 4 bits at a time
:0001.1EC1  mov al , bl
:0001.1EC3  and al, 0F
:0001.1EC5  add al, 40      // and transform into an ascii char
:0001.1EC7  stosb
:0001.1EC8  loop 1EBD
  This little routine takes ax and dx together as one 32 bit register, goes trough it
  4 bits as a time, and for every 4 bits, it adds 40 and saves it like a byte. This
  gives a 8 bytes long string, working like this:
    dx =   a  0  c  d
    ax =               3  3  6  7
          4a 40 4c 4d 43 43 46 47  << The result. The meaning of this is to make
                                      the number in ascii format. This example
                                      will be perfectly readable as "J@LMCCFG".
  The result is stored as an argument to "NE.QZ_", creating a string that now reads
  "C:\PROGRAM FILES\NE\NE.QZ_  J@LMCCFG". (There is actually an extra space between
  the filename and the argument. Guess it's some kind of bug.)
  Then, just a couple of lines down, there it is:
:0001.1ED5  call far word ptr [076E]   // Call KRNL386.WinExec
  Our file "NE.QZ_" if finally run.
  Stop and summarize
  Let's sum up what data "NE.QZ_" has got when it's run now. There are two channels
  where it get's it's data: The key HKEY_CLASSES_ROOT\BRABOKER_NE1100898.CRC in the
  registry, and the command line argument.
  The registry keeps the 32 bit result from TimeGetTime, where the high word has
  been xored with a checksum from the string "BRABOKER_NE1100898".
  The command line argument keeps the 32 bit result from GetTickCount, perhaps destroyed
  by [0329] and [35fe] if we have been altering with "NE.QZ_", and where the high word
  has been xored with a checksum from the string "NE1100898" and the product number
  "3773".
  End of part one
DISASSEMBLING NE.QZ_
  A great thing about these files are that you never need to search to find the
  fun parts. They're all over, the entire program is just a big protection. First
  of all, I just want to do a little comment on the second line in the new
  disassembly:
:0040D145  call 0040D14A
  This call to the next line, is not really a call. It's a push 40d14a.
  Then there is a little checksum routine:
:0040D150  xor ebp, ebp
:0040D152  mov edi, 00000007
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:0040D17B(C)
|
:0040D157  mov esi, dword ptr [esp+10]   // esi = 40d14a
:0040D15B  mov ecx, 00000068
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:0040D0EB(C)
|
:0040D160  xor bp, word ptr [esi]        // xor each word to ebx
:0040D163  xchg edi, ecx
:0040D165  rol ebp, cl                   // rotate ebx dl bits left
:0040D167  xchg edi, ecx
:0040D169  cmp word ptr [esi], 15FF      // skip checksum for CALLs
:0040D16E  jne 0040D176
:0040D170  inc esi
:0040D171  inc esi
:0040D172  inc esi
:0040D173  inc esi
:0040D174  dec ecx
:0040D175  dec ecx
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:0040D16E(C)
|
:0040D176  inc esi
:0040D177  inc esi
:0040D178  loop 0040D160
:0040D17A  dec edi
:0040D17B  jne 0040D157
  This ones makes a checksum of 0x68 words, starting at 40d14a, and down, trough itself
  Calls are skipped, as they may be reallocated. The whole thing is repeated 7 times,
  and each time the number of bits ebp is rotated by is decreased by one. The fact that
  it's checking itself does that we cannot change it, nor put a breakpoint somewhere in
  it, checking what the result is. But let's wait with this, and continue to look at the
  code further down.
  Next thing is a call to GetCommandLineA, the last 8 characters are decoded back to
  a 32 bit number and push upon the stack for later use.
  Then there's a call to GetTickCount, and the argument we got is decoded like this
:0040D1B0  pop cx          // low word from GetTickCount
:0040D1B2  pop dx          // high word
:0040D1B4  sub eax, ecx    // eax = our argument decoded
:0040D1B6  shr eax, 10
:0040D1B9  xor ax, dx
:0040D1BC  shl eax, 03
:0040D1BF  mov ebx, 001356F2
:0040D1C4  add eax, ebx                       // and then 0x1356f2 is added
:0040D1C6  mov edi, dword ptr [esp+10]        // edi=40d14a
:0040D1CA  mov ebx, edi
:0040D1CC  add eax, dword ptr [ebx+000000D4]  // [40d21e] = 02eb5b6b
:0040D1D2  xor eax, ebp                       // xor with the checksum from above
:0040D1D4  lea esi, dword ptr [edi+06]        // esi=40d150
:0040D1D7  mov ecx, 0000000C                  // 0x0c dwords
:0040D1DC  mov edi, eax
:0040D1DE  xor eax, dword ptr [esi]
:0040D1E0  add edi, dword ptr [esi]
:0040D1E2  inc esi
:0040D1E3  inc esi
:0040D1E4  inc esi
:0040D1E5  inc esi
:0040D1E6  loop 0040D1DE
  The protection adds the GetTickCount result to the argument just while it's transfered
  between the files, to make sure it will only work if it's read directly after it's been
  written. The same method is used for the data in the registry. Quite a nice one,
  actually, putting a breakpoint between when the data is written and when it is read
  will make the data outdated when the program gets the control back and continues.
  Let's take a closer look of what is done here.
  Our argument is first cleaned from GetTickCount, then 0x1356f2 is added, and then
  0x02eb5b6b is added, then it's xored with the checksum we got. After all this, it's
  copied into two registers. Then there is a new routine that goes trough the program,
  and itself, from 40d150 to 40d180, xoring each dword with one of these two registers,
  and adding the dword to the other register. Phew. What's next?
:0040D1E8  lea ebp, dword ptr [ebx+0000382E]  // 410978
:0040D1EE  add dword ptr [ebp+00], eax
:0040D1F1  sub dword ptr [ebp+00], edi
:0040D1F4  add dword ptr [ebp+00], ebx
:0040D1F7  lea ebp, dword ptr [ebx+00003832]  // 41097c
:0040D1FD  sub dword ptr [ebp+00], eax
:0040D200  sub dword ptr [ebp+00], edi
:0040D203  add dword ptr [ebp+00], ebx
  Here, the registers eax, edi and ebx are used to save something into two different
  locations. eax and edi are the result from the last checksum, and ebx is 40d14a.
  Then there's just one more thing before we can start reversing all those checksums
  and mathematics:
:0040D20B  sub eax, dword ptr [ebx+000000CC]  // Another hardcoded value: 0x00000a16
:0040D211  sub ebx, eax
:0040D213  pop ebp
:0040D214  jmp ebx                            // Jump to 40d14a - eax
  This is how to use checksums; add some, sub some, and then jump to them! Most people
  just check if they are good or bad, and then jumps or not, showing the whole world
  where. Ok, one could probably guess where this jump will land, but it's still a lot
  better.
  Time for reversing -- first checksum
  We will start with the first checksum of the code. The problem here is that it's
  checking itself and the whole code down to this jump, so there's no way we can put a
  breakpoint anywhere here. What I chose to do was to copy the checksum routine out of
  the area it checks, leaving it just like it should, and run it from a safe place.
  I loaded it up in Turbo Debugger, marked the whole area from the program entry point
  at 40d144 and down to the end of the whole program, at 40d222, just to be safe. Then
  copy, looked up, for a nice place to put it. And from 40c9cb to 40d01e (0x653 bytes!
  In decimal 1619 bytes!), there is this gigantic field of NOPs. I've got no idea why,
  but it's a nice place to put your own code!
  So I pasted it in the middle of this NOP-field. Then this CALL 40d14a has to be changed.
  It can't call the correct address, then our breakpoints obviously won't work, and it
  can't call our own little copy, because then it would make a checksum of that one. So I
  changed it into a PUSH 40d14a. Ok, then we're clear to run. I changed the eip to my own
  entry point, and put my first breakpoint at 40ca19, when the whole first checksum is
  done. I ran, and in ebp there is now 0xeb253578. Nice, first checksum down!
  Second checksum
  Now, there is this argument thing. We can't do that one yet, but we've still got another
  checksum we can look at, the one at 40ca7a. This one, however, adds it results into
  registers that already contain data. Bad data, to be specific, as the command line
  argument is very wrong. (I didn't even send it an argument.) So I had to put a
  breakpoint at 40ca78, and clear the two registers. Then a breakpoint at 40ca84, where
  I could read that eax is always XORed with 0xe8243481, and edi always is added with
  0x3272945d.
  Cheating the GetTickCount-trick
  Now, all we need is this data destoryed by the GetTickCount call. We've got to go back
  info NE.exe to catch this one. And how do we do next? Both calls to GetTickCount has
  to give the same return, so we better fill in the result ourselves instead! I chose
  0x01234567 as the current GetTickCount. (Actually, I tried zero at first, but it went
  negative at one spot, and then it was rotated into destruction, so the result went all
  wrong. I'll save you that mistake!)
  Load up NE.exe in Turbo Debugger, and change the CS:IP to 1.1e61. 
  First at all, we've got some initializing to do. 
  The registers has to be set up like this:
  es = ds
  dx = 0x0123  (High word of our faked GetTickCount result)
  Run until 1.1e97, and you'll see that 0x4d7d is going to be xored to our dx, and dx
  will then be 0x4c5e. Then ax with the low word from GetTickCount is poped, untouched,
  and together, they will make ebx. Instead of poping ax, I changed it to 0x4567, and
  in ebx I got 0x4c5e4567. Ok, done with NE.exe, now let's see what NE.QZ_ makes from
  this.
  Load it up and go to 40d1b4. cx and dx has just been POPed here, 
  these two registers are the ax and dx we just looked at in NE.exe. 
  Now initialize like this:
  eax = 0x01234567 (The result from GetTickCount in NE.QZ_)
  ecx = 0x4567     (Low word of our faked GetTickCount result)
  edx = 0x4c5e     (High word of our faked GetTickCount result)
  ebp = 0xeb253578 (Checksum from the first checksum routine)
  I stepped trough the code. At 0x40d1c6, 0x40d14a is fetched from the stack. 
  Fake this one, as our stack isn't like it should. 
  Halt again before 0x40d1de. This is the second checksum routine. 
  Skip it, and xor eax with 0xe8243481 and add 0x3272945d to edi (as
  we earlier could see is what this routine should do).
  eax: 0xe8242b3d xor 0xe8243481 = 0x1fbc
  edi: 0xe8242b3d  +  0x3272945d = 0x1a96bf9a
  Now, let's se what's happening at 40d1e8. 
  After stepping through the lines, the
  contents in 410978 is 0x40d138, and in 41097c it's 0x40d13e. 
  Nice! Then jump down to 40d20b and look where the jump is going, too. 
  It will be to 40c798. Now we've got 
  everything we need. What this part of NE.QZ_ does is to save the two numbers 
  at 410978 and 41097c, and then jumps to the right location, 40c798. 
  As this is the only thing we need from this routinem let's make it a 
  bit shorter! The values at 410978 are already
  hardcoded, so we could just change them at offset 0xd578 and 0xd57c in NE.QZ_. 
  Then just change the program entry point to point to 40c798 instead.
  Taking care of the register data
  So, now I loaded the new version of NE.QZ_ into WDasm. And almost in the 
  beginning, there is a quite expected line:
* Reference To: winmm.timeGetTime, Ord:0000h
                |
:0040C7D7  Call 0040830C
:0040C7DC  mov dword ptr [0040F884], eax
  Ok, timeGetTime is called, and stored in [0040f884]. I searched for usage of this
  address, and it's read again at this location:
:0040C983  call 0040BAD8
:0040C988  and eax, FFFF00FF
:0040C98D  mov dword ptr [0040F888], eax
:0040C992  mov eax, dword ptr [0040F884]  // timeGetTime()
:0040C998  movzx edx, word ptr [0040F878]
:0040C99F  sub eax, edx
:0040C9A1  shr eax, 10
:0040C9A4  movzx edx, word ptr [0040F87A]
:0040C9AB  xor eax, edx
:0040C9AD  mov dword ptr [0040F878], eax
:0040C9B3  or al, ah
:0040C9B5  mov edx, dword ptr [0040F888]
:0040C9BB  xor dl, dh
:0040C9BD  add al, dl
:0040C9BF  shr edx, 10
:0040C9C2  sub dh, dl
:0040C9C4  sub al, dh
:0040C9C6  mov byte ptr [0040F87C], al
  There's a couple of addresses here that we don't know what they are, but nevermind.
  I loaded it into Turbo Debugger again, and put a breakpoint at 40c97e, to look what's
  really happening here. At first run I got this MessageBox saying "Registry key
  invalid or not found!". Oh, it deletes the registry key after use. I ran regedit and
  wrote in a fake key, containing "12345678". Then I went back and ran it again, and this
  time it stops at my breakpoint. the call at 40C983 returns 0x250a4a67. [0040f878] is
  the low word of what's in the registry, and [40f87a] is the high word. Well, that's
  about all we need to know. Wonder what that call does, but as I couldn't see that at
  once, we'll wait with that one, and hope it gives the correct result already.
  So, time to make a fake registry entry, with a value based on our own number instead
  of timeGetTime. This one is a real easy one. We've only got to steal the checksum
  from the small routine in at 1.1e19. Easily done, and it is 0x4a5. 
  This value should be xored with the high value of the timeGetTime. I decided my fake
  timeGetTime should be 0x12345678. XOR 0x4a5 into the high word, and we'll get
  0x16915678. 
  Ok, so then I created the key "HKEY_CLASSES_ROOT\BRABOKER_NE1100898.CRC",
  with the contents "16915678", and then back to NE.QZ_.
  All done, the application runs
  I put a breakpoint at 40c983, and ran the program. At 40c992 I had to change the real
  timeGetTime() into my 0x12345678. Then I stepped down 'til the last line. Al is here
  0xf1, and that seems to be the only thing that's used from the registry code.
  I let the whole thing run from the point, and my program started up! We're in! Now, we
  can easily make the changes to make this file always just start the program. It doesn't
  even need to be started from NE.exe. However, the registry key is still checked, and
  even if we write the result over, it still will break if it can't find it. I tried to
  skip this little check, but I didn't do very well, and when the program came to the
  point where it is to remove the key, instead of returning an error, it removed ALL
  the keys in my registry. I had to reinstall my Windows. (But hey, we all have to do
  that every day anyway. That's what Windows' all about.) So be careful if you try
  something like this!
  Removing the entire envelop
  Ok, should I make the changes needed to make this whole thing run? It would have
  started without any problem, and the CDCops protection would bother no more. But, an
  extra file, just opening and decrypting the original file? Am I happy with that? No,
  I'm NOT! I WANT MORE!! Of course the whole CDCops protection has to be removed, or it
  would just felt wrong every time when I was to run this application, and I had to start
  this extra program, doing nothing but taking my time and my hard disc space.
  Finding where the program is kicked of is no real problem, but if you're in bad luck,
  it can take a while. I happened to stuble over it when I was looking for what that
  byte written into [0040F87C] (based on what's in the registry) really is. The location
  where it's used is here:
:0040C263  mov al, byte ptr [0040F87C]          // our 8-bit key
:0040C268  add dword ptr [0040E2D0], 00000002   // make the jump 2 bytes further
:0040C26F  push ebp
:0040C270  mov ebp, 00000004
:0040C275  call dword ptr [0040E2D0]            // looks like our key is used in here
:0040C27B  pop ebp
:0040C27C  sub dword ptr [0040E2D0], 00000002   // then change it back?
:0040C283  pop edi
:0040C284  pop esi
:0040C285  pop ebx
:0040C286  mov edx, dword ptr [ebp-04]
:0040C289  mov eax, dword ptr [ebp-20]
:0040C28C  add eax, dword ptr [0040F608]
:0040C292  mov ecx, dword ptr [ebp-1C]
:0040C295  call 0040BFA8
  I didn't know what was in [40e2d0], so I ran Turbo Debugger once again. First of all,
  I had to write another registry key with "16915678", and remember to put that breakpoint
  at 40c992, to change the timeGetTime result into 0x12345678. Then, off to 40c263. Step
  down into the call at 40c275, and what do we see? Ah, the 2 bytes we skipped was an
  int 20h, probably would have crashed something if we'd jump at the wrong place. And
  what's next? An decrypting routine! Decrypting a section as big as 0x89e00 bytes!
  Could that be..? Of course it is! It's decrypting our code section in NE.W_X. Starting
  at 0x7709a8 and the whole way to 0x7fa7a8, there is our code section from NE.W_X.
  Better save this to disk! On my machine, I hade to wait 68 seconds for Turbo Debugger
  to save this block of memory. Terrible.
  Well, well! Exit Turbo Debugger, exit everything else. HEdit NE.W_X, find the right
  section (0x400 for my application), and paste the block. Save, and run. Everything
  runs perfectly as it should.
  THE END.
Nice protection, it took some work. But as usual, it contained a lot of holes. The code
one has to enter in the beginning, for instance. That one should be used for something!
I didn't even looked at it! (Actually I did later, it's in another of these CDCops.3
encrypted sections. We didn't even decrypt that section!).
Sad. And all our work gave us one single byte in the end, that was needed for decrypting
the application. Isn't that a bit cheap? It could as well be bruteforced!
But it was still interesting. Thank you for spending your time reading all this!
Questions and comments are welcome to mclallo@hotmail.com!
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. Should you want to STEAL this
   software instead, you don't need to crack its protection scheme at all:
   you'll find it on most Warez sites, complete and already regged,
   farewell, don't come back.