Search code examples
c++obfuscation

Compile-time std::string obfuscation


I trying to write function, for compile-time hide (obfuscate) text strings in the binary code. Purpose: to disable easy search and modify text messages with binary editor.

Algorithm is simple: convert chars within string to deltas, i. e. string abcd must be saved in the binary file as a\001\001\001.

I wrote the following code, it works:

#include <string>
#include <stdio.h>
#include <string.h>

constexpr std::string str_encrypt(const char *str) {
    std::string rc; 
    unsigned char x = 0, c;
    while(c = *str++) {
         rc.push_back(c - x); 
        x = c;
    }   
    return rc; 
}

std::string str_decrypt(const std::string &str) {
    std::string rc; 
    unsigned char x = 0;
    for(unsigned char c : str)
        rc.push_back(x += c); 
    return rc; 
}

const std::string hello(str_encrypt("Hello, world!"));

int main(int argc, char **argv) {
    for(unsigned char c : hello)
        printf("%d ", c); 
    putchar('\n');
    printf("S=[%s]\n", str_decrypt(hello).c_str());
    return 0;
}

But string still is not "encrypted" within binary file:

$ c++ -std=c++2b -O2 constexpr.cpp  && strings a.out | grep Hello
Hello, world!

When I change constexpr to consteval, I see compilation error:

$ c++ -std=c++2b -O2 constexpr.cpp 
constexpr.cpp:23:36: error: ‘std::string{std::__cxx11::basic_string<char>::_Alloc_hider{((char*)(&<anonymous>.std::__cxx11::basic_string<char>::<anonymous>.std::__cxx11::basic_string<char>::<unnamed union>::_M_local_buf))}, 13, std::__cxx11::basic_string<char>::<unnamed union>{char [16]{'H', '\035', '\007', '\000', '\003', '\37777777675', '\37777777764', 'W', '\37777777770', '\003', '\37777777772', '\37777777770', '\37777777675', 0}}}’ is not a constant expression
   23 | const std::string hello(str_encrypt("Hello, world!"));

My question: Is possible to write such compile-time encryption? If so, please suggest, how to do.

My compiler: g++ (GCC) 12.2.1 20230201


Solution

  • The problem is that str_encrypt is not evaluated at compile time.

    When I change constexpr to consteval, I see compilation error

    This is a hint, when we switch to consteval, which requires str_encrypt to be an immediate / compile time function, the compiler gives an error.

    Since C++20, std::string can be used in compile time contexts, if it is a transient allocation, i.e. the string must be destroyed before exiting the compile time context.

    In this example this is not the case, so str_encrypt cannot be a compile time function.

    If we had put:

    constexpr std::string hello(str_encrypt("Hello, world!"));

    Then we would see a similar error to using consteval on str_encrypt.

    However, arrays or std::arrays can be constexpr.

    So if instead of returning a std::string we return std::array then the example can work as expected.

    This modified example works with g++ / clang++ and requires C++17. Using consteval would be advisable, to force compile time evaluation, but of course requires C++20.

    #include <string>
    #include <stdio.h>
    #include <string.h>
    #include <array>
    
    template <size_t N>
    constexpr std::array<char, N> str_encrypt(const char (&str)[N]) {
        std::array<char, N> output{};
        unsigned char x = 0, c = 0;
    
        for (int i = 0; i < N; ++i) {
            c = str[i];
            output[i] = (c - x);
            x = c;
        }
        return output;
    }
    
    template <size_t N>
    std::string str_decrypt(const std::array<char, N> &str) {
        std::string rc;
        unsigned char x = 0;
        for(unsigned char c : str)
            rc.push_back(x += c);
        return rc;
    }
    
    constexpr auto hello = str_encrypt("Hello, world!");
    
    int main(int argc, char **argv) {
        for(unsigned char c : hello)
            printf("%d ", c);
        putchar('\n');
        printf("S=[%s]\n", str_decrypt(hello).c_str());
        return 0;
    }
    

    Output:

    $ ./example
    72 29 7 0 3 189 244 87 248 3 250 248 189 223
    S=[Hello, world!]
    

    The string "Hello, world!" is not in the binary, nor is the str_encrypt function.

    $ strings example | grep Hello
    
    $ objdump -t -C example | grep str_
    0000000000001302  w    F .text  00000000000000a6              std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > str_decrypt<14ul>(std::array<char, 14ul> const&)
    

    Godbolt: https://godbolt.org/z/vPj3bW6Yz