Stack string obfuscation + C function generator
For whatever suspicious reason it may be, you’ve found yourself in a predicament of needing to make the strings you define in your C programs invisible to static analysis software (e.g. floss or antivirus static scanning).
I knew that I wanted to make software that could make it quick to define C functions that produce non-readable strings to static analysis programs, and was initially stuck on how to do it. I guarantee you there are many ways of implementing this, but @Humza (the goat himself) brought up the idea of using relative addressing that’s calculated dynamically to serve as an encryption key (this program just uses a simple XOR).
The idea is instead of storing our strings as character byte arrays, we generate a function that mathmatically adds up your encrypted string in 4 byte (integer) chunks which is then interpreted as characters and appended to an encrypted string (note this process assumes little endian in the target processor). Afterwards you can use the provided decrypt function that takes the encrypted string and decodes by again calculating the target string function offset key.
I’ve attached the main stringGen.c file; if you want an easy to compile with makefile on the side you can just copy and paste the below contents into a file named “Makefile”
Feel free to ask any questions and enjoy :-P
Props to both @yearbarber and @shift for bringing attention to compiler optimization possibily inlining (and screwing up) baseFunction
Makefile
CC = gcc
CFLAGS = -O0 -g0 -s
all: stringGen
stringGen: stringGen.c
$(CC) $(CFLAGS) stringGen.c -o stringGen
.PHONY: clean
clean:
rm -f stringGen
stringGen.c
/*
Made with love and care (aka without AI) for myself and the 332 community by Unified.
This program produces functions that enable stack string obfuscation.
The general flow goes you compile using make, then run the program like `./stringGen "hi there"`
The first part is copying and pasting the appropriate file header + the dummy function.
You will then need to compile and run a version of your software that runs the dummy function to get an offset value.
With this offset value rerun the program such as `./stringGen "hi there" 172` to get a new version of the function to overwrite.
Because the encryption processes uses function offsets to the baseFunction, I really recommend waiting until
you've fully created your program before doing this because recalculating offsets and functions is a pain in the ass.
I've tested the functions generated by this program against floss (at least no decoded strings when I was using it).
Feel free to message me if you notice any bugs that need fixing.
*/
// BEGIN EXAMPLE FILE HEADER (you would put this in your program)
#if defined(_MSC_VER)
#define NOINLINE __declspec(noinline)
#elif defined(__GNUC__) || defined(__clang__)
#define NOINLINE __attribute__((noinline))
#else
#define NOINLINE
#endif
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>
#define STRING_DEBUG 1
NOINLINE int baseFunction() {return 0;}
int numBytes(int inp)
{
int mask = 0xff000000;
int counter = 0;
for (int i = 0; i < 4; i++)
{
counter += (inp & mask) != 0;
mask = mask >> 8;
}
return counter;
}
void decryptString(char* encryptedString, void* encryptFunction)
{
int offset = (encryptFunction - (void*)baseFunction) % 86 + 169;
for (int i = 0; i < strlen(encryptedString); i++)
encryptedString[i] ^= offset;
}
// END EXAMPLE FILE HEADER
// Assume little endian (most processors are little endian)
char* generateStringFunction(char* inpString, int offset)
{
// Create random function ending
char functionSignature[4] = {'A' + (rand() % 26), 'A' + (rand() % 26), 'A' + (rand() % 26), '\0'};
int inpStringLength = strlen(inpString);
// Get number of full chunks
int fullChunks = inpStringLength / 4; // e.g. 9 > 2
int chunkRemainder = inpStringLength % 4; // e.g. 9 > 1
char* outputFunction = malloc(500 + 350 * fullChunks + 100 * chunkRemainder);
char* headerStringUnformatted = "\
void stringFunction%s(char* stringOutput)\n\
{\n\
\tint offset = ((void*)stringFunction%s - (void*) baseFunction) %% 86 + 169;\n\
\tif (STRING_DEBUG)\n\
\t{\n\
\t\tprintf(\"[STRING DEBUG] stringFunction%s %%d\", offset);\n\
\t\treturn;\n\
\t}\n\
\tchar* strPtr = stringOutput;\n\
\tunsigned int encoded;\n\
\tchar* buf = (char*)&encoded;\n\
\tint byteNum = 0;\n\n";
// Format and add header to the top
sprintf(outputFunction, headerStringUnformatted, functionSignature, functionSignature, functionSignature);
int charIndex = 0;
// Add each chunk inside
char* chunkStringUnformatted = "%s\
\tencoded = 0x0;\n\
\tencoded += %d;\n\
\tencoded = encoded << 8;\n\
\tencoded += %d;\n\
\tencoded = encoded << 8;\n\
\tencoded += %d;\n\
\tencoded = encoded << 8;\n\
\tencoded += %d;\n\
\tbyteNum = numBytes(encoded);\n\
\tfor (int i = byteNum - 1; i >= 0; i--)\n\
\t{\n\
\t\t*strPtr = buf[i];\n\
\t\tstrPtr++;\n\
\t}\n\n";
for (int i = 0; i < fullChunks; i++)
{
sprintf(outputFunction, chunkStringUnformatted, outputFunction, inpString[charIndex] ^ offset, inpString[charIndex+1] ^ offset, inpString[charIndex+2] ^ offset, inpString[charIndex+3] ^ offset);
charIndex += 4;
}
if (chunkRemainder != 0)
{
// Define header of last part
sprintf(outputFunction, "%s\n\tencoded = 0x0;\n", outputFunction);
for (int i = 0; i < chunkRemainder - 1; i++)
{
sprintf(outputFunction, "%s\tencoded += %d;\n\tencoded = encoded << 8;\n", outputFunction, inpString[charIndex] ^ offset);
charIndex++;
}
char* lastChunk = "%s\
\tencoded += %d;\n\
\tbyteNum = numBytes(encoded);\n\
\tfor (int i = byteNum - 1; i >= 0; i--)\n\
\t{\n\
\t\t*strPtr = buf[i];\n\
\t\tstrPtr++;\n\
\t}\n";
sprintf(outputFunction, lastChunk, outputFunction, inpString[charIndex] ^ offset);
}
sprintf(outputFunction, "%s}", outputFunction);
// Note; these functions could be independently called so these functions want to make sure something else was ran (ENGAGE trigger)
return outputFunction;
}
// Returns "ABCDEF" encoded with some value (that I forgot)
void exampleStringFunction(char* stringOutput)
{
// Offset between decimal 169 to 255 (extended ASCII)
int offset = ((void*)exampleStringFunction - (void*) baseFunction) % 86 + 169;
if (STRING_DEBUG)
{
printf("[STRING DEBUG] exampleStringFunction %d\n", offset);
return;
}
char* strPtr = stringOutput;
unsigned int encoded;
char* buf = (char*)&encoded;
int byteNum = 0;
// String construct
encoded = 0x0;
encoded += 0xdb;
encoded = encoded << 8;
encoded += 0xd8;
encoded = encoded << 8;
encoded += 0xd9;
encoded = encoded << 8;
encoded += 0xde;
byteNum = numBytes(encoded);
for (int i = byteNum - 1; i >= 0; i--)
{
*strPtr = buf[i];
strPtr++;
}
encoded = 0x0;
encoded += 0xdf;
encoded = encoded << 8;
encoded += 0xdc;
byteNum = numBytes(encoded);
for (int i = byteNum - 1; i >= 0; i--)
{
*strPtr = buf[i];
strPtr++;
}
}
void printUsage()
{
const char* usageString =
"Usage:\n"
"\t./stringGen\n"
"\t\tPrints this help menu\n"
"\t./stringGen \"[TEXT]\"\n"
"\t\tGenerates a dummy function to put in place and gives reminders of how to find offset XOR value\n"
"\t./stringGen \"[TEXT]\" [OFFSET]\n"
"\t\tGenerates full version of function to copy and paste inside of program\n";
printf("%s\n", usageString);
}
void printStarter()
{
char* headerString = "\
COPY AND PASTE AT TOP OF FILE\n\
=============================================================================\n\
#if defined(_MSC_VER)\n\
\t#define NOINLINE __declspec(noinline)\n\
#elif defined(__GNUC__) || defined(__clang__)\n\
\t#define NOINLINE __attribute__((noinline))\n\
#else\n\
\t#define NOINLINE\n\
#endif\n\n\
#define STRING_DEBUG 1\n\
\n\
NOINLINE int baseFunction() {return 0;}\n\
\n\
int numBytes(int inp)\n\
{\n\
\tint mask = 0xff000000;\n\
\tint counter = 0;\n\
\tfor (int i = 0; i < 4; i++)\n\
\t{\n\
\t\tcounter += (inp & mask) != 0;\n\
\t\tmask = mask >> 8;\n\
\t}\n\
\treturn counter;\n\
}\n\
\n\
void decryptString(char* encryptedString, void* encryptFunction)\n\
{\n\
\tint offset = (encryptFunction - (void*)baseFunction) % 86 + 169;\n\
\tfor (int i = 0; i < strlen(encryptedString); i++)\n\
\t\tencryptedString[i] ^= offset;\n\
}\n\
=============================================================================\n\
RUN BELOW ENCRYPT FUNCTION AND GET FUNCTION OFFSET\n";
printf("Please copy and paste, and run with STRING_DEBUG set to 1 and run string function to get offset.\n");
printf("%s\n", headerString);
}
int main(int argc, char** argv)
{
// Check if arguments are not appropriate
if (argc <= 1 || argc >= 4)
{
printUsage();
return 1;
}
int offset = baseFunction();
if (argc == 2)
printStarter();
else
offset = atoi(argv[2]);
// Seed random
srand((unsigned int)time(NULL));
// Generate function with seed
char* outputFunction = generateStringFunction(argv[1], offset);
printf("%s\n", outputFunction);
free(outputFunction);
return 0;
}
Fun fact
If you’re curious about why we calculate our offsets like this:
((void*)exampleStringFunction - (void*) baseFunction) % 86 + 169;
it is to eliminate the possibility of our offset XORing and creating a nullbyte with a standard ascii character. This consequently means that if you’re using extended ascii (especially on the higher end) you run a small, yet existant possibility of generating a function that doesn’t work because you add a nullbyte to the constructed encrypted string (it would screw up decryptString that uses strlen).
Comments could not load. This is often caused by a privacy extension or strict tracking protection blocking GitHub. Try allowlisting this site or opening the page in another browser.