String obfuscation using C++ constexpr
Table of Contents
Obfuscation
Intro
Ever wondered how to obfuscating strings using C++ constexpr? That’s the topic of this post!
Typically, when compiling a program, strings are directly embedded into the binary in their raw form. Opening the final binary executable a hex editor, or even notepad, these strings are readily visible. Various tools, such as “strings” in Linux, can also be used to extract these strings.
String Obfuscation is a basic method to increase software resiliency against reverse engineers. While it does not guarantee absolute invulnerability for your software, it serves as a fundamental measure towards bolstering binary security.
To have obfuscated strings in the final binary, we need to encrypt strings at compile time. Unfortunately, most of the programming languages are not feature rich when it comes to compile-time function execution. Usually, all you have is just a simple engine that enables you to write Macros. Therefore, string obfuscation needs to be done manually with a pre-compilation script. That will be an ugly solution, that imposes an additional step in your build process.
Here, we are going to explore an alternative using C++ constexpr
.
The source codes for this technique is available at GitHub.
C++ constexpr
The C++ constexpr
feature enables us to specify a function or expression to the compiler,
indicating that the computation result can be evaluated at compile time if given constant parameter values.
Here is an example showcasing the calculation of the factorial of 4 during compile time using C++ constexpr
:
#include <iostream>
constexpr int factorial(int n) {
return n <= 1 ? 1 : n * factorial(n - 1);
}
int main() {
constexpr int result = factorial(4);
std::cout << "Factorial of 4 is: " << result << std::endl;
return 0;
}
Designing an string obfuscater
The code below shows how to encrypt strings with simple xor encryption method using constexpr
.
#define KEY 0x55
template <unsigned int N>
struct obfuscator {
// m_data stores the obfuscated string.
char m_data[N] = {0};
// Using constexpr ensures that the strings
// will be obfuscated in this
// constructor function at compile time.
constexpr obfuscator(const char* data) {
//Implement encryption algorithm here.
for (unsigned int i = 0; i < N; i++) {
m_data[i] = data[i] ^ KEY;
}
}
};
int main() {
// Store "Hello" in obfuscated form using simple
// xor encryption.
constexpr auto obfuscated_str = obfuscator<6>("Hello");
return 0;
}
In the above code, obfuscator is a class with a member array that stores data encrypted. Defining obfuscator’s constructor as constexpr ensures that this member array is encrypted at compile time. Although any symmetric encryption algorithm can be used, we employ the simplest one here, XOR encryption. Compiling the above code:
gcc --std=c++14 -O0 -S -masm=intel constexpr2.cc
And examining the generated assembly, indeed we see that the string “Hello” is stored in the encrypted form:
obfuscated_str.2087:
.byte 29
.byte 48
.byte 57
.byte 57
.byte 58
.byte 85
.ident "GCC: (Ubuntu 9.3.0-10ubuntu2) 9.3.0"
.section .note.GNU-stack,"",@progbits
.section .note.gnu.property,"a"
Note that the Ascii code for ‘H’ is 72, and when xored with 85 (0x55), the result will be 29, which shows that “Hello” is stored encrypted.
To use the encrypted strings, we need to have them decrypted first. Therefore, we create a decryption method for the “obfuscator” class:
#include "stdio.h"
#define KEY 0x55
template <unsigned int N>
struct obfuscator {
char m_data[N] = {0};
constexpr obfuscator(const char* data) {/*…*/};
// deobfoscate decrypts the strings. Implement decryption
// algorithm here.
void deobfoscate(unsigned char * des) const{
int i = 0;
do {
des[i] = m_data[i] ^ KEY;
i++;
} while (des[i-1]);
}
};
int main() {
// Store "Hello" in obfuscated form using simple xor encryption.
constexpr auto obfuscated_str = obfuscator<6>("Hello");
// Create a buffed to store decrypted string.
unsigned char buff[0x10] = {0};
// Decrypt the string
obfuscated_str.deobfoscate(buff);
printf("%s", buff); // output: Hello
return 0;
}
Compiling above code
gcc --std=c++14 -fno-stack-protector -O0 -S -masm=intel constexpr3.cc
And looking at the generated assembly code again, we see that this time, “Hello” is stored as a stack string which indeed is even better for binary protection. Not only “Hello” is stored in an encrypted format, but also there are bytecodes in between each character, which confuses auto analysis tools even more.
main:
.LFB2:
.cfi_startproc
endbr64
push rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
mov rbp, rsp
.cfi_def_cfa_register 6
sub rsp, 32
mov BYTE PTR -6[rbp], 29
mov BYTE PTR -5[rbp], 48
mov BYTE PTR -4[rbp], 57
mov BYTE PTR -3[rbp], 57
mov BYTE PTR -2[rbp], 58
mov BYTE PTR -1[rbp], 85
mov QWORD PTR -32[rbp], 0
mov QWORD PTR -24[rbp], 0
lea rdx, -32[rbp]
lea rax, -6[rbp]
mov rsi, rdx
mov rdi, rax
call _ZNK10obfuscatorILj6EE11deobfoscateEPh
lea rax, -32[rbp]
mov rsi, rax
lea rdi, .LC0[rip]
mov eax, 0
call printf@PLT
mov eax, 0
leave
.cfi_def_cfa 7, 8
ret
.cfi_endproc
Packing everything together
A simple macro can be used to pack everything into a single line with the employment of a lambda function:
// This macro is a lambda function to pack all required steps
// into one single command when defining strings.
#define STR(str) \
[]() -> char* { \
constexpr auto size = sizeof(str)/sizeof(str[0]); \
constexpr auto obfuscated_str = obfuscator<size>(str); \
static char original_string[size]; \
obfuscated_str.deobfoscate((unsigned char *)original_string); \
return original_string; \
}()
Using above macro and packing everything in a header file, we can simply encrypt our strings in a C++ application.
#include "obfuscator.hh"
#include "stdio.h"
auto gstr = STR("Global HELLO\n");
int main() {
printf("%s", gstr);
printf("%s", STR("Stack HELLO\n"));
return 0;
}
Conclusion
C++ constexpr
makes it much more convenient to obfuscate strings at compile time.
Using this feature, we won’t need any additional step in your build process.
Everything is wrapped up in our source code which also makes it cleaner.
On the flip side, note that constexpr
is only supported on C++14 or above.
Unfortunately, C does not support constexprs
either.