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