|
LaZ-Calc
Adding functionality to the Windows Calculator
|
Advanced
|
14 September 1999
| by
LaZaRuS
|
|
|
Courtesy of Fravia's page of
reverse engineering
|
|
fra_00xx 980914 LaZaRuS 0010 AD PC
|
A "must read" reversing essays for all true reversers out there: adding functionalities to programs
is the sublimest reversing art, like Sung-style ink painting, and if LaZaRuS will bless us with more essays along these lines I will open
a special (and I am sure most coveted) section for this kind of essays.
Feedback on this essay is MOST important. I hope many will work on this and contribute.
| |
|
There is a crack, a crack in everything
That's how the light gets in
| |
Rating
|
( )Beginner ( )Intermediate (x)Advanced ( )Expert
| |
A small example to the (for me) most interesting part of reverse engineering: Adding
functionality to a program.
LaZ-Calc
Adding functionality to the Windows calculator
Written by
LaZaRuS
You surely know this: You enter huge formula into the Windows Calculator and see that the
result must be wrong. You made a mistake and this means: Reentering the complete formula once
more (this time with more attention). In this essay I will describe how to add an edit field to
the calculator, where you can enter this formula, so you can easily correct it, when a problem
should appear. Well, what should I say: An interesting essay for guys that feel like I do :)
Let's see, I used quite many tools this time:
Soft-Ice (for debugging and assembling in memory)
W32Dasm (for deadlistings)
Hex-Workshop (for applying changes to the file)
Borland Ressource Workshop (for adding objects/strings)
Customizer (for simulating different things)
MASM (for compiling some ASM code)
A Win-API reference is necessary
Target is the wrong expression, as this is no cracking essay, but we deal with the Windows
calculator which was shipped with Win95. You will find the English original (which I used) inside lazcalc.zip.
Don't think there's something important to say here.
At first I define what exactly we want to have when this essay is finished: We need an edit box,
a checkbutton, a normal button and a new menu point "LaZ-Calc" with the sub-menus "Center
Window", "Stay on Top", "Start Notepad", "Help", "About", "Quit". Look at the screenshot
scr1.gif or at lazcalc.exe that comes shipped inside lazcalc.zip to see how it should look like.
We add the components (buttons, menus...) with the Borland Ressource workshop. I won't explain
how this works, only the most important facts about the components:
The edit field, the check button and the standard button are added to the ressource "DIALOG"-
"SC", the Menus are added to "MENU"-"SM". It is important to add the "LaZ-Calc"-Menu after the
last menu (which is "Help"). If you don't do this, the menu will screw up, as the (original)
menus are not checked or enabled through the ident, but through the position.
The "Center Window" menu has the ID 666, the "Stay on Top" has 667, "Start Notepad" is 668,
"Help" is 669, "About" has 670 and "Quit" has 672. Why not 671? I don't know, just saw it right
in this moment. Must have been a typo, when I first created it. You can use 671 if you want, but
don't forget to apply the change to the sourcecode that comes later.
Now we need to add some bytes where we can add code. There are some "caves" (many following
00-Bytes), that cannot be used, as they are too small. Now comes the first weakness of my essay:
As I still can't "mess" with the PE-header, I needed another way to enlarge the file size. I
just added a huge string with BRW to the file. I guessed the size of this string and was lucky
at the end, as I nearly ran out of space. I needed 392h (=914) bytes, so you gotta create one
or more strings with a length of 457d chars, as BRW adds them in Unicode-Format with a 00
between two chars and you get double the amount of bytes of the string length. To see how my
file looked like at the end, see hexdump.txt. If you say that this is a lame way, at least
recocgnize that I found a nice workaround. :)
Now, the edit field. We have to be able to enter a serial. That is a small problem, as when you
try to enter something, the key is interpreted as hotkey and immediately a message to the
corresponding button is send. That's why I have created the check box. If it is checked, it
should be possible to enter formulas and if it is not checked, the keys you press should be
immediately interpreted as hotkeys. So, we have to find the code that does check, if a hotkey
is pressed. What API functions can be used? Don't even try to look for "Hotkey" in your
API-reference. The thing we are dealing with is called "Accelerator". The "Accelerator Table"
can be viewed in BRW, but at this moment, we cannot do anything useful with it. Here comes a
short extract from my API reference about the fuction we will deal with: TranslateAccelerator
The TranslateAccelerator function processes accelerator keys for menu commands. The function
translates a WM_KEYDOWN or WM_SYSKEYDOWN message to a WM_COMMAND or WM_SYSCOMMAND message
(if there is an entry for the key in the specified accelerator table) and then sends the
WM_COMMAND or WM_SYSCOMMAND message directly to the appropriate window procedure.
int TranslateAccelerator(
HWND hWnd, // handle of destination window
HACCEL hAccTable, // handle of accelerator table
LPMSG lpMsg // address of structure with message
);
So, this function checks, if the key (or keycombination) that was pressed is an Accelerator. If
so, it sends a WM_COMMAND to the button, that is associated with that key, which means the
button gets pressed and the function that this button has is executed. Now it is time to get a
disassembling of calc.exe for searching the place TranslateAccelerator is called. You will find
no direct call, but the following code which moves the address where the function is located
is moved to edi.
* Reference To: USER32.TranslateAcceleratorA, Ord:020Ch
|
:00401307 8B3DFCE34000 mov edi, dword ptr [0040E3FC]
Few lines below, you will find that call to edi.
:0040132B 8D45DC lea eax, dword ptr [ebp-24] ;; eax gets addr of MSG struct
:0040132E 50 push eax
:0040132F FF3598B44000 push dword ptr [0040B498] ;; address of AcceleratorTable
:00401335 FF35D8B54000 push dword ptr [0040B5D8] ;; Handle of main window
:0040133B FFD7 call edi ;; call TranslateAccelerator
:0040133D 85C0 test eax, eax ;; was key an Accelerator (eax=1) ?
:0040133F 7510 jne 00401351 ;; if so, then jump
Now we have to redirect the flow of instructions to an area of code we have created in order
to find out, if the checkbutton is checked and executes what to do then. My solution looks like
this:
:0040132B E900050100 jmp 00411830
:00401330 90 nop
:00401331 90 nop
:00401332 90 nop
:00401333 90 nop
:00401334 90 nop
:00401335 90 nop
:00401336 90 nop
:00401337 90 nop
:00401338 90 nop
:00401339 90 nop
:0040133A 90 nop
:0040133B 90 nop
:0040133C 90 nop
:0040133D 85C0 test eax, eax
:0040133F 7510 jne 00401351
It is not necessary to nop out all the pushes and the call, but I had aesthetical reasons for
doing this. I prefer compact code where the pushes are directly in front of the call and not
somewhere in our redirected code. Well, the jump at :004132B goes to offset DA30h in the file.
Here I added the following code:
:00000000 60 pushad ;; save all registers
:00000001 669C pushf ;; save all flags
:00000003 68AEEA4000 push 0040EAAE ;; push "USER32.DLL"
:00000008 FF1590E24000 call dword ptr [0040E290] ;; call GetModuleHandleA
:0000000E 682D1B4100 push 00411B2D ;; push "IsDlgButtonChecked"
:00000013 50 push eax ;; push Handle of User32.dll
:00000014 FF15DCE24000 call dword ptr [0040E2DC] ;; call GetProcAddress
:0000001A 68A3020000 push 000002A3 ;; push handle of check box (2A3=675)
:0000001F FF35D8B54000 push dword ptr [0040B5D8] ;; push handle of main window
:00000025 FFD0 call eax ;; call IsDlgButtonChecked
:00000027 0BC0 or eax, eax ;; check eax
:00000029 751A jne 00000045 ;; if eax == 1 (Button checked), then jump
:0000002B 669D popf ;; get all flags from stack
:0000002D 61 popad ;; get all registers from stack
:0000002E 8D45DC lea eax, dword ptr [ebp-24] ;; here comes exactly
:00000031 50 push eax ;; the same code
:00000032 FF3598B44000 push dword ptr [0040B498] ;; that was there
:00000038 FF35D8B54000 push dword ptr [0040B5D8] ;; before I redirected
:0000003E FFD7 call edi ;; the instructions
:00000040 E9BBFAFEFF jmp FFFEFB00 ;; jump back
:00000045 669D popf ;; get all flags from stack
:00000047 61 popad ;; get all registers from stack
:00000048 33C0 xor eax, eax ;; IMPORTANT: set eax to 0
:0000004A E9B1FAFEFF jmp FFFEFB00 ;; jump back
Well, let's analyse:
The pushad/pushf is absolutely necessary as we have to pass all register and flags and their
current states to the call edi" (TranslateAccelerator) and they are messed up, when we call
all the other functions before. We have to find out, if the checkbox is checked. We can use
the API-function "IsDlgItemChecked" for this purpose. Here starts the first problem: You can
assemble directly in the RAM using SICE, but if you enter "a"-"call IsDlgButtonChecked" SICE
will create a *direct* call to the Kernel of your system. That means it will create Opcodes
that are only valid on your machine. So we have to find another way, an indirect way: I
demonstrate with GetModuleHandleA, as IsDlgButtonChecked will have another problem. Get into
your disassembling in W32Dasm and search for "GetModuleHandleA" somewhere in the middle of the
code. The first appearance will be here:
Remark 1:
Don't think I guessed the source I present you here. I coded a test app, that should simulate
the Windows calculator. Here I developed all functions first. You should do this, too as it
is quite annoying to code larger part directly in SICE (as I experienced many bugs in my V3.23).
End of Remark
* Reference To: KERNEL32.GetModuleHandleA, Ord:010Eh
|
:0040544C FF1590E24000 Call dword ptr [0040E290]
Here we see an example for an indirect call. The function GetModuleHandleA can be called by
a call to dword ptr [0040E290]. We can use this value for our purposes, too. But wait, why do
we deal with this function? We need the dword ptr address for "IsDlgItemChecked". Well, search
for it and you'll find nothing. This function isn't automatically loaded, so we have to do it.
For this purpose we need GetModuleHandleA and GetProcAddress. GetModuleHandleA gets the handle
of a module (read: DLL-file in RAM). As IsDlgItemChecked is part of USER32.DLL (see W32Dasm
API definitions of another file, that uses this function) we have to find out the handle of
this DLL. The string User32.dll is surely to be found somewhere inside calc.exe - So search for
it and you'll find it at offset BEAEh which is :0040EAAE during runtime (s 00400000 l ffffffff
'USER32.dll' to find this address). So we need to "push" this string and then call dword ptr
[0040E290] which is an indirect call to GetModuleHandleA. Then we need to call the function
GetProcAddress to find out where the IsDlgItemChecked it located. We need to push a string with
the name of that function. As there is no such string, we have to create it: I located it at
offset DD2Dh. The second push (push eax) is the handle of USER32.DLL which was given back from
the first call. After GetProcAddress eax contains the location of IsDlgButtonChecked and we
can use it with "call eax". IsDlgButtonChecked wants two parameters. The first one is the handle
of the checkbox, the second one the handle of the main window. Now watch out: There are two
possible opcodes that represent "push 2A3" (for the checkbox handle) - If you assemble with
SICE, the wrong one will be chosen and the API function will only return crap. The reason seems
to be that the API wants a dword as parameter, but the "wrong" SICE-opcode pushes a word. A
small workaround is pushing a dword at first (like "push 11111") and then changing the opcode
with the "e"-command. The rest of the above code should be easy to understand. If the
checkbox is checked, eax gets cleared and the program will keep on running, as if no key was
pressed. If the checkbox is unchecked, TranslateAcceleratorA (call edi) will be called and the
return value will be used for the further execution of the MessageLoop. It is important to clear
eax when the button is checked, or we will mess up the MessageLoop!
Now we have changed the MessageLoops in a way, that we should be able to enter chars into the
edit field, when the checkbox is checked. If it is unchecked, the keys we press are interpreted
as hotkeys. Let's turn to the next thing-to-do now. We have to find the code that is executed
when a menu is chosen by the user. In W32Dasm I have chosen "Menu Reference" and then "Menu:
SM, Item: "Copy Ctrl+C"" - Look where this appears first time any you'll see:
* Possible Ref to Menu: SM, Item: "Paste Ctrl+V"
|
:00403161 3D2D010000 cmp eax, 0000012D
:00403166 7725 ja 0040318D
* Possible Ref to Menu: SM, Item: "Copy Ctrl+C"
|
:00403168 3D2C010000 cmp eax, 0000012C
:0040316D 0F83A1060000 jnb 00403814
This code looks is a first part of the routine, that checks which Menu was chosen. 12D and 12C
are the handles of the menupoints. If you put a breakpoint on :00403161 you and choose a menu
you will see that it breaks. So, we need to insert a jump to another self-coded part here.
I made it this way:
:00403161 E91AE70000 jmp 00411880 ;; this jump leads to our next section
:00403166 7725 ja 0040318D
* Possible Ref to Menu: SM, Item: "Copy Ctrl+C"
|
:00403168 3D2C010000 cmp eax, 0000012C
:0040316D 0F83A1060000 jnb 00403814
Well, we have to make thoughts what to do in the section we jump to. Btw: We still have the
problem, that we have to handle clicks on the button and the checkbox. No, we don't have this
problem. When you push the button (with breakpoint on :00403161) you will see that it'll break
there, too. So we can use the jump to :00411880 for this purpose, too :)
Now let's look at the first code I inserted at :00411880 (which is at offset DA80h):
At first I handle the click at the checkbox. If the checkbox is checked, the button should be
enabled, if not it should be disabled.
:00000000 3DA3020000 cmp eax, 000002A3 ;; is eax the handle of the checkbox?
:00000005 753D jne 00000044 ;; if not, then jump
:00000007 803558B6400001 xor byte ptr [0040B658], 01 ;; see explanation below
:0000000E 90 nop ;; see explanation below
:0000000F 90 nop
:00000010 90 nop
:00000011 90 nop
:00000012 90 nop
:00000013 90 nop
:00000014 90 nop
:00000015 90 nop
:00000016 90 nop
:00000017 90 nop
:00000018 90 nop
:00000019 90 nop
:0000001A 90 nop
:0000001B 90 nop
:0000001C 90 nop
:0000001D 90 nop
:0000001E 90 nop
:0000001F 90 nop
:00000020 68A1020000 push 000002A1 ;; ID of the button
:00000025 FF35D8B54000 push dword ptr [0040B5D8] ;; handle of the main window
:0000002B FF1540E34000 call dword ptr [0040E340] ;; call GetDlgItem
:00000031 8B1D58B64000 mov ebx, dword ptr [0040B658] ;; get Enable-flag value in ebx
:00000037 53 push ebx ;; Enable-flag
:00000038 50 push eax ;; handle of the button
:00000039 FF15DCE34000 call dword ptr [0040E3DC] ;; call EnableWindow
:0000003F E9A218FFFF jmp FFFF18E6 ;; jump to :00403166
So, let's analyze the code. At first I wanted to use the API-function IsDlgButtonChecked, to
find out, if the button has to be enabled. As this API function is not loaded by calc.exe I
have chosen to set a flag for the status of the checkbox. The checkbox is not checked by
default, so I searched a byte that is 0 by default and that is unused by the Calculator.
I found it at 40B658. If this byte is 0, the checkbox is unchecked, if it is 1 the button is
checked. At first I changed the state of this flag with something like this:
if (flag==0)
flag=1;
elseif (flag==1)
flag=0;
That resulted in a bunch of crappy code (where the NOPs are now). That was unacceptable for me,
and I made some thoughts how to change the flag shorter. The solution is XORing the flag with 1.
When it is 0 (at the start) it will become 1, if it is 1 it will become 0. Removing the NOPs
would only work if I rewrite my code again and update the jumps, but I am too lazy for this.
The rest should be clear (at least with an API reference). I get the current handle of the
button with GetDlgItem and then I enable it with EnableWindow where I use the flag in 40B5D8
as parameter for the API.
Now the most important section, the interpretation of the formula in the edit box. The first
thought that has to be done is, if the formula should be entered as "correct" formula, or as
a formula made created with the hotkey of the Windows calculator. I decided for the second one,
as this is *much* easier to code. The formula must be entered into the edit field in exactly
the same way you would enter it directly. Here is an example:
Correct formula: (3*2+sin(5))^2 (result=37,05346503647)
Hotkey formula : (3*2+5s)@ (result=37,05346503647)
The deal should be pretty easy: Read the text of the edit field char by char and then... Yeah,
what then? I tried several possibilities. At first I wanted to send a BN_CLICK to the button
that is associated with the key, but this caused several problems. It would have been quite
a huge if-elseif-else construct to find out the related button. Second problem: The calc.exe
buttons are no real buttons. Neither SICE, nor Customizer can find a handle for them. Next thing
I tried: Simulating a key with WM_CHAR (and WM_KEYDOWN). Didn't work, too. You can easily see
what happens, if you use Customizer to send a WM_CHAR message. Now I decided the correct
solution: A direct call to TranslateAcceleratorA should work. And it worked! See the next part
of the source below:
:00000044 3DA1020000 cmp eax, 000002A1 ;; handle of the button?
:00000049 7577 jne 000000C2 ;; if not, then jump
:0000004B 68A2020000 push 000002A2 ;; ID of edit field
:00000050 FF35D8B54000 push dword ptr [0040B5D8] ;; handle of main window
:00000056 FF1540E34000 call dword ptr [0040E340] ;; call GetDlgItem
:0000005C 6A32 push 00000032 ;; push maximum length of text (32h=50)
:0000005E 6888B64000 push 0040B688 ;; buffer where to write text from edit
:00000063 50 push eax ;; handle of edit field
:00000064 FF15A8E34000 call dword ptr [0040E3A8] ;; GetWindowTextA
:0000006A 3D00000000 cmp eax, 00000000 ;; Length of text = 0?
:0000006F 7451 je 000000C2 ;; if so, then jump
:00000071 BE88B64000 mov esi, 0040B688 ;; buffer of text
:00000076 AC lodsb ;; get one char in al
:00000077 0AC0 or al, al ;; is char=0?
:00000079 7447 je 000000C2 ;; if so, jump
:0000007B 8AD8 mov bl, al ;; move char to bl
:0000007D B880FD6400 mov eax, 0064FD80 ;; address of the MSG structure
:00000082 80FB61 cmp bl, 61 ;; if BL
:00000085 7208 jb 0000008F ;; then jump
:00000087 80FB7A cmp bl, 7A ;; if BL > z
:0000008A 7303 jnb 0000008F ;; then jump
:0000008C 80EB20 sub bl, 20 ;; convert small letter to capital letter
:0000008F 885808 mov byte ptr [eax+08], bl ;; see below
:00000092 80FB41 cmp bl, 41 ;; if BL
:00000095 720D jb 000000A4 ;; then jump
:00000097 80FB59 cmp bl, 59 ;; if BL > Y
:0000009A 7708 ja 000000A4 ;; then jump
:0000009C 66C740040001 mov [eax+04], 0100 ;; 0100=ASCII
:000000A2 EB06 jmp 000000AA
:000000A4 66C740040201 mov [eax+04], 0102 ;; 0102=Virtual Key
:000000AA 50 push eax ;; Here comes
:000000AB FF3598B44000 push dword ptr [0040B498] ;; the call
:000000B1 FF35D8B54000 push dword ptr [0040B5D8] ;; to TranslateAcceleratorA
:000000B7 FF15FCE34000 call dword ptr [0040E3FC] ;; again
:000000BD E9B4FFFFFF jmp 00000076 ;; jump back and get next char
Getting the handle of the edit field and reading the text of it should be pretty clear. The
only question is how I found out where to save the Text. I just looked around in SICE until I
found a vast area with 00-bytes. That became my buffer. Next question should comes at line 7D.
How did I find the value 64FD80? Take the unpatched calc.exe and set a breakpoint on Translate-
AcceleratorA. Eax will *always* have the value 64FD80, when it breaks regularly. This is the
address of the MSG-structure that is passed to TranslateAcceleratorA. To understand what
happens then, we have to look at the definition of the MSG structure:
typedef struct tagMSG {
HWND hwnd; // DWORD (eax)
UINT message; // DWORD (eax+4)
WPARAM wParam; // DWORD (eax+8)
LPARAM lParam;
DWORD time;
POINT pt;
} MSG;
Now, for simulating an Accelerator, we have to change the message and the wParam to our needs.
The wParam is the key that should be simulated ([eax+8]), the message ([eax+4] contains either
"VKey" or "ASCII". See the AcceleratorTable in BRW and you should understand what to use for
which key. At the end we just have to "push" the parameters and call TranslateAcceleratorA.
The hardest things are done now, let's head for the menus.
The first menu I will handle is "Center Window". The menupoint has the handle 29Ah.
:000000C2 3D9A020000 cmp eax, 0000029A ;; is "Center Window" chosen?
:000000C7 756E jne 00000137 ;; if not, then jump
:000000C9 8D45AC lea eax, dword ptr [ebp-54] ;; eax=adress of RECT structure
:000000CC 50 push eax ;; RECT
:000000CD FF35D8B54000 push dword ptr [0040B5D8] ;; handle of main window
:000000D3 FF151CE34000 call dword ptr [0040E31C] ;; GetWindowRect
:000000D9 68AEEA4000 push 0040EAAE ;; "USER32.DLL"
:000000DE FF1590E24000 call dword ptr [0040E290] ;; GetModuleHandle
:000000E4 681C1B4100 push 00411B1C ;; "GetSystemMetrics"
:000000E9 50 push eax ;; handle of User32.dll
:000000EA FF15DCE24000 call dword ptr [0040E2DC] ;; GetProcAdress
:000000F0 50 push eax ;; save adress of GetSystemMetrics
:000000F1 6A00 push 00000000 ;; SM_CXSCREEN
:000000F3 FFD0 call eax ;; GetSystemMetrics
:000000F5 2B45B4 sub eax, dword ptr [ebp-4C] ;; these few lines
:000000F8 0345AC add eax, dword ptr [ebp-54] ;; calculate the
:000000FB D1E8 shr eax, 1 ;; new position of the right border
:000000FD 8BD8 mov ebx, eax ;; and save it in ebx
:000000FF 58 pop eax ;; load adress of GetSystemMetrics
:00000100 6A01 push 00000001 ;; SM_CYSCREEN
:00000102 FFD0 call eax ;; GetSystemMetrics
:00000104 2B45B8 sub eax, dword ptr [ebp-48] ;; these few line
:00000107 0345B0 add eax, dword ptr [ebp-50] ;; calculate the
:0000010A D1E8 shr eax, 1 ;; new position of the upper border
:0000010C 50 push eax ;; and save it
:0000010D 8B45B0 mov eax, dword ptr [ebp-50] ;; these two lines calculate
:00000110 2945B8 sub dword ptr [ebp-48], eax ;; the width of the window
:00000113 8B45AC mov eax, dword ptr [ebp-54] ;; these two lines calculate
:00000116 2945B4 sub dword ptr [ebp-4C], eax ;; the height of the window
:00000119 58 pop eax ;; load position of upper border again
:0000011A 6A40 push 00000040 ;; SWP_SHOWWINDOW
:0000011C FF75B8 push [ebp-48] ;; right border
:0000011F FF75B4 push [ebp-4C] ;; upper border
:00000122 50 push eax ;; width
:00000123 53 push ebx ;; heigth
:00000124 6A00 push 00000000 ;; HWND_TOP
:00000126 FF35D8B54000 push dword ptr [0040B5D8] ;; handle of the window
:0000012C FF1558E34000 call dword ptr [0040E358] ;; call SetWindowPos
:00000132 E9AF17FFFF jmp FFFF18E6 ;; jump back to MessageLoop
Actually nothing special here:
This code-snippet gets the current coordinates of the window first. The address of the RECT
structure (ebp-54) is found accidentely, but works good. Then we have to load the address of
GetSystemMetrics in the way that was mentioned earlier, again as this API-function is not
loaded, too. With the help of the coordinates we get from a call to GetSystemMetrics, the new
position (Centered) of the window is calculated, with the help of the GetWindowRect call the
size of the window is calculated for the SetWindowPos call in the end.
The next part of code is the one that makes the calculator window stay on top. It is quite
similar to the last snippet, as the only changes are, that we needn't get new coordinates, but
only change one parameter of the SetWindowPos call. Furthermore we have to check/uncheck the
menu which decides, if the window stays on top, or not:
Remark 2:
The follwing code is little sloppy, but it works and recoding it in a better way would take
more time as the result will improve it, as neither speed nor size matter here.
End of Remark
:00000137 3D9B020000 cmp eax, 0000029B ;; is it "Stay on top" ?
:0000013C 0F85C2000000 jne 00000204 ;; if not, then jump
:00000142 8D45AC lea eax, dword ptr [ebp-54] ;; prepare the GetWindowRect
:00000145 50 push eax
:00000146 FF35D8B54000 push dword ptr [0040B5D8] ;; handle of main window
:0000014C FF151CE34000 call dword ptr [0040E31C] ;; GetWindowRect
:00000152 8B45B4 mov eax, dword ptr [ebp-4C] ;; the next lines calculate
:00000155 2B45AC sub eax, dword ptr [ebp-54] ;; the width of the window
:00000158 8945B4 mov dword ptr [ebp-4C], eax ;; and save it in EBP-4C
:0000015B 8B45B8 mov eax, dword ptr [ebp-48] ;; the next lines calculate
:0000015E 2B45B0 sub eax, dword ptr [ebp-50] ;; the heigth of the window
:00000161 8945B8 mov dword ptr [ebp-48], eax ;; and save it in EBP-48
:00000164 FF35D8B54000 push dword ptr [0040B5D8] ;; handle of the main window
:0000016A FF1554E34000 call dword ptr [0040E354] ;; GetMenu
:00000170 50 push eax ;; save the handle of the menu
:00000171 50 push eax ;; save the handle of the menu
:00000172 50 push eax ;; save the handle of the menu
:00000173 90 nop ;; part of later improvement
:00000174 90 nop ;; same
:00000175 68AEEA4000 push 0040EAAE ;; "USER32.DLL"
:0000017A FF1590E24000 call dword ptr [0040E290] ;; GetModuleHandle
:00000180 90 nop ;;part of later improvement
:00000181 90 nop ;;part of later improvement
:00000182 90 nop ;;part of later improvement
:00000183 680F1B4100 push 00411B0F ;; "GetMenuState"
:00000188 50 push eax ;; handle of User32.dll
:00000189 FF15DCE24000 call dword ptr [0040E2DC] ;; GetProcAddress
:0000018F 5B pop ebx ;; load handle of the menu
:00000190 6A00 push 00000000 ;; MF_BYCOMMAND
:00000192 689B020000 push 0000029B ;; ID of the menu point
:00000197 53 push ebx ;; handle of the menu
:00000198 FFD0 call eax ;; call GetMenuState
:0000019A 3D08000000 cmp eax, 00000008 ;; is checked?
:0000019F 7533 jne 000001D4 ;; if not, then jump
:000001A1 90 nop ;; fell away when optimizing
:000001A2 90 nop ;; fell away when optimizing
:000001A3 90 nop ;; fell away when optimizing
:000001A4 58 pop eax ;; load handle of menu
:000001A5 6A00 push 00000000 ;; MF_UNCHECKED
:000001A7 689B020000 push 0000029B ;; ID of Menupoint
:000001AC 50 push eax ;; handle of Menu
:000001AD FF154CE34000 call dword ptr [0040E34C] ;; call CheckMenuItem
:000001B3 6A40 push 00000040 ;; The following lines
:000001B5 FF75B8 push [ebp-48] ;; prepare the
:000001B8 FF75B4 push [ebp-4C] ;; call to
:000001BB FF75B0 push [ebp-50] ;; SetWindowPos
:000001BE FF75AC push [ebp-54] ;; still preparing
:000001C1 6AFE push FFFFFFFE ;; HWND_NOTOPMOST
:000001C3 FF35D8B54000 push dword ptr [0040B5D8] ;; handle of window
:000001C9 FF1558E34000 call dword ptr [0040E358] ;; SetWindowPos
:000001CF E91217FFFF jmp FFFF18E6 ;; return to messageloop
:000001D4 5B pop ebx ;; load handle of menu
:000001D5 6A08 push 00000008 ;; MF_CHECKED
:000001D7 689B020000 push 0000029B ;; ID of Menupoint
:000001DC 53 push ebx ;; push handle of menu
:000001DD FF154CE34000 call dword ptr [0040E34C] ;; CheckMenuItem
:000001E3 6A40 push 00000040 ;; and preparing
:000001E5 FF75B8 push [ebp-48] ;; SetWindowPos
:000001E8 FF75B4 push [ebp-4C] ;; once again
:000001EB FF75B0 push [ebp-50]
:000001EE FF75AC push [ebp-54]
:000001F1 6AFF push FFFFFFFF ;; HWND_TOPMOST
:000001F3 FF35D8B54000 push dword ptr [0040B5D8] ;; handle of window
:000001F9 FF1558E34000 call dword ptr [0040E358] ;; SetWindowPos
:000001FF E9E216FFFF jmp FFFF18E6 ;; back to MessageLoop
At first we have to call GetWindowRect again. It will return (after some calculation) the
parameters we use for the SetWindowPos call. The well-known GetModuleHandle/GetProcAddress
for getting the address of GetMenuState. In combination of GetMenu we can find out, whether the
menupoint is checked or not. Then, after the conditional jump (condition: menu checked?) at
:19A we set the new position of the window using the parameters HWND_NOTOPMOST and HWND_TOPMOST.
The code for "Start Notepad":
:00000204 3D9C020000 cmp eax, 0000029C ;; is "Start Notepad" chosen?
:00000209 7525 jne 00000230 ;; if not, then jump
:0000020B 68ACE64000 push 0040E6AC ;; push "KERNEL32.DLL"
:00000210 FF1590E24000 call dword ptr [0040E290] ;; "GetModuleHandle"
:00000216 68071B4100 push 00411B07 ;; "WinExec"
:0000021B 50 push eax ;; handle of Kernel32.dll
:0000021C FF15DCE24000 call dword ptr [0040E2DC] ;; GetProcAddress
:00000222 6A01 push 00000001 ;; SW_SHOW
:00000224 68EA174100 push 004117EA ;; "Notepad.exe"
:00000229 FFD0 call eax ;; call WinExec
:0000022B E9B616FFFF jmp FFFF18E6 ;; back to MessageLoop
You see: It's getting easier :) This time just finding out the address of WinExec and then
passing the string "Notepad.exe" to WinExec which I wrote at offset D9EAh into lazcalc.exe.
I won't explain the rest of my changes, as they can easily be understood just reading the
comments (and perhaps your API reference).
The code for "Help":
:00000230 3D9D020000 cmp eax, 0000029D ;; is "Help" chosen ?
:00000235 7516 jne 0000024D ;; if not, then jump
:00000237 6A00 push 00000000 ;; no additional data
:00000239 6A03 push 00000003 ;; HELP_INDEX
:0000023B 68F6174100 push 004117F6 ;; "lazcalc.hlp"
:00000240 6A00 push 00000000 ;; didn't specify a window handle
:00000242 FF1514E34000 call dword ptr [0040E314] ;; WinHelpA
:00000248 E99916FFFF jmp FFFF18E6 ;; back to messageloop
The code for "About":
:0000024D 3D9E020000 cmp eax, 0000029E ;; is "About" chosen?
:00000252 7519 jne 0000026D ;; if not, then jump
:00000254 6A00 push 00000000 ;; MB_OK
:00000256 68E1174100 push 004117E1 ;; Caption of the box
:0000025B 68AE174100 push 004117AE ;; Text of the box
:00000260 6A00 push 00000000 ;; no window specified
:00000262 FF150CE44000 call dword ptr [0040E40C] ;; MessageBoxA
:00000268 E97916FFFF jmp FFFF18E6 ;; back to messageloop
The code for "Quit":
:0000026D 3DA0020000 cmp eax, 000002A0 ;; is "Quit" chosen?
:00000272 750D jne 00000281 ;; if not, jump
:00000274 6A00 push 00000000 ;; Exit code = 0
:00000276 FF1500E34000 call dword ptr [0040E300] ;; ExitProcess
:0000027C E96516FFFF jmp FFFF18E6 ;; back to MessageLoop
:00000281 E96016FFFF jmp FFFF18E6 ;; back to MessageLoop
The only thing that is worth to ask is why my code ends with two jumps: Well, in this case
it is easier to add new functions, as the second jump is just to be replaced by a "cmp eax,..."
if you want to add more.
Wow, you have come so far, despite my writing style. Either you are hard to bore, or you only
read Introduction and Last Words of the essay.
OK, now the last words:
Don't even think about, that this was an easy task. Also the essay is written as if I needed
only short more than half an hour, I in fact needed nearly two weeks, but nevertheless I believe
that the result is quite acceptable and I felt really good when I finished :)
My thanx go to #cracking4newbies for (most time) quick help when I was stuck. For detailed
greetings consult the helpfile that comes shipped with lazcalc.zip
If you have the desire to add more functions, or to improve my code, please send your result to
lazarus_hf@hotmail.com If you want to tell me something else, use this address, too.
Possible functions you could add:
1. Make LaZ-Calc work in Standard-mode or at least auto-uncheck the checkbox
2. Center window at startup
3. Change the caption of the window
4. Get rid of the checkbox and auto-choose LaZ-Calc options if the edit field is enabled
5. Add hotkeys for LaZ-Calc buttons/menus...
6. Make it possible to use the buttons that need a key combination with STRG or ALT...
7. ... (a million more)
If you need some more information to realize this, don't hesitate to contact me.
Doesn't apply, does it?
You are deep inside fravia's page of reverse engineering,
choose your way out:
homepage
links
search_forms
+ORC
how to protect
academy database
reality cracking
how to search
javascript wars
tools
anonymity academy
cocktails
antismut CGI-scripts
mail_fravia+
Is reverse engineering legal?