MKS toolkit revisited
continuing drlan's work
Not Assigned
25 July 1998
by bb
Courtesy of Reverser's page of reverse engineering
slightly edited
by reverser+
Well, another essay by bb, that shows you how to use your windows' knowledge to fool windows' targets into doing anything you want. I would advice you to read first bb's other (very good) essay about nag screen reversing techniques.
There is a crack, a crack in everything That's how the light gets in
( )Beginner ( )Intermediate ( )Advanced ( )Expert

When cracking a plentitude of targets all obstructed in the same fashion, it's useful to find a common link. This essay finds the common link in 150-or-so tools within the MKS Toolkit, and offers a easy solution to the problem without having to modify all the individual tools.
MKS Toolkit revisited
continuing drlan's work
Written by bb

Well, drlan seems to have already done this, but I didn't like the method. He went about cracking each of over a hundred executables to ignore the date check and the nag. I'm a lazy sonofabitch, that sounds like too much work for me. He had requested at the end of his essay for someone to look into making a generic patcher for all the EXEs. I'll go about doing this a different way, I think (I hope) a somewhat more "elegant" and "just" crack. The goal here is: find a way to crack all 150 executables without having to change them all.

Tools required

Target's URL/FTP
MKS Toolkit 6.1 (for Intel platforms)

Program History
For more information, feel free to read drlan's essay.

So let's start with what we already know from Drlan's essay. There's a 30-day time limit and a nag screen. Each program individually checks for the 30-day time limit, and each calls a CreateProcess to run MKSDEMO.EXE, which contains the nag. Well, the nag will be pretty simple to get rid of, then. Just re-write MKSDEMO.EXE to exit silently, right?

Well, not quite. We also need to fool the caller a bit. Let's take a look at that EnumWindows call that Drlan mentions. For our purposes, it's important. (I'm using the VIW.EXE as our little test proggie.)

Inside the callback function of the EnumWindows, we have:

00017747: 8B7508                       mov    esi,[ebp][00008]
0001774A: 6AEB                         push   0EB
0001774C: 56                           push   esi
0001774D: FF15F8B64300                 call   GetWindowLongA ;USER32.dll
00017753: 3DEBED0900                   cmp    eax,00009EDEB
00017758: 7533                         jne   .00001778D   ---------- (1)
0001775A: 8D459C                       lea    eax,[ebp][-0064]
0001775D: 6A64                         push   064
0001775F: 50                           push   eax
00017760: 56                           push   esi
00017761: FF1538B84300                 call   GetWindowTextA ;USER32.dll
00017767: 8D4D9C                       lea    ecx,[ebp][-0064]
0001776A: 68BCEB4200                   push   00042EBBC ; "MKS Toolkit Demo"
0001776F: 51                           push   ecx
00017770: E8BB540000                   call  .00001CC30   ---------- (2)
00017775: 83C408                       add    esp,008
00017778: 85C0                         test   eax,eax
0001777A: 7511                         jne   .00001778D   ---------- (3)

Ok, we start with a GetWindowLong(arg_8, 0xeb). The 0eb is most probably for the USERDATA. (check your header files if you're unsure..) That would mean the CMP right before jmp#1 is a magic number that our window needs to have in it's userdata. Once it passes that, we have a GetWindowText, which will pull our the window title into the buffer at ebp-64. That buffer, along with an argument @ 42eBBc which just so happens to be the text "MKS Toolkit Demo", gets passed to call#2, so we can guess that's the strcmp.

So we've got to fool this guy with two things: our userdata section has to have the magic number 0x09edeb in it, and our window needs to be called "MKS Toolkit Demo". Right on, let's write some code.

I'm using LCC, so some particulars apply differently to the compiler that you may choose. I've got code that looks like:

#include <win.h>
HWND hwnd;

"STATIC" /* lpClassName */,
"MKS Toolkit Demo" /* lpWindowName */,
0 /* x */,
0 /* y */,
0 /* nWidth */,
0 /* nHeight */,
NULL /* hWndParent */,
NULL /* hMenu */,
NULL /* hInstance */,
0 /* lParam */ );

if (hwnd==NULL) {
  printf("no hwnd\n");
SetWindowText(hwnd,"MKS Toolkit Demo");
We create a window of no size, set the appropriate magic number and window text, then sleep for a short time, allowing our MKS executable to find us and bypass the evil message about being unable to start MKSDEMO.EXE... Compile this and link it with "-subsystem windows", replace the old MKSDEMO.EXE, and you've got no more nag. Now let's move on to the time limit.

What do we have here? Well, we've got a registry value in HKEY_LOCAL_MACHINE called SOFTWARE\Mortice Kern Systems\Toolkit\DemoVersion\DemoNumber which contains some number that represents the software install date, and then we've got another routine that comes along with a GetSystemTime and produces a similar number. A comparison between these two numbers is done, and if it just so happens to differ beyond 0x278d00, or thirty days, the executable refuses to work.

My first thought was to hook the RegQueryValueEx API function to check if we're looking for that registry key, and if we are, do a GetSystemTime and return the appropriate juju. That would probably work, though it would mean we'd need some sort of setup program for MKS to initialize the hook, and that just seems sloppy.

But then it occurred to me, since every executable is calling MKSDEMO, why can't we just have MKSDEMO update the key to the current time everytime a MKS tool is run? After I stopped giggling (for some reason, I found this intensely funny), I realized that this was justice preserved. MKS went through all this trouble to make it hard on us, and here we are using the mechanism of their own obstructionist tactics against them.

So our object now is to figure out how the number in the registry key is developed from GetSystemTime, and then to put that code into our own MKSDEMO.C. Our install time in DemoNumber will get updated to the current time and our 30-day trial will consistently renew itself whenever we run an MKS tool.

At 4175e0 we have our call to RegQueryValueEx, and there's a call to 423420 shortly thereafter, which has our GetSystemTime in it. The function at 423420 returns the number we want in eax, so we've got to reverse this routine. We need to remember the SYSTEMTIME structure, so follow along. (In this code, the WORDs of the structure get shoved into the DWORDs of the registers. Many times the WORD in the high part of the DWORD gets masked out, sometimes it doesn't; the high WORD never matters, though, so I'm ignoring it in the comments.)

typedef struct _SYSTEMTIME {
    WORD wYear; 	// 0x10
    WORD wMonth;	// 0x0e
    WORD wDayOfWeek; 	// 0x0c
    WORD wDay; 		// 0x0a
    WORD wHour; 	// 0x08
    WORD wMinute; 	// 0x06
    WORD wSecond; 	// 0x04
    WORD wMilliseconds; // 0x02

The fairly long code snippet looks like:

00023420: 55                           push   ebp
00023421: 8BEC                         mov    ebp,esp
00023423: 83EC10                       sub    esp,010
00023426: 53                           push   ebx
00023427: 56                           push   esi ; save regs
00023428: 8D45F0                       lea    eax,[ebp][-0010]
0002342B: 57                           push   edi ; save edi
0002342C: 50                           push   eax
0002342D: FF1590B64300                 call   GetSystemTime ; ebp-10=Systemtime
00023433: 8B5DF2                       mov    ebx,[ebp][-000E] ; ebx=wMonth
00023436: 8B45F0                       mov    eax,[ebp][-0010] ; eax=wYear
00023439: 8BCB                         mov    ecx,ebx
0002343B: 8B55F6                       mov    edx,[ebp][-000A] ; edx=wDay
0002343E: 81E1FFFF0000                 and    ecx,00000FFFF
00023444: 4A                           dec    edx ; wDay--
; the table in 42f73e is
;   int mon2day[14]={ 0x0, 0x0, 0x1f, 0x3b, 0x5a, 0x78, 0x97, 0xb5,
;                     0xd4, 0xf3, 0x111, 0x130, 0x14e, 0x16d };
; which, given a month, gives us the number of days for each month since Jan 01
00023445: 668B344D3EF74200             mov    si,[00042F73E][ecx]*2 ; si=mon2day[ecx]
0002344D: 8D8844F8FFFF                 lea    ecx,[eax][0FFFFF844] ;ecx=edi-1980
00023453: 8BF9                         mov    edi,ecx ; edi=wYear-1980
00023455: 6603F2                       add    si,dx ; si += wDay
; si now = the number of days into the year from Jan 01, discounting the leap year
; so we should expect a leap calculation now
00023458: 81E7FFFF0000                 and    edi,00000FFFF
0002345E: 8BC7                         mov    eax,edi ; eax=wYear-1980
00023460: 99                           cdq ; edx=0, (usually a precursor to a modulo?)
00023461: 33C2                         xor    eax,edx
00023463: 2BC2                         sub    eax,edx
00023465: 83E003                       and    eax,003 ; eax = wYear % 3
00023468: 33C2                         xor    eax,edx
0002346A: 2BC2                         sub    eax,edx
0002346C: 7507                         jne   .000023475 ; if we're not in a leap year, jump
0002346E: 6683FB02                     cmp    bx,002 ; if we're not past February yet,
00023472: 7601                         jbe   .000023475 ; jump
00023474: 46                           inc    esi ; add a day
; our leap year calculation is done
00023475: 8D14C9                       lea    edx,[ecx][ecx]*8 ; edx=9*(wYear-1980)
00023478: 8D4703                       lea    eax,[edi][00003] ; eax=(wYear-1980)+3 (+3?)
0002347B: 8D0CD1                       lea    ecx,[ecx][edx]*8 ; ecx=73*(wYear-1980)
; 73 is an important number, 73 * 5 = 365, we should see that nearby
0002347E: 99                           cdq ; edx =0
0002347F: 8BD9                         mov    ebx,ecx
00023481: 83E203                       and    edx,003
00023484: 03DE                         add    ebx,esi ; ebx=(73(wYear-1980)+#days)
00023486: 03C2                         add    eax,edx 
00023488: C1F802                       sar    eax,002 ; eax=(wYear+3)/4
; this eax will be used to calculate how many leap days have past
0002348B: 8D0C8B                       lea    ecx,[ebx][ecx]*4 ; ecx=73*5*wYear+si=365wYear+si
; Here's our 365 days a year we're calculating since 1980
0002348E: 8D8408440E0000               lea    eax,[eax][ecx][000000E44]
; and here we have an adjustment for 3652 days, about 10 years-ish, 1970-ish instead of
; 1980, plus an adjustment for leap days past
; our #days calculation is now complete.
; we need #days * 24 hrs a day * 60 mins an hour * 60 seconds a min
; to compute it in seconds.. We'll see it here.
00023495: 25FFFF0000                   and    eax,00000FFFF
0002349A: 8D1440                       lea    edx,[eax][eax]*2 ; edx=3*#days
0002349D: 8B45F8                       mov    eax,[ebp][-0008] ; eax=whour
000234A0: 25FFFF0000                   and    eax,00000FFFF
000234A5: 8D04D0                       lea    eax,[eax][edx]*8 ; edx=8*3*#days+wHour
; 24 hrs in a day + wHour here, gives us eax = total # of hours so far since begin date.
000234A8: 8B55FA                       mov    edx,[ebp][-0006] ; edx=wMinute
000234AB: 8BC8                         mov    ecx,eax
000234AD: 81E2FFFF0000                 and    edx,00000FFFF
000234B3: C1E104                       shl    ecx,004 ; ecx=totalhrs*16
000234B6: 2BC8                         sub    ecx,eax ; make that totalhrs*15
000234B8: 8D048A                       lea    eax,[edx][ecx]*4 ; eax=15*totalhrs*4+wMinute
; 60 mins per hour + minutes, one last * 60 and we should be done.
000234BB: 8B55FC                       mov    edx,[ebp][-0004] ; edx=wSecond
000234BE: 8BC8                         mov    ecx,eax
000234C0: 81E2FFFF0000                 and    edx,00000FFFF
000234C6: C1E104                       shl    ecx,004 ; * 16
000234C9: 2BC8                         sub    ecx,eax ; * 15
000234CB: 8D048A                       lea    eax,[edx][ecx]*4 ; 15*4*totalsecs+wSecond
; eax = the # of seconds past since some target date.
000234CE: 8B4D08                       mov    ecx,[ebp][00008]
; and finally, if we've got arg to put this in, put it there, otherwise we'll drop it in eax
000234D1: 85C9                         test   ecx,ecx
000234D3: 7402                         je    .0000234D7
000234D5: 8901                         mov    [ecx],eax
000234D7: 5F                           pop    edi
000234D8: 5E                           pop    esi
000234D9: 5B                           pop    ebx
000234DA: 8BE5                         mov    esp,ebp
000234DC: 5D                           pop    ebp
000234DD: C3                           retn

Not bad, certainly easier than some serial # routines. We can simplify this a bit in C:

if ( ! (st.wYear-1980)%4 )
  if (st.wMonth>2)



mytime = st.wSecond + (st.wMinute*60) + (st.wHour*60*60) + (yeardays*60*60*24);

What's curious here is the adjustments of 3,652 days. This calculation starts with Jan 01, 1980, but then instead gives us 10 years extra. The routine actually calculates the number of UTC seconds since Jan 01, 1970, but the programmer chose to calculate the date from 1980 and then add in the 10 extra years. Doubtless we'll never figure out why, but who cares? It's beat, and that's all that matters.

Add this into our MKSDEMO.C program, along with an appropriate registry change, and we're using MKS toolkit, nagless and forever.

#include <win.h>
HWND hwnd;
PHKEY keyhand;
unsigned long mytime;
int mon2day[14]={ 0x0, 0x0, 0x1f, 0x3b, 0x5a, 0x78, 0x97, 0xb5,
                  0xd4, 0xf3, 0x111, 0x130, 0x14e, 0x16d };

hwnd=CreateWindow("STATIC", "MKS Toolkit Demo", WS_DISABLED|WS_POPUP, 0, 0, 0, 0,
                  NULL, NULL, NULL, NULL, 0);
SetWindowText(hwnd,"MKS Toolkit Demo");

if ( ! (st.wYear-1980)%4 )
  if (st.wMonth>2)



mytime = st.wSecond + (st.wMinute*60) + (st.wHour*60*60) + (yeardays*60*60*24);

RegOpenKeyEx(HKEY_LOCAL_MACHINE,"SOFTWARE\\Mortice Kern Systems\\Toolkit\\DemoVersion", 
             0, KEY_WRITE, &keyhand);


Ob Duh
I wont even bother explaining you that you should BUY this target program if you intend to use it for a longer period than the allowed one. Should you want to STEAL this software instead, you don't need to crack its protection scheme at all: you'll find it on most Warez sites, complete and already regged, farewell.

You are deep inside reverser's page of reverse engineering, choose your way out:

redhomepage redlinks redsearch_forms red+ORC redstudents' essays redacademy database
redreality cracking redhow to search redjavascript wars
redtools redanonymity academy redcocktails redantismut CGI-scripts redmail_reverser
redIs reverse engineering legal?