WKT Tutorialz Site
Programa Cuentapasos 3.75 rev 375 W95 / W98 / NT
Descripción Control your telephone call charges
Tipo Shareware
Protección Nag Screen. Trial period.
Dificultad 1) Principiante, 2) Amateur, 3) Aficionado, 4) Profesional, 5) Especialista
Herramientas SoftIce v4.0, SmartCheck 6.0, Spy++
Objetivo Simulate to be registered.
Cracker Mr. Blue
Fecha 18 de Octubre de 1999


This is a classic Spanish program to avoid wasting your money on internet connections. Despite we can find others free programs to do this, a crack for Cuentapasos is very demanded. It´s a Visual Basic application very protected by the programmer, who must have read a lot of reverse engineering articles.

In this tutorial, we are going to show that protecting a VB application is a very difficult task. The last Microsoft's invention on VB, the 'p-code', it's a delicious bite for reverse engineers. I'll show that a 'p-compiled' application may be easier to crack that a conventional compiled one. And we'll also remember others 'forgotten' cracking techniques.

I know there are simplest ways to do this crack, but WKT! is a group interested on developing, showing and teaching cracking techniques, rather than programming a simple crack.

Exploration and identification

First of all, we examine our victim. The nag screen has a button with a code, and two buttons labelled 'Aceptar' and 'Salir'. This screen also show the spent days of the trial period and the remaining ones. Our target is eliminate this nag and "extend" the trial period.

It's very recommendable to use a format-exe detector like GetTyp. It show us that our target is packed, so we'll design a loader instead of a patcher. You can learn how to unpack executables manually with the Mr.Orange's tutorial about manual unpacking with ProcDump.

Virtual mouse

To avoid the nag we are going to use a classic method, very used before the birth of the actual debuggers. The method tries to simulate a mouse click on a button by code. If you don´t know how to do it, I recommend you to try learning it, and you'll understand a lot about how Windows works internally.

First of all, we have to get the process identifier of the executable which the button belongs to. Then, you must get a handle to the parent window where the button is contained. With this handle to the parent window of the button, we'll look for the handle of the button. Finally we'll simulate a click over the button.

As we're developing a loader, we can get the process identifier from the return values of the CreateProcess function, used to load the process.

With the process identifier, we can access to the windows of the process, by EnumThreadWindows. This function will enumerate the windows of a process, until we stop it or it has enumerated all windows of the process. EnumThreadWindows will call a callback function, written by us, and pass the handles to the windows as a parameter. This function'll return FALSE if the window is what we were looking for and we want to stop the enumeration, or TRUE if we want to continue. EnumThreadWindows will return when the callback function returns FALSE or when there are left no windows to enumerate.

And how can we know if a handle it's the handle of the window we're looking for? To do this, the callback function can use several functions:

  • GetClassName. Returns the classname of a window.
  • GetWindowText. Returns the title or label of a window.
  • GetWindowRect. Returns the screen co-ordinates of window.

Now, we must find the handle of the button. The EnumChildWindows function will enumerate the child windows of a parent window. It's works like EnumThreadWindows. Our callback function can use the same functions as above.

We have caught the button. Next, we'll push it. The PostMessage function will let us to send a message to the target button, simulating a mouse click. The BM_CLICK message simulate a push down and a push up of the left button of the mouse.

Examples of this technique on Fravia, '+HCU's Papers' section: Simulating User Input to Eliminate Nag Screens by bb.

With this method, you can do a lot of things, not only simulate mouse clicks. Use your imagination ;-)

Catch that button! Catch it!

Buttons hysteria

To write the callback functions used to find the window and button handles we have to get some information about the window and button we're interested on. With the nag on the screen, execute Spy++ (or similar) and write down classnames, titles and co-ordinates of the nag window and the button labelled 'Aceptar'.

You'll find two windows, the nag a the splash one. Both have ThunderRT5Form as classname but our callback function will distinguish easily by the title. The nag is labelled as "Versión de Evaluación P...", while the splash has no title.

Now the button. Both buttons ('Aceptar' and 'Salir') are of ThunderRT5CommandButton class, have incrusted an image instead of text ('Aceptar' and 'Salir' are images) and have the same size. Besides, they swap their position each execution. At this point, we are sure that the programmer knows something about us.

