Precomp4C is a open-source and extensible precompiler for C/C++, converts variant objects to C source, and participate build.
Usually, we embed binary to program resource, and calling API like "FindResource
" to get binary data at runtime.
But now, Precomp4C provides "Binary
" loader, we can embed file as binary data to code and use it directly. For example, let's embed a png image file to C/C++ source:
First, we write Precomp4C configuration file (Precomp4C.config.json
) to specify which file to be embedded by using "Binary
" loader in Precomp4C:
Precomp4C.config.json
{
"Loaders": {
"Binary": [{
"File": "..\\Resource\\IconMask.png",
"Export": "PNG_Icon_Mask"
}]
}
}
After we integrated Precomp4C to our project, the following outputs will be generated automatically:
Precomp4C.Output.h
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by Precomp4C
// KNSoft(knsoft.org) - Precomp4C V1.0.0.0
// https://github.com/KNSoft/Precomp4C
// Do not change this file manually
// </auto-generated>
//------------------------------------------------------------------------------
#pragma once
extern unsigned char BIN_PNG_Icon_Mask[759];
Precomp4C.Output.c
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by Precomp4C
// KNSoft(knsoft.org) - Precomp4C V1.0.0.0
// https://github.com/KNSoft/Precomp4C
// Do not change this file manually
// </auto-generated>
//------------------------------------------------------------------------------
unsigned char BIN_PNG_Icon_Mask[759] = {
0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x08,
0x06, 0x00, 0x00, 0x00, 0x57, 0x02, 0xF9, 0x87, 0x00, 0x00, 0x02, 0xBE, 0x49, 0x44, 0x41, 0x54, 0x68, 0x81, 0xED, 0x99, 0xEF, 0x71, 0x9B, 0x40, 0x10,
0xC5, 0xDF, 0x7A, 0xF2, 0x5D, 0xE9, 0xC0, 0xEA, 0x40, 0x4A, 0x05, 0x72, 0x07, 0x4A, 0x07, 0x56, 0x07, 0xB8, 0x03, 0xA5, 0x03, 0xBB, 0x83, 0x28, 0x15,
...
Adds outputs (Precomp4C.Output.h
and Precomp4C.Output.c
) to project, and we can get binary data of "..\\Resource\\IconMask.png
"(specified in configuration file, Precomp4C.config.json
) by referencing symbol "BIN_PNG_Icon_Mask
"("BIN_" prefix appended by "Binary" loader, "PNG_Icon_Mask" specified in configuration file).
KNSoft/NTAssassin is an example shows how to use C/C++ writes shellcode and inject to remote process by integrated Precomp4C. Let's take a look:
First, NTAssassin writes shellcode in HijackC.c, the function "Hijack_LoadProcAddr_InjectThread
". Caution, those C source have no external(or global) symbol(or function) reference, as well as the assembly code generated, so the binary code generated can work perfectly in remote process after injection.
Second, NTAssassin have two C/C++ projects complie this source to different target platform, x86 and x64. Whatever EXE or DLL, Precomp4C needs object file (HijackC.obj
) only.
And then we writes Precomp4C.config.json, use "SymExtract
" loader to get binary code complied from "Hijack_LoadProcAddr_InjectThread
" function:
Precomp4C.config.json
{
"Loaders": {
"SymExtract": [{
"File": "..\\NativeASM\\Hijack_x86\\Release\\HijackC.obj",
"Symbols": [{
"Name": "Hijack_LoadProcAddr_InjectThread",
"Export": "Hijack_LoadProcAddr_InjectThread_x86"
}]
},
{
"File": "..\\NativeASM\\Hijack_x64\\Release\\HijackC.obj",
"Symbols": [{
"Name": "Hijack_LoadProcAddr_InjectThread",
"Export": "Hijack_LoadProcAddr_InjectThread_x64"
}]
}
]
}
}
Now, Precomp4C outputs "Hijack_LoadProcAddr_InjectThread
" function code in HijackC.obj(x86)
and named "Hijack_LoadProcAddr_InjectThread_x86
", outputs "Hijack_LoadProcAddr_InjectThread
" function code in HijackC.obj(x64)
and named "Hijack_LoadProcAddr_InjectThread_x64
":
Precomp4C.Output.h
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by Precomp4C
// KNSoft(knsoft.org) - Precomp4C V1.0.0.0
// https://github.com/KNSoft/Precomp4C
// Do not change this file manually
// </auto-generated>
//------------------------------------------------------------------------------
#pragma once
extern unsigned char SYM_Hijack_LoadProcAddr_InjectThread_x86[982];
extern unsigned char SYM_Hijack_LoadProcAddr_InjectThread_x64[1120];
Precomp4C.Output.c
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by Precomp4C
// KNSoft(knsoft.org) - Precomp4C V1.0.0.0
// https://github.com/KNSoft/Precomp4C
// Do not change this file manually
// </auto-generated>
//------------------------------------------------------------------------------
unsigned char SYM_Hijack_LoadProcAddr_InjectThread_x86[982] = {
0x55, 0x8B, 0xEC, 0x8B, 0x45, 0x08, 0x83, 0xEC, 0x38, 0x8B, 0x08, 0x53, 0x56, 0x57, 0x83, 0xF9, 0x04, 0x72, 0x23, 0x8D, 0x58, 0x04, 0xBA, 0x00, 0x00,
0x00, 0x00, 0x89, 0x5D, 0xC8, 0x83, 0xE9, 0x04, 0x74, 0x13, 0x8D, 0x04, 0x12, 0x42, 0x66, 0x83, 0x3C, 0x18, 0x00, 0x89, 0x55, 0xF8, 0x74, 0x13, 0x83,
...
};
unsigned char SYM_Hijack_LoadProcAddr_InjectThread_x64[1120] = {
0x40, 0x55, 0x56, 0x41, 0x54, 0x41, 0x55, 0x41, 0x56, 0x48, 0x81, 0xEC, 0xA0, 0x00, 0x00, 0x00, 0x8B, 0x11, 0x48, 0x83, 0xFA, 0x04, 0x72, 0x2C, 0x48,
0x8D, 0x69, 0x04, 0x41, 0xBD, 0x00, 0x00, 0x00, 0x00, 0x45, 0x8B, 0xDD, 0x48, 0x83, 0xEA, 0x04, 0x74, 0x19, 0x0F, 0x1F, 0x44, 0x00, 0x00, 0x41, 0x8B,
...
};
NTAssassin gets both x86 and x64 shellcode from the same C source by using Precomp4C, and use this shellcode implements Hijack_LoadProcAddr to load DLL and optionally get procedure address in remote process like:
Hijack_LoadProcAddr(ProcessHandle, L"Any.dll", "AnyFunction", ...)
Precomp4C is extensible, we can use Precomp4C to do more by writting more loaders:
- Get fingerprint of certification and embedded in code to verify at runtime, avoid hardcode
- Converts I18N configuration file in JSON format to C source like array or key-value map, KNSoft/AlleyWind is an example
- ...
All the loaders just needs to implement two interfaces, like "Binary
" loader:
public static DateTime GetLastModifyTime(string ProjectDir, CO_Binary[] ConfigObject);
public static bool Process(string ProjectDir, FileStream fsHeader, FileStream fsSource, CO_Binary[] ConfigObject);
ProjectDir
: Project directory which contains Precomp4C configuration file (Precomp4C.config.json)ConfigObject
: JSON configuration object specified in "Precomp4C.config.json" for this loaderGetLastModifyTime()
returns: A DateTime value, last modify time of source file for this loader, feedbacks to Precomp4C main program to determin rebuild could be skip or notfsHeader
: FileStream for Precomp4C.Output.hfsSource
: FileStream for Precomp4C.Output.cProcess()
returns: success or not, Precomp4C main program ignored this return value currently, Loaders call "RTL.RaiseError" to stop build and output error message when encountered a fatal error
- Write
Precomp4C.config.json
in project root directory, configures the loader(s) needed, like examples above - Add Pre-Build Event to call Precomp4C before IDE build, pass project directory to Precomp4C, like:
3rdParty\bin\Precomp4C\Precomp4C.exe $(ProjectDir)
- Run build task,
Precomp4C.Output.h
andPrecomp4C.Output.c
will generated automatically if no error occured, add them to project, the outputs symbols could be referenced now
Those project integrated Precomp4C to improve C/C++ code programmability, maintainability, architecture design:
- KNSoft/NTAssassin: Uses "
SymExtract
" loader to outputs shellcode generated from C source, and targets to multiple platforms (x86 and x64) - KNSoft/AlleyWind: Uses "
I18N
" loader with I18N library in KNSoft/NTAssassin to support I18N (en/en-US and zh/zh-CN)
Precomp4C is written by C#, the latest .NET runtime is required: https://dotnet.microsoft.com/download