|
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" "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 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.
You are deep inside fravia's page of reverse engineering,
choose your way out:
Back to project 4
homepage
links
search_forms
+ORC
how to protect
academy database
reality cracking
how to search
javascript wars
tools
anonymity academy
cocktails
antismut CGI-scripts
mail_fravia+
Is reverse engineering legal?