Someone may remember a function named GetNextWindow. This function let us get the creation order of the windows and visual controls. We can use it to catch a control by its order creation, independently of the position, label... Unfortunately, the programmer doesn´t swap the buttons position, swap their functionality. Definitely we're on trouble.


  • Pay and register the program. :-(
  • Use the solution explained by bb in Simulating User Input to Eliminate Nag Screens.
  • "Fix" the button.

The bb's solution to the nag of WinZip (a very similar problem) it's to use GetPixel to distinguish the images incrusted on the buttons. We catch the window and with a graphic application, we have to find a co-ordinate where there is a difference between both buttons. It's the best solution, because we don't have to alter the binary file and it may works on next versions of the victim.

Instead of this, we'll try to fix the button changing the code to illustrate the facility of cracking a VB application. After fixed it, we'll fire it with PostMessage.

P-Code: Going down Micro$oft hells

Random numbers for random buttons

It's recommended to read about VB cracking before you continue if you don't know anything about SmartCheck. You should read Esiel2's tutorial CRACKEANDO EN VISUAL BASIC (yes, in Spanish).

If we try to open the executable with SmartCheck, we'll find something like:

cpasos32.exe is compiled to p-code...etc...

It sounds like 'Danger, mines'. We go on.

We aren´t going to need logging API calls, so you can disable this option. Run Cuentapasos with SmartCheck and watch the film.Check the position on the 'Aceptar' button, and click on 'Salir'. Now, we're interested on locate where the program decides the position of these buttons.

Look for the the frmRegistro_Load event, which is going to load the nag screen. At the end of this event we find this:

As you can see, the program is setting here the properties values of the buttons of the nag. On 15244 and 15245 the program is loading the 'Aceptar' image on the button 'cmdAceptar(0)'. On 15247 and 15248 the program repeats with 'cmdAceptar(1)' and the 'Salir' image. If you repeat the execution of the program, you'll find that 'cmdAceptar(0)' is the left button and 'cmdAceptar(1)' the right one.

Have you seen the 15243 event? Just after setting the 'Buy' button and before setting the 'Aceptar' and 'Salir' ones, the application call the Rnd() procedure. What do you think about this call? Yes, you're right. The random number returned by this call will decide which button will work like 'Aceptar'. If we modify the code that deals with this random number, we can force the position of the buttons where we want.

We need the location of the Rnd() call and.... from MSVBVM50.DLL!000FE7BD? What's that? All calls are made from MSVBVM50.DLL locations. What's happening?

The crypt door

Let's take your SoftIce. I hope it can explain us what is happening. Remember you must load symbols from MSVBVM50.DLL.

First of all, what are we looking for? We want to find the Rnd() call and check what happens with the random number returned. You can find a list of the symbols exported by this DLL at Mr.Brown's tutorial. There're two suspicious ones:

Addr:0F004A95 Ord: 593 (0251h) Name: rtcRandomNext
Addr:0F03AE08 Ord: 594 (0252h) Name: rtcRandomize

And check your VB help:

  • Randomize. It's used to initialise the random numbers engine. It's used by Cuentapasos at the beginning of the frmRegistro_Load event.
  • Rnd(). Returns a random number between zero and one.

Obviously, the Rnd() function is the rtcRandomNext symbol. Now check the number of Rnd() callings before our target one using the SmartCheck list. Ok, we're interested on the second one.

Descend to hells

Load SoftIce and put a bpx rtcRandomNext. Now, run Cuentapasos from Windows and... BOOM. we're in the first Rnd() call. We're interested on the second one so push CTRL+D. Push F11 and we're just after the Rnd() call, at 0F0FE7C0h location of MSVBVM50.DLL.

The random number is a floating point one so it's loaded on the floating point registers. To see these registers type wf on SoftIce. The random number is in the ST0 register.

Run step by step with F8 and you'll find these codes:

0F0FE7CC mov al, [esi]
0F0FE7CE inc esi
0F0FE7CF jmp ds:[eax*4+0F0FED94]

The program is loading on AL the byte pointer by ESI (0F4h). It increments ESI and jumps to a location calculated with the byte loaded in AL. Check that ESI is pointing to the code section of Cuentapasos. We're not executing the Cuentapasos executable, we're interpreting it. Don't forget it because it's very important if you want to understand how p-code works.

After the jump, we're on 0F0FDE15h. Continue executing step by step. A 0Ah byte is read from the Cuentapasos executable and loaded on the stack. As before, a byte of the executable (0EBh) is used to decide the location of next jump: 0EBh*4+0F0FED94h = 0F0FF140h.

The dword pointer by 0F0FF140h is 0F0FD5E5h, so we land on this location after the jump. Here, the 0Ah byte of the stack is loaded in ST0, the random number is shifted to ST1 and the 0Ah byte is deleted of the stack. Now, other jump similar to the other ones.

Now, we fall in 0F0FDFCBh. It'd be better stop a moment and think about what is happening.

A light at the end of the corridor....

Look at the code window. We're situated on a zone of portions of code (5-10 instructions) ended by the same three instructions. Load in EAX a byte from the executable (pointed by ESI), increment ESI and jump to a location pointed indirectly by the result of EAX*4+0F0FED94h.

It looks like there is a directions table starting at 0F0FED94h. Fox example, look at the 0F0FF140h location, where you'll find the direction (0F0FD5E5h) of the portion code which loaded the 0Ah byte from the stack to the ST0 register. The dwords situated in this table point to portions of code ended by a jump which depends of the contents of this table.

From hell to heaven

In other words, the p-code executable is never executed. The executable bytes are read and interpreted by MSVBVM50.DLL. So, each of the asm instructions we know must have a code portion on the DLL to replace them.

Review the 0F0FE7CCh, just after the return of the Rnd() function. A 0F4h byte is loaded from the code section of the executable. This byte made that the execution jumps to a portion of code which copied the 0Ah byte from the executable to the stack. So the "push 0Ah" asm instruction, which is assembled as "6A 0A" in a 80x86 processor, corresponds to "F4 0A" on VB p-compiled code. The "F4" byte is interpreted by the directions table situated at 0F0FED94h of the DLL as "push the next byte to the stack".

It's the generalisation of the Visual Basic philosophy. On VB3 and VB4, we found that a lot of functions were implemented on shared DLLs, instead of implementing them in the executable. It was a great facility for reverse engineering. With VB5 and VB6, we find that, not only the functions are shared, now all asm instructions are implemented as small shared functions on MSVBVM50.DLL and MSVBVM60.DLL. This is the reason of the name of the dlls: MicroSoft Visual Basic Virtual Machine.

The advantages of this fact will be showed defeating the trial period protection.

Fixing a button

Before defeating the trial period, we've to fix the button problem.

Remember, we have a random number between 0 and 1 loaded on ST0. After this, we load a 0Ah byte from the executable to ST0, shifting the random number to ST1.

We're on 0F0FDFCBh. Here the program multiplies ST0 and ST1, storing the result on ST0. After a new access to the interpretation table, we appear on 0F0FD5B5 and the content of ST0 is rounded to the nearest integer. The round number is loaded on the stack and the FPU flags (the flags of the floating point unit) are saved in EAX..

A new jump and we are on 0F0FDE28h. The DLL copies a dword (00000002) from the executable to the stack. It's a "push" of a dword, instead of the previous "push" a byte. Other visit to the interpretation table and the execution lands on 0F0FE013, where the 02 dword stored on the stack is copied to ECX and the rounded number pushed on the stack popped to EAX. EAX is divided by ECX. The quotient is EAX and the rest EDX. EDX is pushed and we find a new jump.

As you can see, the application is interested on the rest of this div operation, it doesn't saved the quotient value. Possible values of the rest are 1 or 0. We can suppose that the application decides the position of each button with the rest of:

round(x*10) / 2

where 'x' is the random number between 0 and 1.

Try this and you'll discover that a zero value makes that 'Aceptar' appears on the left.

If you remember the log of SmartCheck, we had a array ('cmdAceptar') of two buttons. 'cmdAceptar(0)' is the left button and 'cmdAceptar(1)' the right one. Obviously, the application assigns the accept function to 'cmdAceptar(rest)' and cancel function to 'cmdAceptar(rest xor 1)'.

To fix the button on a side, we can force the rest to take always the same value, zero or one, independently of the random number generated by Rnd().

Two ways to do this:

  • Changing the p-code instructions. Actually we know the p-code equivalents of several instructions: push, idiv, fmul... We can go on investigating the VB virtual machine of MS and collecting more asm equivalents. A "nop" equivalent may be interesting ;-)
  • Changing the operands. Actually it´s the easiest and safest alternative and an elegant election.

We have two operands, a 0Ah byte to multiply the random number and the 00000002 dword to div the rounded result of the multiplication.We can:

  • Multiply the random number by zero instead of ten. It'll always make a zero rest and the 'Aceptar' button fixed on the left.
  • Divide by one instead of two. As before, the rest will be zero.

Choose your favourite.

Defeating P-Code

It's time to think about VB p-compiled programs.

As you know, the cracking advantage of VB programs is that we locate important shared functions implemented on run-time libraries, so we can intercept them to find where the operands are stored. As example, the __vbastrcomp function which is used by VB programs to compare two strings. You can put a breakpoint to see the two strings which are going to be compared. It's a well known technique to find valid serials of VB programs. We can do that because we exactly know WHERE the __vbastrcomp function is implemented, so we know WHERE we must put the breakpoint.

In p-code the change is that, besides there are some functions implemented as shared functions, all assembler instructions are implemented as shared "functions". Searching the VB Virtual Machine will let us to identify lots of important instructions.

Defeating the trial period of Cuentapasos will let me to show you the importance of this fact.

30-6 = 36

The nag screen show us the remaining days of the trial period. Now, our target is to locate the routine used by the program to calculate the remaining days. If we find it, we'll able to modify it or searching where the operands are stored.

The trial period is thirty days long. So, the remaining days may be calculated as 30 minus the number of spent days. This is Zen... ;-)

