-
-
[转贴]发现一篇Sentinel爆破文章(en),希望对大家有用!
-
发表于: 2005-6-27 10:03 9549
-
FlexiSIGN PRO and it's family of products from Amiable -- by Goatass
Published by Tsehp, June 2000.
Introduction:
-------------
First off I want to say that I have purchased this product and I have an
original dongle and license for FlexiSIGN PRO. Amiable is a company that makes
high end software for the sign making industry. A tutorial written by CrackZ a
while back about CASMate was the first paper on the protection used by this
company. I have also cracked Inspire 1.6 by the same company but didn't have
the time to write a paper on it. Anyways this application uses the Sentinel SuperPRO
dongle along with a user number/password which will decide which program you are
allowed to install. In this tutorial I will show you how to find and emulate the
Sentinel dongle and how to bypass any of the related checks. It's gonna be fun.
Tools:
------
http://zencrack2.cjb.net/ --> all the sentinel tutorials (and everything else)
ftp://ftp.rainbow.com/pub/online_documents/ --> recommended reading pro223.pdf, spro_dev.pdf
SoftIce
Hex Editor
IDA or W32Dasm
Lets get jiggy wit it:
----------------------
Ok after installing the program we look at the files, ummm...great now what ?
Well run the App.exe which is the main program, the App2.exe is the Product Manager (we
will worry about that later). What do you seen ? it loads up some stuff and then gives
and error message saying - "This application requires that a Hardware key be installed,
but none was found". This tells us that the program searched our ports to see if there
is a dongle attached and it failed. Having read the sentinel manual (address above) we
know that the program must call the sproFindFirstUnit after initializing the packet record
with sproInitialize. While we are talking about the packet record, what it is is a structer
that holds and will hold dongle information, from the manual again we know that this packet
record MUST be pushed on to the stack before any other dongle API can be called. That is a
good thing for us because it provides us with a land mark so we could find all the API calls
to the dongle. We also know that when a dongle API fails it will return error code 3 in EAX.
That is another helping point, we can look at the return values from calls and see if they
return 3 in EAX, another thing to help us is the dongle lag, when the program tries to access
the dongle and it's not there it will hang for a few seconds, when that happens you can pretty
much say that the CALL was a dongle API call.
Before the actual call to the dongle API the program MUST check if it has a valid packet record
before proceding to the dongle call, that is what we will look for to find the dongle calls.
it looks something like this:
CMP WORD PTR [ESI], 7242
JE address
MOV AX, 0002
POP EDI
POP ESI
RET 000C
that 7242 is the packet record signature and it must be checked and if it fails the error code
that is returned in EAX is 2 which means Invalid Packet.
Ok enough with the theory, lets get to business. After looking at which files are called by the
program we will open each one of them in W32Dasm and look at what functions they import and export.
We come to a file named Sx32w.dll after opening it we see in the Export list that it exports all
the functions that we saw in the Sentinel manual. Interesting....If you HEX edit that file you will
see towards the end it will say "SentinelSuperPro WIN32 DLL" that is the DLL that is provided by
Rainbow to developers to use with their applications, and it's also helpfull to us because we now
have a central place calling the dongle and we know in order to call the dongle this DLL must be
called. What we will do is put our emulator in this file and make all the dongle modifications in
this central file.
The key to cracking a dongle protected program is tracing different paths until you get to the
correct one. Ok so how do we break on the dongle API calls ? What I did, since the APIs are called
from a bunch of different DLLs, I opened Sx32w.dll and looked up the offset to sproFindFirstUnit
then I Hex edited the file, placed the cursor at that offset and replaced the first byte with CC
which is INT 3. You must rememeber the byte that you replaced so we can replace it back. Now open
SoftIce and place a breakpoint on INT3 such as BPINT 3 and exit to windows (x). Now run the App.exe
and wait for the break. SI broke, now we do E EIP to edit the bytes at the Instruction Pointer and
change the CC back to what it was before and press ENTER. Now just BPX EIP and exit SI and go back
to the Hex edit of the file and change back the CC to the original byte. Now we run the App.exe
again and we will have a break on sproFindFirstUnit. When it breaks it looks like this:
Exported fn(): RNBOsproFindFirstUnit - Ord:000Bh
:004072B0 53 push ebx
:004072B1 56 push esi
:004072B2 8B44240C mov eax, dword ptr [esp+0C]
:004072B6 0BC0 or eax, eax <--- checks to see if the program PUSHed
:004072B8 7509 jne 004072C3 the packet record
:004072BA 66B80200 mov ax, 0002
:004072BE 5E pop esi
:004072BF 5B pop ebx
:004072C0 C20800 ret 0008
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:004072B8(C)
|
:004072C3 50 push eax
:004072C4 E8079EFFFF call 004010D0
:004072C9 8BF0 mov esi, eax
:004072CB 66813E4272 cmp word ptr [esi], 7242 <-- this is the trademark check for the
:004072D0 740E je 004072E0 validity of the packet record
:004072D2 66B80200 mov ax, 0002
:004072D6 5E pop esi
:004072D7 5B pop ebx
:004072D8 C20800 ret 0008
The code that follows is the actual call to the dongle but we don't really care about it because at this point there is no return value from the dongle only an error code of 3 if not present or 0 if present.
What we will do is, NOP out the JNE instruction at 004072B8 and change the MOV ax, 0002 to MOV AX,0000
looks like this:
:004072B0 53 push ebx
:004072B1 56 push esi
:004072B2 8B44240C mov eax, dword ptr [esp+0C]
:004072B6 0BC0 or eax, eax
:004072B8 90 nop <-- do away with the useless JNE
:004072B9 90 nop
:004072BA 66B80000 mov ax, 0000 <-- force the error code to be 0 - successful
:004072BE 5E pop esi
:004072BF 5B pop ebx
:004072C0 C20800 ret 0008
That is it for taking care of the first dongle check. Pretty simple eh ? well don't get too happy there is more to do.
After we hard coded this patch using a Hex editor, and I'm gonna assume you know how to do that, we will continue to the next dongle call which will be sproRead. This is where most of the work will take place, but it's not too bad. Lets start by setting up a BPX on INT 3 like we did before with sproFindFirstUnit so we could get to the code in SI. once we do that we break here:
Exported fn(): RNBOsproRead - Ord:0002h
:00407480 56 push esi
:00407481 57 push edi
:00407482 8B44240C mov eax, dword ptr [esp+0C]
:00407486 0BC0 or eax, eax
:00407488 7509 jne 00407493
:0040748A 66B80200 mov ax, 0002
:0040748E 5F pop edi
:0040748F 5E pop esi
:00407490 C20C00 ret 000C
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00407488(C)
|
:00407493 50 push eax
:00407494 E8379CFFFF call 004010D0
:00407499 8BF0 mov esi, eax
:0040749B 66813E4272 cmp word ptr [esi], 7242 <-- trademark check again
:004074A0 740E je 004074B0
:004074A2 66B80200 mov ax, 0002
:004074A6 5F pop edi
:004074A7 5E pop esi
:004074A8 C20C00 ret 000C
some more stupid code.......
:004074C5 8B7C2414 mov edi, dword ptr [esp+14] <-- EDI is holding the buffer to store :004074C9 0BFF or edi, edi the return codes form the dongle
:004074CB 7513 jne 004074E0
:004074CD 66C746061004 mov [esi+06], 0410
:004074D3 66B81000 mov ax, 0010
:004074D7 5F pop edi
:004074D8 5E pop esi
:004074D9 C20C00 ret 000C
:004074DC 8D642400 lea esp, dword ptr [esp]
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:004074CB(C)
|
:004074E0 66C746300A00 mov [esi+30], 000A
:004074E6 668B442410 mov ax, word ptr [esp+10]
:004074EB 66894634 mov word ptr [esi+34], ax
:004074EF 56 push esi
:004074F0 E81BE9FFFF call 00405E10 <-- this leads to the sproRead call, we will emulate it
:004074F5 0AC0 or al, al <-- is AL = 0 ?
:004074F7 7517 jne 00407510 <-- if not u screwed
:004074F9 668B4636 mov ax, word ptr [esi+36] <-- return WORD from the dongle
:004074FD 668907 mov word ptr [edi], ax <-- put that WORD into the buffer
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:0040751E(C)
|
:00407500 668B4606 mov ax, word ptr [esi+06] <-- holds the error code
:00407504 50 push eax
:00407505 E8B6F5FFFF call 00406AC0 <-- checks error code, must return 0 in EAX to succeed
:0040750A 5F pop edi
:0040750B 5E pop esi
:0040750C C20C00 ret 000C
This great code below came from cm32.dll which is the DLL in charge of the main protection. It talkes to the dongle and figures out the return values.
This is the code that called the code above that we looked at. Here is some explanations:
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:10002F55(U)
|
:10002F60 837DFC17 cmp dword ptr [ebp-04], 00000017 <-- checks if we reached CELL 17
:10002F64 7D4A jge 10002FB0
:10002F66 8B4DFC mov ecx, dword ptr [ebp-04] <-- current CELL number
:10002F69 8B550C mov edx, dword ptr [ebp+0C] <-- return buffer
:10002F6C 8D044A lea eax, dword ptr [edx+2*ecx] <-- make room for the next WORD
:10002F6F 50 push eax
:10002F70 8B4DFC mov ecx, dword ptr [ebp-04] <-- CELL to read from
:10002F73 83C108 add ecx, 00000008 <-- skips the CELL 1-7
:10002F76 51 push ecx
:10002F77 8B5508 mov edx, dword ptr [ebp+08] <-- packet record
:10002F7A 52 push edx
* Reference To: SX32W.RNBOsproRead, Ord:0001h
|
:10002F7B E847520200 Call 100281C7 <-- call sproRead
:10002F80 25FFFF0000 and eax, 0000FFFF <-- polish the error code
:10002F85 8945F8 mov dword ptr [ebp-08], eax
:10002F88 837DF800 cmp dword ptr [ebp-08], 00000000 <-- was it successful ?
:10002F8C 7420 je 10002FAE <-- good cracker
:10002F8E 837DF807 cmp dword ptr [ebp-08], 00000007
:10002F92 750C jne 10002FA0
:10002F94 8B45F4 mov eax, dword ptr [ebp-0C]
:10002F97 C7400408000000 mov [eax+04], 00000008
:10002F9E EB0A jmp 10002FAA
This code is looped from CELL 8 to CELL 17 (in HEX).
After we have all the dongle data store in memory we continue on.
The code below is the CALL that called the mess above.
:100029A5 E89B050000 call 10002F45 <-- called the above code (sproRead stuff)
:100029AA 85C0 test eax, eax <-- return value from the CALL must be 1
:100029AC 7504 jne 100029B2 <-- good jump
:100029AE 33C0 xor eax, eax
:100029B0 EB48 jmp 100029FA
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:100029AC(C)
|
:100029B2 8B4DC8 mov ecx, dword ptr [ebp-38] <-- gets one of the returned WORDs from the dongle
:100029B5 8B55D4 mov edx, dword ptr [ebp-2C]
:100029B8 899128040000 mov dword ptr [ecx+00000428], edx
:100029BE 8B45C8 mov eax, dword ptr [ebp-38] <-- buffer to the returned dongle data
:100029C1 8B8828040000 mov ecx, dword ptr [eax+00000428]
:100029C7 3B4D0C cmp ecx, dword ptr [ebp+0C] <-- compares the USER NUMBER form the program with the one returned from the dongle.
:100029CA 7507 jne 100029D3
:100029CC B801000000 mov eax, 00000001 <-- good flag
:100029D1 EB27 jmp 100029FA
:100029C7 3B4D0C cmp ecx, dword ptr [ebp+0C]
At this point ECX holds the returned USER NUMBER from the dongle and [EBP+0C] holds the correct USER NUMBER, well actually the one you used when you installed the program.
So by simply doing D [EBP+0C] we can find out one of the return values. So we write it down and also it's position from the first WORD from the dongle.
After we return from this call we come to a check to see if this all mess suceeded.
:10002428 E847050000 call 10002974 <-- the call we just came back from
:1000242D 85C0 test eax, eax <-- EAX must be equal to 1
:1000242F 0F84A6000000 je 100024DB <-- bad jump
:10002435 C745EC00000000 mov [ebp-14], 00000000 <-- good flag
:10002491 8B4DD8 mov ecx, dword ptr [ebp-28]
:10002494 83790408 cmp dword ptr [ecx+04], 00000008 <-- check a return value from call
:10002498 7509 jne 100024A3 <-- bad jump
:1000249A C745D001000000 mov [ebp-30], 00000001 <-- good flag
:100024A1 EB0F jmp 100024B2 <-- good jump
The check above is not checking a dongle data, it's some check from a call to see if it suceeded.
Once the above compare is done and the JMP 100024B2 is executed we are done for this one part.
Well it's time to look at the sproRead() function on how to emulate it.
When looking at Sx32w.dll and scrolling to the sproRead function we see all the check it does to
make sure the packet record was initialized and that everything else is good before it gets to the
actual reading. The actual reading begins at address 4074E0
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:004074CB(C)
|
:004074E0 66C746300A00 mov [esi+30], 000A
:004074E6 668B442410 mov ax, word ptr [esp+10]
:004074EB 66894634 mov word ptr [esi+34], ax <-- some bullshit
:004074EF 56 push esi
:004074F0 E81BE9FFFF call 00405E10 <-- does the actual reading stuff, not interesting
:004074F5 0AC0 or al, al <-- AL must be 0, if it's 3 means it failed
:004074F7 7517 jne 00407510 <-- jump if reading failed
:004074F9 668B4636 mov ax, word ptr [esi+36] <--return the dongle WORD
:004074FD 668907 mov word ptr [edi], ax <-- save it to buffer
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:0040751E(C)
|
:00407500 668B4606 mov ax, word ptr [esi+06] <-- error code again
:00407504 50 push eax
:00407505 E8B6F5FFFF call 00406AC0 <-- function to clean up the error code
:0040750A 5F pop edi EAX must be 0 after this call.
:0040750B 5E pop esi
:0040750C C20C00 ret 000C
The above code does the reading from the dongle and stores the returned WORD into EDI and then checks
the error code again and returns in EAX the error code to the calling function, so when we RET 000C
from this function AX must be 0.
Ok on with the emulator. Looking at some of CrackZ tutorials on Sentinal, we see that he uses pretty
much the same emulator for all the apps he cracked with sspro. This is what CrackZ wrote:
:007882E1 JZ 007882E7 (0F 84 00 00 00 00) <-- Packet record validated.
:007882E7 PUSH EBP (55)
:007882E7 MOV EAX, [ESP+20] (8B 44 24 20) <-- Get address to read.
:007882EB SHL EAX, 1 (D1 E0) <-- As we are emulating a WORD.
:007882ED CALL $+5 (E8 00 00 00 00)
:007882F3 POP EBP (5D)
:007882F4 LEA EDI, [EBP+1C] (8D 7D 1C) <-- Where the simulated memory will start.
:007882F7 MOVZX EAX, WORD PTR [EAX+EDI] (0F B7 04 38) <-- Retrieve the WORD.
:007882FB MOV EDI, [ESP+24] (8B 7C 24 24) <-- Get address to place dongle WORD.
:007882FF MOV [EDI],AX (66 89 07) <-- Place it.
:00788302 XOR EAX, EAX (33 C0) <-- Success.
:00788304 POP EBP (5D)
:00788305 POP EDI (5F)
:00788306 POP ESI (5E)
:00788307 POP EBX (C9) <-- POP registers from stack as required.
:00788308 LEAVE (C9)
:00788309 RET 0C (C2 0C 00) <-- End.
So I took it and implemented it to my sproRead function but it kept crashing. So here is what I did:
004074E0 push ebp <-- we save EBP
004074E1 call $+5 <-- gets the Delta Offset
004074E6 pop ebp <-- puts the Delta Offset into EBP
004074E7 lea edx, [ebp+4C1Ah] <-- this is where my dongle data is in the file
004074ED pop ebp <-- fix the stack again otherwise it crashes
004074EE shl ecx, 1 <-- ECX holds the WORD we gonna read
004074F0 movzx eax, word ptr [ecx+edx] <-- read the WORd from simulated memory
004074F4 mov dx, 400h <-- hard code a good error code
004074F8 mov [esi+6], dx
004074FC nop
004074FD mov [edi], ax <-- store dongle code in EDI (buffer)
00407500 mov ax, [esi+6]
00407504 push eax
00407505 call sub_406AC0 <-- cleans up error code, EAX must be 0 when leaving.
0040750A pop edi
0040750B pop esi
0040750C retn 0Ch <-- return to the caller function
Since this file we are putting our emulator in is a DLL that means it can be loaded to a different memory page every time the program runs. When running App.exe it gets loaded to 10000000 and when
App2.exe (product manager) runs it gets loaded again but this time to 70000000. So we can't hard
code the address of our dongle data in the file. If you don't follow me, I took the dongle data
that I found and put it into the file right after the last byte of the ..RELOC section.
from 4074E0 to 4074E6 we calculate the Delta Offset which is our EIP + Loaded address of DLL, so no
matter where the DLL is loaded the CALL $+5 will always calculate our correct position.
at 4074E7 I take my current position and add 4C1Ah to it this will put me right at the first WORD
of my simulated dongle data that I hard coded to the file.
123A 223A 323A 423A 0000 0000 0000 0000 <-- end of .RELOC section
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
FFFF FFFF 0100 0200 0100 0000 005E 1A00 <-- begining of my dongle data
The second POP EBP is required here otherwise the stack gets screwed up and you will crash.
After that we get to SHL ECX, 1 this takes the number that comes in as a parameter to the function
which tells it which word in the dongle we need to read and we SHL it with 1 (same as multiplying
by 2) to get the correct WORD, since a word is 2 bytes.
Then we get to MOVZX, what this does is moves the WORD at [ECX+EDX] to EAX and extends the zeros meaning
fill the rest with zeros. ECX is the WORD we need to read and EDX is the address to my dongle data, so
this says get the first WORD from the dongle data.
After this we are done with the emulator, at address 4074F4 we put in the error code 0400 and then we take AX which holds our dongle WORD we got a second ago and puts it into the buffer that was pushed
as a parameter to this function. Then we get to the CALL at 407505 which PUSHes 0400 and cleans it up
to return only AL which will be 00. If the Read function was to fail the error code would be 0403
and after that call AL would be 03 and we know from the docs that this means "Key not found".
We now have a copletely emulated sprRead function, that will run correctly no matter when the DLL is loaded to.
Next we have sproQuery, this is very hard to make a generic emulator for so we just trace it with SI and see what's going on. We look at cm32.dll and see what it does. It really simple, all you have to do is make sure the sproQuery function returns 00 in AL and you are set, there are no checks of the returned values.
cm32.dll file is very much like PMCore.dll which is the main DLL for the product manager so you will have to make some patches to this file as well to make sure the sproFindFirstUnit and sproQuery are fixed.
Ok so we did all this and we run App.exe but when is going on here, everything we changed on cm32.dll and PMCore.dll is gone...WTF !
Ok it's probably some kind of file checker. What we do is set a BPX CreateFileA and run App.exe again and when it breaks we do one F10 and D *(ESP+08) and we see what file is being opened. There are alot
of files opened before cm32.dll so we F5 until we see cm32.dll being opened and we F12 a few times until
we are in Fscore.dll, we then F10 some more until you get to something like:
MOV CL, [ESI]
MOV DL, [EDI]
CMP CL, DL
JZ some_addr
I can't remember the code exactly but it's like this, if you do a D CL and D DL you will see it's comparing the file byte by byte with an image it has stored somewhere so just patch the JZ some_addr
to JMP some_addr and it will not change your patched file. Now run App2.exe and do the same thing to fix the check it has on PMCore.dll.
Run App.exe and it's working..w00h00 :)
Now when u do Rip n' Print everything works fine except there is something there that forces the printer to print white lines on the images text. So we open PMCore.dll and look for some clues and we see something called IsDemoVersion() well we must make this fucntion suceed and we are good to go, the good flag here is 01 so check it out, it's really easy.
Conclusion:
-----------
This protection was fun but it wasn't as hard as I expected it to be. sspro is a decent dongle but it has too many fingerprints that allows us to find it with no problem. This program did not use the sproQuery correct at all and barely used sproRead other then read some shit and check like 2 things in memory which made it very easy to emulate.
The key with dongle reversing is to trace and follow paths, if you don't have the nerves to do this, dongles are not for you. I like dongles they are fun to crack, so stick with it and you will see that most dongle protected apps are very easy to reverse.
Greets to my pals: zip, CrackZ, GzA, int13h
Published by Tsehp, June 2000.
Introduction:
-------------
First off I want to say that I have purchased this product and I have an
original dongle and license for FlexiSIGN PRO. Amiable is a company that makes
high end software for the sign making industry. A tutorial written by CrackZ a
while back about CASMate was the first paper on the protection used by this
company. I have also cracked Inspire 1.6 by the same company but didn't have
the time to write a paper on it. Anyways this application uses the Sentinel SuperPRO
dongle along with a user number/password which will decide which program you are
allowed to install. In this tutorial I will show you how to find and emulate the
Sentinel dongle and how to bypass any of the related checks. It's gonna be fun.
Tools:
------
http://zencrack2.cjb.net/ --> all the sentinel tutorials (and everything else)
ftp://ftp.rainbow.com/pub/online_documents/ --> recommended reading pro223.pdf, spro_dev.pdf
SoftIce
Hex Editor
IDA or W32Dasm
Lets get jiggy wit it:
----------------------
Ok after installing the program we look at the files, ummm...great now what ?
Well run the App.exe which is the main program, the App2.exe is the Product Manager (we
will worry about that later). What do you seen ? it loads up some stuff and then gives
and error message saying - "This application requires that a Hardware key be installed,
but none was found". This tells us that the program searched our ports to see if there
is a dongle attached and it failed. Having read the sentinel manual (address above) we
know that the program must call the sproFindFirstUnit after initializing the packet record
with sproInitialize. While we are talking about the packet record, what it is is a structer
that holds and will hold dongle information, from the manual again we know that this packet
record MUST be pushed on to the stack before any other dongle API can be called. That is a
good thing for us because it provides us with a land mark so we could find all the API calls
to the dongle. We also know that when a dongle API fails it will return error code 3 in EAX.
That is another helping point, we can look at the return values from calls and see if they
return 3 in EAX, another thing to help us is the dongle lag, when the program tries to access
the dongle and it's not there it will hang for a few seconds, when that happens you can pretty
much say that the CALL was a dongle API call.
Before the actual call to the dongle API the program MUST check if it has a valid packet record
before proceding to the dongle call, that is what we will look for to find the dongle calls.
it looks something like this:
CMP WORD PTR [ESI], 7242
JE address
MOV AX, 0002
POP EDI
POP ESI
RET 000C
that 7242 is the packet record signature and it must be checked and if it fails the error code
that is returned in EAX is 2 which means Invalid Packet.
Ok enough with the theory, lets get to business. After looking at which files are called by the
program we will open each one of them in W32Dasm and look at what functions they import and export.
We come to a file named Sx32w.dll after opening it we see in the Export list that it exports all
the functions that we saw in the Sentinel manual. Interesting....If you HEX edit that file you will
see towards the end it will say "SentinelSuperPro WIN32 DLL" that is the DLL that is provided by
Rainbow to developers to use with their applications, and it's also helpfull to us because we now
have a central place calling the dongle and we know in order to call the dongle this DLL must be
called. What we will do is put our emulator in this file and make all the dongle modifications in
this central file.
The key to cracking a dongle protected program is tracing different paths until you get to the
correct one. Ok so how do we break on the dongle API calls ? What I did, since the APIs are called
from a bunch of different DLLs, I opened Sx32w.dll and looked up the offset to sproFindFirstUnit
then I Hex edited the file, placed the cursor at that offset and replaced the first byte with CC
which is INT 3. You must rememeber the byte that you replaced so we can replace it back. Now open
SoftIce and place a breakpoint on INT3 such as BPINT 3 and exit to windows (x). Now run the App.exe
and wait for the break. SI broke, now we do E EIP to edit the bytes at the Instruction Pointer and
change the CC back to what it was before and press ENTER. Now just BPX EIP and exit SI and go back
to the Hex edit of the file and change back the CC to the original byte. Now we run the App.exe
again and we will have a break on sproFindFirstUnit. When it breaks it looks like this:
Exported fn(): RNBOsproFindFirstUnit - Ord:000Bh
:004072B0 53 push ebx
:004072B1 56 push esi
:004072B2 8B44240C mov eax, dword ptr [esp+0C]
:004072B6 0BC0 or eax, eax <--- checks to see if the program PUSHed
:004072B8 7509 jne 004072C3 the packet record
:004072BA 66B80200 mov ax, 0002
:004072BE 5E pop esi
:004072BF 5B pop ebx
:004072C0 C20800 ret 0008
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:004072B8(C)
|
:004072C3 50 push eax
:004072C4 E8079EFFFF call 004010D0
:004072C9 8BF0 mov esi, eax
:004072CB 66813E4272 cmp word ptr [esi], 7242 <-- this is the trademark check for the
:004072D0 740E je 004072E0 validity of the packet record
:004072D2 66B80200 mov ax, 0002
:004072D6 5E pop esi
:004072D7 5B pop ebx
:004072D8 C20800 ret 0008
The code that follows is the actual call to the dongle but we don't really care about it because at this point there is no return value from the dongle only an error code of 3 if not present or 0 if present.
What we will do is, NOP out the JNE instruction at 004072B8 and change the MOV ax, 0002 to MOV AX,0000
looks like this:
:004072B0 53 push ebx
:004072B1 56 push esi
:004072B2 8B44240C mov eax, dword ptr [esp+0C]
:004072B6 0BC0 or eax, eax
:004072B8 90 nop <-- do away with the useless JNE
:004072B9 90 nop
:004072BA 66B80000 mov ax, 0000 <-- force the error code to be 0 - successful
:004072BE 5E pop esi
:004072BF 5B pop ebx
:004072C0 C20800 ret 0008
That is it for taking care of the first dongle check. Pretty simple eh ? well don't get too happy there is more to do.
After we hard coded this patch using a Hex editor, and I'm gonna assume you know how to do that, we will continue to the next dongle call which will be sproRead. This is where most of the work will take place, but it's not too bad. Lets start by setting up a BPX on INT 3 like we did before with sproFindFirstUnit so we could get to the code in SI. once we do that we break here:
Exported fn(): RNBOsproRead - Ord:0002h
:00407480 56 push esi
:00407481 57 push edi
:00407482 8B44240C mov eax, dword ptr [esp+0C]
:00407486 0BC0 or eax, eax
:00407488 7509 jne 00407493
:0040748A 66B80200 mov ax, 0002
:0040748E 5F pop edi
:0040748F 5E pop esi
:00407490 C20C00 ret 000C
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00407488(C)
|
:00407493 50 push eax
:00407494 E8379CFFFF call 004010D0
:00407499 8BF0 mov esi, eax
:0040749B 66813E4272 cmp word ptr [esi], 7242 <-- trademark check again
:004074A0 740E je 004074B0
:004074A2 66B80200 mov ax, 0002
:004074A6 5F pop edi
:004074A7 5E pop esi
:004074A8 C20C00 ret 000C
some more stupid code.......
:004074C5 8B7C2414 mov edi, dword ptr [esp+14] <-- EDI is holding the buffer to store :004074C9 0BFF or edi, edi the return codes form the dongle
:004074CB 7513 jne 004074E0
:004074CD 66C746061004 mov [esi+06], 0410
:004074D3 66B81000 mov ax, 0010
:004074D7 5F pop edi
:004074D8 5E pop esi
:004074D9 C20C00 ret 000C
:004074DC 8D642400 lea esp, dword ptr [esp]
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:004074CB(C)
|
:004074E0 66C746300A00 mov [esi+30], 000A
:004074E6 668B442410 mov ax, word ptr [esp+10]
:004074EB 66894634 mov word ptr [esi+34], ax
:004074EF 56 push esi
:004074F0 E81BE9FFFF call 00405E10 <-- this leads to the sproRead call, we will emulate it
:004074F5 0AC0 or al, al <-- is AL = 0 ?
:004074F7 7517 jne 00407510 <-- if not u screwed
:004074F9 668B4636 mov ax, word ptr [esi+36] <-- return WORD from the dongle
:004074FD 668907 mov word ptr [edi], ax <-- put that WORD into the buffer
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:0040751E(C)
|
:00407500 668B4606 mov ax, word ptr [esi+06] <-- holds the error code
:00407504 50 push eax
:00407505 E8B6F5FFFF call 00406AC0 <-- checks error code, must return 0 in EAX to succeed
:0040750A 5F pop edi
:0040750B 5E pop esi
:0040750C C20C00 ret 000C
This great code below came from cm32.dll which is the DLL in charge of the main protection. It talkes to the dongle and figures out the return values.
This is the code that called the code above that we looked at. Here is some explanations:
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:10002F55(U)
|
:10002F60 837DFC17 cmp dword ptr [ebp-04], 00000017 <-- checks if we reached CELL 17
:10002F64 7D4A jge 10002FB0
:10002F66 8B4DFC mov ecx, dword ptr [ebp-04] <-- current CELL number
:10002F69 8B550C mov edx, dword ptr [ebp+0C] <-- return buffer
:10002F6C 8D044A lea eax, dword ptr [edx+2*ecx] <-- make room for the next WORD
:10002F6F 50 push eax
:10002F70 8B4DFC mov ecx, dword ptr [ebp-04] <-- CELL to read from
:10002F73 83C108 add ecx, 00000008 <-- skips the CELL 1-7
:10002F76 51 push ecx
:10002F77 8B5508 mov edx, dword ptr [ebp+08] <-- packet record
:10002F7A 52 push edx
* Reference To: SX32W.RNBOsproRead, Ord:0001h
|
:10002F7B E847520200 Call 100281C7 <-- call sproRead
:10002F80 25FFFF0000 and eax, 0000FFFF <-- polish the error code
:10002F85 8945F8 mov dword ptr [ebp-08], eax
:10002F88 837DF800 cmp dword ptr [ebp-08], 00000000 <-- was it successful ?
:10002F8C 7420 je 10002FAE <-- good cracker
:10002F8E 837DF807 cmp dword ptr [ebp-08], 00000007
:10002F92 750C jne 10002FA0
:10002F94 8B45F4 mov eax, dword ptr [ebp-0C]
:10002F97 C7400408000000 mov [eax+04], 00000008
:10002F9E EB0A jmp 10002FAA
This code is looped from CELL 8 to CELL 17 (in HEX).
After we have all the dongle data store in memory we continue on.
The code below is the CALL that called the mess above.
:100029A5 E89B050000 call 10002F45 <-- called the above code (sproRead stuff)
:100029AA 85C0 test eax, eax <-- return value from the CALL must be 1
:100029AC 7504 jne 100029B2 <-- good jump
:100029AE 33C0 xor eax, eax
:100029B0 EB48 jmp 100029FA
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:100029AC(C)
|
:100029B2 8B4DC8 mov ecx, dword ptr [ebp-38] <-- gets one of the returned WORDs from the dongle
:100029B5 8B55D4 mov edx, dword ptr [ebp-2C]
:100029B8 899128040000 mov dword ptr [ecx+00000428], edx
:100029BE 8B45C8 mov eax, dword ptr [ebp-38] <-- buffer to the returned dongle data
:100029C1 8B8828040000 mov ecx, dword ptr [eax+00000428]
:100029C7 3B4D0C cmp ecx, dword ptr [ebp+0C] <-- compares the USER NUMBER form the program with the one returned from the dongle.
:100029CA 7507 jne 100029D3
:100029CC B801000000 mov eax, 00000001 <-- good flag
:100029D1 EB27 jmp 100029FA
:100029C7 3B4D0C cmp ecx, dword ptr [ebp+0C]
At this point ECX holds the returned USER NUMBER from the dongle and [EBP+0C] holds the correct USER NUMBER, well actually the one you used when you installed the program.
So by simply doing D [EBP+0C] we can find out one of the return values. So we write it down and also it's position from the first WORD from the dongle.
After we return from this call we come to a check to see if this all mess suceeded.
:10002428 E847050000 call 10002974 <-- the call we just came back from
:1000242D 85C0 test eax, eax <-- EAX must be equal to 1
:1000242F 0F84A6000000 je 100024DB <-- bad jump
:10002435 C745EC00000000 mov [ebp-14], 00000000 <-- good flag
:10002491 8B4DD8 mov ecx, dword ptr [ebp-28]
:10002494 83790408 cmp dword ptr [ecx+04], 00000008 <-- check a return value from call
:10002498 7509 jne 100024A3 <-- bad jump
:1000249A C745D001000000 mov [ebp-30], 00000001 <-- good flag
:100024A1 EB0F jmp 100024B2 <-- good jump
The check above is not checking a dongle data, it's some check from a call to see if it suceeded.
Once the above compare is done and the JMP 100024B2 is executed we are done for this one part.
Well it's time to look at the sproRead() function on how to emulate it.
When looking at Sx32w.dll and scrolling to the sproRead function we see all the check it does to
make sure the packet record was initialized and that everything else is good before it gets to the
actual reading. The actual reading begins at address 4074E0
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:004074CB(C)
|
:004074E0 66C746300A00 mov [esi+30], 000A
:004074E6 668B442410 mov ax, word ptr [esp+10]
:004074EB 66894634 mov word ptr [esi+34], ax <-- some bullshit
:004074EF 56 push esi
:004074F0 E81BE9FFFF call 00405E10 <-- does the actual reading stuff, not interesting
:004074F5 0AC0 or al, al <-- AL must be 0, if it's 3 means it failed
:004074F7 7517 jne 00407510 <-- jump if reading failed
:004074F9 668B4636 mov ax, word ptr [esi+36] <--return the dongle WORD
:004074FD 668907 mov word ptr [edi], ax <-- save it to buffer
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:0040751E(C)
|
:00407500 668B4606 mov ax, word ptr [esi+06] <-- error code again
:00407504 50 push eax
:00407505 E8B6F5FFFF call 00406AC0 <-- function to clean up the error code
:0040750A 5F pop edi EAX must be 0 after this call.
:0040750B 5E pop esi
:0040750C C20C00 ret 000C
The above code does the reading from the dongle and stores the returned WORD into EDI and then checks
the error code again and returns in EAX the error code to the calling function, so when we RET 000C
from this function AX must be 0.
Ok on with the emulator. Looking at some of CrackZ tutorials on Sentinal, we see that he uses pretty
much the same emulator for all the apps he cracked with sspro. This is what CrackZ wrote:
:007882E1 JZ 007882E7 (0F 84 00 00 00 00) <-- Packet record validated.
:007882E7 PUSH EBP (55)
:007882E7 MOV EAX, [ESP+20] (8B 44 24 20) <-- Get address to read.
:007882EB SHL EAX, 1 (D1 E0) <-- As we are emulating a WORD.
:007882ED CALL $+5 (E8 00 00 00 00)
:007882F3 POP EBP (5D)
:007882F4 LEA EDI, [EBP+1C] (8D 7D 1C) <-- Where the simulated memory will start.
:007882F7 MOVZX EAX, WORD PTR [EAX+EDI] (0F B7 04 38) <-- Retrieve the WORD.
:007882FB MOV EDI, [ESP+24] (8B 7C 24 24) <-- Get address to place dongle WORD.
:007882FF MOV [EDI],AX (66 89 07) <-- Place it.
:00788302 XOR EAX, EAX (33 C0) <-- Success.
:00788304 POP EBP (5D)
:00788305 POP EDI (5F)
:00788306 POP ESI (5E)
:00788307 POP EBX (C9) <-- POP registers from stack as required.
:00788308 LEAVE (C9)
:00788309 RET 0C (C2 0C 00) <-- End.
So I took it and implemented it to my sproRead function but it kept crashing. So here is what I did:
004074E0 push ebp <-- we save EBP
004074E1 call $+5 <-- gets the Delta Offset
004074E6 pop ebp <-- puts the Delta Offset into EBP
004074E7 lea edx, [ebp+4C1Ah] <-- this is where my dongle data is in the file
004074ED pop ebp <-- fix the stack again otherwise it crashes
004074EE shl ecx, 1 <-- ECX holds the WORD we gonna read
004074F0 movzx eax, word ptr [ecx+edx] <-- read the WORd from simulated memory
004074F4 mov dx, 400h <-- hard code a good error code
004074F8 mov [esi+6], dx
004074FC nop
004074FD mov [edi], ax <-- store dongle code in EDI (buffer)
00407500 mov ax, [esi+6]
00407504 push eax
00407505 call sub_406AC0 <-- cleans up error code, EAX must be 0 when leaving.
0040750A pop edi
0040750B pop esi
0040750C retn 0Ch <-- return to the caller function
Since this file we are putting our emulator in is a DLL that means it can be loaded to a different memory page every time the program runs. When running App.exe it gets loaded to 10000000 and when
App2.exe (product manager) runs it gets loaded again but this time to 70000000. So we can't hard
code the address of our dongle data in the file. If you don't follow me, I took the dongle data
that I found and put it into the file right after the last byte of the ..RELOC section.
from 4074E0 to 4074E6 we calculate the Delta Offset which is our EIP + Loaded address of DLL, so no
matter where the DLL is loaded the CALL $+5 will always calculate our correct position.
at 4074E7 I take my current position and add 4C1Ah to it this will put me right at the first WORD
of my simulated dongle data that I hard coded to the file.
123A 223A 323A 423A 0000 0000 0000 0000 <-- end of .RELOC section
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
FFFF FFFF 0100 0200 0100 0000 005E 1A00 <-- begining of my dongle data
The second POP EBP is required here otherwise the stack gets screwed up and you will crash.
After that we get to SHL ECX, 1 this takes the number that comes in as a parameter to the function
which tells it which word in the dongle we need to read and we SHL it with 1 (same as multiplying
by 2) to get the correct WORD, since a word is 2 bytes.
Then we get to MOVZX, what this does is moves the WORD at [ECX+EDX] to EAX and extends the zeros meaning
fill the rest with zeros. ECX is the WORD we need to read and EDX is the address to my dongle data, so
this says get the first WORD from the dongle data.
After this we are done with the emulator, at address 4074F4 we put in the error code 0400 and then we take AX which holds our dongle WORD we got a second ago and puts it into the buffer that was pushed
as a parameter to this function. Then we get to the CALL at 407505 which PUSHes 0400 and cleans it up
to return only AL which will be 00. If the Read function was to fail the error code would be 0403
and after that call AL would be 03 and we know from the docs that this means "Key not found".
We now have a copletely emulated sprRead function, that will run correctly no matter when the DLL is loaded to.
Next we have sproQuery, this is very hard to make a generic emulator for so we just trace it with SI and see what's going on. We look at cm32.dll and see what it does. It really simple, all you have to do is make sure the sproQuery function returns 00 in AL and you are set, there are no checks of the returned values.
cm32.dll file is very much like PMCore.dll which is the main DLL for the product manager so you will have to make some patches to this file as well to make sure the sproFindFirstUnit and sproQuery are fixed.
Ok so we did all this and we run App.exe but when is going on here, everything we changed on cm32.dll and PMCore.dll is gone...WTF !
Ok it's probably some kind of file checker. What we do is set a BPX CreateFileA and run App.exe again and when it breaks we do one F10 and D *(ESP+08) and we see what file is being opened. There are alot
of files opened before cm32.dll so we F5 until we see cm32.dll being opened and we F12 a few times until
we are in Fscore.dll, we then F10 some more until you get to something like:
MOV CL, [ESI]
MOV DL, [EDI]
CMP CL, DL
JZ some_addr
I can't remember the code exactly but it's like this, if you do a D CL and D DL you will see it's comparing the file byte by byte with an image it has stored somewhere so just patch the JZ some_addr
to JMP some_addr and it will not change your patched file. Now run App2.exe and do the same thing to fix the check it has on PMCore.dll.
Run App.exe and it's working..w00h00 :)
Now when u do Rip n' Print everything works fine except there is something there that forces the printer to print white lines on the images text. So we open PMCore.dll and look for some clues and we see something called IsDemoVersion() well we must make this fucntion suceed and we are good to go, the good flag here is 01 so check it out, it's really easy.
Conclusion:
-----------
This protection was fun but it wasn't as hard as I expected it to be. sspro is a decent dongle but it has too many fingerprints that allows us to find it with no problem. This program did not use the sproQuery correct at all and barely used sproRead other then read some shit and check like 2 things in memory which made it very easy to emulate.
The key with dongle reversing is to trace and follow paths, if you don't have the nerves to do this, dongles are not for you. I like dongles they are fun to crack, so stick with it and you will see that most dongle protected apps are very easy to reverse.
Greets to my pals: zip, CrackZ, GzA, int13h
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)
赞赏
他的文章
看原图
赞赏
雪币:
留言: