KeyLok targets in general, http://www.keylok.com
KeyLok SDK available on request.
Double click the string reference "KeyLok" (easy enough). For the resources I had "Keylok" a common occurence, although, it will not be valid for all cases. There maybe several references but either one will be fine, they all lead to the main API calls. Scroll up a few lines until you get to the beginning of this function, this will be explained more later, atm its just something to introduce the target.
NOTE : In this specific application the import data for the dongle was embedded within the app. Using this information I loaded it into symbol loader and translated the exe. Once done the CALL appeared to me as they would appear to the author. I will have a //API next to each call through this tutorial, however, assume it does not exist.
push -1
push offset loc_410F10
mov eax, large fs:0
push eax
mov large fs:0, esp
sub esp, 0E8h
lea ecx, [esp+0F4h+var_E8]
call sub_40C830 // CDongle::CDongle *1
lea ecx, [esp+0F4h+var_E8]
mov [esp+0F4h+var_4], 0
call sub_40C9A0 // AuthenticateDongle *2
test al, al
jz loc_402C36
lea ecx, [esp+0F4h+var_E8]
call sub_40CA60 // GetReadAuthorization
lea eax, [esp+0F4h+var_EC]
push eax
lea ecx, [esp+0F8h+var_E8]
call sub_40CAA0 // GetSerialNumber
test al, al
jz loc_402C36
mov eax, [esp+0F4h+arg_0]
push ebx
push ebp
push esi
push edi
lea ecx, [esp+104h+var_D4]
push ecx
lea edx, [esp+108h+var_DC]
push edx
push 0
push 0F003Fh
push 0
push offset unk_41A2F8 <--"KeyLok"
OK, set an int 3 at the top of this subroutine and ensure that the program does call it and if it fails (test al,al) it should issue a warning, which it does. OK good now for the real stuff. Take a look at the first call, call sub_40C830 (*1). Trace inside it to see what it contains :
mov eax, ecx
xor cl, cl
mov dword ptr [eax], 414934h
mov [eax+4], cl
mov [eax+5], cl
mov [eax+6], cl
retn
After analyzing this small call one can conclude that it is harmless, and is probably just some data used for the developer such as a developer ID or possibly used in serial number generation. NOTE : Most dongles have some sort of way of identifying themselves for specific applications. For example, if you owned a copy of 3dsmax and Lightwave you would know that they both use Sentinel dongles, these two dongles must have a way to differentiate each other, so they have Developer ID's and other parameters to keep them unique to their application.
So our next step is to check the next call in the series which is call sub_40C9A0 (*2). At first glance it looks like we have encountered a very important call in this dongle. Why? If you check the xrefs in this you will see about 20 or so references. Below is a code snippet and analysis :
sub esp, 8
push ebx
push esi
push edi
mov esi, ecx
lea eax, [esp+14h+var_4]
push eax
lea ecx, [esp+18h+var_8]
push ecx
push 492Dh <-- seed values for dongle
push 0B537h <-- as above
xor ebx, ebx
push 0A326h <- another seed parameter
push 1
mov ecx, esi
mov [esi+4], bl
mov [esi+5], bl
mov [esi+6], bl
call sub_40C910 <-- first call to 40C910 // CallKeyLock_XXXX
mov edx, [esp+14h+var_4]
mov eax, [esp+14h+var_8]
and edx, 7
push edx
push eax
mov ecx, esi
call sub_40C880 <-- first call to 40C880 // RotateLowerShortLeft
mov ecx, [esp+14h+var_8]
mov edi, eax
mov eax, [esp+14h+var_4]
and ecx, 0Fh
push ecx
xor edi, eax
push eax
mov ecx, esi
xor edi, 15Eh
call sub_40C880 <-- 2nd call to 40C880
lea edx, [esp+14h+var_4]
push edx
mov edx, [esp+18h+var_8]
lea ecx, [esp+18h+var_8]
push ecx
mov ecx, [esp+1Ch+var_4]
push ebx
xor edx, ecx
push edx
push eax
push edi
mov ecx, esi
call sub_40C910 <-- 2nd to 40c910
cmp [esp+14h+var_8], 0DC9h <-- Return value 0DC9h
jnz short loc_40CA3F <-- assume bad
cmp [esp+14h+var_4], 0F3EAh <-- Return value 0F3EAh
jnz short loc_40CA3F <-- assume is bad for now
mov ebx, 1 <-- Move good flag
mov [esi+4], bl
pop edi
mov dword_41B834, 1
mov al, [esi+4]
pop esi
pop ebx
add esp, 8
retn
OK, essentially what this call is made up of is two sub calls. sub_40C880 & sub_40C910. Under closer examination both these calls have no xrefs to anywhere except for this API location. Now, we need to analyse them to see what they do. Starting with 40C910 :
mov eax, [esp+arg_C]
mov ecx, [esp+arg_8]
mov edx, [esp+arg_4]
push eax
mov eax, [esp+4+arg_0]
push ecx
push edx
push eax
call sub_40D5A0 // KFUNC, the main security call of the dongle
mov edx, [esp+10h+arg_10]
mov ecx, eax
and ecx, 0FFFFh
mov [edx], ecx
mov ecx, [esp+10h+arg_14]
shr eax, 10h
add esp, 10h
mov [ecx], eax
retn 18h
Interesting, it moves some values into the stack and then makes one more call then moves some other return values. Below inside 40D5A0 we find the real dongle communication via DeviceIoControl() and also :
loc_40D6A3:
push ebp
push ebx
push edi
push esi
call _KFUNC32S <-- looks like the actual security call.
add esp, 10h
pop ebp
pop edi
pop esi
pop ebx
add esp, 10Ch
retn
I am not going to butcher you with tons of code so I did not include the entire function, just a snippet. The reason for this paste was to show that this is where DeviceIoControl() is called and this call is where it makes the actual security call to the dongle. Armed with the SDK, we know this call is called KFUNC. KFUNC basically takes the values that were pushed onto the stack and generates some algorithmic return value from them. This is actually the core of the dongles protection. Almost all of the API calls made by the dongle eventually or somehow lead to KFUNC.
In this case there are only 4 xrefs to KFUNC, this is not very many calls to the root of the protection, however, if you were to go to one of the xref calls you would see that it had about 20, this means that is it very important to emulate these perfectly or else there will be invalid results somewhere in the application. So our goal is to emulate this call along with the 4 other security calls that call it. Lets investigate now.
Lets stop for a moment and backtrace to the other call which was sub_40C880. Inside this call we find the following :
sub_40C880 proc near
mov ecx, [esp+arg_4]
test ecx, ecx
mov eax, [esp+arg_0]
jle short loc_40C89A
mov edx, eax
shr edx, 0Fh
and edx, 1
dec ecx
lea eax, [edx+eax*2]
jnz short loc_40C88C
and eax, 0FFFFh
retn 8
sub_40C880 endp
Nothing out of the ordinary here. It just shifts some bytes and it also has only 2 xrefs which were calls from the API call GetAuthorization. In the SDK we find something similiar :
/******************************* ROTATELEFT *******************************/ unsigned int RotateLeft(unsigned int Target, int Counts) // This function rotates the bits in the Target left the number of positions // identified by the argument Counts { unsigned int LocalTarget, HighBit; int i; LocalTarget = Target; for (i=0; i<Counts; i++) { HighBit = LocalTarget & 0X8000; LocalTarget = (LocalTarget << 1) + (HighBit >> 15); } LocalTarget = LocalTarget & 0XFFFF; /* For 32 bit applications */ return (LocalTarget); } /*********************************************************************************/
So we can conlclude its harmless and used only for shifting some bits in the calculation of return codes. Now back to the function that we originally came from (sub_40C9A0), and time for reanalysis. Lets look at one of the calls that called 40C9A0, from the very begining again, we see this :
call sub_40C9A0
test al, al
jz loc_402C36
Now just for the hell of it, in your debugger make al = 1 when your at test al, al and you should be able to pass this function to the next function. Now let al = 0 and notice it fails and calls a dongle error. Well, now we know the first part of our emulation code, it should return a 1 to succeed. So lets start creating a pseudo emulation for 40C9A0.
40C9A0:
xor eax, eax
inc eax
<unknown>
ret
Now, lets take a look back at the SDK and we find that GetAuthorization() looks exactly like this code snippet.
cmp [esp+14h+var_8], 0DC9h <-- good code
jnz short loc_40CA3F <-- bad
cmp [esp+14h+var_4], 0F3EAh <-- good code
jnz short loc_40CA3F <-- bad
Now we need to try to add on more to our emulation, so we must gather what this call needs to succeed. It needs a return value of 1 and the returns need to equal the hardcoded values. So lets add to our emulation. You may be wondering what happened to the two other calls such as KFUNC and rotate. Well if you debug the application and follow the code you will see that the only use for those 2 calls were to retrieve values that would eventually just be compared to those 2 seed values that were hardcoded. There is no reason to mimic those calls if we can get the desired results by using a few instruction lines to do the same result. This is not always the case of course, but in this example it was found to be valid.
sub esp, 8
push ebx
push esi
push edi
mov esi, ecx
xor eax, eax
inc eax
mov d,[0041b834], 1 <-- this address will change from program
to program...
mov [esi+4], al
pop edi
pop esi
pop ebx
ret
In the event a dongle was plugged in we could assume that would
be the desired result according to the code we have
present. So lets continue to the next call after 40C9A0 which
is sub_40CA60. First notice that this call has a ton of xrefs
also. Another thing to notice is the call to 40C9A0 which means
it calls the API we just emulated.
sub_40CA60 proc near
push esi
mov esi, ecx
call sub_40C9A0 <-- api we emulated
mov al, [esi+4]
test al, al
jnz short loc_40CA73 <-- if it passed then go on to the real
call
xor al, al
pop esi
retn
loc_40CA73:
push 15Eh
push 11ADh <-- some parameters for KFUNC
push 0BA99h
push 2
mov ecx, esi
call sub_40C980
mov al, 1
mov [esi+5], al
pop esi
retn
sub_40CA60 endp
Because we emulated the test al, al should pass and we can continue with execution flow. We now land at 40CA73. It appears that it does not matter what happens here, we get our desired result of al = 1. So the jump we made earlier settled everything so this call we can leave it as is. As for the 40C980 this call is simply another call which leads to KFUNC so no harm in this one. Now for the next API in the series, sub_40CAA0. Here are the contents of this call :
sub_40CAA0 proc near
push esi
mov esi, ecx
call sub_40CA60
mov al, [esi+5]
test al, al
jnz short loc_40CAB5 // If the initial check passes go get the
dongle id
xor al, al
pop esi
retn 4
loc_40CAB5:
mov eax, [esp+arg_0] // GetDongleID will be stored here esp+8
push eax
push 0
push 0 <-- random parameter info
push 0
push 3
mov ecx, esi
call sub_40C950
mov eax, 1
mov dword_41B838, eax
pop esi
retn 4
sub_40CAA0 endp
Like the previous API calls this also has plenty of xrefs. This call is a function to get the dongles serial number. OK, whats important in this case is to look at the parameters. So I referenced the SDK.
GetSerialNumber PROC NEAR
mov ax, GETSN <-- Address where dongle id is stored
push ax ; Desired task
push ax ; Dummy arguments (Random information OK)
push ax
push ax
call KTASK
mov di, offset SerialNo
mov DataWord, ax
call WordToTable
mov dx, offset SNMessage
call WriteMessage
call Read
ret
GetSerialNumber ENDP
The mov ax, GETSN is the destination where the serial number should be held. Take a look at 40C950.This is once again another call that leads to KFUNC. From observation we can assume that KFUNC sends back a serial number which identifies the dongle. In this specific application it was used in the authorization key tied to the protection (I won't go into the license system since it isn't related to KeyLok). There is something that does need patching here and thats KFUNC again.
sub_40C950 proc
mov eax, [esp+arg_C]
mov ecx, [esp+arg_8]
mov edx, [esp+arg_4]
push eax
mov eax, [esp+4+arg_0]
push ecx
push edx
push eax
call sub_40D5A0
mov ecx, [esp+10h+arg_10]
and eax, 0FFFFh
add esp, 10h
mov [ecx], eax
retn 14h
sub_40C950 endp
Each time KFUNC is called, because there is no dongle plugged in, you get weird return values each time. By tracing every KFUNC call you see there are no other compare values, so my emulation for 40D5A0 can be as simple as XOR EAX, EAX / RET. If one wanted to be more aesthetic you could look at the parameters called by each API call that leads to KFUNC and hardcode a good return value. For example GetAuthorization would have a specific value on the stack so a emulation could look something like this :
cmp [esp+0c], 15E
jnz next_compare
mov eax, FFFFF3EA
ret
As far as emulation for GetSerialNumber() goes, if you want to return a specific dongle ID, use the code below :
GetSerialNumber:
mov eax, [esp+8]
mov [eax], anydatahere
xor eax, eax
inc eax
ret
Continuing on, after the license checks (not related to KeyLok) I found myself at the following call :
lea eax, [esp+13h]
push eax
lea ecx, [esp+20h]
call sub_40CAE0 <-- this is the API HasExpirationDate
mov al, [esp+13h]
test al, al
jz loc_403E53
lea ecx, [esp+12h]
push ecx
lea ecx, [esp+20h]
mov byte ptr [esp+16h], 0
call sub_40CBC0 <-- CheckExpirationDate
mov al, [esp+12h]
test al, al
jnz loc_403E53
Just common sense tells us not to make a emulator with an expiration date, that just makes more trouble, so lets make it so the flags pass on the first test al, al and it has no expiration. Time to look inside 40CAE0 which leads via sub_40C8B0 to another call to KFUNC. Lets have a look at some of the other xrefs to this HasExpirationDate API, because [eax] probably has something to do with the return result.
lea ecx, [esp+17h]
push ecx
lea ecx, [esp+20h]
call sub_40CAE0
mov al, [esp+17h] <-- here is what we are looking for
jz loc_40BD7
All calls to this API set the key flag using the following code :
call sub_40C8B0
cmp dword ptr [esp+4], 0FFFFFFFCh <-- we end up here (really
KFUNC)
mov eax, [esp+10h] <-- interesting
setnz dl
mov [eax], dl <-- very interesting
mov al, 1
pop esi
add esp, 8
A simple modification, say setnz dl patched to xor dl, dl + nop will ensure success.
KFUNC is based on an algorithm response. What this means is
that the dongle itself has an instruction set that takes in data,
runs it through some mathematical equation and returns a value
to the program. The theory behind the protection is that the cracker
(not hacker) cannot view this algorithm because it is hidden inside
the dongle. However, in most cases the problem
resides with the author or protector. Often the dongle is not
used to its full potential of protection. In the above example
I
analysed it was easy to see that the author put the actual return
results needed to make the program function properly.
This totally defeats the purpose of the KeyLok dongle. The dongle has a weak API structure and should not be used in such a fashion. My thoughts concluded that the only way to allow the KeyLok dongle to be secure in any way is that the algorithm response is used in the execution flow of the program. KeyLok has tons of ways to in which one can crack it. The above way is just one approach, I hope in the future that the algorithm can be discovered and a full emulation made.
Sab.