FlexiSIGN PRO and it's family of products from Amiable  -- by Goatass

Published by Tsehp, June 2000.





      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.





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


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



  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:



: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:



: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:



: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:



: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:



: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:



: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:



: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:



: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:





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.






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