REMIO Q | 21/06/2025 | Simple C++ Compile-time string obfuscation Header
When you write a program in C++, whether it’s a normal executable or a DLL, a lot of the strings you use such as file paths, function names etc. are just sitting there in the binary. Anyone can open it in a hex editor or use a static analysis tool like Ghidra or x64dbg, and those strings will be very easy to find. This is problem if you’re building something stealthy like malware, red team implants, loaders, or anything else that might be analysed or flagged…
In native C++ development, especially when working on offensive softwares or malware, it’s critical to avoid exposing sensitive strings in the release binary. Static analysis tools and reverse engineers frequently scan binaries for such literals in the
This header only implementation demonstrates how to one may start tp think about achieving this using modern C++. Features like
By using a custom linear congruential engine (LCE) that we seed with
The approach works with both ANSI and wide strings via simple macros, requires no heap allocations (unless using the ANSI wide converter), and introduces zero external dependencies. While the encryption used here is intentionally simple and not cryptographically secure, it’s used to clearly demonstrate the concept of compile-time obfuscation. This is ideal for stealth-focused development where binary footprint, performance, and evasion are more important than bulletproof crypto.
Main Features:
Usage Example:
ANSI string (runtime decryption on first use):
Unicode string (e.g., for registry/file usage):
TCHAR support depending on UNICODE/_UNICODE:
How It Works internally:
The encryption mechanism is based on a Linear Congruential Generator (LCG) a simple but effective pseudorandom number generator. By seeding it with TIME each build yields a unique stream of pseudo random values, ensuring per build encryption variance. From this stream, two prime-derived key components, A & B, are generated to form the core of the encryption logic.
Encryption is performed using the formula:
(A × char + B) % 127
which transforms each character into a printable ASCII value, avoiding control characters and ensuring compatibility.
Macros:
ANSI to Wide Conversion (included utility):
To decrypt, the system uses the Extended Euclidean Algorithm to compute the modular inverse of A, allowing exact reversal of the encryption at runtime. This decryption occurs only once per string, thanks to a lightweight caching system that stores the result after the first access. Subsequent accesses retrieve the cached version, minimizing performance impact.
String Obfuscation header (stringobf.h)
Notes:
This is not bulletproof security, neither is the encryption. It just adds an extra layer of stealth. It does not protect against full memory dumping or runtime tracingetc but works well for avoiding detection via static strings in
When you write a program in C++, whether it’s a normal executable or a DLL, a lot of the strings you use such as file paths, function names etc. are just sitting there in the binary. Anyone can open it in a hex editor or use a static analysis tool like Ghidra or x64dbg, and those strings will be very easy to find. This is problem if you’re building something stealthy like malware, red team implants, loaders, or anything else that might be analysed or flagged…
In native C++ development, especially when working on offensive softwares or malware, it’s critical to avoid exposing sensitive strings in the release binary. Static analysis tools and reverse engineers frequently scan binaries for such literals in the
.rdata or .text sections to fingerprint and classify malware. Compile time string obfuscation is a lightweight technique that hides these strings during compilation using deterministic encryption logic, and only decrypts them at runtime on first access with very minimal overhead.This header only implementation demonstrates how to one may start tp think about achieving this using modern C++. Features like
constexpr to do all the encryption at compile time. By using a custom linear congruential engine (LCE) that we seed with
__TIME__, it generates pseudo-random keys on a per build basis. These are used in modular arithmetic to encrypt each byte of the string statically, ensuring uniquely encrypted blobs for each compilation. At runtime the string is decrypted in place using a modular multiplicative inverse (calculated via the Extended Euclidean Algorithm https://en.wikipedia.org/wiki/Extended_Euclidean_algorithm), and cached for reuse. The approach works with both ANSI and wide strings via simple macros, requires no heap allocations (unless using the ANSI wide converter), and introduces zero external dependencies. While the encryption used here is intentionally simple and not cryptographically secure, it’s used to clearly demonstrate the concept of compile-time obfuscation. This is ideal for stealth-focused development where binary footprint, performance, and evasion are more important than bulletproof crypto.
Main Features:
- Full compile-time string encryption using constexpr
- Modular arithmetic & we use extended euclidean algorithm for reversible encryption
- ANSI and Unicode support via macros
- Unique obfuscation per build by usingTIME seed
Usage Example:
ANSI string (runtime decryption on first use):
C++:
// Obfuscated ANSI string
MessageBoxA(NULL, OBF_STR(“Stealth mode enabled”), “Info”, MB_OK);
Unicode string (e.g., for registry/file usage):
C++:
// Obfuscated Unicode string
CreateDirectoryW(OBF_WSTR(L”C:\\Users\\nealm\\StealthDir”), NULL);
TCHAR support depending on UNICODE/_UNICODE:
C++:
_tprintf(_T(“Path: %s\n”), OBF_T(“C:\\hidden\\secret.txt”));
How It Works internally:
The encryption mechanism is based on a Linear Congruential Generator (LCG) a simple but effective pseudorandom number generator. By seeding it with TIME each build yields a unique stream of pseudo random values, ensuring per build encryption variance. From this stream, two prime-derived key components, A & B, are generated to form the core of the encryption logic.
Encryption is performed using the formula:
(A × char + B) % 127
which transforms each character into a printable ASCII value, avoiding control characters and ensuring compatibility.
Macros:
C++:
// Treat as wide file extension string (e.g., L”.exe”)
auto ext = OBF_EXT(“.exe”);
// TCHAR-wide/ansi auto-detect
auto dynamic = OBF_T(“secret command”);
ANSI to Wide Conversion (included utility):
C++:
WCHAR* wide = AnsiToWide(OBF_STR(“DropperPayload”));
wprintf(L”%s\n”, wide);
FreeWideString(wide);
String Obfuscation header (stringobf.h)
Код:
#pragma once
#include <array>
#include <utility>
#include <Windows.h>
// compile time random number generation
constexpr int RandomSeed(void)
{
return ‘0’ * -40271 +
__TIME__[7] * 1 +
__TIME__[6] * 10 +
__TIME__[4] * 60 +
__TIME__[3] * 600 +
__TIME__[1] * 3600 +
__TIME__[0] * 36000;
}
template <unsigned int a, unsigned int c, unsigned int seed, unsigned int Limit>
struct LinearCongruentialEngine
{
enum { value = (a * LinearCongruentialEngine<a, c - 1, seed, Limit>::value + c) % Limit };
};
template <unsigned int a, unsigned int seed, unsigned int Limit>
struct LinearCongruentialEngine<a, 0, seed, Limit>
{
enum { value = (a * seed) % Limit };
};
template <int N, int Limit>
struct MetaRandom
{
enum { value = LinearCongruentialEngine<16807, N, RandomSeed(), Limit>::value };
};
// Extended Euclidean Algorithm for modular multiplicative inverse
template <int A, int B>
struct ExtendedEuclidian
{
enum
{
d = ExtendedEuclidian<B, A% B>::d,
x = ExtendedEuclidian<B, A% B>::y,
y = ExtendedEuclidian<B, A% B>::x - (A / B) * ExtendedEuclidian<B, A% B>::y
};
};
template <int A>
struct ExtendedEuclidian<A, 0>
{
enum
{
d = A,
x = 1,
y = 0
};
};
// prime numbers for encryption
constexpr std::array<int, 10> PrimeNumbers = { 2, 3, 5, 7, 11, 13, 17, 19, 23, 29 };
// Positive modulo helper function
constexpr int positive_modulo(int a, int n)
{
return (a % n + n) % n;
}
// Helper for string length calculation at compile time
constexpr size_t constexpr_strlen(const char* str)
{
return *str ? 1 + constexpr_strlen(str + 1) : 0;
}
constexpr size_t constexpr_wcslen(const wchar_t* str)
{
return *str ? 1 + constexpr_wcslen(str + 1) : 0;
}
//string buffer with encryption/decryption ANSI version
template<unsigned char A, unsigned char B, size_t Size>
class StringBuffer
{
private:
mutable bool m_isDecrypted = false;
mutable unsigned char m_buffer[Size + 1] = {};
constexpr unsigned char encrypt(unsigned char byte) const
{
return (A * byte + B) % 127;
}
unsigned char decrypt(unsigned char byte) const
{
return static_cast<unsigned char>(positive_modulo(ExtendedEuclidian<127, A>::y * (byte - B), 127));
}
public:
constexpr StringBuffer(const char* str)
{
for (size_t i = 0; i < Size; i++) {
m_buffer[i] = encrypt(static_cast<unsigned char>(str[i]));
}
m_buffer[Size] = 0; // Null terminator
}
const char* get() const
{
if (!m_isDecrypted)
{
for (size_t i = 0; i < Size; i++) {
m_buffer[i] = decrypt(m_buffer[i]);
}
m_isDecrypted = true;
}
return reinterpret_cast<const char*>(m_buffer);
}
};
// String buffer with encryption/decryption Wide version
template<unsigned char A, unsigned char B, size_t Size>
class WideStringBuffer
{
private:
mutable bool m_isDecrypted = false;
mutable unsigned char m_buffer[Size * 2 + 2] = {};
mutable wchar_t m_wideBuffer[Size + 1] = {};
constexpr unsigned char encrypt(unsigned char byte) const
{
return (A * byte + B) % 127;
}
unsigned char decrypt(unsigned char byte) const
{
return static_cast<unsigned char>(positive_modulo(ExtendedEuclidian<127, A>::y * (byte - B), 127));
}
public:
constexpr WideStringBuffer(const wchar_t* str)
{
for (size_t i = 0; i < Size; i++) {
wchar_t ch = str[i];
m_buffer[i * 2] = encrypt(static_cast<unsigned char>(ch & 0xFF));
m_buffer[i * 2 + 1] = encrypt(static_cast<unsigned char>((ch >> 8) & 0xFF));
}
m_buffer[Size * 2] = 0; // Null terminator
m_buffer[Size * 2 + 1] = 0; // Null terminator
}
const wchar_t* get() const
{
if (!m_isDecrypted)
{
// First decrypt the raw bytes
for (size_t i = 0; i < Size * 2; i++) {
m_buffer[i] = decrypt(m_buffer[i]);
}
// Then assemble into proper wchar_t values
for (size_t i = 0; i < Size; i++) {
m_wideBuffer[i] = static_cast<wchar_t>(
(static_cast<unsigned char>(m_buffer[i * 2])) |
(static_cast<unsigned char>(m_buffer[i * 2 + 1]) << 8)
);
}
m_wideBuffer[Size] = L'\0'; // Ensure null termination
m_isDecrypted = true;
}
return m_wideBuffer;
}
};
// macro for obfuscated string literals ANSI version
#define OBF_STR(str) \
([]() -> const char* { \
static constexpr auto buffer = StringBuffer<\
std::get<MetaRandom<__COUNTER__, 10>::value>(PrimeNumbers), \
MetaRandom<__COUNTER__, 126>::value, \
constexpr_strlen(str)>(str); \
return buffer.get(); \
})()
// macro for obfuscated string literals Wide version
#define OBF_WSTR(str) \
([]() -> const wchar_t* { \
static constexpr auto buffer = WideStringBuffer<\
std::get<MetaRandom<__COUNTER__, 10>::value>(PrimeNumbers), \
MetaRandom<__COUNTER__, 126>::value, \
constexpr_wcslen(L##str)>(L##str); \
return buffer.get(); \
})()
// Platform specific macros
#if defined(UNICODE) || defined(_UNICODE)
#define OBF_T OBF_WSTR
#else
#define OBF_T OBF_STR
#endif
// Specialized macro for file extensions
#define OBF_EXT(str) OBF_WSTR(str)
// Simple string to wide string conversion without std::string/std::wstring
inline WCHAR* AnsiToWide(const char* szAnsi)
{
int nLen = MultiByteToWideChar(CP_ACP, 0, szAnsi, -1, NULL, 0);
WCHAR* szwWide = (WCHAR*)malloc((nLen + 1) * sizeof(WCHAR));
if (szwWide) {
MultiByteToWideChar(CP_ACP, 0, szAnsi, -1, szwWide, nLen);
szwWide[nLen] = 0;
}
return szwWide;
}
// Utility to free the convert string
inline void FreeWideString(WCHAR* szwWide)
{
if (szwWide)
free(szwWide);
}
This is not bulletproof security, neither is the encryption. It just adds an extra layer of stealth. It does not protect against full memory dumping or runtime tracingetc but works well for avoiding detection via static strings in
.rdata or .text sections on x64dbg etc.