Smaller C Payloads on Window

Introduction

Many thanks to 0xPat for his post on malware development, which is where the inspiration for this post came from.

When writing payloads for use in penetration tests or red team engagements, smaller is generally better. No matter the language you use, there is always a certain amount of overhead required for running a binary, and in the case of C, this is the C runtime library, or CRT. The C runtime is “a set of low-level routines used by a compiler to invoke some of the behaviors of a runtime environment, by inserting calls to the runtime library into compiled executable binary. The runtime environment implements the execution model, built-in functions, and other fundamental behaviors of a programming language”. On Windows, this means the various *crt.lib libraries that are linked against when you use C/C++. You might be familiar with the common compiler flags /MT and /MTd, which statically link the C runtime into the final binary. This is commonly done when you don’t want to rely on using the versioned Visual C++ runtime that ships with the version of Visual Studio you happen to be using, as the target machine may not have this exact version. In that case you would need to include the Visual C++ Redistributable or somehow have the end user install it. Clearly this is not an ideal situation for pentesters and red teamers. The alternative is to statically link the C runtime when you build your payload file, which works well and does not rely on preexisting redistributables, but unfortunately increases the size of the binary.

How can we get around this?

Introducing msvcrt.dll

msvcrt.dll is a copy of the C runtime which is included in every version of Windows from Windows 95 on. It is present even on a fresh install of Windows that does not have any additional Visual C++ redistributables installed. This makes it an ideal candidate to use for our payload. The trick is how to reference it. 0xPat points to a StackOverflow answer that describes this process in rather general terms, but without some tinkering it is not immediately obvious how to get it working. This post is aimed at saving others some time figuring this part out (shout out to AsaurusRex!).

Creating msvcrt.lib

The idea is to find all the functions that msvcrt.dll exports and add them to a library file so the linker can reference them. The process flow is to dump the exports into a file with dumpbin.exe, parse the results into .def format, which can then be converted into a library file with lib.exe. I have created a GitHub gist here that contains the commands to do this. I use Windows for dumping the exports and creating the .lib file, and Linux to do some text processing to create the .def file. I won’t go over the steps here in detail here as they are well commented in the gist.

Some Caveats

It is important to note that using msvcrt.dll is not a perfect replacement for the full C runtime. It will provide you with the C standard library functions, but not the full set of features that the runtime normally provides. This includes things like initializing everything before calling the usual main function, handling command line arguments, and probably a lot of other stuff I have not yet run into. So depending on how many features of the runtime you use, this may or may not be a problem. C++ will likely have more issues than pure C, as many C++ features involving classes and constructors are handled by the runtime, especially during program initialization.

Using msvcrt.lib

Using msvcrt.lib is fairly straight forward, as long as you know the proper compiler and linker incantations. The first step is to define _NO_CRT_STDIO_INLINE at the top of your source files. This presumably disables the use of the CRT, though I’ve not seen this explicitly defined by Microsoft anywhere. I have noticed that this definition alone is not enough. There are several compiler and linker flags that need to be set as well. I will list them here in the context of C/C++ Visual Studio project settings, as well as providing the command line argument equivalents.

Visual Studio Project Settings

  • Linker settings:
    • Advanced -> Entrypoint -> something other than main/wmain/WinMain etc.
    • Input -> Ignore All Default Libraries -> YES
    • Input -> Additional Dependencies -> add the custom msvcrt.lib path, kernel32.lib, any other libraries you may need, like ntdll.dll
  • Compiler settings:
    • Code Generation -> Runtime Library -> /MT
    • Code Generation -> /GS- (off)
    • Advanced -> Compile As -> /TC (only if you’re using C and not C++)
    • All Options -> Basic Runtime Checks -> Default

cl.exe Settings

cl.exe /MT /GS- /Tc myfile.c /link C:\path\to\msvcrt.lib "kernel32.lib" "ntdll.lib" /ENTRY:"YourEntrypointFunction" /NODEFAULTLIB

Some notes on these settings. You must have an entrypoint that is not named one of the standard Windows C/C++ function names, like main or WinMain. These are used by the C runtime, and as the full C runtime is not included, they cannot be used. Likewise, runtime buffer overflow checks (/GS) and other runtime checks are part of the C library and so not available to us.

If you plan on using command line arguments, you can still do so, but you’ll need to use CommandLineToArgvW and link against Shell32.lib.

Conclusion

Using this method I’ve seen a size reduction from 8x-12x in the resulting binary. I hope this post can serve as helpful documentation for others trying to get this working. Feel free to contact me if you have any issues or questions, and especially if you have any improvements or better ways of accomplishing this.

- Solomon Sklash