Back to blog
Sep 17, 2024
5 min read

Obfuscation

How to obfuscate a specific string in your software?

This project demonstrates a straightforward obfuscation technique that leverages a pseudo-random number generator (PRNG) and bitwise operations. It features functions to:

-Generate a seed from an entropy string.

-Obfuscate data using the generated seed.


Explanation

As an amateur reverse engineer, I have been exploring anti-debugging and obfuscation techniques to deepen my understanding. Today, I will explain these techniques and share my implementations, showcasing what I’ve learned in the process.

The project consists of 4 functions that guide the obfuscation process. Let’s dive into each function and explore how they contribute to the overall obfuscation technique.

Code:

constexpr uint32_t modulus() {
    return 0x7fffffff;
}

The name of the function is modulus and not because i liked it but because the main function of it is to return a constant value that would help us to constrain the output range of the PRNG.

if we go to the next function Code:

constexpr uint32_t prng(const uint32_t input) {
	return (input * 48271) % modulus();
}

We can see our PRNG function which return an interesting value return (input * 48271) % modulus(); , we have one argument called input by using a LCG method we are able to produce a pseudo-random output wthout exceding the limit, therefore we got a range.

Code:

template<size_t N>
constexpr uint32_t seed(const char(&entropy)[N], const uint32_t init_val = 0) {
	auto value{ init_val };
	for (size_t i{ 0 }; i < N; i++) {
		value = (value & ((~0) << 8)) | ((value & 0xFF) ^ entropy[i]);
		value = value << 8 | value >> ((sizeof(value) * 8) - 8);
	}

	while (value > modulus()) {
		value = value >> 1;
	}
	return value << 1 | 1;
}

The seed function generates a pseudo-random seed value based on an input entropy string and an optional initial value. This seed value can then be used to initialize a pseudo-random number generator. In other words tt uses bitwise operations and rotations to mix the bits of the entropy into the seed value. After processing, it ensures the seed is within the desired range by adjusting it with bit shifts and ensuring it is odd.

But okay, i still dont understand what is happening. Okay let me break it down for you :)

for (size_t i{ 0 }; i < N; i++) {
		value = (value & ((~0) << 8)) | ((value & 0xFF) ^ entropy[i]);
		value = value << 8 | value >> ((sizeof(value) * 8) - 8);
	}

Why do we need a loop?πŸ˜”

That loop iterates through each character in the entropy array, and yeah N is the length of the entropy array. :)

value = (value & ((~0) << 8)) | ((value & 0xFF) ^ entropy[i]);

(value & ((~0) << 8)) -> it clears the least significant 8 bits of our variable by masking them with with a bitwaise AND operation(set to 0 )

(~0) << 8 -> creates a mask where only the highest bits are set to 1

Okay now we know the first part of our line of code what about the other part??? (value & 0xFF) -> we extract the least significant 8 bits of value

((value & 0xFF) ^ entropy[i]) -> WE XORs these 8 bits with the current entropy character. yeah yeah, this operation obfuscates the value based on the current character from entropy , BRAVO.

By the way by using this beautiful character ”|” we combine both parts.

value = value << 8 | value >> ((sizeof(value) * 8) - 8);

value << 8: -> so yeah, we shift value 8 bits to the left

value >> ((sizeof(value) * 8) - 8) -> okay vice versa we shift value by the number of bits calculated in (sizeof(value) * 8) - 8

SO if we combine that line of code we ensure that the bits shifted out on one side come back in from the other side.

ANDDDD The last function Code:

template <typename T, size_t N>
struct obfuscate {
	int seed;
	T data[N];
};
template<size_t N>
constexpr auto crypt(const char(&input)[N], const uint32_t seed = 0) {
	obfuscate<char, N>obf{};
	obf.seed = seed;
	for (uint32_t index{ 0 }, stream{ seed }; index < N; index++) {
		obf.data[index] = input[index] ^ stream;
		stream = prng(stream);
	}

	return obf;
}

(I assume you know what a template and a struct are :] )

As thename of the function says it obfuscate/encrypt the value we send as a string.

But how? πŸ€”πŸ€”πŸ€”

->Creates an obfuscate struct named obf to store the obfuscated data and the seed used.

->Loop through Input: *For each character in the input array, it XORs the character with the current PRNG value *Updates obf.data[index] with the obfuscated character *Updates the PRNG value using the prng function to generate the next value in the sequence

obf.data[index] = input[index] ^ stream;
		stream = prng(stream);

What can we say about it in the end?? The obfuscate struct and the crypt function work together to obfuscate data using a simple PRNG-based method. Specifically, the crypt function takes a string and a seed as input, applies a bitwise XOR to each character using the PRNG, and stores the result along with the seed in an obfuscate struct.

Besides that, even tho it obfuscate the data (from input) is still far better.


Result:

Result.

Now you know(basic): Yep


Full Code:

#include<iostream>
#include<Windows.h>

#define key 0x55

constexpr uint32_t modulus() {
	return 0x7fffffff;
}
//pseudo-random number generator (PRNG)
constexpr uint32_t prng(const uint32_t input) {
	return (input * 48271) % modulus();
}


template<size_t N>
constexpr uint32_t seed(const char(&entropy)[N], const uint32_t init_val = 0) {
	auto value{ init_val };
	for (size_t i{ 0 }; i < N; i++) {
		value = (value & ((~0) << 8)) | ((value & 0xFF) ^ entropy[i]);
		value = value << 8 | value >> ((sizeof(value) * 8) - 8);
	}

	while (value > modulus()) {
		value = value >> 1;
	}

	return value << 1 | 1;
}

template <typename T, size_t N>
struct obfuscate {
	int seed;
	T data[N];
};

template<size_t N>
constexpr auto crypt(const char(&input)[N], const uint32_t seed = 0) {
	obfuscate<char, N>dark{};
	dark.seed = seed;
	for (uint32_t index{ 0 }, stream{ seed }; index < N; index++) {
		dark.data[index] = input[index] ^ stream;
		stream = prng(stream);
	}

	return dark;
}

int main() {
	
	constexpr auto obfuscated_str = crypt("Hello", key);

	
	std::string pre_obfuscated;
	for (const auto& c : obfuscated_str.data) {
		pre_obfuscated.push_back(c);
	}
	std::cout << pre_obfuscated;

	return 0;

}