Simulating User Input to Eliminate Nag Screens
OR how I fell in love with the Bomb, and later learned to regret it.
papers
+HCU Papers
25 July 1998
by bb
Courtesy of Fravia's page of reverse engineering
 
fra_00xx
989715
bb
0100
PA
PC
I like a lot this essay. Using the very timer intended to nag you in order to push the nag button that was -also- intended to nag you is a truly beautiful reversing approach!
Bb is a capable cracker, and his approach is very precise and clear, you'll learn quite a lot reading this work, because there are many techniques in their own right (that have been handled inside other essays) that are used here in a context of 'applied reversing'. The fact that bb has moreover chosen three different (albeit similar) targets as examples will also help you fully understand how you should proceed, from now on, when you find those annoying slow nag-screens (or even quick splash-screens if we accept bb's definition for them :-) and you just 'don't have the bock' to go down into the dead listing. Besides, the 'pixel trick' used in the last example is quite interesting per se and you'll be able to adapt it to MANY other reversing endeavours (that's actually why I have enclosed this essay into our papers section)... thank a lot, bb!
There is a crack, a crack in everything That's how the light gets in
Rating
( )Beginner (x)Intermediate ( )Advanced ( )Expert

Most definitely NOT the best way to go about craking proggies, but still good clean fun for the whole family, this essay will tell you how to isolate windows which need a-clickin' and how to make windows click'em automatically, gosh durn it. I guess it would be useful to anyone who doesn't already have a grasp of this kind of stuff.
Simulating User Input to Eliminate Nag Screens
OR Twenty reasons why I want to poke Tori Amos
Written by bb


Introduction

"Nag, nag, NAG!"
--Heathers

I've always felt that the difference between a "splash-screen" and a "nag-screen" lied in how easily it could be ignored. If a screen came up and went away, I'd call it a "splash-screen" and I wouldn't care about it; but if a screen popped up and said "You must click OK before this program will start", I'd hate it and I'd call it names and I'd do everything I could to kill it and make it stay dead. Now I'd call that a nag.

The usual way to eliminate these little nagging beasties required delving into unfriendly code and figuring out the what, where, and how these screens work, before NOPping the hell out of them. Without a doubt, this preferred method has not changed. "But it would be interesting", I thought, "to do this another way."

Let's make Windows do all that work that we don't want to do. This essay will show you how to make Windows simulate mouse clicks. It will show you how to do this from within your target program, and it will show you how to build a front-end loader to call your target program and determine which window handle should get the message. We'll use three separate targets as examples here, all of which have annoying nags that deserve to die: DSTune, Multi-Edit, and WinZip.



Tools required



Target's URL/FTP

Dubbeldam Software's DSTune

American Cybernetic's Multi-Edit

Nico Mak's WinZip

Program History

DSTune, written by Dubbeldam Software, lets guitarists tune their instruments from their PC. It has an annoying nag screen. It was chosen as the target for the demonstration of this method primarily because it has the timer code ready-baked, which makes our job alot easier. The code was last revised in 1995, so the code base seems pretty darn stable at this point. The revision we're working with is the 32-bit version 2.2.

Multi-Edit, sold by American Cybernetics, is a multi-function programmer's text/hex editor with an extensize Macro language of it's own. It, too, has an annoying nag screen. It was chosen as a target because it could provide a simple introduction to a front-end loader. We'll be dealing with the newly-released version 8. The "real" crack to this program was actually pretty enjoyable and might make an interesting essay in it's own right. I don't want to spoil anyone's fun just yet, so download it and get rid of that annoying nag, then grab the 8.0 to 8.0b upgrade and get rid of the annoying serial# the same way. It's a little different than your average Windows crack.

WinZip is, well, WinZip. You know it, you love it, you can't live without it. It has an annoying nag screen. It was chosen because it presents a few challenges to the front-end loader model developed for Multi-Edit.



Essay

DSTune, v2.2

Running DSTune initially gives us a nag screen that tells us we have a 30-day evaluation period and that we must hit "Continue" to continue. But wait a moment, the continue button is grayed out for one, two, about three seconds! We've got to sit there for three seconds before we can actually run the program, and we actually have to click a damn button to get there? No thanks. Time for a change. Let's see what's happening here.

Load up BRW and let's take a look that resource. It's called DIALOG_4. Edit that resource and then bring up the properties for the "Continue" control. It starts it's life "disabled". Well, we can fix that by un-checking the disabled box and saving. Now it starts up with "Continue" all ready to go, but we still have to click it.

Let's go to the code and find out where it enables the control. The Dialog Box gets created at 410419, and gets passed 4149c5 as it's DlgProc.

149C5 loc_4149C5:                  	          ; DATA XREF: sub_41038A+75?o
149C5            push   ebp
149C6            mov    ebp, esp
149C8            add    esp, 0FFFFFF68h
149CE            push   ebx
149CF            push   esi
149D0            push   edi
149D1            mov    eax, [ebp+0Ch]	  	  ; eax=(UINT)uMsg
149D4            mov    esi, offset unk_420100    ; ???
149D9            lea    edi, [ebp-48h] 	          ; ???
149DC            mov    ecx, 5
149E1            rep    movsd      		  ; strncpy( (ebp-48), 420100 , 5) 
149E3            mov    edx, eax 		  ; edx=uMsg
149E5            cmp    edx, 110h 		  ; msg = 110 (WM_INITDIALOG)
149EB            jg     short loc_414A07 	  ; msgs > 110 go to 414a07
149ED            jz     short loc_414A35 	  ; WM_INITDIALOG goes to 414a35
149EF            dec    edx
149F0            jz     short loc_414A21 	  ; WM_CREATE(1) goes to 414A21
149F2            sub    edx, 0Eh
149F5            jz     loc_414C8C 		  ; WM_PRINT(0f) goes to 414c8c
149FB            dec    edx
149FC            jz     loc_4150D8 		  ; WM_CLOSE(10) goes to 4150d8
14A02            jmp    loc_4150ED 		  ; return
14A07 loc_414A07:                            	  ; CODE XREF: CODE:149EB?j
14A07            sub    edx, 111h 		  ;
14A0D            jz     loc_414F20 		  ; WM_COMMAND goes to 414F20
14A13            sub    edx, 2
14A16            jz     loc_414EF9 		  ; WM_TIMER goes to 414EF9
14A1C            jmp    loc_4150ED 		  ; return

There are a number of ways to create timed events (sleep, select, GetTickCount, etc), but here we see a reference to WM_TIMER. This usually indicates a previous call to SetTimer, which tips us off as to the method being used here. Take a look at 414EF9, and you'll see a call to KillTimer, and as luck would have it, very close to this KillTimer is the call to SetTimer at 414EEA.

14EDE                 push    0
14EE0                 push    0BB8h
14EE5                 push    64h
14EE7                 push    dword ptr [ebp+8]
14EEA                 call    j_SetTimer
14EEF                 mov     dword_421674, eax
14EF4                 jmp     loc_4150ED
14EF9 loc_414EF9:                             ; CODE XREF: CODE:14A16?j
14EF9                 push    dword_421674
14EFF                 push    dword ptr [ebp+8]
14F02                 call    j_KillTimer
14F07                 push    1      ; pushed for EnableWindow call
14F09                 push    66h ; Control ID 
14F0B                 push    dword ptr [ebp+8] ; handle of DialogBox
14F0E                 call    j_GetDlgItem
14F13                 push    eax ; Window Handle of control
14F14                 call    j_EnableWindow
14F19                 xor     eax, eax
14F1B                 jmp     loc_4150EF

Now we can change the timeout value on the timer, currently BB8 or 3000 milliseconds, to whatever we want by changing 414EE0. The GetDlgItem call gives us the window handle of the Control 66, which is the "Continue" button (you can verify this with BRW), and then it gets passed on to EnableWindow. Now we know completely how this button gets enabled.

Since we enabled the button previously through BRW, we don't need the timer or this code to enable the window. But here's the part where you begin to think, "you know, since they've got the timer code in here already, wouldn't it be just as easy to turn the nag screen into a splash screen by having it PUSH the button, instead of just enabling it?"

So how would we do that, anyway? Well, what happens when you click on a button? You generate messages, specifically, the messages WM_LBUTTONDOWN and WM_LBUTTONUP. Those messages gets processed by your DialogProc or WndProc or the default system procedure. If none of the custom procedures intercepts these messages, the parent window will eventually get a WM_COMMAND message from the control stating that BN_CLICKED, and the parent will process the button click. That's what's happening at 414A0D above.

Apparently then, what we need to do is to send a message to the control saying WM_LBUTTONDOWN and then WM_LBUTTONUP. It's actually a little easier than that. All we really need to do is send the message BM_CLICK to the control. From the API help file: An application sends a BM_CLICK message to simulate the user clicking a button. This message causes the button to receive a WM_LBUTTONDOWN and a WM_LBUTTONUP message, and the button's parent window to receive a BN_CLICKED notification message.

Right on. Now we just need to use SendMessage or PostMessage to get the message to the control. Looking at what we have immediately available, we see that PostMessageA can be called indirectly through 41AB4F. Fantastic, we'll use that.

Now we just need to place somewhere in our program a piece of code that looks like:

mov eax, window_handle_of_control
push 0
push 0
push F5 (BM_CLICK)
push eax
call 41AB4F

Since the program has already called GetDlgItem above, we have the handle of the control in eax. We'll want to keep the timer code for our new "splash" screen, and we don't really want to disturb the surrounding code too much. It seems we've only got about 6 bytes or so to play with here, in that the only thing we can really destroy is the push eax and the call to EnableWindow. So, let's see how much space is at the end of the code segment? Plenty! Let's put our new code at 414c3f, NOP the push eax, and change the call at 414f14 to go to our new code. Don't forget to NOP out the push at 414F07! This looks like it's supposed to be for the call to GetDlgItem, but it's not. NOP it or suffer stack corruption.

So we change our call and make 41ac3f look like:

.0001AC3F: 6800000000                   push   000000000
.0001AC44: 6800000000                   push   000000000
.0001AC49: 68F5000000                   push   0000000F5
.0001AC4E: 50                           push   eax
.0001AC4F: E8FBFEFFFF                   call  .00001AB4F
.0001AC54: C3                           retn

Test it. Wait 3 seconds (or however long you've made the SetTimer) and watch it click itself away. The nag nags no more; it merely splashes. Cool, if you like that sort of thing.


Multi-Edit, demo version 8.0

We'll be having a front-end program press the 'OK' button on the nag/splash screen.

So, we've got to run the program, let the splash screen come up, get the handle for the "OK" button and then press it. Easy enough.

First, we use CreateProcess to run the proggie. We'll grab the startupinfo from our process and send it along to avoid having to fill in the STARTUPINFO data manually.

GetStartupInfo(&startinfo);
if ( CreateProcess(NULL,proggie,NULL,NULL,FALSE,NORMAL_PRIORITY_CLASS,NULL,
	NULL,&startinfo,&procinfo) == 0 ) {
  fprintf(stderr,"Cannot create process\n");
  exit(-1);
  }

Now the program will run and we'll grab the main window handle. We use EnumThreadWindows for that, which requires a callback function to receive the window handles. This callback (you'll see it called "EnumThreadWndProc" in your API reference) processes the handles it receives one by one, returning TRUE each time it needs another handle. When the callback returns FALSE or when there are no more handles to process, EnumThreadWindows returns. Since the first handle sent to us will be the handle to the main window, we can simply return FALSE. We can make the HWND variable global so that both our main and our callback can access it. The callback function should look like:

BOOL CALLBACK enumMain(HWND myhwnd, LPARAM lparam)
{
printf("MAIN HWND: %04x\n",myhwnd);
mewhwnd=myhwnd;
return(FALSE);
}

Get the first handle, then stop enumeration. Boom, done. Now before our call to EnumThreadWindows, we should make sure that the nag screen is displayed. We can simply WaitForInputIdle on the process, and that way we can be sure that the program is waiting for something, and not doing something, before we try anything.

WaitForInputIdle(procinfo.hProcess,4000);
EnumThreadWindows(procinfo.dwThreadId,&enumMain,0);

Enumerate the Windows for the Thread ID returned from our CreateProcess into the callback enumMain. Boom. Now we need to get the window handle of the button, and then we need to make sure it's the right button. We'll use EnumChildWindows and search through all the handles we get until we find the right one. To determine which is the handle we need, let's look at what we know: we know that it's a Button class, and we know that the text says 'OK'. If we're unsure about either of these premises, it's simply a matter of running Spy++ or IvySpy and to validate our beliefs.

We can get the classname of a window handle from GetClassName, and then we can find the text from GetWindowText. We'll make a new global HWND for the child handle, and we'll keep enumerating until we have a match. The new callback for EnumChildWindows should look something like:

BOOL CALLBACK enumChild(HWND myhwnd, LPARAM lparam)
{
char class[200],text[200];

GetClassName(myhwnd,class,sizeof(class));
GetWindowText(myhwnd,text,sizeof(text));
if (!strcmp(class,"Button") && !strcmp(text,"OK")) {
  printf("found OK box\n");
  clickithwnd=myhwnd;
  return(FALSE);
  }
return(TRUE);
}

and we add to our main:

WaitForInputIdle(procinfo.hProcess,1000);
EnumChildWindows(mewhwnd,&enumChild,0);

Now we have the appropriate window handle, and we can use the same procedure that we used in DsTune, PostMessage BM_CLICK. Multi-Edit, however, seems to require about a 1-second delay before we the message can be processed correctly, otherwise we get a system error MessageBeep. So, we do:

Sleep(1000);
printf("\nClick\n");
PostMessage(clickithwnd,BM_CLICK,0,0);

and we should be done. Put it all together and it should turn that "nag" screen into something more like a "splash" screen.

----------proggie-----------
#include <stdio.h>
#include <windows.h>
#include <winuser.h>

char *proggie="c:\\mew8\\mew32.exe";
HWND mewhwnd,clickithwnd;

BOOL CALLBACK enumMain(HWND myhwnd, LPARAM lparam)
{
printf("MAIN HWND: %04x\n",myhwnd);
mewhwnd=myhwnd;
return(FALSE);
}

BOOL CALLBACK enumChild(HWND myhwnd, LPARAM lparam)
{
char class[200],text[200];

GetClassName(myhwnd,class,sizeof(class));
GetWindowText(myhwnd,text,sizeof(text));
printf("HWND: %04x %s ",myhwnd,class);
printf("%s %s %s %s\n",
  IsWindowEnabled(myhwnd)?"ENA":"DIS",
  IsWindowVisible(myhwnd)?"VIS":"INV",
  IsWindowUnicode(myhwnd)?"UNI":"ASC",
  text);
if (!strcmp(class,"Button") && !strcmp(text,"OK")) {
  printf("found OK box\n");
  clickithwnd=myhwnd;
  return(FALSE);
  }
return(TRUE);
}

main(int argc,char **argv)
{
STARTUPINFO startinfo;
PROCESS_INFORMATION procinfo;

GetStartupInfo(&startinfo);
if ( CreateProcess(NULL,proggie,NULL,NULL,FALSE,NORMAL_PRIORITY_CLASS,NULL,
	NULL,&startinfo,&procinfo) == 0 ) {
  fprintf(stderr,"Cannot create process\n");
  exit(-1);
  }

WaitForInputIdle(procinfo.hProcess,4000);
EnumThreadWindows(procinfo.dwThreadId,&enumMain,0);
WaitForInputIdle(procinfo.hProcess,1000);
EnumChildWindows(mewhwnd,&enumChild,0);
Sleep(1000);
printf("\nClick\n");
PostMessage(clickithwnd,BM_CLICK,0,0);
}
---------end of proggie---------




WinZip, the indefatigable

Having gone through Multi-Edit, we'll start on WinZip in the same manner. Use CreateProcess, then EnumThreadWindows to get the main handle, then EnumChildWindows to get the handles of the various controls. Look for a BUTTON class with text that says either "I &Agree" or "I Agr&ee". Find it?

No, you didn't, because it's not in there. Check through the handles of the child windows in the callback; printf the classnames and the text. You'll find quite a few Static text boxes, and four or five Buttons, one labeled "&Ordering Information" and the others blank. Load up Spy++ (IvySpy doesn't work quite as well in this case) and take a look for yourself to make sure your C code isn't lying. So how the heck do these buttons get labeled?

