Writing Shellcode in Visual Studio

Windows Shellcode

Exploit shellcode is often carefully crafted to behave in a way that may not be able to be expressed in a high level language. But writing large amounts of shellcode is a pain and multiple stages are often used to take advantage of code reuse and to alleviate some of the burden. This post will introduce the reader to writing shellcode in C with Visual Studio 2017.

First off, what makes something shellcode? Shellcode is position independent code that can run from any address. It must also dynamically resolve any run time dependencies by loading modules and finding exports. On Linux, this is straightforward as the syscall interface is stable. However on Windows, syscalls may change from OS to OS or even service pack to service pack. The solution is to use a technique called reflective loading which obtains a list of loaded modules from a stable location and walks the export tables to find needed APIs such as kernel32!LoadLibraryA and kernel32!GetProcAddress. Without such APIs, your shellcode can't do much.

Ideally, you would be able to compile a C file on Windows, extract the .text section from the executable, and have your shellcode. For reasons mentioned above, this doesn't work as imported APIs must be resolved at run time by the loader to ensure your executable runs across a wide range of Windows systems.

But what if you don't link any external dependencies into your code? Then can you treat your .text section as shellcode? The answer is yes, if you write your code carefully and set the right compiler and linker flags. If you need API access, however, you need to step in for the loader and resolve dependencies yourself using reflective loading techniques. Certain coding practices will also have to be avoided such as switch statements and stack cookies, which generate relocations that the loader must fix.

Example

Complete code can be found on Github:

Let's build a Visual Studio project that generates shellcode based off of this classic reflective loader. This project can be built into your DLL to make it self-loading when invoking the ReflectiveLoader export. This requires calculating the function's offset and calling it directly or doing what meterpreter does and insert a call instruction, overwriting part of the DOS header. This technique allows execution to begin at the start of the DLL.

Patching meterpreter DLL header

Instead of writing a DLL that loads itself, we are going to write shellcode that uses these self-loading techniques to load an arbitrary DLL and we will write it in C. We start by copying ReflectiveLoader.c and removing some of the stuff we don't need. Also, instead of walking backwards from the instruction pointer to find the MZ signature, we are going to walk forward to find the MZ signature of an appended DLL.

Stepping uiLibraryAddress until we find "MZ"

With our modifications in place, it's time to compile our loader and extract the .text section and see if it works. The solution contains the Extract project which will perform this action every time our loader is rebuilt. The TestExe project generates TestExe.exe which can be used to test the shellcode and TestDll gives us a DLL which should pop a message box. We can test by building for Release|x64 and running the following from the solution dir:

    x64\Release\TestExe.exe TestDll\TestDll.x64.Release.bin

With the default compiler and linker settings, the DLL failed to load and TestExe.exe simply returned without doing anything. By looking at the disassembly the problem becomes clear:

Loader shellcode start

The beginning of our shellcode is actually the ror routine, and not our entry point. We want our entry point, ReflectiveLoader, to come first. Fortunately, Visual Studio has a flag called "Enable Function-Level Linking" which can be used with the /ORDER linker option to order our functions.

Enable /Gy in the compiler options

While we are in there we also disable stack cookies as they can generate unwanted relocations. With the /Gy flag added, we can now specify that we want our loader routine, ReflectiveLoader, to appear first.

Specify our entry point and an order file

/ORDER takes a text file with a list of ordered symbol names. If you aren't sure what name to use, you can always run "dumpbin /symbols" on your object file. We also specify our entry point to avoid linking in the C runtime. Compile Release|x64 again and start the debugger.

Function ordering enabled

Already this looks much better. We can see that ror no longer comes first but instead, we see the entry point. If you run "x64\Release\TestExe.exe TestDll\TestDll.x64.Release.bin" again you should see the following:

The test DLL calls MessageBoxA then exits

To test your own DLLs, just append them to the loader shellcode of the correct architecture and run it:

    copy /b loader.x64.bin+MyModule.dll MyModule.bin
    TestExe.exe MyModule.bin

Final Thoughts

This post was intended as an intro to writing shellcode in Visual C. This method does not give the author control over bad characters (eg null bytes) and the compiler may insert unwanted instructions. However, it may be useful for post-exploitation code injection and staged payloads. Additionally, the loader shellcode can be used to load arbitrary DLLs meaning you can avoid writing shellcode altogether!