Windows 95 System Programming SECRETS
(Windows 95 system program design big mystery)
Original: Matt Pietrek
Note: Simon Wan
Find a quiet visit to yourself
I want to discuss this one of the three Windows programmers to explore the mystery:
l Archive Tilting Tool (File Dumping Utilities)
l API Function and Windows Message Symnask
l Contrast Translation (Disasembly)
Adventures with Dumping Tools
Usually an explorestive action of a program, the first step is to pour the contents of its archives. This step allows you to quickly know
The archive type of your adventure object, as well as it possible. Table 9-1 lists several common archive anatomical tools
And its capabilities.
The most useful information you can obtain from the output of the file is usually used by this executable file.
And the API card.
Advent with Spying Tools
Although the file is very interesting, it is also rich in information, but it is often unable to tell you everything you need to know. The tools that can spy the program activities can be competent. The most famous Windows sprout tool is a message sprout software: Microsoft's Spy and Borland's Winsight. The message sprint software can display any messages received by the window and the response to this message.
Adventure with DISASSEMBLY tools
Although the anti-group translation is very complicated, it is often the only way to solve mysterious algorithms or technology.
Reverse group translation, Zen and art
There is no single criterion for all anti-group translation actions. What I will describe here is a guidelines for me. If other guidelines apply to you, don't hesitate. My anti-group translation is basic philosophy, and it is "dispersion conquer, all breaks". After getting a reverse group decoding, I don't deal with the entire letter or whole code, but the range breaks, narrows, and then offensive through a series of procedures, and there is a big success rate. My ultimate goal is to convert the anti-group decoding into the C-form code that is easy to read after. The importance of the following points may change in accordance with the code you have processed. First of all, I will explain the general terminology in the reverse group translation. Then I jump to the core question: Parameter authentication, regional variables, conditional judgment, card-calling ... and so on. Finally, I will demonstrate an actual example that uses some anti-group translation output results to translate into useful information. When a function is translated, the step you need is listed below.
Step 1: Translate the archive
Step 2: Make the label for known objects
Step 3: Disclosure of the instruction sequence (Instruction Sequences)
Step 4: Add a string (String Literals)
Step 5: Condensate the instruction as a single C language narrative
Step 6: Identification condition sentence
Step 7: Repeat (if necessary)
Identify common program code and habits
After discussing how to reversely translated a one, now I want to verify some common code, and some habits of assembly language. This can help you translate assembled language as a high-level language code.
l Identify functions and programs
Seeing the anti-group translation output code, the first thing is to identify where is the beginning and end of a function. The method that is easier to find a letter starting position is to find the standard ProLogue code generated by the compiler.
If the standard Prologue code looks like this: Push EBP ;; Save Caller's EBP FRAME.
MOV EBP, ESP ;; SET UP New EBP FRAME.
SUB ESP, XX ;; make space for local variables on stack.
Push ESI; ESI, EDI, AND EBX Are Commonly Used AS
Push EDI ;. Register variables.
Push EBX
Determine the start and end of a letter, remember, keep back, is the beginning of another one. in case
You see some image simulations is Epilogue, please find another paragraph like ProLogue, with it. Such as
If you haven't found it, then one may be the optimization action of the compiler to make the proLogue disappear, and the other may be
There is a multiple exit in a single function.
l
When the function is transmitted back, it is sent back to a buffer (or a set of buffers). In order to determine if the functions have been used, please check the functions of the functions to see if they have those buffers. If you see a copy of a group, use those buffers that are dedicated to pass back to the value, but you don't have the value in advance, you know that it is using a letter back to return. For example, if you see a single file in the program code, then use AX without setting its value first, you can determine that the called fifth is transmitted back to its return value.
In the 32-bit code, the functions are used to Eax as the medium for return values. 16-bit code uses AX to pass a 16-bit value, deliver 32-bit values in DX: AX. If the program is completed in assembly language, programmers can use any of their favorite buffers to complete the delivery work. One habit of constraints usually follow is that if the function is only back to a true holiday (representing success or failure), it usually sets or clears Carry Flag (CF). Search for JC and JNC instructions followed by the CALL instruction, it is easy to sniff this kind of form.
l Identification of parameters
If you already know the parameters you are dismantling, you will be very easy to post the label for them in assembly language. In addition to an exception (I will be brought later), the compiler always uses the stack to transfer the functional parameters. Just know the size of each parameter, you can easily locate each parameter in the stack. However, before I show the example, I must first explain the "Calling CallingConvention" "used by WINDOWS and WIN32.
In the 16-bit Windows code, most of the output functions use the PASCAL call habits - the call end is responsible for pushing the parameters into the stack, the order in which the push is the leftmost parameter to the rightmost parameter.
In addition to the order of parameters pushing in the stack from left to right, Pascal call habits also require "called end" to be responsible for clearing the stack before returning. In terms of this FOO letter I cited, it must launch the 6 bytes of the stack before returning to the stack. Maybe it will use a "RETF 6" instruction.
Standard C / C Runtime Library uses C call habits - parameters are pushed by the rightmost to the leftmost pile (the benefit of this is that it can handle the parameters like Printf). The work as to clear the stack is responsible for "call end".
Win32, Microsoft accepted STDCALL call habits, applied to almost all System DLLS output functions (Exported function). Stdcall is a mix of C and PASCAL. Its parameter transfer order is the responsibility of the right to left and clear stacks like c, which is in the "called end", which is like Pascal. In addition, when you use stdcall call habits in Microsoft C , the compiler will automatically (internal) add "@xx" string after the function name, XX is used to represent the total byte number of parameters. For example _GetWindowlong @ 8 or _peekMessage @ 20. After understanding the card-calling habits, you can decide the location in the stack. After knowing the parameter after the stack offset position, you can find any instructions to refer to the memory address, then replace the assembly language address with a symbol name. The symbol name is a great help for anti-group translation.
l Identify zone variables
Like the function parameters, the zone variables are usually in the stack. The most critical difference between the functional parameters and the regional variable is that the zone variable is positioned at a negative offset value in the stack system. For example, [EBP-4] in the [BP-4] or 32-bit code in the 16-bit code is [EBP-4]. Unlike the functional parameters, there is no formulation of the regional variable to determine the type of variable, use, and location. You have to consider how the card code uses a special address. Sometimes it is easy to determine the meaning of a regional variable,
For example, below Win
32
code
:
Push DWORD PTR [EBP 08]
Call getParent
MOV [EBP
-0c
], EAX
GetParent is a Win32 API that requires an HWND parameter to return to the HWnd of the parent window with EAX. Copy EAX to [EBP]
-0c
], Very clear [EBP
-0c
] Is an HWND. From this you can make a wild guess: the parameters may be named "hwndparent". In this step, you can use the "Search and Replace" function of your text editor, put all [EBP
-0c
] Change to [hwndparent]. After completing, look at your anti-group decoding, is it more refreshing?
Some people may ask: "It is very good, Matt, but not every zone variable is so easy to pick it out! Yes, we have other methods. Sometimes we are easily identified by a regional variable to make them with other functions. Below is a Win32 instance:
Lea EAX, [EBP-30]; Get Address of EBP-30H INTO EAX.
Push Eax; Push IT as an LPRECT.
Push [EBP 08]; Push An Hwnd (a parameter).
Call getWindowRect; call inTo user32 to get the reference coordinates.
Take a look at the getWindowRect data in the SDK manual, we know that it requires an HWND parameter, a pointer parameter to the Rect structure. Since getWindowRect is a stdcall function, the Rect indicator should be pushed into the stack before it is hwnd. In the above list, we see that the program code pushes an EBP-30H address to the stack for the LPRECT parameter, and therefore, it must be a RECT type region variable at the [EBP-30H] address. This is an unexpected information because WINDEF.H contains the Rect Structure Format (4 DWORDs), so we can learn all the RECT fields in the stack: Rect.Left = [EBP-30]
Rect.top = [EBP
-2C
]
Rect.right = [EBP-28]
Rect.bottom = [EBP-24]
Once again, we can make more meaningful symbols, search and replace those [EBP-XX] addresses. The compiler can temporarily copy the zone variable (and the function parameters) into the buffer. This saves some program code space and execution time. When working with the anti-group decoding, you must remain vigilant for those who start using the buffer variable. As long as you see that the buffer is used, it takes place with meaningful variable name. Note that compilers (or assembler) may store different variables in different locations.
In the 16-bit program, Si and Di are most often used as a buffer variable. Since these two buffers have only 16 long, they usually don't have to be on the indicator, because most of the 16-bit programs are 32-bit remote indicators. Si and Di are usually used for 16-bit values such as HWNDS or DCS. In Win32 programs, ESI, EDI, and EBX are most often used as a buffer variable. Win32's pointer is a 32-bit proximity pointer, so these three buffers can also be used to place the indicator. Ah, there is no road is rugged or fast, when processing the buffer variable, use your intuition and judgment.
l Identify the whole country variable
The whole country variables used by the decision program are much easier. Almost any of the written memory addresses is a national variable. The whole domain variable does not require something like EBP to set out positions. In the 32-bit code, the whole variable may look like this:
Mov Eax, [00464398]
If you are lucky, and the anti-group translator has a symbolic table, it can be referred to, perhaps [00464398] will replace with the raw code of the raw code. If this is not the case, you have to search for the entire anti-group translation list, replaced by symbol name [00464398]. If you don't have a symbolic table in your hands, you can try it to decide your meaningful name. In the 16-digit code, identifying the work and 32-bit codes of the whole country, the difference is only 16- / 32-bit. But if the program has multiple data section, you must be particularly careful. The problem is that the same offset address may appear in several data section. When you handle the domain variables of the data segment other than DGROUP, the program code sets a segment buffer (usually ES) to point to this segment, and then the program code is used in conjunction with the originally written offset value by the buffer. ,E.g
MOV AX, ES: [
001C
].
If you have a symbolic table in your hand, you may have two cases in the list of memory locations. The first case is that the memory address is used for a STATIC variable. If your symbol form contains only public symbols, this variable will not be displayed. The second case is that you may see a field in a structure or array. For example, 16-bit programs have a variable Msg mymsg; located in the 0364H position of the DGROUP section, where the WPARAM field accounts for 4 bytes, then mymsg.wparam should be at 0368H. The symbolic table of this program will list the MYMSG located at 0364H, but will not list something at 0368h. No gains don't gain! Looking for a symbol closest to the 0368H address, I see a guy called mymsg at 0364H. According to this name, I assume that it is an MSG structure. Of course, I have to test my hypothesis. If it is really an MSG structure at 0364h, it should be a field of this structure at 0368H, right? Correct! But I should find other guarantees before I have not sure my hypothesis is correct. 0368h looks like WPARAM? The next field (036ah) looks like an LPARAM? Unfortunately, there is no rugged and fast technology to be used. I have to continue to maintain my hypothesis until I have enough confidence. The good news is that the compiler rarely places the whole variable into the buffer. l Identify string
Many API functions require string parameters. Put the functions of the function as ASCII, and you can do some ideas for your function. For example, in the 16-bit code you may experience the following instructions:
Push DS
Push 0437
Call getModuleHandle
In the 32-bit code you may experience the following instructions:
Push 00471784
Call getModuleHandle
Open your API manual, you will find that getModuleHandle has a parameter and is a string pointer. Those PUSH instructions push the string pointer into the stack when doing the parameters of getModuleHandle. Therefore, at address 00471784 (or DS: 0437 in the 16-bit code) should have a string (e.g., "User32") with NULL as ending characters. If your anti-group translator puts out the data segmentation, please move forward to the address and take it out of its string content, then return to the program code, plus a paragraph, for example:
Push 00471784 ;; "User
32
"
Call getModuleHandle
If you use a large pile of procedures, you may be surprised by the results after doing so, because it is too refreshing. This process is more tricky in the anti-group transcript. Some executables put the string in the Code section. Usually the string is placed below the program code using it. A good anti-group translator can notice this situation and temporarily switch to HEX tilting mode. However, the general anti-group translator often errors. Sometimes you have to look at the yards you can decide where you start, where is the data end. Typically, embedded data (JMP tables generated by the Switch instruction) will produce some temporary garbage in your anti-group decoding. Take a look at the code you have, you can often get some clues, tell you what is true code, what is embedded. You can feed these results to the anti-group translator, make a second list, distinguish program code and data. No one said that the anti-group translation is an easy difference, right!
l Identify IF command
The simplest condition is judging is an IF action:
IF (some test) {do some sequence of code
}
Before discussing various changes, I want to display their assembly language. From the reverse group decoding, you will encounter three main test forms:
Equal test: if (a == b), if (a! = B) ..., etc..
2. Boolean test: if (a), if (b) ... and more.
3. Bit test: if (a & 0x0040) ..., etc..
Although the compiler produces different codes for testing of different types, its objectives are set or clear of the CPU Zero Flag (ZF). After setting or clearing the zero flag, the program code uses JZ (JUMP if Zero) or JNZ (Jump ifnot Zero) to determine, see if you want to execute or skip the subsequent code. You can also think of JZ as JE (JUMP ifqual), think of JNZ as JNE (Jump if not equals). "Test, and then have conditionally jump out of this mode" If the test result is negative, the CPU performs conditional jump, subsequent {} or begin / End will not be executed. If the test result is affirmative, the execution order will not jump, so the code in {} or begin / end will be executed.
WARNING: I am just the simplest case here. The code in the real world may be much more complicated. For example, in a 16-bit program, there may be a JZ or JNZ to jump from a regular JMP instruction. This situation occurs when the code ratio of 127 bytes in the IF block is 127 bytes - that is a 16-bit code to a conditional jumping limit. Of course, the basic premise I have described earlier has not changed. For "equal test", the compiler uses the CMP instruction. The following is the output piece of "DUMPBIN / DISASM":
0000101E: CMP DWORD PTR [EBP-04], 04
00001022: JNE 0000102E
00001028: Inc Byte Ptr [EBP-04]
0000102b: increst PTR [EBP-08]
0000102e: ...
The first instruction is compared to DWORD at [EBP-04] and 4 comparison. If the CMP instruction sets zero flag, it will clear Zero Flag. The next instruction (JNE) skips the following code - but only when Zeroflag is cleared. Therefore, two INC instructions are only executed when the Zero Flag is set up. Rewrite in C language, we get:
IF (Somevariable1 == 4)
{
Somevariable1 ; // inc [EBP-04]
Somevariable2 ; // inc [EBP-08]
}
If the IF command is tested by True or False, the compiler has two options for the generation of the machine code. One choice is to make a code like the aforementioned IF instruction, such as if (myVariable), can also be considered IF (MyVariable! = 0). Another situation is that the value in the word sentence is in the buffer. In this case, the compiler can use fewer instructions to determine if its value is TRUE or FALSE. The so-called less command is a "or register," "instruction, like this:
0000102e: Call 00001000
00001033: OR EAX, EAX
00001035: JE 0000103E0000103B: increst PTR [EBP-04]
0000103e: ...
Where the first instruction calls a function, the return value is placed in EAX. Then the compiler does not use instructions such as CMP EAX, 0, and use the OR instruction. OR instructions do Logical OR operations for each bit in Eax. If no one is set up (EAX == 0), ZERO FLAG will be set. The third case is a bit test. There are many Word and DWORDs in Windows programming, consisting of one bitmap value, such as the WS_XXX required for CreateWindow. The program often uses the AND operation to check if a flag set up.
Take a look at the C program code below:
DWORD WINFLAGS = Getwinflags ();
IF (Winflags & WF_CPU386)
IS386 = TRUE;
The assembled language code is like this:
0000102e: Test Byte PTR [EBP-08], 04 ;; WF_CPU386 == 0004H
00001032: JE
0000103F
00001038: MOV DWORD PTR [EBP
-0c
], 00000001
0000103F
: Sub Eax, EAX
The first instruction uses the CPU TEST instruction to see if a bit is set. The TEST directive performs the Logical and operations for both operands, but does not change the value of any operand. If the result is not set up, Zero Flag will be set up, otherwise it will be cleared to 0. When Zero Flag is set up, JE instructions will not jump, so [EBP
-0c
The DWORD value in the] will become 1. If you carefully observe the Test instructions in the previous piece, you will find some strange things. In the C program, Winflags is a DWORD, but the assembly language code is only in the bottom of its most bottom BYTE. This is because the compiler is optimized to use the least amount of instructions as much as possible. If there is no optimization, the first line described above should be:
Test DWORD PTR [EBP-08] 00000004
I have proposed this is not to emphasize the optimization, but tell you to observe the Test Directive: The address and bit shielded by Test may not look like you expect. In the former example, if we change the WF_80x87, its value is 00000400h, the TEST directive should become "TEST [EBP-08], 00000400h", is it? wrong! It will become "TEST [EBP-07], 04". It seems that the memory address is a byte higher than Winflags DWORD (at EBP-08). In order to compensate this, the compiler will remove the flag-tested flag to right. This is a bit despicable! If the bit being tested is placed in a variable, the compiler will call its address and draw a gourd. Now we have seen all three basic IF judgments. Let us come back again. A slightly complementary IF sentence is the IF-ELSE sentence. Consider the following code:
IF (i == 4)
{
i ;
J ;
}
Else
J-;
The compiler produces such a code for it:
0000101E: CMP DWORD PTR [EBP-04], 04
00001022: JNE 00001033
00001028: Inc Byte Ptr [EBP-04]
0000102b: increst PTR [EBP-08] 0000102E: JMP 00001036
00001033: Dec byte PTR [EBP-08]
00001036: ...
The first two instructions appear to be the same as a separate IF narrative sentence. It is a key to a JMP instruction from a far away. JMP Skip after the response action of the "judgment sentence is true", skip the ELSE clause. The address jumped by JMP is an important clue to "ELSE clauses". When you try to identify an IF-ELSE sentence type, pay attention to two things: When you start JE / JNE instructions, is it next location, is it followed by a JMP instruction? Does the JMP instruction jump to a higher address (that is, the backbeat is not forwarded in front)?
Another complicated IF judgment sentence pattern is multiple conditions. E.g:
IF ((i == 4) && (j == 2) && (k == 6))
{
i ;
J ;
}
The compiler produces the following code:
0000101E: CMP DWORD PTR [EBP-08], 04
JNE 00001042 ;; JUMP PAST CODE INSIDE {} 's.
00001028: CMP DWORD PTR [EBP
-0c
], 02
0000102C
: JNE 00001042 ;; JUMP PAST CODE INSIDE {} 's.
00001032: CMP DWORD PTR [EBP-04], 06
JNE 00001042 ;; JUMP PAST CODE INSIDE {} 's.
0000103C
: increst PTR [EBP-08]
0000103F
: Inc Byte Ptr [EBP
-0c
]
00001042: ...
These yards are very straightforward, and there are three tests to occur. Any one of these failed, the program code will jump out of the {} area. If you see a combination of "testing and jump", you may face an IF sentence pattern with multiple conditions. In the IF sentence pattern of multiple conditions, it is different from the organizer of the subsequent clause and the Scerase subsequent sentence. You will also see a series of continuous "testing and jump" actions. All tests, in addition to the last one, jump to {} - if its test results are True. If the test result is false, it will fall to the next instruction within {}. If the last test fails, it is also a jump {}.
This section covers basic sentence patterns. I didn't discuss the For load or the While loop. You may have encountered more complicated things, but almost every sentence pattern you encounter can be broken down into the combination of these these kinds of these
Deformation.
l Identify Switch instructions
Identify a Switch sentence type very simple - although it also has three common modifications. The simplest is my so-called idiot code. It waste a lot of space, it is very easy to infer, assemble language code looks like this:
MOV EAX, [EBP
0C
]
CMP Eax, 00000045
Je Someaddress
CMP Eax, 00000169
Je SomeAddress2
CMP Eax, 00000265
Je Someaddress3
The first instructions put the Switch's parameter load buffer - this example is EAX, but it can also be other buffers such as EDI. The 16-digit code seems to always use AX. After loading the value of the value to be tested, the program code enters a series of CMP / JE combinations. For each case in the Switch sentence, there is a corresponding CMP / JE combination. So we can easily find a handle routine for a Switch test value. If the program uses Switch to dispatch the message in the window in the window, you only need to find the WM_XXX you interested in the assembly language code. That is a simple job - just searching for the CMP instruction and compares its test value. Contains the address of the message processing routine in JE instructions. If you want to disassemble the entire sentence type, see how it handles every message, then the message name is a tag for the process routine is very helpful. The second deformation of the Switch sentence type is very close to the first deformation, and the difference is that the test instructions use less bytes,
And ask your value to keep the value of the intermediate process. Take a look at this code:
MOV EAX, [EBP
0C
]
SUB EAX, 2
Je Someaddress
Dec EAX
Je SomeAddress2
Dec EAX
Je Someaddress3
Sub eax, 5
Je Someaddress4
In a glimpion, this code looks confusing. It does not compare any value like the first type, the only real action is that EAX's value is constantly going down. In order to make this code look reasonably, you must know that the calculation results of the DEC and SUB instructions are 0, Zero Flag will set up. Each DEC and SUB instructions will take some input values. When this value is 0, it is here, the JE instruction assists it to the appropriate process routine. The input value is low, and the time to send is faster; the input value is high, and the time to dispatch is slower.
To see which value in the JE directive is tested, you must make a sum of all the previously lost values. In the face of this Switch sentence type, I found that the tag of "current values" next to each JE directive is very helpful. Below is my comment on the previous code:
MOV EAX, [EBP
0C
]; Load eax with the switch () argument.
SUB EAX, 2
JE Someaddress; 2 (Jumps Only if Eax Was Initially 2.)
Dec EAX
Je Someaddress2; 3 (Jumps Only if Eax Was Initially 3.)
Dec EAX
JE Someaddress3; 4 (Jumps Only if Eax Was Initially 4.)
Sub eax, 5
JE Someaddress4; 9 (Jumps Only if Eax Was Initially 9.)
The third deformation of the Switch sentence is called a jump table. If the input value is very close, the compiler may establish an address array, each array element corresponding to a Case value. The advantage of this method is that the execution speed is very fast, because it does not need to be inspected for each possible input value. Please see the following code:
Switch (i)
{
Case 0x0: i = 2; Break;
Case 0x1: J = 2; Break;
Case 0x2: k = 3; Break;
// Cases 3 THROUGH 8 NOT Shown.
Case 0x9: J = J K I; Break;
}
The results after compilation are as follows:
00001008: MOV Eax, DWORD PTR [EBP
-0c
]
0000100B: CMP Eax, 090000100E: ja 00001068
00001010: JMP DWORD PTR [EAX * 4
0040108F
]
The first instruction puts the input value of the switch in EAX. The following two instructions determine if the input value is within the legal scope. If not, the JA instruction will jump away from the Switch sentence. The last code uses EAX as an array index to find the address of the processing routine, and then jump to the point. In the front program code, the compiler places the address array of the processing routine in the data segment of the executable. However, if the array follows the JMP instruction, you don't surprise. This is particularly common in the 16-bit program. At this time, JMP instructions use CS as part of the memory address. In this case, you may see some temporary instructions that are not used, because the anti-group translator does not know those bytes to the program or data. A good anti-group translator should recognize this situation, or at least let you tell you that "some of the Code section is actually information."
An anti-group translation example
I have covered the basic idea of the anti-group translator, let us see an actual example, demonstrate how to use these concepts. I will use Windows NT's clock.exe as an example, it can switch the program status as a (or no) window title. I have two reasons for this program. First, I have tested this program from the perspective of Spy, we can further test, then compare the results obtained by two methods. Second, Microsoft provides clock.exe original code to Win32 programmer, so you can judge the accuracy of the reverse group translation.
For this example, I use my own anti-group translator. Microsoft's Dumpbin is of course, but my anti-group translator will automatically do something (especially the API call is matched with its symbol name), but you must be done manually when using Dumpbin as a tool. Below is the initial output result of the anti-group translator:
12F
3B00: PUSH ESI
12F
3B01: Push EDI
12F
3B02: MOV ESI, DWORD PTR [ESP
0C
]
12F
3B06: PUSH F0
12F
3B08: Push ESI
12F
3B09: Call getWindowlonga
12F
3B0E: MOV EDI, EAX
12F
3b10: CMP DWORD PTR [
012F
612C
], 00
12F
3b17: JE
012F
3b30
12F
3b19: And EDI, FFB4FFF
12F
3b
Allf
: Push 00
12F
3B21: Push F4
12F
3B23: Push ESI
12F
3b24: Call setWindowlonga
12F
3B29: MOV [
012F
6000], EAX
12F
3B2E: JMP
012F
3B44
12F
3b30: OR EDI, 00CF0000
12F
3B36: MOV EAX, [
012F
6000]
12F
3B3B: Push EAX
12F
3b
3C
: Push f4
12F
3B3E: Push ESI
12F
3B
3F
: Call SetWindowlonga
12F
3B44: Push EDI
12F
3B45: PUSH F0
12F
3b47: Push ESI
12F
3B48: Call setWindowlonga
12F
3B4D: Push 27
12F
3b
4f
: Push 00
12F
3b51: push 00
12F
3B53: Push 00
12F
3B55: Push 00
12F
3B57: Push 00
12F
3B59: Push ESI
12F
3b
5A
: Call SetWindowPOS
12F
3b
5F
: Push 05
12F
3B61: Push ESI
12F
3B62: Call showwindow
12F
3B67: POP EDI
12F
3B68: POP ESI
12F
3B69: RET 0004
The first two lines and the last three lines are the ProLogue code and the Epilogue code. Only two things have a bit interest: "RET 0004" tells us that this correspondence requires a parameter (all parameters in Win32 are 4 bytes). Second, the program code does not set an EBP stack framework, so we must track things on the stack to determine where the parameters are. Fortunately, this code is only one instruction to use the parameters in the stack. That is below the ProLogue code:
MOV ESI, DWORD PTR [ESP
0C
]
This instruction copies the parameters to ESI, and ESI will be used in many other places. It seems that ESI seems to be some kind of buffer variable. Hey ... what may ESI? Scan the segment code, we found that ESI is used to do the parameters of getWindowlong, SetWindowlong, SetWindowPos, showwindow. Will ESI a hWnd? It seems indeed. Let us rewrite the above program code with what we find, and eliminate ProLogue and Epilogue code:
12F
3B02: MOV HWND (ESI), DWORD PTR [ESP
0C
]
12F
3B06: PUSH F0
12F
3B08: Push Hwnd (ESI)
12F
3B09: Call getWindowlonga
12F
3B0E: MOV EDI, EAX
12F
3b10: CMP DWORD PTR [
012F
612C
], 00
12F
3b17: JE
012F
3b30
12F
3b19: And EDI, FFB4FFF
12F
3b
Allf
: Push 00
12F
3B21: Push F4
12F
3B23: Push Hwnd (ESI)
12F
3b24: Call setWindowlonga
12F
3B29: MOV [
012F
6000], EAX
12F
3B2E: JMP
012F
3B44
12F
3b30: OR EDI, 00CF0000
12F
3B36: MOV EAX, [
012F
6000]
12F
3B3B: Push EAX
12F
3B
3C
: Push f4
12F
3B3E: Push Hwnd (ESI)
12F
3B
3F
: Call SetWindowlonga
12F
3B44: Push EDI
12F
3B45: PUSH F0
12F
3B47: Push Hwnd (ESI)
12F
3B48: Call setWindowlonga
12F
3B4D: Push 27
12F
3b
4f
: Push 00
12F
3b51: push 00
12F
3B53: Push 00
12F
3B55: Push 00
12F
3B57: Push 00
12F
3B59: Push Hwnd (ESI)
12F
3b
5A
: Call SetWindowPOS
12F
3B
5F
: Push 05
12F
3B61: Push Hwnd (ESI)
12F
3B62: Call showwindow
Now, we have to convert several functions (GetWindowlong, SetWindowlong, SetWindowPos, and ShowWindow) to C. The parameters of these functions are not the hWnd we find, which is a value that can be accessed from Windows.h. Let's rewrite the program code as follows: 12F
3B02: MOV HWND (ESI), DWORD PTR [ESP
0C
]
GetWindowlong (hwnd, gwl_style); // GWL_Style == -16 ==
0F
0H
12F
3B0E: MOV EDI, EAX
12F
3b10: CMP DWORD PTR [
012F
612C
], 00
12F
3b17: JE
012F
3b30
12F
3b19: And EDI, FFB4FFF
Setwindowlong (hwnd, gwl_id, 0); // GWL_ID == -12 ==
0F
4h
12F
3B29: MOV [
012F
6000], EAX
12F
3B2E: JMP
012F
3B44
12F
3b30: OR EDI, 00CF0000
12F
3B36: MOV EAX, [
012F
6000]
12F
3B3B: Push EAX
12F
3B
3C
: Push f4
12F
3B3E: Push Hwnd (ESI)
12F
3B
3F
: Call SetWindowlonga
12F
3B44: Push EDI
12F
3B45: PUSH F0
12F
3B47: Push Hwnd (ESI)
12F
3B48: Call setWindowlonga
SetWindowPos (HWND, 0, 0, 0, 0, // 0x27 == THE FLAGS on the next line
SWP_NOSIZE | SWP_NOMOVE | SWP_NOZORDER | SWP_FRAMECHANGED);
ShowWindow (hwnd, sw_show);
Although we can rewrite some written words as C, there is not enough information to understand what the last two setWindowlong parameters is. For one of them, we must know what is included in the EDI. For another, we must know [
012F
6000] Which all domain variable is at the address. and many more! We have already seen that getWindowlong removes the flag value of the window style, copy to the EDI. EDI may be another buffer variable to store the window style flag. For [
012F
6000] Address, please note that the program code stores the return value of setWindowlong (GWL_ID). I have said it earlier, and the window recognition code field (GWL_ID) is used to store HMenu. String these facts, you can guess, [
012F
6000] is a national variable, with a Menu Handle (HMENU). Let us rewrite the entire program code based on these two new discovery:
WinStyle = getWindowlong (hwnd, gwl_style);
12F
3b10: CMP DWORD PTR [
012F
612C
], 00
12F
3b17: JE
012F
3b30
WinStyle & = ~ (WS_DLGFRAME | WS_SYSMENU | WS_MINIMIZEBOX |
WS_MAXIMIZEBOX);
HMENU = SETWINDOWLONG (HWND, GWL_ID, 0); 12F
3B2E: JMP
012F
3B44
12F
3b30:
WinStyle | = (WS_Border | WS_DLGFRAME | WS_SYSMENU |
WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX;
Setwindowlong (HWND, GWL_ID, HMENU);
12F
3b44:
Setwindowlong (hwnd, gwl_style, winstyle);
SetwindowPos (HWND, 0, 0, 0, 0, 0,
SWP_NOSIZE | SWP_NOMOVE | SWP_NOZORDER | SWP_FRAMECHANGED);
ShowWindow (hwnd, sw_show);
There is only only left [
012F
3b10] The conditional sentence is. CMP instruction comparison [
012F
612C
The whole domain variable is 0, and then immediately jump with a JMP instruction. It seems that this seems to be a standard IF-ELSE action. [
012F
612C
] It seems that it seems to be a boolean. Temporarily give it a name, it is called "MyBool". Insert some {} and regenerate, the program code nowcomes this:
WinStyle = getWindowlong (hwnd, gwl_style);
IF (MyBool! = 0)
{
// Turn Off The Style Bits Need for the title, Boxes, And Menu.
WinStyle & = ~ (WS_DLGFRAME | WS_SYSMENU | WS_MINIMIZEBOX |
WS_MAXIMIZEBOX);
// set The window's hmenu field to 0.
Hmenu = setwindowlong (hwnd, gwl_id, 0);
}
Else
{
// Turn on the style bits needed for a titlebar, boxes, and menu.
WinStyle | = (WS_Border | WS_DLGFRAME | WS_SYSMENU |
WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX;
// set the window's hmenu field back to whatver it is before.
Setwindowlong (HWND, GWL_ID, HMENU);
}
// Blast The Style Bits Into the window.
Setwindowlong (hwnd, gwl_style, winstyle);
// force windows to recalculate and repaint what the window shouth look.
SetwindowPos (HWND, 0, 0, 0, 0, 0,
SWP_NOSIZE | SWP_NOMOVE | SWP_NOZORDER | SWP_FRAMECHANGED);
ShowWindow (hwnd, sw_show);
It is really amazing. We convert a assembly language code into easy-to-read C language. If you compare this code with SPY observations, you will find everything. However, the data obtained by the anti-group translation is much more rich than you get from the Spy tool. For example, SPY software will definitely not tell you that there are two national variables involved (a HMENU and a Boolean value).