Last Updated:

What is Fluke?

Among the general set of articles and libraries on the Internet, I have not yet come across a single convenient and visual tool that would allow you to intercept and control input and output parameters of any type of functions, both imported functions into the application and functions inside the executable process. The Fluke project fills in this omission. Its performance is divided into three parts, and each is strictly responsible for its own functionality.

To begin with, I want to shine the reader into an unusual term that has become entrenched in my projects over the years. In a nutshell, our task is to gain control over any application written under Windows. This means that we want to create some virtual environment around the application by enamelling the behavior of the operating system and internal functions within the executable process. Through such transformations, without changing the source code, we can force anything to do, any application under Windows.

The translation of the word Fluke from English is luck, or happiness.

Library

If you want to use the code developed within the framework of this project, then this can be easily done. Since all modules are made as independent libraries with a flexible interface. They are available for download via the version control system svndir:trunk/library/fluke, svndir:trunk/library/iofluke. Connecting to the project and managing the function call takes place in three stages, the first stage is the loading of executable code, this is described in Fluke. The second stage is the interception of I/O functions, this is described by IOFluke. The third step is to create an environment for convenient work with an application in a high-level language, such as Java or Visual Basic, this is described by FlukeScript.

Fluke

When the task of connecting to the executable process appears, you have to rewrite most of the code and reinvent the wheel. This project standardizes the process of connecting to any local application and makes it easier. Moreover, you can use this development in any project for any tasks, since the code is portable and separated into an independent module.

What is the most common goal when we talk about connecting to an application? Naturally, there can be many options, but among the main ones is the management and control of the application. That is, the task will most likely be reduced to intercepting the call to functions and transferring them either to the side of the main application or processing directly inside the application to which the connection is made. In this article, I'll describe the process, methods, and possible options for connecting to an application. The interception of functions is described in the following IOFluke article.

Types of connection

We need to choose a method to transfer control to the target process. Any control over the application is reduced to generating executable code and passing it to the workflow. To do this, let's define the form in which we want to pass this code. It can be a simple function written in C++ or Assembler. And carefully transferred from one memory area to another, there is no difficulty with the implementation of such an algorithm, but we are looking for a universal mechanism that will allow you to integrate into any process and will be convenient.

Toggle a function

Here is the original function written in assembler, the code for throwing between processes is in the body between the comments of the function body. After calling the GetBody() function, the output is a binary array with assembly code:

     xor eax,eax;
     nop;
     nop;
     nop;
     retn;

Here's an example of a function:

 std::vector<unsigned char> GetBody()
 {
   unsigned char* start;
   unsigned char* end;
   __asm
   {
     call process;
     // function body start
     xor eax,eax;
     nop;
     nop;
     nop;
     retn;
     // function body end
 process:
     mov [end],offset process;
     pop eax;
     mov [start],eax;
   };
   return std::vector<unsigned char>(start,end);
 }

Let the reader not be frightened by the prostate of the example, he describes in sufficient accuracy the mechanism by which any experiment can be implemented. The code of the form xor eax, eax can be replaced by the code for processing and obtaining the address of the system library kernel32.dll, as well as all addresses of library functions. After that, we get a dynamic, fully functional program, for which it does not matter at what memory addresses to be launched. This technique uses the SEH register.

Returning to our functions, having received a dynamic array with code in memory, we must think about options for passing this code to the side of another process.

Passing executable code

Transferring a binary array is possible by directly writing the contents of the array to the executable process. There are several ways to do this.

  • Using WINAPI WriteProcessMemory.
  • Through driver programming.

Since the scheme with drivers is very much tied to the version of the operating system, I do not consider it promising. The second method, through the use of WriteProcessMemory, requires us to be careful about the procedure of putting binary data into the executable process.

In other words, we need to answer the question in which memory area we want to put the data. This primarily depends on the purpose of our implementation. If our task is to specifically transform the code of the executable process, then we need to determine the address in the space of the executable process and make a record. Of course, beforehand, stopping the execution of the thread inside the target process. Another option, if we do not care about the memory area in which our code will be executed - only the fact of execution is important. And to do this, we will have to use the main entry or stop points that the operating system itself dictates to us.