As I've said, we know that all assembler instructions are implemented of the VB Virtual Machine, "SUB" operation included. We only have to locate where the "SUB" instruction is implemented on MSVBVM50.DLL, put a conditional breakpoint on it and then, use the imagination. This way of cracking can be used with other type of protections as limited number of executions.

The "SUB" implementation is located on 0F0FDF78h, together with the rest of arithmetics operations (add, mul, div...) Each operation has an implementation for 16 bits operands, other for 32 bits and other one for floating point...

This is the implementation for 32 bits "SUB":

0F0FDF78 pop eax
0F0FDF79 sub [esp], eax
0F0FDF7C jo 0F0FDAC4
0F0FDF82 xor eax, eax
0F0FDF84 mov al, [esi]
0F0FDF86 inc esi
0F0FDF87 jmp ds:[eax*4+0F0FED94]

In this code, the operands are stored in the stack. The result of the operation is returned in the stack, too. All we have to do is to put a condicional breakpoint in this function, to take the execution control when the program calculates the remaining days of the trial period.

I've spent 6 days so I must put something like bpx 0F0FDF79 if (*(esp)==1E)&(eax==6). I also put a breakpoint in the 16 bits version: bpx 0F0FDF62 if (*(esp)==1E)&(ax==6).

Type Ctrl+D, run Cuentapasos and we land at 0F0FDF79. The application is trying to do 30-6. Check that ESI is pointing to 4ADF48h, which is the next instruction to execute after the "SUB" operation. With the VB Virtual Machine, ESI register is supposed to be the "EIP" to the p-code. The actual "SUB" instruction is located at 4ADF47h, coded as '0AEh'. It's other equivalence, 32 bits "SUB" is coded as '0AEh'

Here, as before, we can change tow items:

  • The operands. To do that, we would have to go back by putting a read breakpoint some bytes before the position of the "SUB" code. This is other equivalence, a execution breakpoint in p-code must be typed as "bpm" instead of "bpx". With this "bpm", we can trace the program to search the locations of the operands.
  • The operation. We can change the code of the "SUB" operation with the code of other operation. What about an addition?

First of all, we need the code of the "ADD" operation. You can find the 32 bits "ADD" operation at 0F0FDF3Dh. Now, we need the interpretation table. The pointer to the "SUB" operation must be found in the table at:

4*0AEh + 0F0FED94h = 0F0FF04Ch

We look at the table and the "SUB" pointer is located at the calculated address. Where is the "ADD" pointer? It's four dwords before, at 0F0FF03Ch. So the "ADD" code must be:

(0F0FF03Ch - 0F0FED94h) / 4 = 0AAh

Now, we've only to change the "SUB" operation (coded as 0AEh) by the "ADD" equivalent (coded as 0AAh). If you change it, the application will calculate "30+6" instead of "30-6".

Push Ctrl+D. This time, we find other "SUB" operation to calculate the remaining trial days at 4ADFA9h. Repeat the change and go on. Finally, you have had to change the "SUB" codes located at 4ADF47h, 4ADFA9h, 4A4716h and 4A4769h with the "ADD" code.

So, besides the nag, the loader will patch these locations to defeated the trial period protection. You can find, as example, the source of a loader with other Esiel2's tutorial, which is about the previous version of Cuentapasos.

To try the patches, remove the breakpoints, push Ctrl+D and ......

..... ;-) p-code rulezzz!!


I've shown how a well implemented nagscreen can be easily defeated because the programmer's "mistake" of programming with Visual Basic. By defeating the nag, besides we've remembered an ancient technique very useful.

The novelty is the p-code. Initially, tracing a p-compiled program may break your mind. It's a good approach to the independent-machine code dream, like Java. With some changes, you'll be able to run the same p-compiled application in any machine, if there is a VB Virtual Machine implemented for that machine.

On the other side, a VB p-compiled program is slower than the VB standard-compiled program. You'll need to buy a faster processor to run the same program with the same performance qualities. I'm sure Intel is very interested on p-compiled programs ;-)

For us, p-code will become our targets easier to defeat. By identifying several important instructions, we'll obtain a powerful "how to crack" technique. This technique will defeated p-compiled programs protections as trial periods, limited number of executions or licenses, ... You'll have to be used to work with ESI instead of EIP, "bpm" instead of "bpx"...


Esiel2/TNT .... you'd have really enjoy cracking this version... ;-P
Mr.BlacK/WkT! .... thanks for your orientations ....
bb .... I don't know you, but your tutorial is really funny.....
Fravia .... On-Line Bible ....
WkT! .... thanks to believe on me ....
English readers .... Sorry for my English. It's a good idea learning Spanish next summer ;-) ....
Spanish readers .... Gracias por leerme en inglés, pero hay una versión en castellano XDD ....
Toby, the programmer .... it has been a pleasure "cracking" you (advise: uninstall VB from your life) ....

Mr. Blue