The function that begins at 0040392B does all the dirty work. The function creates a device context, selects fonts, computes the size of the text, sets various colors, builds a bitmap, and draws the text into it. You can watch them as their labels get applied. The code at 00403A5C calls DrawTextA, so run winzip32 under the SoftIce loader and do a bpx 403a5c do "d @(ebp+08)". You'll get to see the text drawn into the buttons one at a time in the data window of SoftIce. The code for "I &Agree" and "I Agr&ee" calls this routine from 00403667 and 0040368C, respectively.

Spy++ can give us much more information about these buttons, so let's check it and see what's different. Well, both buttons have the same size (75x23 non-resizable), the same styles and the same window structures and class structures; in fact, most everything looks the same for both buttons. Their handles obviously differ, but we can't distinguish between the two by their handles. Their control ID's differ, but they can't help us differentiate between the "I agree" and the "quit" buttons.

(The code from 403602-403632 shows us how the control ID's get assigned independent of the text labels. It uses the last bit of the return value from GetTickCount to determine which control ID gets placed into which pointer.)

The question remains, then, how can we differentiate between these two similar, interchangable buttons? We resort to screen-scraping. The buttons, after all, have different bitmaps drawn into their text; if no other difference appears between the two, then we must do what we must do.

On the plus side, we only need to look for one pixel. The position of the "I" never really changes with respect to the bounding rectangle of the button, so if we pick one pixel in the "I" that appears in "I Agree" and never appears in "Quit", we can effectively differentiate between the two buttons. Examining the bitmaps for the buttons closely, which can easily be done by modifying our C code to display the bitmaps as text, we choose appropriately. I selected (21,8). If this pixel equals 0x00000000(black), then we have the "I agree" button; otherwise, we have "Quit". Our callback routine, then, should look for a 75x23 pixel button, and if it finds one, it must check for the color of the pixel at our selected location and compare it with 0.

We'll need to use GetPixel to grab the data from the screen. This has a major drawback: if the WinZip window lies underneath another window, we'll receive the pixel data from the window on top of it; likewise, if the WinZip window has yet to be painted, we'll receive the pixel data of the window or desktop that currently occupies that space. This means that we'll require a small Sleep before doing any work lest we can find ourselves getting incorrect data. Also, we'll need to call SetForegroundWindow before we read any pixel data just in case we've switched windows during that short period of sleep time. GetPixel requires the device context passed as an argument, and we can easily pull that from GetDC since we already have the window handle of the button. I've left in the code to display the image as text, because a 75x23 button displays so well on a 80x24 screen. We'll replace the global HWND variables with pointers passed in the LPARAM parameter of the Enum calls, just to show one possible way for that parameter to be used. Also, we'll make the button blink prior to clicking it, just because we can. Our child callback should now read:

BOOL CALLBACK enumChild(HWND hwnd, LPARAM lParam)
{
char classname[256];
HDC hdc;
RECT rect;
int i,j,k;
long wh,wl;

GetClassName(hwnd,classname,sizeof(classname));
GetWindowRect(hwnd,&rect);
wh=rect.bottom-rect.top;
wl=rect.right-rect.left;
if (!strcmp(classname,"Button") && wh==23 && wl==75) {
  hdc=GetDC(hwnd);
  for(i=0;i<23;i++) {
    for(j=0;j<75;j++)
      printf("%c",GetPixel(hdc,j,i)?' ':'*');
    printf("\n");
    }
  if (GetPixel(hdc,21,8)==(COLORREF)0) {
    memcpy(lParam,&hwnd,sizeof(HWND));
    for(k=0;k<8;k++) {
      for(i=0;i<23;i++) {
	for(j=0;j<75;j++) {
	  SetPixel(hdc,j,i,GetPixel(hdc,j,i)^0x00c0c0c0);
	  }                                             
	}
      Sleep(300);
      }
    return(FALSE);
    }
  }
return(TRUE);
}

and our Enum calls will be changed slightly:

WaitForInputIdle(procinfo.hProcess,4000);
EnumThreadWindows(procinfo.dwThreadId, &enumMain, (LPARAM)&wzhand);
Sleep(100);
SetForegroundWindow(wzhand);
EnumChildWindows(wzhand,&enumChild, (LPARAM)&iagreehand);
if (iagreehand) {
  PostMessage(iagreehand,BM_CLICK,0,0);
  } else {
  MessageBox(0,"Could not get handle to agree button\n",title,MB_OK|MB_ICONSTOP);
  }

Build the program and test it. The "I Agree" button now blinks a few times, and then gets clicked. You now have an obnoxious front-end to WinZip.

----------wz.c-----------
#include <stdio.h>
#include <windows.h>
#include <winuser.h>
/*#include <wingdi.h>*/ /* LCC doesn't need this, most others will */

char *cwd="C:\\Progra~1\\winzip";
char *prog="c:\\progra~1\\winzip\\winzip32.exe";

BOOL CALLBACK enumMain(HWND hwnd, LPARAM lParam)
{
memcpy(lParam,&hwnd,sizeof(HWND));
return(FALSE);
}

BOOL CALLBACK enumChild(HWND hwnd, LPARAM lParam)
{
char classname[256];
HDC hdc;
RECT rect;
int i,j,k;
long wh,wl;

GetClassName(hwnd,classname,sizeof(classname));
GetWindowRect(hwnd,&rect);
wh=rect.bottom-rect.top;
wl=rect.right-rect.left;
if (!strcmp(classname,"Button") && wh==23 && wl==75) {
  hdc=GetDC(hwnd);
  for(i=0;i<23;i++) {
    for(j=0;j<75;j++)
      printf("%c",GetPixel(hdc,j,i)?' ':'*');
    printf("\n");
    }
  if (GetPixel(hdc,21,8)==(COLORREF)0) {
    memcpy(lParam,&hwnd,sizeof(HWND));
    for(k=0;k<8;k++) {
      for(i=0;i<23;i++) {
	for(j=0;j<75;j++) {
	  SetPixel(hdc,j,i,GetPixel(hdc,j,i)^0x00c0c0c0);
	  }                                             
	}
      Sleep(300);
      }
    return(FALSE);
    }
  }
return(TRUE);
}

main(argc,argv)
int argc;
char **argv;
{
PROCESS_INFORMATION procinfo;
STARTUPINFO startinfo;
HWND wzhand;
HWND iagreehand=(HWND)0;
char *title="WinZip Startup Button pusher thingamajig";

GetStartupInfo(&startinfo);
if ( ! CreateProcess(
	NULL, /*lpAppName*/
	prog, /*lpCommandLine*/
	NULL, /*lpProcAttr*/
	NULL, /*lpThreadAttr*/
	FALSE, /*BoolInheritHandles*/
	NORMAL_PRIORITY_CLASS, /*dwCreationFlags*/
	NULL, /*lpEnv*/
	cwd, /*lpcwd*/
	&startinfo, /*lpStartupInfo*/
	&procinfo /*lpProcInfo*/ ) ) {
  MessageBox(0,"Cannot CreateProcess",title,MB_OK|MB_ICONSTOP);
  exit(-1);
  }
WaitForInputIdle(procinfo.hProcess,4000);
EnumThreadWindows(procinfo.dwThreadId, &enumMain, (LPARAM)&wzhand);
Sleep(100);
SetForegroundWindow(wzhand);
EnumChildWindows(wzhand,&enumChild, (LPARAM)&iagreehand);
if (iagreehand) {
  PostMessage(iagreehand,BM_CLICK,0,0);
  } else {
  MessageBox(0,"Could not get handle to agree button\n",title,MB_OK|MB_ICONSTOP);
  }
}
------------stop cutting here-----------


Final Notes

Well, that's it, really. Now you know a little bit about how those Windows Macro utilities work. I don't know if it's really useful information, but now it's in your brain and you're stuck with it.



Ob Duh

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

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

papers
Back to Papers

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_Fravia
redIs reverse engineering legal?