DLL Export Forwarding with MSVC --[ 1. Introduction You may be familiar with the concept of function exporting from dynamic libraries. It is a fundamental premise of code reuse in computing systems and there is a slew of content readily available online on the topic of dynamic linking. However, there is significantly less information available on the somewhat more esoteric subject of Dynamically Linked Library (DLL) export forwarding on Windows. This technique has proven to be extremely useful over the years to facilitate Elevation of Privilege (EoP) attacks on Windows. Indeed, the Windows 11 UAC bypass exploit that I found back in 2020 uses precisely this method to abuse PkgMgr's "autoElevate" privileges and gain code execution at High Integrity. However, the technique also has use cases outside of exploit development. A generic approach to executing code within the context of a running process without the ability (or desire) to recompile it is certainly an attractive proposition, opening the door to the ability to modify or augment the behavior of a software system for which you do not have source code. --[ 2. Export Forwarding As previously mentioned, there is a surplus of information on the topic of DLL function exporting online, so I will not cover that aspect in any great detail. Fundamentally, it is possible to define a function and instruct the linker to add an entry to the Export Address Table (and Export Names Table) such that an external callee is able to reference and call that routine by linking the library at run time. However, the design of Microsoft's Portable Executable (PE) allows a developer to define an export which simply redirects the dynamic loader to another library. Consider an example application which consists of a single Executable PE file (.exe) and a DLL (.dll). The machinations of Import/Export Address Name and Address Tables has been simplified in the below example. |--------------------| |----------------| | foo.exe | | foo.dll | |====================| |================| | Imports: | | Exports: | | foo.dll:myFunc() |----->| myFunc() | |--------------------|<, / |----------------| | .text | | | | .text | | main(){ | | `>| int myFunc(){ | | myFunc(); |/ | // | | } | | } | |--------------------| |----------------| In this example, the application loader has parsed the import table of foo.exe to find the location of the named import "myFunc". This is listed as belonging to foo.dll. The loader then follows the usual dll search order rules to find the first PE file called foo.dll and scans its export table for the matching named export. Finally, once it finds the function, it performs the necessary plumbing in the application to link calls to the correct offsets in the library. However, the PE specification also allows for a DLL to expose a something called a "forwarded" export. The new model now looks like this. |--------------------| |-------------------| |----------------| | foo.exe | | foo.dll | | bar.dll | |====================| |===================| |================| | Imports: | | Exports: | | Exports: | | foo.dll:myFunc() |--->| bar.dll:myFunc()|---->| myFunc() | |--------------------|<, |-------------------| / |----------------| | .text | | | .text | | | .text | | main(){ | | | | `>| int myFunc(){ | | myFunc(); |/ | | | // | | } | | | | } | |--------------------| |-------------------| |----------------| In this example, foo exports the named function myFunc but instructs the loader to instead look for it in bar.dll. The loader will diligently follow the chain and dynamically link bar.dll's version of myFunc into foo.exe. You may be asking yourself "But why is this useful?" The answer is that by renaming an existing dll within a writable directory and adding a replacement, we are able to execute code within the context of the calling process. And by forwarding all of the exports to the renamed original, the program will continue to run normally. If for example, foo.exe runs with high privileges, we get to execute arbitrary code at that level when our replacement foo.dll is loaded. --[ 3. Forwarding Exports with MSVC The process to forward exports using Visual Studio's compiler is not particularly well documented but there are two ways to achieve it, both variations of informing the linker to add the forwarding to the export names table. ----[ 3.1 Using Linker Commands The easiest way to achieve this is to pass linker command switches to the compiler. The switch which specifies exports is (perhaps unsurprisingly) /EXPORT. The switch takes the exported function name, the target dll and the target function. From the example above, we are forwarding myFunc to bar.dll so the directive would look like this. /EXPORT:myFunc=bar Notice the removal of the ".dll" extension on the target filename. We can even go a step further and specify the ordinal to match the target. This will ensure that any imports by ordinal are also correctly handled with the following (assuming that myFunc is at ordinal 1 as dllmain is usually ordinal 0). /EXPORT:myFunc=bar.#1,@1 However, manually adding switches to the build options is not particularly scalable. Every time the library changes we will need to manually add them to the build configuration. Fortunately, Visual studio allows you to add linker commands directly in the source using the #pragma directives. As the Visual C compiler documentation states, "Pragma directives specify machine-specific or operating system-specific compiler features." #pragma comment(linker, "/EXPORT:myFunc=bar.#1,@1") This way, you can create and maintain a single file which contains all of the exports for the target DLL and simply #include it in your source. ----[ 3.2 Using a Definition File Visual studio allows you to add a definition file to instruct the linker which functions to export when compiling a dynamic library. In this case, the format is a little different. LIBRARY EXPORTS myFunc=bar.@1 @1 --[ 4. Automating with Python Clearly, manually creating these forwarded exports is a rather tedious task. This is the reason that I created a python script to do all of the heavy lifting. The script can be found at https://github.com/kryc/forwardexports