Developers apply API hooking practices to better understand how a system works, alter the behavior of an operating system or an application, detect malicious code, and build strong products.
The majority of guides and tutorials on Windows API hooking are based on using compiled languages like C or C++, which makes this practice inaccessible for developers who don't know these languages. However, compiled languages aren’t the only option. Interpreted languages like Python can also be used for API hooking and have some advantages over compiled languages.
In this article, we explore when and why it’s best to choose Python for hooking Windows APIs and explain how to use this language for setting hooks based on easy-to-follow examples. We also show how two Python libraries — Deviare and WinAppDbg — can be used for API hooking.
API hooking covers a range of techniques for altering or augmenting the behavior of an operating system (OS), application, or other software component by intercepting API function calls, messages, or events passed between software components. Code that handles such interception is called a hook. As we mentioned in the article 3 Effective DLL Injection Techniques for Setting API Hooks, API hooking for Windows can be performed using various methods such as injecting dynamic-link libraries (DLLs), injecting code, and using the Win32 Debug API toolset.
However, most API hooking examples you will encounter on the internet use C or C++. If you are lucky, you’ll find examples in C# or Visual Basic.
Since API hooking is a low-level technique, compiled languages like C and C++ may seem to be the only choice. Before we explain why this isn’t true and when you can use interpreted languages for API hooking, let’s briefly refresh your memory as to the key differences between those two groups of programming languages.
A compiled language is a programming language that is implemented using a compiler. A compiler is a program that translates statements written in a particular programming language into another language, usually machine code. C, C++, and Go are common examples of compiled languages.
Let’s compare how working with С/С++ differs from programming with Python. Say you just want to write a small utility or a patch for some application. When using С or С++, you’ll have to spend extra time preparing the environment, which requires you to:
- Install an IDE
- Download and build a framework for API hooking
- Install and build third-party libraries, as the standard library may not provide all the utilities you need
Once you’ve installed everything, it’s still unlikely that you’ll be able to build a project on the first try. The reason is that it’s quite common for C/C++ developers to find errors like missing DLL files or inappropriate library versions.
Python, on the contrary, has convenient tools like pip and virtual environments for handling dependencies. Also, it allows for fast development, has various useful third-party libraries, and offers convenient environment configuration. So if you don’t know C or C++, you can definitely use Python as an alternative for Windows API hooking.
Before we dig into how to hook API functions with Python, let’s explore how this language works.
How Python works
Python is one of the most popular interactive programming languages. Its biggest advantage is its simple syntax that allows you to write programs with less code than other programming languages require. And since its syntax is highly readable and similar to English, all team members can easily understand it.
Python works on the following platforms:
You can execute Python code right after it's written. However, since Python is an interpreted language, it requires an executor — an interpreter called the Python virtual machine.
|Note: The Python virtual machine is an essential component to run Python code.|
This is how a program written in Python works:
- Python source code is sent to the compiler, which produces an OS-neutral intermediate file format called Python bytecode.
- The Python virtual machine interprets the Python bytecode file to produce machine-specific commands for the processor.
Now, let’s see how we can inject Python code into an application’s process.
As we wrote in our previous article, to hook API functions, the hooking code should be injected inside the memory address space of the target process. If our hooking code is written in Python, the target process should be able to execute it.
But the target application may not know about Python, its virtual machine, or any interpreted language at all. To make Python code run inside the target application, you may need to inject a Python virtual machine into it.
To make the Python virtual machine run in the target process, you only need to perform a few steps:
- Make the application load a python.dll file so it can run the Python code
- Make the application call the Py_Initialize function to initialize the Python interpreter
|Note: All of the following examples (before the “Python libraries for API hooking” section) are performed on Windows 7 x32 with Python 3.8.5.|
Let’s see how these two steps can be performed in Python using the ctypes and mayhem library modules:
Now, we will load the phyton.dll library in the target process and make that process call the Py_InitializeEx function:
The target process can now execute any Python code. To make it run our code, we’ll use the PyRun_SimpleString function from the python.dll file. Let’s call this function in a separate thread with our Python code:
To make this code reusable, let’s put it in the Injector class to run arbitrary Python code in the target process:
Now we know the basics, so let’s explore a practical example of Python code injection.
An example of Python code injection
For this example, we’ll inject some Python code in the notepad.exe process, making it show a message box with some text. Here’s the code responsible for python.dll injection:
Python.dll is injected in the remote process. Let’s check the presence of python.dll in the list of the notepad.exe processes using the Process Explorer utility from Sysinternals tools:
We can see that python.dll is loaded to the notepad.exe process. Now, let’s make Notepad show a message box with the text “Hello from Python”:
Once we inject the Python code into notepad.exe, Notepad shows us the following message box:
The Process Explorer shows that there’s a thread in the notepad.exe process running the Python code:
However, the example described above isn’t the only possible option for Python code injection. Let’s look at the ways you can perform API hooking using the Import Address Table structure.
The Windows Portable Executable format contains a structure called the Import Address Table (IAT). This structure contains library names used by a certain application. For each library, the IAT also contains a list of features imported from this library. Thus, when launching an application, a loader can know what libraries to load and how to connect function addresses from these libraries.
The IAT contains pointers to information that is critical for an executable to do its job:
- A list of DLLs an executable depends on for providing the expected functionality
- A list of function names and their addresses from those DLLs that may be called by the binary at some point
It’s possible to hook function pointers specified in the IAT by overwriting the target function’s address with another function address. To access the IAT of the current module, we’ll use the ImageDirectoryEntryToData function:
The IMAGE_IMPORT_DESCRIPTOR structure contains information on all imported modules required for work (used by the current module). Every imported module has a list of functions imported from it by the current process. Each imported function is defined by another structure — IMAGE_THUNK_DATA — that is different for x32 and x64 systems. Let’s explore both options:
To hook a function, we have to take the following steps:
- Find the import descriptor of the target module
- Find the import descriptor of the module that exports the target function
- Replace the address of the target function in the IAT with the address of the function we want to call instead (set a hook)
For example, let’s see how we can hook the CreateFileW function:
As you can see, we switch addresses in the replace_function_address function. It’s quite straightforward — we just need to look through all the imported functions from the kernel32.dll file, and when we find the address of the needed function, we replace it with the WriteProcessMemory function:
This is how you can hook API functions with the help of the IAT structure. However, hooking API functions in Python also requires using various libraries. In the next section, we talk about two Python libraries that can help you in setting API hooks: Deviare and WinAppDbg.
In the previous section, we discussed that dynamic languages may not be the best choice for such low-level development tasks as injecting code and modifying import tables because all internal structures should be defined almost from scratch. However, Python has lots of useful third-party libraries that can significantly simplify various development tasks.
In this section, we explore two Python libraries that can ease the process of WinAPI function hooking.
|Note: All of the following examples are performed on Windows 7 x32 with Python 2.7.16 because the WinAppDbg library only works with Python 2.|
Deviare is a professional open-source hooking library for instrumenting arbitrary Win32 functions, COM objects, and functions whose symbols are located in program databases (PDBs).
The main advantage of this library is that it’s implemented as a Component Object Model (COM) component, so it can be integrated with all programming languages that support COM, such as C/C++, VB, C#, Delphi, and Python.
All hooking is performed through a special Spy Manager class contained in the Deviare COM DLL file. We can use this class to set hooks and set up callbacks for different system events like creating and terminating processes or loading dynamic libraries:
To hook a function, we need to perform only three steps:
1. Create a hook instance, providing the name of the function to be hooked and some optional hook flags. Make sure all function names have the format "[dll]![function name]". Example: "kernel32.dll!CreateFileW".
2. Attach the hook to the target process:
3. Activate the hook in all attached processes:
An example of API hooking using the Deviare library
Let’s write a simple hook for Internet Explorer and try to redirect all requests destined for www.google.com to www.bing.com.
Internet Explorer uses the InternetConnectW function to open an HTTP session:
Let’s use the Deviare library to hook the InternetConnectW function and replace its second parameter with www.bing.com:
Now, let’s launch the script and try to access Google. You’ll see that even though the address field contains the address of the Google search engine, the browser redirects us to Bing:
We can also enable some kind of warning when access to Google is requested. Let’s change the script a little to show a message box saying that access to google.com is forbidden:
Now, when we try to access google.com, we’ll see the following message:
We can use the Deviare library in various scenarios that involve API hooking. It’s easy to use with various programming languages, since its interface is almost the same in C++, C#, and Python.
Let’s look at another library that can be used for Windows API hooking with Python — WinAppDbg.
To start using it, download the WinAppDbg library directly from its official website and run the .msi file to install it.
|Note: Currently, the WinAppDbg library is only available for Python 2.7.|
WinAppDbg exposes some types that can be used to monitor the entire operating system as well as specific processes: System, Process, Thread, Module, and Window. Each one acts as a snapshot of processes, threads, and DLL modules in the system.
All hooking is performed through the Debug class. You can receive notifications of debugging events by passing a custom event handler to the Debug object. When a certain event occurs during execution, a relevant method in the EventHandler class is automatically called.
Inside that method, we can write code that handles that event:
The hook_function method of the Debug object sets a code breakpoint at the beginning of the target function and allows you to set two callbacks: one when entering the function and another when returning from it.
Let’s see how the hook_function method works for the CreateFile function:
Let’s explain in detail what happens in the code above:
- Inside the load_dll function, we check if we’re loading the kernel32.dll file. If we are, we call the module.resolve() method for the CreateFileA and CreateFileW functions, which return the addresses for exported functions in the target process.
- Then, we create signatures for each hooked function. Our signature for both CreateFile functions is PVOID, DWORD, DWORD, PVOID, DWORD, DWORD, HANDLE.
|It’s important to use the PVOID signature for all pointers. Otherwise, according to the WinAppDbg documentation, the ctypes function library can crash a process.|
- Next, we hook each function using event.debug.hook_function. The most important parameters are preCB and postCB. They define callback functions that will be called before the actual WinAPI call and just after it, respectively. These functions have the following prototypes:
It took a lot of code to make this simple hook. But fortunately, the WinAppDbg library provides an easier way of setting hooks through the apiHooks class:
An example of API hooking using the WinAppDbg library
To see how this works in practice, let’s write an example of a simple hook using the WinAppDbg library.
This example is quite artificial, but it’s sufficient to show how to use the library. We’ll try to change Skype’s destination folder for downloading attached files. First, let’s see how Skype downloads and saves files using the Process Monitor from Sysinternals tools:
We can see that Skype uses the CreateFileW function with the CREATE_ALWAYS disposition to save downloaded files. So to change the destination folder, we just need to hook the CreateFileW function in the skype.exe process:
In the code above, we enumerated all processes to find all the running skype.exe instances. Now, let’s hook the CreateFileW function:
Now, we can try to run the application and see if the save operation is redirected to another folder:
As you can see from the results, files are now successfully redirected to be saved in the SomeNewFolder folder instead of the original destination folder.
It’s noteworthy that the WinAppDbg library wasn’t specifically designed for API hooking. It provides some other utilities to manipulate processes, threads, and windows and can even launch a command line debugger for an application. Therefore, this library will be more useful when hooking is not the main purpose of your application.
Knowing compiled languages like C and C++ isn’t necessary if you want to perform Windows API hooking. Dynamic languages like Python can be a great alternative and have their own advantages. Python shows good development speed, offers convenient environment configuration, and has lots of helpful third-party libraries.
In this article, we showed how to set hooks in different processes using Python and the Deviare and WinAppDbg libraries. With this knowledge, you can better understand how an operating system or a certain application works, manipulate various processes, and improve your products.
At Apriorit, we have professional teams of dedicated software developers who can help you create high-quality IT products. Contact us to start discussing your dream project.