I,
Preface for most Windows developers, how to intercept API function calls in Win32 systems has been a very challenging topic, because it will
The computer knowledge you have mastered is a comprehensive test, especially the knowledge that is not commonly used for software development using rad today, this includes operating system principles, assembly languages, and even machine instruction code.
(It sounds a little scary, but this is a fact ).
Windows operating systems, such as Windows 9x and windows
NT/2 K, both provide a relatively robust mechanism to make the memory address space of each process independent of each other, that is to say, a valid memory address in a process is meaningless for another process.
This memory protection measure greatly increases the system stability. However, this makes it much more difficult to intercept system-level APIs.
Of course, what I refer to here is a more elegant interception method. By modifying the codes of executable files in the memory image, we can dynamically intercept API calls; instead of using brute-force methods, you can directly rewrite the machine code in the disk storage of executable files.
II,
API
The general framework of the hook system. We call the process of Intercepting API calls to install an API hook (
Hook ). An API hook consists of at least two modules: one is the hook server module, which is generally in the form of EXE; the other is the hook Driver (Hook
Driver) module, generally in the form of DLL.
The server is mainly responsible for injecting the drive into the target process so that the drive works in the address space of the target process. This is the key first step. The driver is responsible for the actual API Interception, so that we can do the work we need before and after the API function calls that we care about.
I
An example of a common API hook is the essential function of some real-time translation software (such as Kingsoft): screen-capturing, which is mainly for some GDI
The function intercepts and obtains the strings in their input parameters, which are displayed in their own window. For the above two parts, we need to consider the following two points:
What DLL injection technology is used and what API Interception Mechanism is used?
III,
The injection technology is used because the addresses of various processes in the Win32 system are independent of each other
We cannot modify the code of another process in one process. You must perform this operation to complete API hooks. Therefore, we must adopt some unique means to make
An API hook (or a hook drive accurately) can be a part of the target process, so that it is possible to modify the data and code of the target process.
There are usually the following injection methods:
1.
Using the registry, if the process we are going to intercept connects to user32.dll, that is, using the API in USER32 (generally, the graphic interface applications meet this condition), then you can
Add the name of your hook driver dll as a value to the following registry key:
HKEY_LOCAL_MACHINE/software/Microsoft/WindowsNT/CurrentVersion/Windows/appinit_dlls
The value format can be a single DLL file name or a group of DLL file names. Adjacent names are separated by commas or spaces. All DLL identified by this value will be started when the qualified application starts
Waiting for loading. This is a built-in mechanism of the operating system, which is less risky than other methods, but it has some obvious disadvantages:
This method is only applicable to NT/2 k operating systems. To activate or stop hook injection, you must restart Windows. This seems too inconvenient.
You cannot use this method to inject DLL to an application that does not use USER32, such as a console application.
Hook dll will be injected into every GUI application, whether necessary or not, which will lead to a reduction in the overall system performance
2.
Establish system scope
Windows hooks need to inject DLL into a process. A common and simple method is to build on a standard Windows Hook. Windows hooks are generally stored in
DLL implementation, this is a global Windows Hook basic requirements, which also meets our needs. After successfully calling the setwindowshookex function
A message hook is installed in the system, which can be for a process or all processes in the system. Once a message of this type is generated in a process, the operating system automatically hooks
The DLL of the child is mapped to the address space of the process, so that the message callback function (specified in the setwindowshookex parameter) can properly process the message.
Of course, we are not interested in processing messages. Therefore, in the message callback function, we only need to pass the message hook back, however, the DLL we need has been successfully injected into the target process
Address Space to complete subsequent work.
We know that the DLL used in different processes cannot directly share data because they are active in different address spaces. But in Windows Hook DLL, there are some
For example, the Windows Hook handle hhook is returned by the setwindowshookex function, and
Number and unhookjavaseshookex functions are used. It is clear that the process using the setwindowshookex function and the process using the callnexthookex Function
Generally, a process is not the same process. Therefore, we must make it meaningful to make the handle effective in all the address spaces. That is to say, the value must be
Shared. To achieve this goal, we should store it in a shared data area.
In VC ++, we can use precompiled commands # pragma
Data_seg creates a new segment in the DLL file, and sets the attribute of this segment to "shared" in the def file, so that a shared data segment is created. For
People in Delphi are not so lucky: there is no such simple method (maybe some, but I didn't find it ). However, we can still use the memory image technology to apply for a process
Memory areas that can be shared, mainly using the createfilemapping and mapviewoffile functions. This is a general method and is suitable for all development languages.
As long as it can use Windows APIs.
There is an instruction in Borland's BCB # pragma
# Pragma in codeseg and VC ++
The data_seg command is a bit similar and can play the same role, but I tried it and it didn't work, and BCB's online help didn't mention much about it, I don't know how to use it correctly.
Once the hook DLL is loaded into the address space of the target process, it cannot be stopped before we call the unhookwindowshookex function unless the target process is closed.
This
DLL injection methods have two advantages: this mechanism is supported in win 9x/me and Win NT/2 K, and is expected to be supported in future versions.
When hook DLL is not needed, we can actively call unhookwindowshookex to uninstall it, which is much easier than using the registry mechanism.
The method is concise and clear, but it also has some obvious disadvantages:
First of all, it is worth noting that Windows hooks will reduce the performance of the entire system, because they increase the system's time in message processing.
Secondly, only when the target process is preparing to accept a message will the DLL where the hook is located be mapped to the address space of the process. Only then can the hook really start to play a role. Therefore, if we want
This method is used to monitor API calls throughout the lifecycle.
3.
Use
The createremotethread function is a great method in my opinion. However, unfortunately, the createremotethread function can only be used in Windows
NT/2 k systems are supported.
In 9x, this API can also be safely called without errors, but it does nothing except to return a null value. The entire DLL injection process is very simple. We know that any process can use
Loadlibrary to dynamically load a DLL. But the question is, how can we let the target process load our hook DLL (that is, the hook driver) under our control? Here is
The createremotethread API function allows you to establish and run a remote thread in a process.
To call this API, you must specify a thread
The number pointer is used as a parameter. The prototype of this thread function is as follows: function threadproc (lpparam: pointer ):
DWORD; let's take a look at the function prototype of loadlibrary: function loadlibrary (lpfilename: pchar ):
Hmodule; we can see that the two function prototypes are essentially identical (in fact, whether the return values are the same does not matter, because we cannot get the return values of remote thread functions), but they are called differently.
The same way, we can use loadlibrary directly as a thread function to load the hook DLL in the target process.
Similarly, when we need
When you uninstall the hook DLL, freelibrary can also be used as a thread function to remove the hook DLL from the target process. Everything seems very simple and convenient. By calling
Getprocaddress function. We can get the address of the loadlibrary function. Because loadlibrary is a function in Kernel32, this system
The DLL ing address is the same for every process, so is the address of the loadlibrary function. This will ensure that we can pass the function address as a valid parameter
Createremotethread.
Addrofloadlibrary: = getprocaddress (getmodulehandle ('kernel32. dll '), 'loadlibrary ');
Hremotethread: = createremotethread (htargetprocess, nil, 0, addrofloadlibrary, hookdllname, 0, nil );
Yes
To use createremotethread, we need the handle of the target process as the parameter. When we use the OpenProcess function to obtain the process handle, we usually want
With full access, the process is opened with process_all_access. However, for some system-level processes, it is obviously not feasible to directly do this. Only one blank sentence can be returned.
Handle (value: 0 ). To this end, we must set ourselves to have debugging-level privileges so that we can have the maximum access permissions so that we can perform necessary operations on these system-level processes.
4.
Connect
Through BHO to inject DLL sometimes, the object we want to inject DLL is just Internet
Explorer. Fortunately, the Windows operating system provides us with a simple archiving method (which ensures its reliability)-using Browser Helper
Objects (BHO ). A bho is
The COM Object implemented in DLL mainly implements an iobjectwithsite interface. When ie runs, it automatically loads all COM objects that implement this interface.
IV,
Block
There are two types of API Interception mechanisms at the system level of hook applications: kernel-level interception and user-level interception. Kernel-level hooks are mainly implemented through a kernel-mode driver. Obviously
The function should be the most powerful. It can capture any details of system activities, but it is also difficult, not within the scope of our discussion (especially for those who use Delphi, not involved in this field, because
);
User-level hooks are usually used to intercept the entire API in common DLL. This is what we focus on now. You can use the following methods to intercept API function calls:
1.
A feasible method that is easy to think of as a proxy DLL (Trojan Horse) is to use a DLL with the same name to replace the DLL that originally outputs the API we are going to intercept. Of course, the agent dll must be consistent with the original
Output all functions. If we think that the DLL may output hundreds of functions, we should understand that the efficiency of this method is not high. In addition, we have to consider the dll version.
2. Many methods to intercept rewrite and execute code are based on executable code rewriting. One of them is to change the function address used in the call command. This method is difficult and error-prone. The basic idea is to retrieve the call command of the API you want to intercept in the memory, and then change the original address to the address of the function provided for you.
Another
The implementation method of another code rewriting method is more complicated. The main implementation step is to first find the address of the original API function, then replace the several bytes at the beginning of the function with a JMP command (sometimes
So that the call to this API function can be switched to our own function call. The implementation of this method involves a series of lower-layer operations, such as the pressure stack and the out stack.
Their knowledge of the assembly language and the underlying operating system is a test. This method is similar to the infection mechanism of many viruses.
3. Intercept another optional
The method is to place a debugging breakpoint in the target function so that the process runs here and enters the debugging status. However, these problems also follow. The most important one is that the generation of debugging exceptions will put all the lines in the process
All threads are suspended. It also requires an additional debugging module to handle all exceptions. The entire process will continue to run in the debugging status until it finishes running.
4. Rewrite the input address table.
The good structure of executable files (including EXE files and DLL files) used in today's Windows systems-PE File Format (portable executable
File Format), so it is quite stable and easy to use. To understand how this method works, you must first understand the PE file format.
A pe File
The structure is roughly as shown in: Generally, a PE file is a DOS program at the beginning. When your program runs in a Windows environment that does not support windows, it will display "this program
Cannot be run in DOS
Mode ", and then the DOS file header starts the real PE file content. The first is a piece of data called "image_nt_header", which contains many
The message of the entire PE file.
Directory data table, which can be used to quickly locate the middle section addresses of PE files. After this data segment, it is
The list of "image_section_header". Each item details the information of the next segment. Next, it is the most important segment data in the PE file.
Codes, data, and resources are stored in these segments respectively.
Among all these segments, there is a segment called ". idata" (the input data segment) which is worth noting. This segment contains some of the input address tables (IAT, import
Address
Table. Each DLL of an API that is implicitly loaded corresponds to an IAT, and the address of an API corresponds to an IAT item. When an application
After being loaded into the memory, the following Assembly commands are generated for each API function call:
Jmp dword ptr [XXXXXXXX]
If _ delcspec (import) is used in VC ++, the corresponding command becomes
Call dword ptr [XXXXXXXX].
No
The above square brackets always point to an item in the input address table, which is a DWORD, which is the real address of the API function in memory. Therefore, we want
To intercept an API call, if you simply change the DWORD to the address of our own function, all calls to this API will be transferred to our own function, the interception work will be publicized.
It was successful. Note that the call form of a custom function should be the API call method, that is, the stdcall method. In Delphi, the default function is Pascal's call.
The use method, that is, the register method, has a big difference in parameter transmission methods.
In addition, the parameter format of a custom function can be the same as that of the original API.
Functions are the same, but this is not necessary. In this case, some problems may occur, which I will mention later. Therefore, to intercept API calls, we must first obtain the corresponding IAT
Address. When the system loads a process module into the memory, the PE file is mapped to the address space of the process, and the module handle hmodule is actually the module image.
The addresses of some data items in the PE file are the offsets relative to the addresses. Therefore, the addresses are called relative virtual addresses (RVA, relative virtual
Address ).
So we can get the IAT address from the hmodule through a series of address offsets. But here I have a simple method, which enables
An existing API function is used.
Imagedirectoryentrytodata, which helps us to take a few steps while locating IAT, saving us from mistaken offset addresses and detours. However, purely using RVA from
The hmodule is not difficult to locate the IAT address, but it also helps us to understand the structure of PE files. The API function mentioned above is in dbghelp. dll
Output (this is from win 2 K, which was previously provided by imagehlp. dll). For details about this function, see msdn.
After finding the IAT, we just need to traverse it, find the API address we need, and then overwrite it with our own function address. The following provides the corresponding source code:
Procedure redirectapicall; var importdesc: pimage_import_descriptor; firstthunk: pimage_thunk_data32; SZ: DWORD;
Begin
//
Obtain the first address of the input description structure list. Each DLL corresponds to a structure like this.
Importdesc: = imagedirectoryentrytodata (pointer (htargetmodule), true,
Image_directory_entry_import, SZ );
While pointer (importdesc. Name) <> nil do
Begin // determine if it is the required DLL input description
If stricomp (pchar (dllname), pchar (htargetmodule + importdesc. Name) = 0 then begin
// Obtain the first IAT address.
Firstthunk: = pimage_thunk_data32 (htargetmodule + importdesc. firstthunk );
While firstthunk. func <> nil do
Begin
If firstthunk. func = oldaddressofapi then
Begin
// Found the matched API address ......
// Rewrite the API address
Break;
End;
INC (firstthunk );
End;
End;
INC (importdesc );
End;
End;
At last, we should point out that if we manually execute the hook DLL to exit the target process, we should change the function call address back to the original address before exiting, that is, the real address of the API, because once your DLL exits, the new address will point to a meaningless memory area, and then it will obviously lead to an invalid operation.
V,
The preparation of the replacement function is completed in the previous two key steps, and an API hook is basically completed. However, there are some related things that need to be studied, including how to make a replacement function. The following is a step to replace a function: first, without being general, we assume that there is an API function, and its prototype is as follows:
Function someapi (param1: pchar; param2: integer): DWORD;
Then create a function with the same parameters and return values:
Type functype = function (param1: pchar; param2: integer): DWORD;
Then we store the someapi function address in the oldaddress pointer. Then we can replace the code of the function by hand:
Function dummyfunc (param1: pchar; param2: integer): DWORD; begin ......
// Perform some operations before the call
Result: = functype (oldaddress) (param1, param2 );
// Call the original API function ......
// Perform some operations after the call
End;
Me
Save the address of this function to newaddress, and overwrite the address of the original API with this address. In this way, when the target process calls this API, it actually calls our own
Function, in which we can perform some operations, and then call the original API function, the result is like nothing has happened. Of course, we can also change the input parameters, or even block calls to this API function.
.
Although the above method is feasible, there is an obvious deficiency: The method for making the replace function is not universal and can only be used for a small number of functions. If only a few APIs are required
Intercept, you only need to repeat the above several times. However, if there are a variety of APIs to process, the number and type of their parameters and the type of the returned value are different, or this method is not enough.
Efficiency.
Indeed, the above is just the simplest and easiest way to think of. It is just a basic framework for replacing functions. As I mentioned earlier, replace the function with the original
The parameter types of API functions do not need to be the same. Generally, we can design a function that does not call parameters or return values. Through some techniques, we can adapt it to various API function calls, however
You must have a certain understanding of the assembly language.
I will introduce this in detail below.
First, let's take a look at the stack before the execution of a function (here the function call method is stdcall ).
As shown in the preceding example, function call parameters are pushed to the stack in the order from right to left (stacks are developed from high-end to low-end ), A function return address is also pushed in. Before entering the function, ESP
Pointing to the return address. Therefore, we only need to get the call parameters of this function from ESP + 4, and each time we get a parameter increases by 4. In addition, when returned from a function, the function is generally stored in eax.
.
After understanding the above knowledge, we can design the following generic replacement function, which utilizes the features of Delphi's embedded assembly language.
Procedure dummyfunc;
ASM add ESP, 4 mov eax, esp // obtain the first parameter
MoV eax, esp + 4 // obtain the second parameter ......
// Perform some processing. Make sure that ESP is restored after this.
Call oldaddress // call the original API function ......
// Do other things
End;
When
However, this replacement function is relatively simple. You can call some functions or processes written purely in the op language to complete some more complex operations (if it is completed by assembly, you have to be busy.
But the call methods of these functions should be set to the stdcall method in a unified manner, so that they only use the stack to pass parameters, therefore, you only need to keep abreast of stack changes. If you direct
The machine commands corresponding to the above assembly code are stored in a byte array, and the address of the array is used as the function address. The effect is the same.
The above code is implemented in win 2 k/XP & Delphi 6.0.
Vi. Postscript
Do
An API hook is really not easy, especially for people who use Delphi, in order to solve a problem, we often look for information in Op, C ++, and assembler languages in the Middle East.
Some unexpected things happen from time to time during debugging, And you are confused. However, I am very happy to have made a prototype of an API hook, and I am very familiar with computer systems.
I also learned a lot and benefited a lot. Before writing this article, I just wanted to translate an English document (www.codeproject.com)
The name of the article is "API hook revealed". The sample source code is written in VC ++. I have to admire the level of foreigners here. The article is written in depth, and every detail is detailed ).
But when I flip it over, I feel that I am actually at a limited level. Although I understand many places, I just don't know how to express my meaning in Chinese, so I had to use the translation.
In addition to some of my own experiences and experiences in actual operations, I have written the above article. Although it may be a bit nondescribable, it has also made me a lot of time and I am so exhausted, sorry !), XI
Don't laugh. Please give me more advice.