Among such points may be:

  • Entry point to the program
  • Call point of any library function
  • Message Queuing Processing Point

I think the list goes on.

One limitation we may encounter is the size of the binary code we are trying to put into the target process. Unfortunately, there may be a situation in which the size of the code is larger than the allowed size for embedding. For example, the body of the message processing function contains only one operation of switching to the main code within the process. And after changing the process, we will not be able to guarantee that our binary code will begin to execute from its starting position. Or our code will replace the data necessary for the correct functioning of the application. In order to solve this problem, it is necessary to use embedding the library in the executable process. This task is successfully carried out by this Fluke project.

Connect a library

Implementing binary code in a library is a very popular method of writing a control module. This solves a lot of issues. Firstly, we are not obliged to use assembly, secondly, we do not spend effort on dynamic addressing, and thirdly, we use debugging information for our library. We also move away from limiting the amount of executable code. Let's look at the process of connecting any library to any executable process.

Synchronize with the host application

An important point is the order of connection, loading of several libraries, as well as the order of execution of functions. Our project allows you to connect to several executable processes at once. This means that we need to ensure appropriate synchronization between different libraries within the same application wizard. All synchronization and connection handling is hidden inside the library, the developer gets a user-friendly interface inside, which contains additional information about the ID of the connected process.

Let's take a closer look at the problem, and then we will understand why it is necessary to use synchronization objects in the application. Suppose we need to use the Application Wizard to create multiple child processes, working independently with each. To do this, we start the first process and give the command to connect the library to the newly created process. As you know, none of the available connection methods guarantee that the library will load. Therefore, we do not know at what point we can start the next process and connect the second library. This situation is certainly solved through the mechanisms available in Windows IPC, but in my opinion, the Fluke module is obliged to guarantee and signal erroneous situations. That is, if it is not possible for any reason to connect the library, the developer will know about it through an exceptional event and will be able to perform an adequate action for elimination, which will not lead to a violation of the logic inside the application wizard.

Writing a library

Creating a new library is an arbitrary process in which practically no restrictions are introduced. For example, we create a project in any environment, and connect a static library to it in the way available to us. Source, which is available at svndir:trunk/library/fluke. Next, in the library code, you must create a global object that inherits from the Fluke::CFlukeSlave class. And in the constructor of this class make a call to the Fluke::CFlukeSlave::Create(); Here's an example:

 class MyApp:public Fluke::CFlukeSlave
 {
 public:
   MyApp()
   {
     Create();
   }
 };
 MyApp g_app;

First, you need to connect the svnfile:trunk/library/fluke/flukeslave.h file to the project. Which is also available in the repository with the project. At this point, the library is provisioned.

Application Wizard

The next step is to develop the application wizard. Its task includes the basic logic for selecting and maintaining communication with the subordinate application. As well as the way to connect to it. All these actions are already programmed and require only the selection of the appropriate function in the library interface. This means that the developer of the application wizard determines how to embed itself in the executable process, and then calls the flukemaster function with the appropriate parameters. This procedure is sufficient for the normal functioning of the wizard application bundle and the target process.

A list of all the functions that the library supports for connecting to a process is described here: svnfile:trunk/library/fluke/flukemaster.h.

Getting the sources

To build the library, you need to get:

  • svndir:trunk/library/fluke
  • svndir:trunk/library/ErrorReport
  • svndir:trunk/library/debugger

Links

  • Operating SEH in a Win32 Environment

IOFluke Memory Space

After successfully completing the process connection step, we will be working in the same memory space as our process, so the further description will take place on behalf of the process whose input substitution we want to implement.

Overriding I/O

There are several ways to intercept the call of functions, for a general presentation I will try to analyze the most famous, but on this issue you can find a lot of information on the Internet. Therefore, my acquaintance will be superficial, and do not contain detailed examples.

Replacing a Library

