So it turns out that doing recursive LCG with COUNTER introduces a significant bottleneck. Essentially because the compiler has to instantiate a increasing chain of templates for each new value and since COUNTER increments with every macro use the total cost grows rapidly.
To fix this and make it more scalable for larger projects I refactored the random number generation logic to use a cost time, hash based approach instead.
Original LCG implementation has a recursive structure like this:
At COUNTER = 500 the compiler would instantiate 500 recursive templates just to produce a single value and in a projwith a load of obfuscated strings this quickly scales and can stretch compile times from seconds into minutes.
Indeed useful to const time Hashing I replaced the recursive LCG with a hash based generator that computes in constant time:
This has several key benefits.
It ensures constant-time (O(1)) compile time performance regardless of COUNTER. It also avoids recursive template instantiations entirely and offers better value distribution compared to previous basic LCG. This should still work as a functional drop in replacement for the previous implimentation.
Essentially for larger codebases the build time efficiency is something we should consider first before any metaprogramming wizardries.
Updated compile Time obfuscation Single Header
To fix this and make it more scalable for larger projects I refactored the random number generation logic to use a cost time, hash based approach instead.
Original LCG implementation has a recursive structure like this:
C++:
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 };
};
Indeed useful to const time Hashing I replaced the recursive LCG with a hash based generator that computes in constant time:
C++:
template <unsigned int N>
struct NoiseGenerator
{
static constexpr unsigned int hash(unsigned int x)
{
x ^= x >> 16;
x *= 0x85ebca6b;
x ^= x >> 13;
x *= 0xc2b2ae35;
x ^= x >> 16;
return x;
}
enum {
raw_value = hash(N ^ RandomSeed()),
value = raw_value % 127
};
};
This has several key benefits.
It ensures constant-time (O(1)) compile time performance regardless of COUNTER. It also avoids recursive template instantiations entirely and offers better value distribution compared to previous basic LCG. This should still work as a functional drop in replacement for the previous implimentation.
Essentially for larger codebases the build time efficiency is something we should consider first before any metaprogramming wizardries.
Updated compile Time obfuscation Single Header
C++:
#pragma once
#include <array>
#include <utility>
#include <Windows.h>
constexpr int RandomSeed(void) // same compile time seed generation
{
return ‘0’ * -40271 +
__TIME__[7] * 1 +
__TIME__[6] * 10 +
__TIME__[4] * 60 +
__TIME__[3] * 600 +
__TIME__[1] * 3600 +
__TIME__[0] * 36000;
}
//enhanced seed with file and line specific entropy
constexpr int EnhancedSeed(const char* file, int line)
{
int file_hash = 0;
for (int i = 0; file[i] && i < 32; ++i) {
file_hash = file_hash * 33 + file[i];
}
return RandomSeed() ^
(line * 0x9e3779b9) ^
(file_hash * 0x85ebca77) ^
(sizeof(void*) == 8 ? 0x12345678 : 0x87654321);
}
// hash based noise generator with constant compile time
template <unsigned int N>
struct NoiseGenerator
{
// simple hash function combining multiple operations
static constexpr unsigned int hash(unsigned int x)
{
x ^= x >> 16;
x *= 0x85ebca6b;
x ^= x >> 13;
x *= 0xc2b2ae35;
x ^= x >> 16;
return x;
}
// Generate value using hash of counter + enhanced seed
enum {
raw_value = hash(N ^ EnhancedSeed(__FILE__, __LINE__)),
value = raw_value % 127 // Keep in valid range
};
};
// enhanced noise generator with file and line context
template <unsigned int N, int Line>
struct ContextualNoiseGenerator
{
// simple hash function combining multiple operations
static constexpr unsigned int hash(unsigned int x)
{
x ^= x >> 16;
x *= 0x85ebca6b;
x ^= x >> 13;
x *= 0xc2b2ae35;
x ^= x >> 16;
return x;
}
// Generate value using hash of counter + line + enhanced seed
enum {
raw_value = hash(N ^ Line ^ EnhancedSeed(__FILE__, Line)),
value = raw_value % 127 // Keep in valid range
};
};
// Specialized generator for prime selection in 0-9 range
template <unsigned int N>
struct PrimeSelector
{
enum { value = NoiseGenerator<N>::raw_value % 10 };
};
template <unsigned int N, int Line> // enhanced prime selector with line context
struct ContextualPrimeSelector
{
enum { value = ContextualNoiseGenerator<N, Line>::raw_value % 10 };
};
// extended euclidean algorithm
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 function for string length calculation on 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);
}
};
// str 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;
m_buffer[Size * 2 + 1] = 0; // null terminators
}
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;
}
};
// optimized macros using enhanced noise generator with contextual entropy
#define OBF_STR(str) \
([]() -> const char* { \
static constexpr auto buffer = StringBuffer<\
std::get<ContextualPrimeSelector<__COUNTER__, __LINE__>::value>(PrimeNumbers), \
ContextualNoiseGenerator<__COUNTER__ + 1000, __LINE__>::value, \
constexpr_strlen(str)>(str); \
return buffer.get(); \
})()
#define OBF_WSTR(str) \
([]() -> const wchar_t* { \
static constexpr auto buffer = WideStringBuffer<\
std::get<ContextualPrimeSelector<__COUNTER__, __LINE__>::value>(PrimeNumbers), \
ContextualNoiseGenerator<__COUNTER__ + 2000, __LINE__>::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
// 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;
}
//free up the convert string
inline void FreeWideString(WCHAR* szwWide)
{
if (szwWide)
free(szwWide);
}
Последнее редактирование: