Implementation of API hook

Source: Internet
Author: User

I. Preface
For most Windows developers, how to intercept API function calls in Win32 systems has been a very challenging issue, this will be a comprehensive test of your computer knowledge, especially the knowledge that is not commonly used for software development using rad today, this includes operating system principles, assembly languages, and even machine commands (it sounds a little scary, but this is a fact ).

Windows operating systems, such as win 9x and Win NT/2 K, A relatively robust mechanism is provided to ensure that the memory address space of each process is independent from each other. That is to say, a valid memory address in one process is meaningless for another process, this memory protection 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. General framework of the API hook System
Generally, we call the process of Intercepting API calls to install an API hook ). An API hook is basically composed of two modules: one is the hook server module, which is generally in the form of EXE; the other is the hook driver module, it is generally in the DLL format.

The hook server is mainly responsible for injecting the hook drive into the target process so that the hook drive runs in the address space of the target process. This is the key first step, the hook driver is responsible for the actual API Interception and processing, so that we can do what we want before or after the API function calls that we care about. An example of a common API hook is the essential function of some real-time translation software (such as Kingsoft): screen-based word capturing. It mainly blocks the GDI functions in some Win32 APIs, obtains the strings in their input parameters, and then displays them in its own window.

For the above two parts about API hooks, we need to consider the following two points: What DLL injection technology is used and what API Interception Mechanism is used.

Iii. Selection of Injection Technology
In the Win32 system, the addresses of various processes are independent from each other, so we cannot effectively modify the code of another process in one process, but this is also required if you want to complete the API hook. Therefore, we must adopt a unique method to make API hooks (precisely the hook driver) part of the target process, to control the data and code of the target process.

There are several injection methods available:

1. Use the Registry
If the process we are going to intercept is connected to user32.dll, that is, the API in user32.dll is used (generally, the graphic interface application meets this condition ), then you can simply add the name of your hook driver dll as the value under the registry key: HKEY_LOCAL_MACHINE/software/Microsoft/WindowsNT/CurrentVersion/Windows/appinit_dlls values can be in the form of 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 loaded when the qualified application starts. This is a built-in mechanism of the operating system, which is less risky than other methods, but it also has some obvious disadvantages: This method is only applicable to NT/2 k operating systems, obviously, you can understand the key name. If you need to activate or stop hook injection, you only need to restart Windows. This seems too inconvenient. The last point is also very clear, you cannot use this method to inject DLL to applications that do not use user32.dll, such as console applications. In addition, whatever you want, hook dll will be injected into every GUI application, which will lead to a reduction in the overall system performance!

2. Create a Windows hook for the system.
To inject DLL into a process, a very common and simple method is to build on the basis of a standard Windows Hook. Windows hooks are generally implemented in DLL, which is the basic requirement of a global Windows Hook, which is also in line with our needs. After successfully calling the setwindowshookex function, a message hook of some type is installed in the system. This Hook can be for a process or all processes in the system. Once a process generates a message of this type, the operating system automatically maps the DLL of the hook to the address space of the process, so that the message callback function (specified in the setwindowshookex parameter) can properly process the message. Here, we are not interested in processing the message, of course, therefore, you only need to pass the message hook back in the message callback function, but the DLL we need has been successfully injected into the address space of the target process to complete subsequent work.

We know that different processes cannot directly share data because they are active in different address spaces. However, Windows Hook dll contains some data, such as Windows Hook handle hhook, Which is returned by the setwindowshookex function and will be used as a parameter in callnexthookex and unhookpoliceshookex functions, obviously, the process using the setwindowshookex function and the process using the callnexthookex function are generally not the same process. Therefore, we must be able to make the handle effective and meaningful in all address spaces, that is, the value must be shared among the processes that these hook DLL hooks. To achieve this goal, we should store it in a shared data area.

In VC ++, we can use the pre-compiled command # pragma data_seg to create a new segment in the DLL file, and set the attribute of this segment to "shared" in the def file ", in this way, a shared data segment is created. People who use Delphi are not so lucky: there is no such simple method (maybe yes, but I didn't find it ). However, we can still use the memory image technology to apply for a memory area that can be shared by various processes, mainly using the createfilemapping and mapviewoffile functions, which is a common method, suitable for all development languages, as long as it can directly or indirectly use Windows APIs.

