archive: add 5 repo prompt(s) [skip ci]

This commit is contained in:
github-actions[bot]
2026-02-24 13:58:42 +00:00
parent c781dd029a
commit 6bd4138407
5 changed files with 118594 additions and 0 deletions
+734
View File
@@ -0,0 +1,734 @@
Project Path: arc_adamyaxley_Obfuscate_qk_04zlc
Source Tree:
```txt
arc_adamyaxley_Obfuscate_qk_04zlc
├── CMakeLists.txt
├── LICENSE
├── README.md
├── obfuscate.h
├── test_bloat.cpp
├── test_bloat.py
├── test_obfuscated.cpp
├── test_obfuscated.py
└── test_unit.cpp
```
`CMakeLists.txt`:
```txt
cmake_minimum_required(VERSION 3.14)
project(Obfuscate)
option(GENERATE_TESTS "GENERATE_TESTS" ON)
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# Set unix platforms to output executables to Release and Debug folders (Windows does by default)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE ${CMAKE_BINARY_DIR}/Release)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG ${CMAKE_BINARY_DIR}/Debug)
# Unit tests
if (GENERATE_TESTS)
include(CTest)
include(FetchContent)
FetchContent_Declare(
googletest
# Specify the commit you depend on and update it regularly.
URL https://github.com/google/googletest/archive/609281088cfefc76f9d0ce82e1ff6c30cc3591e5.zip
)
# For Windows: Prevent overriding the parent project's compiler/linker settings
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
FetchContent_MakeAvailable(googletest)
add_executable(test_unit obfuscate.h test_unit.cpp)
target_link_libraries(test_unit gtest_main)
add_test(NAME test_unit COMMAND test_unit)
add_executable(test_obfuscated obfuscate.h test_obfuscated.cpp)
set_target_properties(test_obfuscated PROPERTIES SUFFIX ".out")
endif()
# Bloat test
add_executable(test_bloat_on obfuscate.h test_bloat.cpp)
add_executable(test_bloat_off obfuscate.h test_bloat.cpp)
target_compile_definitions(test_bloat_on PRIVATE USE_AY_OBFUSCATE=1)
```
`LICENSE`:
```
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.
In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to <http://unlicense.org>
```
`README.md`:
```md
**[Source available on GitHub](https://github.com/adamyaxley/Obfuscate)**
# Obfuscate
Guaranteed compile-time string literal obfuscation header-only library for C++14.
## Quick start guide
1. Copy `obfuscate.h` into your project
2. Wrap strings with `AY_OBFUSCATE("My String")`
Now your project will not expose those strings in plain text in the binary image.
_Note that these strings will still be accessible to determined hackers. Using obfuscation to hide private passwords or any other security sensitive strings is not recommended by the author._
## Whats the problem?
When plain text string literals are used in C++ programs, they will be compiled as-is into the resultant binary. This causes them to be incredibly easy to find. One can simply open up the binary file in a text editor to see all of the embedded string literals in plain view. A special utility called [strings](https://en.wikipedia.org/wiki/Strings_(Unix)) exists which can be used to search binary files for plain text strings.
## What does this library do?
This header-only library seeks to make it difficult (but not impossible) for embedded string literals in binary files to be found by encrypting them with an XOR cipher at compile-time using a constant expression, forcing the compiler to _work with_ the encrypted string instead of the plain text literal. Usage of `AY_OBFUSCATE` additionally removes the need for a const pointer to the string, which more often than not (for small strings) convinces the compiler to inline the encrypted string, building it up at runtime in a series of assembly operations, protecting the binary image against simple XOR decryption attacks. Encrypted strings will then be decrypted at runtime to be utilised within the program.
### Technical features
* _Guaranteed compile-time obfuscation_ - the string is compiled with a constexpr expression.
* _Global lifetime (per-thread)_ - the obfuscated string is stored in a thread local variable in a unique lambda.
* _Implicitly convertible to a char*_ - easy to integrate into existing codebases.
* _Random 64-bit key_ - obfusated with a random key each time.
By simply wrapping your string literal `"My String"` with `AY_OBFUSCATE("My String")` it will be encrypted at compile time with a random 64 bit key and stored in an `ay::obfuscated_data` object which you can manipulate at runtime. For convenience it is also implicitly convertable to a `char*`.
For example, the following program will not store the string "Hello World" in plain text anywhere in the compiled executable.
```c++
#include "obfuscate.h"
int main()
{
std::cout << AY_OBFUSCATE("Hello World") << std::endl;
return 0;
}
```
### Examples of usage
Because the obfuscated string that is generated by `AY_OBFUSCATE` has global lifetime per-thread, it is completely fine to also use it in both a local and a temporary context.
```c++
char* var = AY_OBFUSCATE("string");
const char* var = AY_OBFUSCATE("string");
static const char* var = AY_OBFUSCATE("string");
std::string var(AY_OBFUSCATE("string"));
function_that_takes_char_pointer(AY_OBFUSCATE("string"));
```
### Thread safety
This library can be used in a multi-threaded environment only if `AY_OBFUSCATE` is used in a local context per thread. This is because the obfuscated string is internally stored with `thread_local` storage. The following usage is supported:
```c++
void fun()
{
auto var = AY_OBFUSCATE("Thread Safe");
var.decrypt();
std::cout << var << std::endl;
var.encrypt();
}
int main()
{
std::thread thread1(fun);
std::thread thread2(fun);
thread1.join();
thread2.join();
return 0;
}
```
Conversely, sharing an obfuscated string returned from `AY_OBFUSCATE` between multiple threads is *not* supported. In this case you must put locks in appropriate places in your code to ensure that only one thread accesses it at a time. The following usage is *not* supported:
```c++
int main()
{
for (size_t i = 0; i < 1000; i++)
{
auto var = AY_OBFUSCATE("NOT Thread Safe");
var.decrypt();
std::thread thread([&var]() {
std::cout << var << std::endl;
});
// We are encrypting the string here, but outputting it in
// another thread at the same time. This will not work.
var.encrypt();
thread.join();
}
return 0;
}
```
## Binary file size overhead
This does come at a small cost. In a very naive login program, which obfuscates two strings (username and password) the following binary file bloat exists.
| Config | Plain string literals | Obfuscated strings | Bloat |
|:------:|:---------------------:|:------------------:|:-----:|
| Release | 18944 | 21504 | 2560 (13.5%) |
| Debug | 95232 | 101888 | 6656 (7.0%) |
This output is generated by running test_bloat.py
```
`obfuscate.h`:
```h
/* --------------------------------- ABOUT -------------------------------------
Original Author: Adam Yaxley
Website: https://github.com/adamyaxley
License: See end of file
Obfuscate
Guaranteed compile-time string literal obfuscation library for C++14
Usage:
Pass string literals into the AY_OBFUSCATE macro to obfuscate them at compile
time. AY_OBFUSCATE returns a reference to an ay::obfuscated_data object with the
following traits:
- Guaranteed obfuscation of string
The passed string is encrypted with a simple XOR cipher at compile-time to
prevent it being viewable in the binary image
- Global lifetime
The actual instantiation of the ay::obfuscated_data takes place inside a
lambda as a function level static
- Implicitly convertible to a char*
This means that you can pass it directly into functions that would normally
take a char* or a const char*
Example:
const char* obfuscated_string = AY_OBFUSCATE("Hello World");
std::cout << obfuscated_string << std::endl;
----------------------------------------------------------------------------- */
#pragma once
#if __cplusplus >= 202002L
#define AY_CONSTEVAL consteval
#else
#define AY_CONSTEVAL constexpr
#endif
// Workaround for __LINE__ not being constexpr when /ZI (Edit and Continue) is enabled in Visual Studio
// See: https://developercommunity.visualstudio.com/t/-line-cannot-be-used-as-an-argument-for-constexpr/195665
#ifdef _MSC_VER
#define AY_CAT(X,Y) AY_CAT2(X,Y)
#define AY_CAT2(X,Y) X##Y
#define AY_LINE int(AY_CAT(__LINE__,U))
#else
#define AY_LINE __LINE__
#endif
#ifndef AY_OBFUSCATE_DEFAULT_KEY
// The default 64 bit key to obfuscate strings with.
// This can be user specified by defining AY_OBFUSCATE_DEFAULT_KEY before
// including obfuscate.h
#define AY_OBFUSCATE_DEFAULT_KEY ay::generate_key(AY_LINE)
#endif
namespace ay
{
using size_type = unsigned long long;
using key_type = unsigned long long;
// libstdc++ has std::remove_cvref_t<T> since C++20, but because not every user will be
// able or willing to link to the STL, we prefer to do this functionality ourselves here.
template <typename T>
struct remove_const_ref {
using type = T;
};
template <typename T>
struct remove_const_ref<T&> {
using type = T;
};
template <typename T>
struct remove_const_ref<const T> {
using type = T;
};
template <typename T>
struct remove_const_ref<const T&> {
using type = T;
};
template <typename T>
using char_type = typename remove_const_ref<T>::type;
// Generate a pseudo-random key that spans all 8 bytes
AY_CONSTEVAL key_type generate_key(key_type seed)
{
// Use the MurmurHash3 64-bit finalizer to hash our seed
key_type key = seed;
key ^= (key >> 33);
key *= 0xff51afd7ed558ccd;
key ^= (key >> 33);
key *= 0xc4ceb9fe1a85ec53;
key ^= (key >> 33);
// Make sure that a bit in each byte is set
key |= 0x0101010101010101ull;
return key;
}
// Obfuscates or deobfuscates data with key
template <typename CHAR_TYPE>
constexpr void cipher(CHAR_TYPE* data, size_type size, key_type key)
{
// Obfuscate with a simple XOR cipher based on key
for (size_type i = 0; i < size; i++)
{
data[i] ^= CHAR_TYPE((key >> ((i % 8) * 8)) & 0xFF);
}
}
// Obfuscates a string at compile time
template <size_type N, key_type KEY, typename CHAR_TYPE = char>
class obfuscator
{
public:
// Obfuscates the string 'data' on construction
AY_CONSTEVAL obfuscator(const CHAR_TYPE* data)
{
// Copy data
for (size_type i = 0; i < N; i++)
{
m_data[i] = data[i];
}
// On construction each of the characters in the string is
// obfuscated with an XOR cipher based on key
cipher(m_data, N, KEY);
}
constexpr const CHAR_TYPE* data() const
{
return &m_data[0];
}
AY_CONSTEVAL size_type size() const
{
return N;
}
AY_CONSTEVAL key_type key() const
{
return KEY;
}
private:
CHAR_TYPE m_data[N]{};
};
// Handles decryption and re-encryption of an encrypted string at runtime
template <size_type N, key_type KEY, typename CHAR_TYPE = char>
class obfuscated_data
{
public:
obfuscated_data(const obfuscator<N, KEY, CHAR_TYPE>& obfuscator)
{
// Copy obfuscated data
for (size_type i = 0; i < N; i++)
{
m_data[i] = obfuscator.data()[i];
}
}
~obfuscated_data()
{
// Zero m_data to remove it from memory
for (size_type i = 0; i < N; i++)
{
m_data[i] = 0;
}
}
// Returns a pointer to the plain text string, decrypting it if
// necessary
operator CHAR_TYPE* ()
{
decrypt();
return m_data;
}
// Manually decrypt the string
void decrypt()
{
if (m_encrypted)
{
cipher(m_data, N, KEY);
m_encrypted = false;
}
}
// Manually re-encrypt the string
void encrypt()
{
if (!m_encrypted)
{
cipher(m_data, N, KEY);
m_encrypted = true;
}
}
// Returns true if this string is currently encrypted, false otherwise.
bool is_encrypted() const
{
return m_encrypted;
}
private:
// Local storage for the string. Call is_encrypted() to check whether or
// not the string is currently obfuscated.
CHAR_TYPE m_data[N];
// Whether data is currently encrypted
bool m_encrypted{ true };
};
// This function exists purely to extract the number of elements 'N' in the
// array 'data'
template <size_type N, key_type KEY = AY_OBFUSCATE_DEFAULT_KEY, typename CHAR_TYPE = char>
AY_CONSTEVAL auto make_obfuscator(const CHAR_TYPE(&data)[N])
{
return obfuscator<N, KEY, CHAR_TYPE>(data);
}
}
// Obfuscates the string 'data' at compile-time and returns a reference to a
// ay::obfuscated_data object with global lifetime that has functions for
// decrypting the string and is also implicitly convertable to a char*
#define AY_OBFUSCATE(data) AY_OBFUSCATE_KEY(data, AY_OBFUSCATE_DEFAULT_KEY)
// Obfuscates the string 'data' with 'key' at compile-time and returns a
// reference to a ay::obfuscated_data object with global lifetime that has
// functions for decrypting the string and is also implicitly convertable to a
// char*
#define AY_OBFUSCATE_KEY(data, key) \
[]() -> ay::obfuscated_data<sizeof(data)/sizeof(data[0]), key, ay::char_type<decltype(*data)>>& { \
static_assert(sizeof(decltype(key)) == sizeof(ay::key_type), "key must be a 64 bit unsigned integer"); \
static_assert((key) >= (1ull << 56), "key must span all 8 bytes"); \
using char_type = ay::char_type<decltype(*data)>; \
constexpr auto n = sizeof(data)/sizeof(data[0]); \
constexpr auto obfuscator = ay::make_obfuscator<n, key, char_type>(data); \
thread_local auto obfuscated_data = ay::obfuscated_data<n, key, char_type>(obfuscator); \
return obfuscated_data; \
}()
/* -------------------------------- LICENSE ------------------------------------
Public Domain (http://www.unlicense.org)
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
software, either in source code form or as a compiled binary, for any purpose,
commercial or non-commercial, and by any means.
In jurisdictions that recognize copyright laws, the author or authors of this
software dedicate any and all copyright interest in the software to the public
domain. We make this dedication for the benefit of the public at large and to
the detriment of our heirs and successors. We intend this dedication to be an
overt act of relinquishment in perpetuity of all present and future rights to
this software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
----------------------------------------------------------------------------- */
```
`test_bloat.cpp`:
```cpp
/* ------------------------------- ABOUT ---------------------------------------
Obfuscate naive login example (bloat test)
This example login program demonstrates how much bloat is added to the final
executable if AY_OBFUSCATE is used to obfuscate security sensitive strings at
compile-time.
Run test_bloat.py to output a table comparing release and debug configs.
----------------------------------------------------------------------------- */
#include <iostream>
#include <string>
#ifdef USE_AY_OBFUSCATE
#include "obfuscate.h"
#else
#define AY_OBFUSCATE(text) text
#endif
int main()
{
const std::string username(AY_OBFUSCATE("root"));
const std::string password(AY_OBFUSCATE("password"));
std::cout << "Obfuscate naive login example (bloat test)" << std::endl;
std::string input_username;
std::string input_password;
while (true)
{
std::cout << "Username: ";
std::cin >> input_username;
std::cout << "Password: ";
std::cin >> input_password;
if (input_username == username && input_password == password)
{
std::cout << "Login success!" << std::endl;
break;
}
else
{
std::cout << "Login failure: unrecognised username and password"
"combination." << std::endl;
}
}
return 0;
}
```
`test_bloat.py`:
```py
import os, errno, subprocess, sys
def callCommand(command):
process = subprocess.Popen(command, stdout=subprocess.PIPE)
process.communicate()
process.wait()
def main():
dir = "test_bloat"
try:
os.makedirs(dir)
except OSError as e:
if e.errno != errno.EEXIST:
raise
os.chdir(dir)
callCommand(["cmake", ".."])
callCommand(["cmake", "--build", ".", "--config", "Debug"])
callCommand(["cmake", "--build", ".", "--config", "Release"])
os.chdir("..")
print ("| Config | Plain string literals | Obfuscated strings | Bloat |")
print ("|:------:|:---------------------:|:------------------:|:-----:|")
for config in ["Release", "Debug"]:
sizeOn = os.stat(os.path.join(dir, config, "test_bloat_on.exe")).st_size
sizeOff = os.stat(os.path.join(dir, config, "test_bloat_off.exe")).st_size
print ("| {} | {} | {} | {} ({:.1f}%) |".format(config, sizeOff, sizeOn, sizeOn - sizeOff, (sizeOn / sizeOff - 1.0) * 100))
if __name__ == "__main__":
main()
```
`test_obfuscated.cpp`:
```cpp
#include "obfuscate.h"
#include <cstdio>
int main()
{
{
auto str = AY_OBFUSCATE("1 auto str");
puts(str);
}
{
char* str = AY_OBFUSCATE("2 char* str");
puts(str);
}
{
const char* str = AY_OBFUSCATE("3 const char* str");
puts(str);
}
{
static const char* str = AY_OBFUSCATE("4 static const char* str");
puts(str);
}
{
auto str = AY_OBFUSCATE("5 Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent sed ullamcorper lectus. Cras dapibus, turpis in dignissim consequat, justo massa vehicula sem, vestibulum condimentum dui risus vitae nunc. Mauris tincidunt condimentum nulla, non elementum lectus molestie id. Ut eget libero lorem. Proin vitae massa vehicula, hendrerit est a, tincidunt est. Sed aliquam velit quam, laoreet commodo massa aliquam et. Phasellus in nulla ac risus facilisis porttitor sit amet at enim. Vivamus ornare libero sit amet enim egestas semper vel non dolor.Ut vitae scelerisque elit, sed tincidunt tellus.Maecenas a accumsan justo, sit amet auctor nisl.Donec et quam mollis.");
puts(str);
}
return 0;
}
```
`test_obfuscated.py`:
```py
# Usage:
# test_obfuscated.py target.exe "Hello World"
# returns exit code 0 if obfuscated, 1 if not
import sys
def test_obfuscated(path, string):
with open(path, 'rb') as f:
contents = f.read()
return (contents.find(string.encode()) == -1)
if __name__ == "__main__":
path = sys.argv[1]
string = sys.argv[2]
if (test_obfuscated(path, string)):
print("SUCCESS - string \"{}\" is obfuscated".format(string))
sys.exit(0)
else:
print("FAILURE - string \"{}\" is not obfuscated".format(string))
sys.exit(1)
```
`test_unit.cpp`:
```cpp
#include "gtest/gtest.h"
#include "obfuscate.h"
#include <cstdio>
#include <string>
// Test AY_OBFUSCATE (main test)
TEST(Obfuscate, AY_OBFUSCATE)
{
// Encrypt a string literal with an XOR cipher at compile time and store
// it in test.
auto test = AY_OBFUSCATE("Hello World");
// The string starts out as encrypted
ASSERT_TRUE(test.is_encrypted());
// Manually decrypt the string. This is useful for pre-processing
// especially large strings.
test.decrypt();
ASSERT_TRUE(!test.is_encrypted());
// Output the plain text string to the console (implicitly converts to
// const char*)
puts(test);
// Re-encrypt the string so that it cannot be seen in it's plain text
// form in memory.
test.encrypt();
ASSERT_TRUE(test.is_encrypted());
// The encrypted string will be automatically decrypted if necessary
// when implicitly converting to a const char*
puts(test);
// The string is in a decrypted state
ASSERT_TRUE(!test.is_encrypted());
// Test comparison
ASSERT_TRUE(std::string("Hello World") == (char*)test);
}
// Test AY_OBFUSCATE_KEY
TEST(Obfuscate, AY_OBFUSCATE_KEY)
{
auto test = AY_OBFUSCATE_KEY("Hello World", 0xf8d3481a4bc32d83ull);
puts(test);
// Test comparison
ASSERT_TRUE(std::string("Hello World") == (char*)test);
}
// Test direct API usage
TEST(Obfuscate, API)
{
constexpr auto obfuscator = ay::make_obfuscator("Hello World");
auto test = ay::obfuscated_data<obfuscator.size(), obfuscator.key()>(obfuscator);
puts(test);
// Test comparison
ASSERT_TRUE(std::string("Hello World") == (char*)test);
}
static const char* g_global_static1 = AY_OBFUSCATE("global_static1");
static const char* g_global_static2 = AY_OBFUSCATE("global_static2");
// Test global static const char* variables
TEST(Obfuscate, GlobalConstCharPtr)
{
ASSERT_TRUE(strcmp(g_global_static1, "global_static1") == 0);
puts(g_global_static1);
ASSERT_TRUE(strcmp(g_global_static2, "global_static2") == 0);
puts(g_global_static2);
}
// Test local const char* variables
TEST(Obfuscate, LocalConstCharPtr)
{
const char* local1 = AY_OBFUSCATE("local1");
const char* local2 = AY_OBFUSCATE("local2");
ASSERT_TRUE(strcmp(local1, "local1") == 0);
puts(local1);
ASSERT_TRUE(strcmp(local2, "local2") == 0);
puts(local2);
}
// Test non-null terminated strings
TEST(Obfuscate, NonNullTerminatedStr)
{
constexpr auto obfuscator = ay::obfuscator<10, AY_OBFUSCATE_DEFAULT_KEY>("1234567890");
auto test = ay::obfuscated_data<obfuscator.size(), obfuscator.key()>(obfuscator);
puts(test);
// Test comparison
assert(std::string("1234567890") == (char*)test);
}
```
+422
View File
@@ -0,0 +1,422 @@
Project Path: arc_android1337_crystr_vn8aeoyf
Source Tree:
```txt
arc_android1337_crystr_vn8aeoyf
├── LICENSE
├── README.md
├── include
│ └── crystr.hpp
└── src
└── main.cpp
```
`LICENSE`:
```
MIT License
Copyright (c) 2023 Android1337
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
```
`README.md`:
```md
# crystr | Compile-Time Strings and Numbers Encryption for C++20
## Description
This repository provides a compile-time strings and numbers encryption mechanism based on xor bitwise operations using mathematical operations at compile-time in order to make the strings and numbers challenging to decompile.\
Users can easily encrypt their strings using the `crystr` macro provided in the header, and numbers using `crynum` and `crynum_long`.\
The repository includes an example demonstrating the usage of `crystr`, `crynum` and `crynum_long` to use encrypted strings and numbers.\
It's recommended, but not necessarily, to `disable the optimization under C/C++ -> Optimization -> "Optimization" and "Whole Program Optimization"`\
When using the virtual functions option it `needs RTTI OFF (Run-Time Type Information under C/C++ -> Language (/GR-))`
## Key Aspects
- The xor key is generated by mathematical operations based on a uniqueid that is different every time the program is compiled and for each encrypted string/number.
- The uniqueid is an hash based on the date, time and a counter which increases every time a string/number encrypts: ```const_hash(__DATE__ __TIME__) + __COUNTER__ * __COUNTER__```.
- When using crystr every character is encrypted with a different xor key based on the index of the character inside the string.
- It is possible to choose between virtual and inline functions to decrypt the strings/numbers.
- If using the virtual implementation it works well with `crycall` ([see the repo](https://github.com/Android1337/crycall)).
- All the variables used to retrieve the xor key are thread_local, meaning they're stored as `(NtCurrentTeb()->ThreadLocalStoragePointer + TlsIndex)->x_var`, making it more challenging to decompile and **not** ready-pastable from decompiler tools such as ida or ghidra.
- The string is never copied but instead it's being written on, in order to prevent unwanted decrypted copies of the string in memory.
- The decrypted string can be re-encrypted at any time using the built-in `encrypt` function.
- The decrypted/encrypted string/number can be removed from memory at any time using the built-in `clear` function.
- The string/number will show as never referenced in most common decompiler tools such as ida or ghidra see ([How it shows](https://github.com/Android1337/crystr/tree/main#how-it-shows)).
- Compatible with char[], wchar_t[], int, __int64, unsigned int, unsigned __int64, float, double.
- Supports C++20 and higher versions.
## How it shows
[Look here](https://imgur.com/a/acamGoW)\
How it shows using [inline functions option](https://crystr-inline.tiiny.site)
## Repository Structure
- **`include/`**: Contains the `crystr.hpp` header file providing the compile-time string/number encryption mechanism.
- **`src/`**: Holds the example `main.cpp` file showcasing the usage of `crystr`, `crynum`, `crynum_long`.
- **`LICENSE`**: Licensing information for the provided code.
- **`README.md`**: Documentation explaining how to use everything.
## Usage Example
The repository includes an example demonstrating the usage of the `crystr`, `crynum`, `crynum_long` macros:
### `main.cpp`
```cpp
//#include "crycall.hpp" // see https://github.com/Android1337/crycall for an all-potential virtual implementation
#include "crystr.hpp"
int main() {
auto encrypted_str = crystr("Hello, this is an encrypted string!");
printf("Decrypted String: %s\n", encrypted_str.decrypt());
encrypted_str.clear();
auto encrypted_int = crynum(1234);
printf("Decrypted Int: %d\n", encrypted_int.decrypt());
encrypted_int.clear();
auto encrypted_double = crynum_long(1.234);
printf("Decrypted Double: %f\n", encrypted_double.decrypt());
encrypted_double.clear();
return 0;
}
```
```
`include/crystr.hpp`:
```hpp
/*
* MIT License https://github.com/Android1337/crystr/blob/main/LICENSE
*
* Copyright (c) 2023 Android1337
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#ifndef CRYSTR_HPP
#define CRYSTR_HPP
#include <bit> // std::bit_cast
#define USE_INLINE_FUNCTIONS 1 // you can either use virtual or __forceinline
#if USE_INLINE_FUNCTIONS == 1
#define CRYSTR_TYPE __forceinline
#else
#define CRYSTR_TYPE virtual
#ifdef _CPPRTTI
#error("Please disable Run-Time Type Information under C/C++ -> Language (/GR-)")
#endif
#endif
constexpr unsigned long long const_hash(const char* input, unsigned long long value = 0) {
return *input ? static_cast<unsigned long long>(*input) + 33 * const_hash(input + 1, value)
: (5381 + 33 * value);
}
#define crystr(str) []() { \
constexpr static auto const_str = crys::cryStr \
<const_hash(__DATE__ __TIME__, __COUNTER__), sizeof(str) / sizeof(str[0]), std::remove_const_t<std::remove_reference_t<decltype(str[0])>>>((std::remove_const_t<std::remove_reference_t<decltype(str[0])>>*)str); \
return const_str; }()
#define crynum(num) []() { \
constexpr thread_local auto val = num; \
constexpr static auto const_str = crys::cryNum \
<const_hash(__DATE__ __TIME__, __COUNTER__), decltype(val)>(std::bit_cast<unsigned int, decltype(val)>(num)); \
return const_str; }()
#define crynum_long(num) []() { \
constexpr thread_local auto val = num; \
constexpr static auto const_str = crys::cryNum \
<const_hash(__DATE__ __TIME__, __COUNTER__), decltype(val)>(std::bit_cast<unsigned long long, decltype(val)>(num)); \
return const_str; }()
namespace crys
{
template <unsigned long long uniqueid, unsigned int data_size, typename T>
class cryStr
{
public:
__forceinline constexpr cryStr(T* str)
{
for (int i = 0; i < data_size; i++)
{
auto v = (uniqueid + i) * double((3.1415926535897932f / (180.f)) / 2.f);
auto y = v - (2.0f * 3.1415926535897932f) * double((0.31830988618f * 0.5f) * v + 0.5f);
auto w = double(0);
if (y > 3.1415926535897932f / 2)
{
y = 3.1415926535897932f - y;
w = (-1.f * ((((-2.6051615e-07f * (y * y) + 2.4760495e-05f) * (y * y) - 0.0013888378f) * (y * y) + 0.041666638f) * (y * y) - 0.5f) * (y * y) + 1.0f);
}
else if (y < (3.1415926535897932f / 2) * -1)
{
y = -3.1415926535897932f - y;
w = (-1.f * ((((-2.6051615e-07f * (y * y) + 2.4760495e-05f) * (y * y) - 0.0013888378f) * (y * y) + 0.041666638f) * (y * y) - 0.5f) * (y * y) + 1.0f);
}
else
w = (1.f * ((((-2.6051615e-07f * (y * y) + 2.4760495e-05f) * (y * y) - 0.0013888378f) * (y * y) + 0.041666638f) * (y * y) - 0.5f) * (y * y) + 1.0f);
auto z = (((((-2.3889859e-08f * (y * y) + 2.7525562e-06f) * (y * y) - 0.00019840874f) * (y * y) + 0.0083333310f) * (y * y) - 0.16666667f) * (y * y) + 1.0f);
data[i] = str[i] ^ T(std::bit_cast<unsigned long long>(z * y + w) + i);
}
}
__forceinline void encrypt()
{
#if defined(CRYCALL_HPP) && USE_INLINE_FUNCTIONS == 0
crycall_virtual(void, this, 0x2);
#else
xor_encrypt();
#endif
}
__forceinline T* decrypt()
{
#if defined(CRYCALL_HPP) && USE_INLINE_FUNCTIONS == 0
crycall_virtual(void, this, 0x3);
#else
xor_decrypt();
#endif
thread_local auto ret = data;
return ret;
}
__forceinline void clear()
{
for (unsigned int i = 0; i < data_size; i++)
data[i] = 0;
}
private:
CRYSTR_TYPE unsigned long long get_xor_key(unsigned int i)
{
thread_local auto d = double((3.1415926535897932f / (180.f)) / 2.f);
thread_local auto v = (uniqueid + i) * d;
thread_local auto q = double((0.31830988618f * 0.5f) * v + 0.5f);
thread_local auto y = v - (2.0f * 3.1415926535897932f) * q;
if (y > 3.1415926535897932f / 2)
{
thread_local auto ny = 3.1415926535897932f - y;
thread_local auto z = (((((-2.3889859e-08f * (ny * ny) + 2.7525562e-06f) * (ny * ny) - 0.00019840874f) * (ny * ny) + 0.0083333310f) * (ny * ny) - 0.16666667f) * (ny * ny) + 1.0f);
thread_local auto w = (-1.f * ((((-2.6051615e-07f * (ny * ny) + 2.4760495e-05f) * (ny * ny) - 0.0013888378f) * (ny * ny) + 0.041666638f) * (ny * ny) - 0.5f) * (ny * ny) + 1.0f);
thread_local auto ret = z * ny + w;
return std::bit_cast<unsigned long long>(ret) + i;
}
else if (y < (3.1415926535897932f / 2) * -1)
{
thread_local auto ny = -3.1415926535897932f - y;
thread_local auto z = (((((-2.3889859e-08f * (ny * ny) + 2.7525562e-06f) * (ny * ny) - 0.00019840874f) * (ny * ny) + 0.0083333310f) * (ny * ny) - 0.16666667f) * (ny * ny) + 1.0f);
thread_local auto w = (-1.f * ((((-2.6051615e-07f * (ny * ny) + 2.4760495e-05f) * (ny * ny) - 0.0013888378f) * (ny * ny) + 0.041666638f) * (ny * ny) - 0.5f) * (ny * ny) + 1.0f);
thread_local auto ret = z * ny + w;
return std::bit_cast<unsigned long long>(ret) + i;
}
thread_local auto z = (((((-2.3889859e-08f * (y * y) + 2.7525562e-06f) * (y * y) - 0.00019840874f) * (y * y) + 0.0083333310f) * (y * y) - 0.16666667f) * (y * y) + 1.0f);
thread_local auto w = (1.f * ((((-2.6051615e-07f * (y * y) + 2.4760495e-05f) * (y * y) - 0.0013888378f) * (y * y) + 0.041666638f) * (y * y) - 0.5f) * (y * y) + 1.0f);
thread_local auto ret = z * y + w;
return std::bit_cast<unsigned long long>(ret) + i;
}
CRYSTR_TYPE void xor_byte(T* out, T* data, unsigned int i)
{
#if defined(CRYCALL_HPP) && USE_INLINE_FUNCTIONS == 0
out[i] = data[i] ^ T(crycall_virtual(unsigned long long, this, 0x0, i));
#else
out[i] = data[i] ^ T(get_xor_key(i));
#endif
}
CRYSTR_TYPE void xor_encrypt()
{
if (data[data_size - 1] == 0)
{
for (unsigned int i = 0; i < data_size; i++)
{
#if defined(CRYCALL_HPP) && USE_INLINE_FUNCTIONS == 0
crycall_virtual(void, this, 0x1, data, data, i);
#else
xor_byte(data, data, i);
#endif
}
data[data_size - 1] = 0xFF;
}
}
CRYSTR_TYPE void xor_decrypt()
{
if (data[data_size - 1] != 0)
{
for (unsigned int i = 0; i < data_size; i++)
{
#if defined(CRYCALL_HPP) && USE_INLINE_FUNCTIONS == 0
crycall_virtual(void, this, 0x1, data, data, i);
#else
xor_byte(data, data, i);
#endif
}
data[data_size - 1] = 0;
}
}
T data[data_size] = {};
};
template <unsigned long long uniqueid, typename T>
class cryNum
{
public:
__forceinline constexpr cryNum(unsigned long long value)
{
auto v = uniqueid * double((3.1415926535897932f / (180.f)) / 2.f);
auto y = v - (2.0f * 3.1415926535897932f) * double((0.31830988618f * 0.5f) * v + 0.5f);
auto w = double(0);
if (y > 3.1415926535897932f / 2)
{
y = 3.1415926535897932f - y;
w = (-1.f * ((((-2.6051615e-07f * (y * y) + 2.4760495e-05f) * (y * y) - 0.0013888378f) * (y * y) + 0.041666638f) * (y * y) - 0.5f) * (y * y) + 1.0f);
}
else if (y < (3.1415926535897932f / 2) * -1)
{
y = -3.1415926535897932f - y;
w = (-1.f * ((((-2.6051615e-07f * (y * y) + 2.4760495e-05f) * (y * y) - 0.0013888378f) * (y * y) + 0.041666638f) * (y * y) - 0.5f) * (y * y) + 1.0f);
}
else
w = (1.f * ((((-2.6051615e-07f * (y * y) + 2.4760495e-05f) * (y * y) - 0.0013888378f) * (y * y) + 0.041666638f) * (y * y) - 0.5f) * (y * y) + 1.0f);
auto z = (((((-2.3889859e-08f * (y * y) + 2.7525562e-06f) * (y * y) - 0.00019840874f) * (y * y) + 0.0083333310f) * (y * y) - 0.16666667f) * (y * y) + 1.0f);
data = value ^ std::bit_cast<unsigned long long>(z * y + w);
}
__forceinline T decrypt()
{
#if defined(CRYCALL_HPP) && USE_INLINE_FUNCTIONS == 0
thread_local auto ret = crycall_virtual(unsigned long long, this, 0x2);
#else
thread_local auto ret = xor_decrypt();
#endif
return *(T*)&ret;
}
__forceinline void clear()
{
data = 0;
}
private:
CRYSTR_TYPE unsigned long long get_xor_key()
{
thread_local auto d = double((3.1415926535897932f / (180.f)) / 2.f);
thread_local auto v = uniqueid * d;
thread_local auto q = double((0.31830988618f * 0.5f) * v + 0.5f);
thread_local auto y = v - (2.0f * 3.1415926535897932f) * q;
if (y > 3.1415926535897932f / 2)
{
thread_local auto ny = 3.1415926535897932f - y;
thread_local auto z = (((((-2.3889859e-08f * (ny * ny) + 2.7525562e-06f) * (ny * ny) - 0.00019840874f) * (ny * ny) + 0.0083333310f) * (ny * ny) - 0.16666667f) * (ny * ny) + 1.0f);
thread_local auto w = (-1.f * ((((-2.6051615e-07f * (ny * ny) + 2.4760495e-05f) * (ny * ny) - 0.0013888378f) * (ny * ny) + 0.041666638f) * (ny * ny) - 0.5f) * (ny * ny) + 1.0f);
thread_local auto ret = z * ny + w;
return std::bit_cast<unsigned long long>(ret);
}
else if (y < (3.1415926535897932f / 2) * -1)
{
thread_local auto ny = -3.1415926535897932f - y;
thread_local auto z = (((((-2.3889859e-08f * (ny * ny) + 2.7525562e-06f) * (ny * ny) - 0.00019840874f) * (ny * ny) + 0.0083333310f) * (ny * ny) - 0.16666667f) * (ny * ny) + 1.0f);
thread_local auto w = (-1.f * ((((-2.6051615e-07f * (ny * ny) + 2.4760495e-05f) * (ny * ny) - 0.0013888378f) * (ny * ny) + 0.041666638f) * (ny * ny) - 0.5f) * (ny * ny) + 1.0f);
thread_local auto ret = z * ny + w;
return std::bit_cast<unsigned long long>(ret);
}
thread_local auto z = (((((-2.3889859e-08f * (y * y) + 2.7525562e-06f) * (y * y) - 0.00019840874f) * (y * y) + 0.0083333310f) * (y * y) - 0.16666667f) * (y * y) + 1.0f);
thread_local auto w = (1.f * ((((-2.6051615e-07f * (y * y) + 2.4760495e-05f) * (y * y) - 0.0013888378f) * (y * y) + 0.041666638f) * (y * y) - 0.5f) * (y * y) + 1.0f);
thread_local auto ret = z * y + w;
return std::bit_cast<unsigned long long>(ret);
}
CRYSTR_TYPE void xor_byte(unsigned long long* out, unsigned long long data)
{
#if defined(CRYCALL_HPP) && USE_INLINE_FUNCTIONS == 0
*out = data ^ crycall_virtual(unsigned long long, this, 0x0);
#else
*out = data ^ get_xor_key();
#endif
}
CRYSTR_TYPE unsigned long long xor_decrypt()
{
unsigned long long ret{};
#if defined(CRYCALL_HPP) && USE_INLINE_FUNCTIONS == 0
crycall_virtual(void, this, 0x1, &ret, data);
#else
xor_byte(&ret, data);
#endif
return ret;
}
unsigned long long data = {};
};
}
#endif // include guard
```
`src/main.cpp`:
```cpp
//#include "crycall.hpp" // see https://github.com/Android1337/crycall for an all-potential virtual implementation
#include "crystr.hpp"
int main() {
auto encrypted_str = crystr("Hello, this is an encrypted string!");
printf("Decrypted String: %s", encrypted_str.decrypt());
encrypted_str.clear();
return 0;
}
```
File diff suppressed because it is too large Load Diff
+298
View File
@@ -0,0 +1,298 @@
Project Path: arc_igozdev_xorlit_f5odk732
Source Tree:
```txt
arc_igozdev_xorlit_f5odk732
├── LICENSE
├── README.md
└── include
└── xorlit.hpp
```
`LICENSE`:
```
MIT License
Copyright (c) 2023 igozdev
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
```
`README.md`:
```md
# xorlit
> Compile time string literal encryptor for C++.
[![license][badge.license]][license]
[![release][badge.release]][release]
[![godbolt][badge.godbolt]][godbolt]
[badge.license]: https://img.shields.io/badge/license-mit-green.svg
[license]: https://github.com/igozdev/xorlit/blob/main/LICENSE
[badge.release]: https://img.shields.io/github/release/igozdev/xorlit.svg
[release]: https://github.com/igozdev/xorlit/releases/latest
[badge.godbolt]: https://img.shields.io/badge/try_it-on_godbolt-indigo.svg
[godbolt]: https://godbolt.org/z/Ksd36MMo7
* [Usage](#usage)
* [Example](#example)
# Usage
## XORLIT
The `XORLIT` macro can be used to create an encrypted string which evaluates to its original contents (e.g. `XORLIT("foo")` would evalute as the string "foo", but be stored as some other encrypted value). As it wraps `xorlit::make_str`, one can pass either one argument, a string literal, or two arguments, a string literal and a key. In the case that one argument is passed, the key used will default to `xorlit::seed`.
> [!WARNING]
> Exercise caution when using `XORLIT`, `xorlit::make_str(__VA_ARGS__).rexor()`, or `xorlit::string`s in any context where they are not explicitly stored, as some compilers, such as MSVC, may sporadically encrypt strings non-constexpr-ly depending on the string literal's content or the context of its usage. It may be advisable to search binary contents for any unencrypted string literals in this case.
## xorlit::seed
`xorlit::seed` is a `constexpr std::uint_least32_t` which is based on the predefined `__TIME__` macro to generate values. It is the default value used by `xorlit::make_str` and by extension `XORLIT` for the key. Since `xorlit::seed` or `static_cast<char>(xorlit::seed)` may evaluate to 0, which, in such a case, would lead to an unencrypted string, by defining `XORLIT_SEED_STATIC_ASSERT` before including the `xorlit.hpp` header, a static assertion by the compiler will be enabled to check if `xorlit::seed` is equal to 0.
## xorlit::string
`xorlit::string` is the provided type for storing encrypted strings. A `xorlit::string` should always be decorated with the `constexpr` specifier to ensure it is stored in its encrypted form.
`xorlit::string` defines the following member functions:
* `xorlit::string::rexor()`: xors the data of the string and returns it.
* `xorlit::string::xor_data()`: returns a new `const char*` with a copy of the string's data xored.
* `xorlit::string::data`: returns the data stored in the string. `const` and non-`const` variants are provided.
* `xorlit::string::key`: returns the key stored in the string.
> [!IMPORTANT]
> Results of `xorlit::string::xor_data()` must be managed manually, whether that be via `delete`, smart pointers, etc.
# Example
```c++
#include <string>
#include <iostream>
#include <format>
#define XORLIT_SEED_STATIC_ASSERT // enable compiler check if xorlit::seed is 0
#include <xorlit.hpp>
int main()
{
std::cout
<< XORLIT("Default, xorlit::seed is passed as the key\n")
<< XORLIT("Or you can use xorlit::seed yourself\n", xorlit::seed + __LINE__) // when passing your own seeds, you should check that they aren't 0
<< XORLIT("Or you can choose your own key\n", 12) << std::endl;
constexpr auto s = xorlit::make_str("You can manually store a xorlit::string"); // always declare the results of xorlit::make_str constexpr
const std::string raw(s.data(), decltype(s)::size);
const char* original_data = s.xor_data(); // results of xorlit::string::xor_data must be manually managed, smart pointers are recommended
const std::string original(original_data, decltype(s)::size);
delete[] original_data;
std::cout << std::format("Raw: {}\nOriginal: {}", raw, original) << std::endl;
}
////////////////// Possible output: //////////////////
// Default, xorlit::seed is passed as the key
// Or you can use xorlit::seed yourself
// Or you can choose your own seed
//
// Raw: ?%p31>p=1>%1<<)p#$?"5p1p(?"<9$jj#$"9>7P
// Original: You can manually store a xorlit::string
```
```
`include/xorlit.hpp`:
```hpp
/*
* MIT License
*
* Copyright (c) 2023 igozdev
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#pragma once
#include <cstdint>
#include <cstddef>
namespace xorlit
{
constexpr std::uint_least32_t seed =
static_cast<std::uint_least32_t>(__TIME__[0] - '0') |
static_cast<std::uint_least32_t>(__TIME__[1] - '0') << 4 |
static_cast<std::uint_least32_t>(__TIME__[3] - '0') << 8 |
static_cast<std::uint_least32_t>(__TIME__[4] - '0') << 12 |
static_cast<std::uint_least32_t>(__TIME__[6] - '0') << 16 |
static_cast<std::uint_least32_t>(__TIME__[7] - '0') << 20;
#if defined(XORLIT_SEED_STATIC_ASSERT)
#if defined(__cpp_static_assert) && __cpp_static_assert >= 200410L
static_assert(static_cast<char>(seed) != 0, "xorlit::seed acts as zero! Default usage of xorlit::make_str will result in an unchanged string!");
#endif
#endif
template <std::size_t I>
#if defined(_MSC_VER)
__forceinline
#else
__attribute__((always_inline))
#endif
constexpr void xor_str(char* d, char key, const char* s)
{
d[I] = s[I] ^ key;
xor_str<I - 1>(d, key, s);
}
template <>
#if defined(_MSC_VER)
__forceinline
#else
__attribute__((always_inline))
#endif
constexpr void xor_str<0>(char* d, char key, const char* s)
{
d[0] = s[0] ^ key;
}
template <std::size_t Size>
struct string
{
public:
#if defined(_MSC_VER)
__forceinline
#else
__attribute__((always_inline))
#endif
constexpr string(const char(&s)[Size], char key)
: m_data(), m_key(key)
{
xor_str<Size - 1>(m_data, m_key, s);
}
#if defined(_MSC_VER)
__forceinline
#else
__attribute__((always_inline))
#endif
const char* rexor()
{
for (char& c : m_data) {
c ^= m_key;
}
return m_data;
}
#if defined(__cpp_attributes) && __cpp_attributes >= 200809L
[[nodiscard]]
#endif
#if defined(_MSC_VER)
__forceinline
#else
__attribute__((always_inline))
#endif
const char* xor_data() const
{
char* data = new char[Size];
for (std::size_t i = 0; i < Size; i++) {
data[i] = m_data[i] ^ m_key;
}
return data;
}
#if defined(__cpp_attributes) && __cpp_attributes >= 200809L
[[nodiscard]]
#endif
#if defined(_MSC_VER)
__forceinline
#else
__attribute__((always_inline))
#endif
char* data()
#if defined(__cpp_noexcept_function_type) && __cpp_noexcept_function_type >= 201510L
noexcept
#endif
{
return m_data;
}
#if defined(__cpp_attributes) && __cpp_attributes >= 200809L
[[nodiscard]]
#endif
#if defined(_MSC_VER)
__forceinline
#else
__attribute__((always_inline))
#endif
const char* data() const
#if defined(__cpp_noexcept_function_type) && __cpp_noexcept_function_type >= 201510L
noexcept
#endif
{
return m_data;
}
#if defined(__cpp_attributes) && __cpp_attributes >= 200809L
[[nodiscard]]
#endif
#if defined(_MSC_VER)
__forceinline
#else
__attribute__((always_inline))
#endif
char key() const
#if defined(__cpp_noexcept_function_type) && __cpp_noexcept_function_type >= 201510L
noexcept
#endif
{
return m_key;
}
constexpr static std::size_t size = Size;
private:
const char m_key;
char m_data[Size];
};
template <std::size_t Size>
#if defined(__cpp_attributes) && __cpp_attributes >= 200809L
[[nodiscard]]
#endif
#if defined(_MSC_VER)
__forceinline
#else
__attribute__((always_inline))
#endif
constexpr string<Size> make_str(const char(&s)[Size], char key = static_cast<char>(xorlit::seed))
{
return string<Size>(s, key);
}
}
#define XORLIT(...) ::xorlit::make_str(__VA_ARGS__).rexor()
```
+589
View File
@@ -0,0 +1,589 @@
Project Path: arc_pykaso_Swift-String-Obfuscator_hty9v8dm
Source Tree:
```txt
arc_pykaso_Swift-String-Obfuscator_hty9v8dm
├── Makefile
├── Package.resolved
├── Package.swift
├── README.md
├── Sources
│ ├── SwiftStringObfuscatorCore
│ │ ├── Extensions.swift
│ │ ├── ObfuscateStringsRewritter.swift
│ │ └── StringObfuscator.swift
│ └── swift_string_obfuscator
│ └── main.swift
├── Tests
│ ├── LinuxMain.swift
│ └── SwiftStringObfuscatorTests
│ ├── SwiftStringObfuscatorTests.swift
│ └── XCTestManifests.swift
└── github
├── decompiled_string.png
├── string_in_app.png
└── string_obfuscated.png
```
`Makefile`:
```
INSTALL_PATH = /usr/local/bin/swift_string_obfuscator
build:
swift package update
swift build -c release
install: build
cp -f .build/release/swift_string_obfuscator $(INSTALL_PATH)
clean:
rm -rf .build
uninstall:
rm -f $(INSTALL_PATH)
xcode:
swift package generate-xcodeproj
xed .
```
`Package.resolved`:
```resolved
{
"object": {
"pins": [
{
"package": "swift-argument-parser",
"repositoryURL": "https://github.com/apple/swift-argument-parser",
"state": {
"branch": null,
"revision": "92646c0cdbaca076c8d3d0207891785b3379cbff",
"version": "0.3.1"
}
},
{
"package": "SwiftSyntax",
"repositoryURL": "https://github.com/apple/swift-syntax.git",
"state": {
"branch": null,
"revision": "844574d683f53d0737a9c6d706c3ef31ed2955eb",
"version": "0.50300.0"
}
}
]
},
"version": 1
}
```
`Package.swift`:
```swift
// swift-tools-version:5.3
import PackageDescription
let package = Package(
name: "SwiftStringObfuscator",
platforms: [
.macOS(.v10_13),
],
products: [
.executable(name: "swift_string_obfuscator", targets: ["swift_string_obfuscator"]),
],
dependencies: [
.package(name: "SwiftSyntax", url: "https://github.com/apple/swift-syntax.git", .exact("0.50300.0")),
.package(url: "https://github.com/apple/swift-argument-parser", from: "0.3.0"),
],
targets: [
.target(
name: "swift_string_obfuscator",
dependencies: [
"SwiftStringObfuscatorCore",
.product(name: "ArgumentParser", package: "swift-argument-parser"),
]
),
.target(
name: "SwiftStringObfuscatorCore",
dependencies: ["SwiftSyntax"]
),
.testTarget(
name: "SwiftStringObfuscatorTests",
dependencies: ["SwiftStringObfuscatorCore"]),
]
)
```
`README.md`:
```md
# Swift String Obfuscator
Are You storing more or less sensitive strings like API keys directly in the application code? For exaple like this:
![String stored in app](/github/string_in_app.png)
Do You know that's relatively easy for anyone to disassemble the application and get these strings?
![decompiled_string](/github/decompiled_string.png)
This utility convert strings annotated with a comment `//:obfuscate` into byte arrays and make them more complicated to find in disassembled code. It's not a bulletproof solution but it's better than leaving them readable at first sight.
![string_obfuscated](/github/string_obfuscated.png)
# Installation
Clone the repo and run `make install`.
# Usage
1. Install the `swift_string_obfuscator` utility
2. Annotate sensitive strings with a comment `\\:obfuscate`
3. Create a new Run Script Phase and run `swift_string_obfuscator` for files with sensitive strings. Source and target file could be the same.
Another possible solution is to use two files. One file, excluded from the build, with plain strings and second, with obfuscated strings included in the build.
```bash
swift_string_obfuscator -s ${PROJECT_DIR}/SampleApp/API.swift -t ${PROJECT_DIR}/SampleApp/API.swift
```
## Sample
Input with plain strings.
```swift
//:obfuscate
let apiKey = "something-secret"
//:obfuscate
let apiKey2="something-secret-without-spaces"
//:obfuscate
//useless line, only for test purposes
let nonObfuscated: String = "non-obfuscated-string"
struct XStruct {
let x: Int
//:obfuscate
let apiKey3 = "key-in-struct"
//:obfuscate
var param: String {
return "key-in-computed-property"
}
//:obfuscate
var dynamic2: String { "key-in-computed-property-2" }
}
class Y {
//:obfuscate
static let keyInClass: String = "api-key-in-class"
func apiFuncParam(_ key: String) { return }
}
func test() {
let testClass = Y()
//:obfuscate
testClass.apiFuncParam("api_key_func_param")
}
```
Obfuscated output:
```swift
//:obfuscate
let apiKey = String(bytes: [115,111,109,101,116,104,105,110,103,45,115,101,99,114,101,116], encoding: .utf8)
//:obfuscate
let apiKey2=String(bytes: [115,111,109,101,116,104,105,110,103,45,115,101,99,114,101,116,45,119,105,116,104,111,117,116,45,115,112,97,99,101,115], encoding: .utf8)
//:obfuscate
//useless line, only for test purposes
let nonObfuscated: String = "non-obfuscated-string"
struct XStruct {
let x: Int
//:obfuscate
let apiKey3 = String(bytes: [107,101,121,45,105,110,45,115,116,114,117,99,116], encoding: .utf8)
//:obfuscate
var param: String {
return String(bytes: [107,101,121,45,105,110,45,99,111,109,112,117,116,101,100,45,112,114,111,112,101,114,116,121], encoding: .utf8)
}
//:obfuscate
var dynamic2: String { String(bytes: [107,101,121,45,105,110,45,99,111,109,112,117,116,101,100,45,112,114,111,112,101,114,116,121,45,50], encoding: .utf8)}
}
class Y {
//:obfuscate
static let keyInClass: String = String(bytes: [97,112,105,45,107,101,121,45,105,110,45,99,108,97,115,115], encoding: .utf8)
func apiFuncParam(_ key: String) { return }
}
func test() {
let testClass = Y()
//:obfuscate
testClass.apiFuncParam(String(bytes: [97,112,105,95,107,101,121,95,102,117,110,99,95,112,97,114,97,109], encoding: .utf8))
}
```
```
`Sources/SwiftStringObfuscatorCore/Extensions.swift`:
```swift
//
// Extensions.swift
// string_obfuscator
//
// Created by Lukas Gergel on 27.12.2020.
//
import Foundation
extension String {
var data: Data { .init(utf8) }
var bytes: [UInt8] { .init(utf8) }
}
```
`Sources/SwiftStringObfuscatorCore/ObfuscateStringsRewritter.swift`:
```swift
//
// ObfuscateStringsRewritter.swift
// string_obfuscator
//
// Created by Lukas Gergel on 01.01.2021.
//
import Foundation
import SwiftSyntax
enum State {
case reading
case command
}
class ObfuscateStringsRewritter: SyntaxRewriter {
var state: State = .reading
func integerLiteralElement(_ int: Int, addComma: Bool = true) -> ArrayElementSyntax {
let literal = SyntaxFactory.makeIntegerLiteral("\(int)")
return SyntaxFactory.makeArrayElement(
expression: ExprSyntax(SyntaxFactory.makeIntegerLiteralExpr(digits: literal)),
trailingComma: addComma ? SyntaxFactory.makeCommaToken() : nil)
}
override open func visit(_ node: StringLiteralExprSyntax) -> ExprSyntax {
defer {
state = .reading
}
guard case .command = state else { return super.visit(node) }
let origValue = "\(node.segments)"
let bytes = origValue.bytes.enumerated().map { (i, element) -> ArrayElementSyntax in
integerLiteralElement(Int(element), addComma: i < origValue.bytes.count - 1)
}
let arrayElementList = SyntaxFactory.makeArrayElementList(bytes)
let bytesArg = SyntaxFactory.makeTupleExprElement(
label: SyntaxFactory.makeIdentifier("bytes"),
colon: SyntaxFactory.makeColonToken(leadingTrivia: .zero, trailingTrivia: .spaces(1)),
expression: ExprSyntax(SyntaxFactory.makeArrayExpr(
leftSquare: SyntaxFactory.makeLeftSquareBracketToken(),
elements: arrayElementList,
rightSquare: SyntaxFactory.makeRightSquareBracketToken())),
trailingComma: SyntaxFactory.makeCommaToken()
)
let encodingArg = SyntaxFactory.makeTupleExprElement(
label: SyntaxFactory.makeIdentifier("encoding"),
colon: SyntaxFactory.makeColonToken(leadingTrivia: .zero, trailingTrivia: .spaces(1)),
expression: ExprSyntax(SyntaxFactory.makeIdentifierExpr(identifier: SyntaxFactory.makeIdentifier(".utf8"),
declNameArguments: nil)),
trailingComma: nil
).withLeadingTrivia(.spaces(1))
let newCall =
SyntaxFactory.makeFunctionCallExpr(
calledExpression: ExprSyntax(
SyntaxFactory.makeIdentifierExpr(
identifier: SyntaxFactory.makeIdentifier("String"),
declNameArguments: nil
)
),
leftParen: SyntaxFactory.makeLeftParenToken(),
argumentList: SyntaxFactory.makeTupleExprElementList([bytesArg, encodingArg]),
rightParen: SyntaxFactory.makeRightParenToken(),
trailingClosure: nil,
additionalTrailingClosures: nil
)
return super.visit(newCall)
}
override func visit(_ token: TokenSyntax) -> Syntax {
let withoutSpaces = token.leadingTrivia.filter { if case .spaces = $0 { return false }; return true }
guard withoutSpaces.count > 1 else { return super.visit(token) }
let lastNewLine = withoutSpaces.last
let commandLine = withoutSpaces[withoutSpaces.count-2]
if state == .reading, case .newlines(1) = lastNewLine, case .lineComment("//:obfuscate") = commandLine {
state = .command
}
return super.visit(token)
}
}
struct FileHandlerOutputStream: TextOutputStream {
private let fileHandle: FileHandle
let encoding: String.Encoding
init(_ fileHandle: FileHandle, encoding: String.Encoding = .utf8) {
self.fileHandle = fileHandle
self.encoding = encoding
}
mutating func write(_ string: String) {
if let data = string.data(using: encoding) {
fileHandle.write(data)
}
}
}
```
`Sources/SwiftStringObfuscatorCore/StringObfuscator.swift`:
```swift
//
// StringObfuscator.swift
//
//
// Created by Lukas Gergel on 02.01.2021.
//
import Foundation
import SwiftSyntax
public class StringObfuscator {
public static func getObfuscatedContent(for sourceFile: URL) throws -> String {
let sourceFile = try SyntaxParser.parse(sourceFile)
var output = ""
let obfuscated = ObfuscateStringsRewritter().visit(sourceFile)
obfuscated.write(to: &output)
return output
}
public static func obfuscateContent(sourceFile: URL, targetFile: URL) throws {
let sourceFile = try SyntaxParser.parse(sourceFile)
let fileHandle = try FileHandle(forWritingTo: targetFile)
var output = FileHandlerOutputStream(fileHandle)
let obfuscated = ObfuscateStringsRewritter().visit(sourceFile)
obfuscated.write(to: &output)
}
}
```
`Sources/swift_string_obfuscator/main.swift`:
```swift
//
// main.swift
// string_obfuscator
//
// Created by Lukas Gergel on 27.12.2020.
//
import ArgumentParser
import Foundation
import SwiftStringObfuscatorCore
struct SwiftStringObfuscator: ParsableCommand {
static let configuration = CommandConfiguration(
abstract: "A Swift command-line tool to convert string api-keys to byte arrays.")
@Option(name: .shortAndLong, help: "Source file name.")
var sourceFile: String
@Option(name: .shortAndLong, help: "Target file name.")
var targetFile: String
mutating func run() throws {
let inUrl = URL(fileURLWithPath: sourceFile)
let outUrl = URL(fileURLWithPath: targetFile)
try! StringObfuscator.obfuscateContent(sourceFile: inUrl, targetFile: outUrl)
}
}
SwiftStringObfuscator.main()
```
`Tests/LinuxMain.swift`:
```swift
import XCTest
import string_obfuscatorTests
var tests = [XCTestCaseEntry]()
tests += string_obfuscatorTests.allTests()
XCTMain(tests)
```
`Tests/SwiftStringObfuscatorTests/SwiftStringObfuscatorTests.swift`:
```swift
import XCTest
@testable import SwiftStringObfuscatorCore
import SwiftSyntax
final class SwiftStringObfuscatorTests: XCTestCase {
let sampleFileURL = urlTempString("""
//:obfuscate
let apiKey = "something-secret"
//:obfuscate
let apiKey2="something-secret-without-spaces"
//:obfuscate
//useless line, only for test purposes
let nonObfuscated: String = "non-obfuscated-string"
struct XStruct {
let x: Int
//:obfuscate
let apiKey3 = "key-in-struct"
//:obfuscate
var param: String {
return "key-in-computed-property"
}
//:obfuscate
var dynamic2: String { "key-in-computed-property-2" }
}
class Y {
//:obfuscate
static let keyInClass: String = "api-key-in-class"
func apiFuncParam(_ key: String) { return }
}
func test() {
let testClass = Y()
//:obfuscate
testClass.apiFuncParam("api_key_func_param")
}
""")
let sampleObfuscatedOutput = """
//:obfuscate
let apiKey = String(bytes: [115,111,109,101,116,104,105,110,103,45,115,101,99,114,101,116], encoding: .utf8)
//:obfuscate
let apiKey2=String(bytes: [115,111,109,101,116,104,105,110,103,45,115,101,99,114,101,116,45,119,105,116,104,111,117,116,45,115,112,97,99,101,115], encoding: .utf8)
//:obfuscate
//useless line, only for test purposes
let nonObfuscated: String = "non-obfuscated-string"
struct XStruct {
let x: Int
//:obfuscate
let apiKey3 = String(bytes: [107,101,121,45,105,110,45,115,116,114,117,99,116], encoding: .utf8)
//:obfuscate
var param: String {
return String(bytes: [107,101,121,45,105,110,45,99,111,109,112,117,116,101,100,45,112,114,111,112,101,114,116,121], encoding: .utf8)
}
//:obfuscate
var dynamic2: String { String(bytes: [107,101,121,45,105,110,45,99,111,109,112,117,116,101,100,45,112,114,111,112,101,114,116,121,45,50], encoding: .utf8)}
}
class Y {
//:obfuscate
static let keyInClass: String = String(bytes: [97,112,105,45,107,101,121,45,105,110,45,99,108,97,115,115], encoding: .utf8)
func apiFuncParam(_ key: String) { return }
}
func test() {
let testClass = Y()
//:obfuscate
testClass.apiFuncParam(String(bytes: [97,112,105,95,107,101,121,95,102,117,110,99,95,112,97,114,97,109], encoding: .utf8))
}
"""
func testObfuscator() throws {
let obfuscated = try? StringObfuscator.getObfuscatedContent(for: sampleFileURL)
XCTAssertEqual(obfuscated, sampleObfuscatedOutput)
}
static var allTests = [("testObfuscator", testObfuscator)]
static func urlTempString(_ str: String) -> URL {
let directory = NSTemporaryDirectory()
let fileName = NSUUID().uuidString
let fullURL = NSURL.fileURL(withPathComponents: [directory, fileName])!
try! str.write(to: fullURL, atomically: true, encoding: .utf8)
return fullURL
}
}
```
`Tests/SwiftStringObfuscatorTests/XCTestManifests.swift`:
```swift
import XCTest
#if !canImport(ObjectiveC)
public func allTests() -> [XCTestCaseEntry] {
return [
testCase(string_obfuscatorTests.allTests),
]
}
#endif
```