One of the popular methods of interception, and the simplest is the use of the library of the same name. If our task is to find out what data passes between the process under study and the library function, then this is very easy to implement. We'll need to create the same library with the same name and feature set that the target process uses. After that, using the rule of searching libraries in the Windows operating system Dynamic-Link Library Search Order, we can safely put our new library in the directory with the executable file. Once launched, the process will use our fake library instead of the system library. Next, depending on the desired result, we put the call handler in the library itself, or we program the add-in to pass parameters to the control process.

Using Hooks

The Windows operating system uses a trap system to track some calls to system functions. This process is described in detail in the Win32 Hooks operating system documentation. For this method to work, you need to create a library with the exported function, then, following the documentation, call a function from the application wizard with parameters indicating the specific process and the action to be examined. The SetWindowsHookEx function takes as input to the handler procedure, and the thread ID as an optional parameter. A more detailed description is also available on the Internet in unlimited quantities.

Virtual Device Driver

This is already a non-standard and little-known way to monitor the operation of the system, it uses a virtual device driver at its core. Thanks to this, not only does it remain absolutely invisible, but also very effective. One of the projects I've been able to find online with this technology is Sysinternals' Regmon. If you refer to the documentation for the program, you will find there three different ways to implement such an algorithm, they are based on the use of Drivers Hooks.

Overriding an Import Partition

The main point of this operation is to override the address of the function that we want to intercept in a special table that is used by the main process to call library functions. This table is located in the executable file and is modified after loading, each field of which points to the address of the imported function. Therefore, the interception of the function is reduced to finding this address and replacing it with a new one, the address of our fake function. A detailed description of the structure of executable files, disk and memory can be found in the A Tour of the Win32 Portable Executable File Format documentation. The Internet is full of examples of how to work and replace functions through the import table.

Replacing Memory Contents

The name implies that we are going to modify the contents of the function in order to put an unconditional transition to our code, and call the appropriate handler. To do this, we need to find the address of the function in memory, memorize the old contents of the function and replace it with the code of the unconditional transition command. A function can be of two types, the first to be exported from the current process or imported from another module. Finding such a function then boils down to calling the GetProcAddress method. And replacing the content is a procedure for writing to this address - a new assembly command to unconditionally go to the address of our new function. In this case, as in the previous ones, I will not yet consider the implementation of such a mechanism in more detail, it will be disclosed when studying the IOFluke package.

Example of working with the library

After we've covered all the methods of taking control, let's look at how it's done using the IOFluke library. The implementation of methods is hidden inside the code, and does not burden the developer. But to fully understand its functions, you need to know about the methods of intercepting functions that are implemented inside the library:

  • Replace an address in the import table
  • Memory substitution within a process.

With these methods, we can implement almost any data processing logic. We will feel especially strongly the flexibility of this product when implementing the method of replacing the memory of a function, for any function within the executed process. We are talking about functions that are hidden from the developer and are not brought out anywhere in the project. To work with such functions, you must first determine their address and call type.

Determining the Type of Function Call

The first, most important, point when working with the library is the mechanism for determining the type of function. Unfortunately, this needs to be diagnosed manually. To do this, we need to have a good idea of how the function is called at the lowest level. This basic knowledge will allow us to correctly write the definitions of the functions that we want to intercept and ensure the correct operation of the library. The function type determines how parameters are passed within the function. This leads mainly to the rules: how to clear the values on the stack and the order in which the parameters are passed.

Possible types of function calls are:

  • this call
  • stdcall
  • cdecl
  • fastcall

Each function should be tied to one of the above type of call, the success of the implementation in someone else's process directly depends on the success of this operation. In order to determine the type of call, you can use different tools. The easiest way is to refer to the documentation, if the intercepted function is described in detail, then the call agreement is indicated among all the parameters. That is what needs to be specified. Another way to use the Depends.Exe utility, when looking at the names of the exported functions, we can see decorated names. By changing the name of the function, in some cases, you can understand the number of parameters and how to pass them inside the function, which will determine the type of function call. If the above methods are not applicable to determining the convention on calling a function, you must analyze the code by reviewing the project and by the structure of the code, the way parameters are placed, the method of returning from the function, you can uniquely determine the type of function you are looking for.

Import table replacement

We came to the most interesting thing - the use of the library. In this section, you will learn how to use part of the IOFluke library to intercept imported functions. The interception mechanism is quite simple:

  • creating a function
  • create a link between the old function name and the address of the new one
  • search and redirect the call.

To preserve the object approach when developing an application, the library requires the use of classes. This means that any group of functions that we intercept must belong to any class. I think this is not a limitation, but a criterion that requires ensuring the modularity of your project.

Let's create a class with interception functions:

 class CClientUOSecure
  {
    static void hook_closesocket(CMemMngr* mngr,va_list args)
    {
      CClientUOSecure* _this=dynamic_cast<CClientUOSecure*>(mngr);
      // empty function, does nothing
    }
    static void hook_send(CMemMngr* mngr,va_list args)
    {
      CClientUOSecure* current=dynamic_cast<CClientUOSecure*>(mngr);
 
      SOCKET s=va_arg(args,SOCKET);
      char* buf=va_arg(args,char*);
      intlen=va_arg(args,int);
      int flags=va_arg(args,int);
 
      // we process parameters and call additional functions
    }
    static void hook_send_leave(CMemMngr* mngr,int *retval)
    {
      CClientUOSecure* current=dynamic_cast<CClientUOSecure*>(mngr);
     
      // make changes to the result returned by the send function
    }
  };

Our class will intercept all network functions, and after successfully connecting to the workflow, all function calls will go through our filter class.

Next, in the library code, we need to map the name of the imported function to the address of our procedures:

 CatchImportFunction("send","wsock32.dll",hook_send,hook_send_leave);
 CatchImportFunction("closesocket","wsock32.dll",hook_closesocket);

I want to draw attention to the fact that the type of intercepted function is not indicated anywhere and does not matter. Also, the developer is freed from the need to monitor the stack and registers, which he can change during the operation of the interceptor. All the work to preserve the state and support of the algorithm is hidden, the task of the developer who uses this library is the effective implementation of the data processing algorithm. Once again I want to emphasize the user-friendly interface of the library and the clarity of the interfaces.

Memory Replacement

The function substitution algorithm is identical to the function substitution algorithm in the import section. So let's go straight to an example.

Class with interceptor functions:

 class CClientUOCommands
 {
   static void FromClient(CMemMngr*,va_list args)
   {
     CClientUOCommands* pclt=dynamic_cast<CClientUOCommands*>(client);
 
     unsigned char* input=va_arg(args,unsigned char*);
     int size=va_arg(args,int);
   }
   static void SetNewPlayersCoord(CMemMngr* client,int *retval)
   {
     CClientUO* pclt=dynamic_cast<CClientUO*>(client);
   }
 };

Invoke the matching of the function address to the interceptor address:

 CatchRetFunction(0x004C0E30,FromClient);
 CatchRetFunction(0x00477C80,0,SetNewPlayersCoord);

In the first case, we intercept the data that is passed to the FromClient function in the code of the executable process, and we have the opportunity to change these parameters. In the second case, the SetNewPlayersCoord interceptor takes control after executing the specified function and has the ability to change the result of the return, as well as the data in memory that the original function could change.

Code Analysis

Our task is to analyze the client, isolate the functional parts and implant our functions inside the workflow. In order for us to manage the target process, it is necessary to identify key functions and intercept them. There are several main ways to get function addresses:

  • Disassembler
  • Debugging information
  • Analysis of object interfaces
  • Static Library Analyzers

Thanks to the universal interface of the library, there is no difference between the methods used and the definition of our substitute functions. Therefore, working with the library is well systematized and does not require knowledge of a large number of constructions. The description of these approaches requires the creation of a separate article, now I have only given a list of possible methods of analysis.

Getting the sources

To build the library, you need to get the source code for the following modules:

  • svndir:trunk/library/iofluke
  • svndir:trunk/library/debugger
  • svndir:trunk/library/misc