In Borland's BCB, there is a command # pragma codeseg is a bit similar to the # pragma data_seg command in VC ++. It should also play the same role, but I tried it and it didn't work, in addition, BCB's online help does not mention much about this. I don't know how to use it correctly (maybe another command, haha ).

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 method has 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. Although this method is quite concise and clear, it also has some obvious disadvantages: first, it is worth noting that Windows hooks will reduce the performance of the entire system, because it increases the system's message processing time. Second, only when the target process is ready to accept a message, the DLL where the hook is located will be mapped to the address space of the process by the system, so that the hook can really start to play a role, therefore, if we want to monitor the API calls of some processes throughout the lifecycle, using this method will obviously omit some API calls.

 

3. Use the createremotethread Function
In my opinion, this is a great method, but unfortunately, the createremotethread function can only be supported in Win NT/2 k systems, although in Windows 9x, this API can be safely called without errors, but it does not return anything except a null value. The injection process is also 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 (which may be running) load our hook DLL (that is, the hook driver) under our control? There is an API function createremotethread, which can be used to establish and run a remote thread in a process. Does this seem to have nothing to do with injection? Look down!

To call this API, you must specify a thread function pointer as a parameter. The prototype of the thread function is function threadproc (lpparam: pointer): DWORD. Let's take a look at the function prototype of loadlibrary: function loadlibrary (lpfilename: pchar): hmodule. Found it! The two function prototypes are almost the same (in fact, it does not matter whether the return values are the same, because we cannot get the return values of the remote thread function ), similar to this, we can use loadlibrary directly as a thread function to load the hook DLL in the target process.

Similarly, when we need to uninstall the hook DLL, we can also use freelibrary as a thread function to uninstall the hook DLL in the target process. Everything seems very simple and convenient. Call the getprocaddress function to obtain the address of the loadlibrary function. Because loadlibrary is a function in Kernel32, And the ing address of this system DLL is the same for every process, so is the address of loadlibrary function. This will ensure that the address of the function can be passed to createremotethread as a valid parameter. Freelibrary is the same.

Addrofloadlibrary: = getprocaddress (getmodulehandle ('kernel32. dll '), 'loadlibrary ');

Hremotethread: = createremotethread (htargetprocess, nil, 0, addrofloadlibrary, hookdllname, 0, nil );

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 to have full access to this process, that is, to open the process with process_all_access. But for some system-level processes, it is obviously not possible to directly do this. Only one empty handle (with a value of 0) can be returned ). 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. Inject DLL through BHO
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 guarantees its reliability !) -Use Browser Helper Objects (BHO ). A bho is a COM Object implemented in a DLL. It mainly implements an iobjectwithsite interface. When ie runs, it automatically loads all COM objects that implement this interface.

Iv. Interception Mechanism
At the system level of the hook application, there are two types of API Interception mechanisms: kernel-level interception and user-level interception. Kernel-level hooks are mainly implemented through a kernel-mode driver. Obviously, they have the most powerful functions and can capture any details of system activities, but they are also difficult, it is not within the scope of this article (especially for those who use Delphi who have not been involved in this field, so they cannot discuss it, huh, huh ).

User-level hooks are usually used to intercept the entire API in common DLL, which is the focus of this time. You can use the following methods to intercept API function calls:

1. Proxy DLL (Trojan Horse
One easy way to think of 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 proxy dll should also output all functions like the original one. But if we think that the DLL may output hundreds of functions, we should understand that the efficiency of this method is not high, and it is estimated that it will be exhausting. In addition, we have to consider the dll version, which is very troublesome.

2. Rewrite and execute the code
Many interception methods 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.

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 first several bytes of the function with a JMP command (sometimes you have to switch to an int command), 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. It is obviously a test of our knowledge on the underlying aspects of the assembly language and operating system. This method is similar to the infection mechanism of many file viruses.

3. Intercept as a debugger
Another optional 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 major issue is that the generation of debugging exceptions will suspend all threads in the process. 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 of the PE File
This method is mainly benefited from the excellent structure of executable files (including EXE files and DLL files) used in today's Windows systems-the PE File Format (portable Executable File Format ), therefore, it is quite stable and easy to use. To understand how this method works, you must first understand the PE file format.

The structure of a PE file is roughly as follows: Generally, a PE file is a DOS program at the beginning. When your program runs in an environment that does not support windows, it will display warning statements such as "this program cannot be run in DOS mode". Then, this DOS file header starts the real PE file content, the first is a piece of data called "image_nt_header", which contains many messages about the entire PE file. At the end of the data section, it is a data table called data directory, it can be used to quickly locate the middle section addresses of PE files. After this section, it is a list of "image_section_header, each item describes the relevant information of the next segment in detail; then it is the most important segment data in the PE file, code execution, Data, resources, and other information are stored in these segments.

In all these segments, one is called ". the segment (input data segment) of idata is worth noting that this segment contains a list of data called the input Address Table (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 is 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].

In any case, the above square brackets always point to an item in the input address table, which is a DWORD, and this DWORD is the real address of the API function in memory. Therefore, if we want to intercept an API call, as long as we 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 was successful. It should be noted that the custom function call Convention should be the API call Convention, that is, stdcall, and the default call convention in Delphi is register, they differ greatly in parameter transfer methods.

In addition, the parameter form of the custom function is generally the same as that of the original API function, but this is not necessary, and in this case, some problems may occur in some cases, I will mention it 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 almost intact, the module handle hmodule is actually the address of the module image in the memory. The addresses of some data items in the PE file are all offset relative to this address. Therefore, it is called the relative virtual address (RVA, relative virtual address ).

So we can get the IAT address from the hmodule through a series of address offsets. However, I have a simple method here. It uses an existing API function imagedirectoryentrytodata, which helps us to take a few steps while locating IAT, saving us from mistaken offset addresses and taking a detour. However, it is not difficult to use RVA to locate the IAT address from the hmodule. 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, before this is 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 shows 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 an importdesc: = imagedirectoryentrytodata (pointer (htargetmodule), true, image_directory_entry_import, SZ) structure );

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

// The matched API address is found ......
// 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. If the target process uses this function again, an invalid operation will appear.

5. Writing replacement Functions
The previous two steps are complete, and an API hook is basically complete. 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

// Call the replaced function. You can also choose not to call it.
Result: = functype (oldaddress) (param1, param2 );

// Perform some operations after the call

End;

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 value of the input parameter, 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 there are only a few APIs to intercept, you only need to repeat them several times as described above. However, if a variety of APIs are to be processed, the number and type of their parameters and the type of the returned value are different, it is too inefficient to use this method.

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, the parameter types of the replacement function do not need to be the same as those of the original API function. Generally, we can design a function that does not call a parameter or return a value, you can use certain skills to adapt to a variety of API function calls, but this requires you to have a certain understanding of the assembly language.

First, let's take a look at the system stack before execution into a function (here the function call method is stdcall ), function call parameters are pushed to the stack in the order from right to left (the stack is developed from high-end to low-end), and a function return address is also pushed. Before entering the function, esp points 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 return value of 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;

Of course, 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, but you should set the call methods of these functions to the stdcall mode, so that they only use the stack to pass parameters, therefore, you only need to keep abreast of stack changes. If you directly store the machine commands corresponding to the above assembly code in a byte array and use the address of the array as the function address, the effect is the same.

Vi. Postscript
It is really not easy to create an API hook, especially for people who use Delphi, to solve a problem, I often look for information in Op, C ++, and assembler languages in the Middle East. Some unexpected things happen from time to time during program debugging, And I am confused. However, I am very happy to have made an API hook prototype. I have mastered a lot of computer system knowledge and benefited a lot from it. Before writing this article, I just wanted to translate an article in English that was down from the Internet (the URL is http://www.codeproject.com/, the article name is "API hook revealed ", the sample source code is written in VC ++. I have to admire the level of foreigners here. The article is very deep and detailed in every detail ).

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.