Fixed string obf for x32 devices

This commit is contained in:
WolverinDEV 2020-01-24 22:01:47 +01:00
parent ba89fe72ab
commit 42feb48467

View File

@ -2,11 +2,12 @@
#include <cstring> #include <cstring>
#include <array> #include <array>
#include <string_view> #include <string_view>
#include <iostream>
namespace str_obf { namespace str_obf {
constexpr static auto max_key_power = 6; /* 64 bytes max */
namespace internal { namespace internal {
template <typename char_t, size_t buffer_size, size_t size, typename key_t> template <typename char_t, std::uint64_t buffer_size, std::uint64_t size, typename key_t>
struct message { struct message {
/* helper to access the types */ /* helper to access the types */
static constexpr auto _size = size; static constexpr auto _size = size;
@ -14,33 +15,33 @@ namespace str_obf {
using _key_t = key_t; using _key_t = key_t;
/* memory */ /* memory */
std::array<char_t, buffer_size> buffer{0}; std::array<char_t, buffer_size> buffer{};
key_t key{}; key_t key{};
/* some memory access helpers */ /* some memory access helpers */
std::string_view string_view() const noexcept { return {this->buffer.begin(), this->length}; } [[nodiscard]] std::string_view string_view() const noexcept { return {this->buffer.begin(), this->length}; }
std::string string() const { return {this->buffer.begin(), this->length}; } [[nodiscard]] std::string string() const { return {this->buffer.begin(), this->length}; }
const char* c_str() const noexcept { return &this->buffer[0]; } [[nodiscard]] const char* c_str() const noexcept { return &this->buffer[0]; }
}; };
constexpr auto time_seed() noexcept { constexpr auto time_seed() noexcept {
std::uint64_t shifted = 0; std::uint64_t shifted = 0;
for( const auto c : __TIME__ ) for(const auto c : __TIME__)
{ {
shifted <<= 8; shifted <<= 8U;
shifted |= c; shifted |= (unsigned) c;
} }
return shifted; return shifted;
} }
constexpr uint64_t string_hash(const char* str, int h = 0) noexcept { constexpr uint64_t string_hash(const char* str, int h = 0) noexcept {
return !str[h] ? 5381 : (string_hash(str, h + 1) * 33) ^ str[h]; return !str[h] ? 5381 : (unsigned) (string_hash(str, h + 1) * 33) ^ (unsigned) str[h];
} }
#ifdef WIN32 #ifdef WIN32
#pragma warning(disable: 4146) // unary minus operator applied to unsigned type, result still unsigned #pragma warning(disable: 4146) // unary minus operator applied to unsigned type, result still unsigned
#endif #endif
constexpr std::uint32_t rng32_next(std::uint64_t& state, const std::uint32_t& inc) noexcept { constexpr std::uint32_t rng32_next(std::uint64_t& state, const std::uint32_t& inc) noexcept {
std::uint64_t oldstate = state; std::uint64_t oldstate = state;
@ -52,47 +53,34 @@ namespace str_obf {
return (xorshifted >> rot) | (xorshifted << ((-rot) & 31)); return (xorshifted >> rot) | (xorshifted << ((-rot) & 31));
} }
#ifdef WIN32 #ifdef WIN32
#pragma warning(default: 4146) // unary minus operator applied to unsigned type, result still unsigned #pragma warning(default: 4146) // unary minus operator applied to unsigned type, result still unsigned
#endif #endif
/* we use a buffer dividable by 8 so the compiler could do crazy shit, when loading (moving) the characters */ /* we use a buffer dividable by 8 so the compiler could do crazy shit, when loading (moving) the characters */
constexpr size_t recommand_message_buffer(size_t message_size) noexcept { constexpr std::uint64_t recommand_message_buffer(std::uint64_t message_size) noexcept {
if(message_size <= 4) return 4; /* we could use the eax register here */
return (message_size & 0xFFFFFFF8) + ((message_size & 0x7) > 0 ? 8 : 0); return (message_size & 0xFFFFFFF8) + ((message_size & 0x7) > 0 ? 8 : 0);
} }
} }
inline void _invalid_key_size() {}
template <typename char_t, typename key_t> template <typename char_t, typename key_t>
constexpr inline void crypt(char_t* begin, size_t length, const key_t& key) noexcept { constexpr inline void crypt(char_t* begin, std::uint64_t length, const key_t& key) noexcept {
static_assert(sizeof(char_t) == 1, "Currently only 8 bit supported"); static_assert(sizeof(char_t) == 1, "Currently only 8 bit supported");
if(length == 0) return; if(length == 0) return;
if(key.size() == 0) _invalid_key_size();
auto kbegin = std::begin(key); if(key.size() & (key.size() - 1)) _invalid_key_size(); /* key must be an power of 2 */
auto kend = std::end(key);
if(kbegin == kend) return;
auto it = kbegin;
auto left = length; auto left = length;
size_t key_index{0};
#ifdef __clang__
/*
* Enforce clang here to not evaluate this loop at compile time as long its not called in a constexpr context!
* We lose compiler opts. here a bit, but cases where a xor was made over larger than 8 bit registers were really rare!
*/
#pragma nounroll
#endif
while(left-- > 0) { while(left-- > 0) {
if(it == kend) key_index &= key.size();
it = kbegin; *begin ^= key[key_index + key_index];
*begin ^= *it;
it++;
begin++;
} }
} }
template <typename char_t, size_t message_size, typename key_t> template <typename char_t, std::uint64_t message_size, typename key_t>
constexpr inline auto encode(const char_t(&message)[message_size], const key_t& key) noexcept { constexpr inline auto encode(const char_t(&message)[message_size], const key_t& key) noexcept {
constexpr auto message_buffer_size = internal::recommand_message_buffer(message_size); constexpr auto message_buffer_size = internal::recommand_message_buffer(message_size);
internal::message<char_t, message_buffer_size, message_size, key_t> result{}; internal::message<char_t, message_buffer_size, message_size, key_t> result{};
@ -102,11 +90,11 @@ namespace str_obf {
auto bit = result.buffer.begin(); auto bit = result.buffer.begin();
auto mit = message; auto mit = message;
size_t index = message_size; std::uint64_t index = message_size;
while(index-- > 0) while(index-- > 0)
*bit++ = *mit++; *bit++ = *mit++;
size_t padding = message_buffer_size - message_size; std::uint64_t padding = message_buffer_size - message_size;
if(padding) { /* to make the string end less obvious we add some noise here (it does not harm user performance) */ if(padding) { /* to make the string end less obvious we add some noise here (it does not harm user performance) */
std::uint64_t rng_seed = internal::time_seed() ^ internal::string_hash(message, 0); std::uint64_t rng_seed = internal::time_seed() ^ internal::string_hash(message, 0);
std::uint64_t rng_base = rng_seed; std::uint64_t rng_base = rng_seed;
@ -119,10 +107,10 @@ namespace str_obf {
return result; return result;
} }
template <typename char_t, size_t length> template <typename char_t, std::uint64_t length>
constexpr inline auto str_length(const char_t(&message)[length]) noexcept { return length; } constexpr inline auto str_length(const char_t(&message)[length]) noexcept { return length; }
template <typename char_t, size_t buffer_size, size_t message_size, typename key_t> template <typename char_t, std::uint64_t buffer_size, std::uint64_t message_size, typename key_t>
inline std::string decode(const internal::message<char_t, buffer_size, message_size, key_t>& message) { inline std::string decode(const internal::message<char_t, buffer_size, message_size, key_t>& message) {
std::string result{}; std::string result{};
result.resize(message_size); result.resize(message_size);
@ -133,26 +121,32 @@ namespace str_obf {
return result; return result;
} }
constexpr inline size_t generate_key_length(std::uint64_t seed, size_t max_size) noexcept { /* length is a power of 2! */
if(max_size <= 8) return max_size; /* We dont need a longer key then the message itself. As well compiler opt. doesn't matter here */ constexpr inline std::uint64_t generate_key_length(std::uint64_t seed, std::uint64_t message_size) noexcept {
if(max_size > 64) max_size = 64; if(message_size <= 1) return 1;
size_t power2{0};
while(message_size >>= 1U)
power2++;
if(power2 > max_key_power)
power2 = max_key_power;
std::uint64_t rng_base = seed; std::uint64_t rng_base = seed;
size_t length = 0; std::uint64_t length = 0;
do { do {
length = (size_t) ((internal::rng32_next(rng_base, (uint32_t) seed) >> 12UL) & 0xFFUL); length = (std::uint64_t) ((internal::rng32_next(rng_base, (uint32_t) seed) >> 12UL) & 0xFFUL);
} while(length < 8 || length >= max_size); } while(length == 0 || length > power2);
/* it does not really matter if we have a 8 byte aligned number here, because we iterate so or so byte for byte */ return 1U << length;
return length;
} }
template <size_t line_number, size_t max_size> template <uint64_t line_number, std::uint64_t message_size>
constexpr inline auto generate_key(const char* _str_seed) noexcept { constexpr inline auto generate_key(const char* _str_seed) noexcept {
std::uint64_t rng_seed = internal::time_seed() ^ internal::string_hash(_str_seed, 0) ^ line_number; std::uint64_t rng_seed = internal::time_seed() ^ internal::string_hash(_str_seed, 0) ^ line_number;
std::uint64_t rng_base = rng_seed; std::uint64_t rng_base = rng_seed;
constexpr size_t key_length = generate_key_length(internal::time_seed() ^ (line_number << 37UL), max_size); constexpr std::uint64_t key_length = generate_key_length(internal::time_seed() ^ (line_number << 37UL), message_size);
std::array<uint8_t, key_length> result{}; std::array<uint8_t, key_length> result{};
for(auto& it : result) for(auto& it : result)
it = (internal::rng32_next(rng_base, (uint32_t) rng_seed) >> 16UL) & 0xFFUL; it = (internal::rng32_next(rng_base, (uint32_t) rng_seed) >> 16UL) & 0xFFUL;
@ -202,4 +196,4 @@ constexpr auto variable_name = ::str_obf::encode(string, str_obf::generate_key<_
(([]{ \ (([]{ \
static strobf_define(_, message); \ static strobf_define(_, message); \
return strobf_val(_); \ return strobf_val(_); \
})()) })())