Creating a proxy dll for hijack dll operation checks

    When I examine software security, one of the points to check is working with dynamic libraries. Attacks like the hijack DLL (“dll spoofing” or “dll interception”) are very rare. Most likely, this is due to the fact that Windows developers add security mechanisms to prevent attacks, and software developers are more careful about security. But all the more interesting is the situation when the target software is vulnerable.

    Briefly describing the attack, the hijack DLL is creating a situation in which some executable tries to load the dll, but the attacker intervenes in this process, and instead of the expected library, a specially prepared dll is loaded with the payload from the attacker. As a result, the code from the dll will be executed with the rights of the launched application, therefore applications with higher rights are usually selected as the target.

    For the library to load correctly, a number of conditions must be met: the bit size of the executable file and the library must match, and if the library is loaded when the application starts, the dll must export all the functions that this application expects to import. Often, one import is not enough - it is very desirable that the application continues its work after loading the dll. For this, it is necessary that the functions of the prepared library work the same as the original. The easiest way to do this is by simply passing the function calls from one library to another. These are the dlls called proxy dlls.



    Under the cut will be several options for creating such libraries - both in the form of code and utilities.

    A small theoretical review


    Libraries are more often loaded using the LoadLibrary function, into which the library name is passed. If instead of the name you pass the full path, then the application will try to load the specified library. For example, calling LoadLibrary (“C: \ Windows \ system32 \ version.dll”) will load the specified dll. Or, if the library does not exist, it will not be loaded.

    A bit of tediousness
    If some dll is already loaded into the application, then it will not be loaded again. Given that version.dll is loaded at the start of almost any exe-file, in fact, the call above will not actually load anything. But we still consider the general case, consider the example as a call to some abstract library.

    It is quite another matter if you write LoadLibrary (“version.dll”). In a normal situation, the result will be exactly the same as in the previous case - C: \ Windows \ system32 \ version.dll will load, but not so simple.

    First, a library will be searched, which will go in the following order :

    1. Executable folder
    2. Folder C: \ Windows \ System32
    3. Folder C: \ Windows \ System
    4. Folder C: \ Windows
    5. The folder set as current for the application
    6. Folders from the PATH environment variable

    Some more tediousness
    When starting 32-bit applications on a 64-bit system, all calls to C: \ Windows \ system32 will be forwarded to C: \ Windows \ SysWOW64. This is just for the accuracy of the description, from the point of view of the attacker the difference is not particularly important.

    When you run the exe file, the OS loads all the libraries from the file import section. In a general sense, we can assume that the OS forces the file to call LoadLibrary, passing all the library names that are written in the import section. Since in 99.9% of cases there are names and not paths, when the application starts, all loaded libraries will be searched in the system.

    From the list of dll search locations, two points are really important to us - 1 and 6. If we put version.dll in the same folder from where the file is launched, then instead of the system one the loaded one will be loaded. This situation is almost never encountered, because if there is an opportunity to put a library, then, most likely, it is possible to replace the executable file itself. But still, such situations are possible. For example, if the executable file is located in a writable folder and is a service with an auto start, then it cannot be changed while the service itself is running. Or the launched file is checked externally by checksum before starting, then replacing the file is still not an option. But to put the library next to it will be quite real.

    You may not be able to create files next to executable files, but you can create folders. In this situation, the WinSxS redirect mechanism (aka “DotLocal”) may work.

    Briefly about DotLocal
    The manifest of the file may contain a dependency on the library of a specific version. In this case, when starting the executable file (for example, let it be application.exe), the OS will check for the existence of a folder named application.exe.local in the same folder as the file itself. This folder should have a subfolder with a complex name like amd64_microsoft.windows.common-controls_6595b64144ccf1df_6.0.9600.19291_none_6248a9f3ecb5e89b inside which there is already a comctl32.dll library. The library name and information for the folder name should be indicated in the manifest, here is just an example from the first process that came across. If there are no folders or file, then the library will be taken from C: \ Windows \ WinSxS. In the example, C: \ Windows \ WinSxS \ amd64_microsoft.windows.common-controls_6595b64144ccf1df_6.0.9600.19291_none_6248a9f3ecb5e89b \ comctl32.dll.

    But this is more the exception than the rule. But the situations when the dll search reaches the 6th number in the list are quite real. If the application tries to load a dll that is not on the system or next to the file, then all searches will go up to 6 points, which could potentially be writable folders.

    For example, a typical Python installation most often occurs in the C: \ Python (or close) folder. The python installer itself suggests adding its folders to the PATH system variable. As a result, we have a good springboard for starting an attack - the folder is writable by all users and any attempt to load a nonexistent library will go to the path search from PATH.

    Now that the theory has been completed, consider the creation of the payload — the proxy libraries themselves.

    First option. Honest proxy library


    Let's start with a relatively simple one - we’ll make an honest proxy library. Honesty in this case implies that all functions in the dll will be explicitly registered, and for each function a function call with the same name from the original library will be written. Working with such a library will be completely transparent for the called code: if it calls some function, then it will receive the correct answer, the result, and everything that should happen side by side.

    Here is a link to the finished example ( github ) of the version.dll library.

    Code Highlights:

    • All function prototypes from the export table of the original library are honestly described.
    • The original library is loaded and all calls to our functions are thrown into it.

    Conveniently , the application continues to work correctly, without experiencing any "special effects." It is inconvenient that I had to write a bunch of uniform code for each of the functions, moreover, carefully checking the coincidence of the prototypes.

    The second option. Simplify code writing


    When dealing with a library like version.dll, where the import table is small, there are only 17 functions, and the prototypes are simple, then an honest proxy library is a good choice.



    But if the proxy for the library, for example, bcrypt, then everything is more complicated. Here is her import table:



    57 functions! And here are a couple of examples of prototypes:




    Let's just say that nothing is impossible, but to make an honest proxy for such a library is not very pleasant.

    You can simplify the code if you cheat a little with functions. We will declare all functions in the library as __declspec (naked), and in the body we will use assembler code that simply makes jmp on the function from the original library. This will allow us not to use long prototypes, but to put simple declarations everywhere without parameters of the form:

    void foo ()

    When the application calls our function, the proxy library will not perform any manipulations with the register and the stack, allowing the original function to do all the work as it should.

    An example ( github ) of the version.dll library with this approach.

    Highlights:

    • The original library is loaded, and all calls to our functions are thrown into it. Function bodies and loading are wrapped in macros.

    Convenient and correct operation of the application and the fact that even a large number of functions are easily described, thanks to macros. It is inconvenient that rather unexpected rake in x64. Visual Studio (somewhere since 2012, if I remember correctly) forbids using naked and asm inserts in 64-bit code. When writing a proxy from scratch, it is necessary for each function to verify that it is described in the def-file, that the original is loaded, and the body of the function is described.

    The third option. We throw out the body in general


    Using naked suggests one more option. You can create an import table that for all functions will refer to one real line of code:

    void nop () {}

    Such a library will be loaded by the application, but will not work. When calling any of the functions, the stack will most likely be torn or some other muck will happen. But this is not always bad - if, for example, the goal of a dll injection is to simply run the code with the necessary rights, then it is enough to execute the payload from the DllMain proxy library and quietly terminate the application immediately. In this case, it will not come to a real call to the functions, and there will be no fall-errors.

    An example on a github , again for version.dll.

    Code Highlights:

    • All functions from the def file refer to one nop function.

    Conveniently such a proxy library is written just for a couple of minutes. It is inconvenient that the called application stops working.

    The fourth option. Take ready-made utilities


    Writing a dll is good, but not always convenient and not very fast, so you should consider automated options.

    You can follow the path of old viruses - take the library whose proxies we want to create, create an executable section of code in it, write down the payload there and change the entry point to this section. Not the easiest way, because you can accidentally break something, you have to write in assembler, remember the device of the PE file. This is not our way.

    To operate the dll hijack we will add another dll hijack.



    This is relatively easy to do. We copy the library whose proxy we want to make and add some dll with an arbitrary function to the import table of this copy. Now the download will go along the chain - at the start of the executable file the proxy dll will be loaded, which will load the specified library itself.

    “Hey, you replaced loading one library with another. What's the point? All the same it will be necessary to code dll! ". Everything is correct, but there is still a sense. There will now be fewer requirements for a library with a payload. You can specify any name, the main thing is to export only one function, which can have any prototype. Enter the main name of the library and function in the import table.

    A library with a payload can be one for all occasions.

    You can modify the import table with many PE editors, for example CFF explorer or pe-bear. For myself, I wrote a small utility in C # that corrects a table without unnecessary gestures. Sources on github , binar in the Release section .

    Conclusion


    In the article, I tried to disclose the basic methods for creating proxy dll, which I used myself. It remains only to tell how to defend.

    There are not many universal recommendations:

    • Do not store executable files, especially those run with high permissions, in folders writable to users.
    • It is better to first find and verify the existence of the library before doing LoadLibrary.
    • Look at the existing protection methods available in the OS. For example, in Windows 10, you can set the PreferSystem32 flag so that the dll search does not begin with the executable file folder, but with system32.

    Thank you for your attention, I will be glad to hear questions, suggestions, suggestions and comments.

    UPD: On the advice of commentators, I remind you that you need to choose a library carefully and carefully. If the library is included in the KnownDlls list or the name is similar to MinWin (ApiSetSchema, api-ms-win-core-console-l1-1-0.dll - that's all), then most likely it will not be possible to intercept it due to the processing features such dlls in the OS.

    Also popular now: