From bf35f9731dce6130f59fe6cbe13c3c513c1f89f6 Mon Sep 17 00:00:00 2001 From: tmpest Date: Thu, 29 Jan 2026 14:33:44 +0400 Subject: [PATCH] Snapshot of current state --- .gitignore | 32 ++ LICENSE | 21 ++ Makefile | 56 +++ README.md | 71 ++++ examples/01-simple-printf/Makefile | 31 ++ examples/01-simple-printf/main.c | 15 + examples/02-messagebox/Makefile | 32 ++ examples/02-messagebox/main.c | 29 ++ examples/03-messagebox-wide/Makefile | 32 ++ examples/03-messagebox-wide/main.c | 30 ++ examples/04-advanced-peb/Makefile | 70 ++++ examples/04-advanced-peb/main.c | 264 ++++++++++++++ examples/04-advanced-peb/minimal.s | 14 + examples/04-advanced-peb/patcher.py | 58 ++++ examples/05-non-obfuscated/Makefile | 26 ++ examples/05-non-obfuscated/main.c | 18 + examples/06-batch-mode/Makefile | 37 ++ examples/06-batch-mode/greet.c | 7 + examples/06-batch-mode/info.c | 9 + examples/06-batch-mode/main.c | 17 + obfuscator/go.mod | 3 + obfuscator/main.go | 496 +++++++++++++++++++++++++++ pic_str.h | 67 ++++ 23 files changed, 1435 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 README.md create mode 100644 examples/01-simple-printf/Makefile create mode 100644 examples/01-simple-printf/main.c create mode 100644 examples/02-messagebox/Makefile create mode 100644 examples/02-messagebox/main.c create mode 100644 examples/03-messagebox-wide/Makefile create mode 100644 examples/03-messagebox-wide/main.c create mode 100644 examples/04-advanced-peb/Makefile create mode 100644 examples/04-advanced-peb/main.c create mode 100644 examples/04-advanced-peb/minimal.s create mode 100644 examples/04-advanced-peb/patcher.py create mode 100644 examples/05-non-obfuscated/Makefile create mode 100644 examples/05-non-obfuscated/main.c create mode 100644 examples/06-batch-mode/Makefile create mode 100644 examples/06-batch-mode/greet.c create mode 100644 examples/06-batch-mode/info.c create mode 100644 examples/06-batch-mode/main.c create mode 100644 obfuscator/go.mod create mode 100644 obfuscator/main.go create mode 100644 pic_str.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3a4a2e9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,32 @@ +# Build directories +build/ +**/build/ + +# Binaries +*.exe +*.bin +*.o +*.obj + +# Go binaries +obfuscator/enc_pic_str + +# IDE/Editor files +compile_commands.json +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# OS files +.DS_Store +Thumbs.db + +# Temporary files +*.tmp +*.log +*.map + +# Obfuscated output +**/obfuscated.c diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..9d95cd2 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 tmpest + +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. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..593ce5f --- /dev/null +++ b/Makefile @@ -0,0 +1,56 @@ +# ============================================================================= +# PIC String Obfuscator - Main Makefile +# Builds: Obfuscator tool + All 6 examples +# ============================================================================= + +GO = go +OBFUSCATOR_DIR = ./obfuscator +OBFUSCATOR_BIN = $(OBFUSCATOR_DIR)/enc_pic_str + +EXAMPLE_DIRS = examples/01-simple-printf \ + examples/02-messagebox \ + examples/03-messagebox-wide \ + examples/04-advanced-peb \ + examples/05-non-obfuscated \ + examples/06-batch-mode + +.PHONY: all clean obfuscator examples + +all: obfuscator examples + +obfuscator: + @echo "" + @echo "============================================" + @echo "Building Go Obfuscator" + @echo "============================================" + @cd $(OBFUSCATOR_DIR) && $(GO) build -o enc_pic_str . + @echo "[+] Success: $(OBFUSCATOR_BIN)" + +examples: + @for dir in $(EXAMPLE_DIRS); do \ + echo ""; \ + echo "============================================"; \ + echo "Building $$dir"; \ + echo "============================================"; \ + $(MAKE) -C $$dir || exit 1; \ + done + @echo "" + @echo "============================================" + @echo "Build Complete!" + @echo "============================================" + @echo "Example 1: examples/01-simple-printf/build/example1.exe" + @echo "Example 2: examples/02-messagebox/build/example2.exe" + @echo "Example 3: examples/03-messagebox-wide/build/example3.exe" + @echo "Example 4: examples/04-advanced-peb/build/example4.exe" + @echo "Example 5: examples/05-non-obfuscated/build/example5.exe" + @echo "Example 6: examples/06-batch-mode/build/example6.exe" + @echo "============================================" + +clean: + @echo "[*] Cleaning obfuscator..." + @rm -f $(OBFUSCATOR_BIN) + @echo "[*] Cleaning all examples..." + @for dir in $(EXAMPLE_DIRS); do \ + $(MAKE) -C $$dir clean; \ + done + @echo "[*] Cleaned." diff --git a/README.md b/README.md new file mode 100644 index 0000000..ebaec68 --- /dev/null +++ b/README.md @@ -0,0 +1,71 @@ +# Encrypted PIC String + +## Usage + +Run the obfuscator on a C file: + +```bash +enc_pic_str -i input.c -o output.c +``` + +It turns this: + +```c +puts(pic_str("Hello, World!")); +``` + +Into this: + +```c +puts(((const char *)(({ + struct _str_struct_1 { + char buf[14]; + } _str_inst_1; + unsigned char *_data_ptr; + volatile unsigned long long _key_val; + volatile unsigned long _len = 13; + __asm__ volatile( + "jmp skip_str_%=\n" + "str_data_%=:\n" + ".byte 0x9c, 0xc5, 0xc6, 0xef, 0xf5, 0x27, 0xe5, 0xbd, 0xbb, 0xd2, 0xc6, 0xe7, 0xbb\n" + "str_key_%=:\n" + ".quad 0xeac50b9a83aaa0d4\n" + "skip_str_%=:\n" + "lea str_data_%=(%%rip), %0\n" + "movq str_key_%=(%%rip), %1\n" + : "=r"(_data_ptr), "=r"(_key_val) + : + :); + for (volatile unsigned long _i = 0; _i < _len; _i++) { + volatile unsigned char _key_byte = (_key_val >> ((_i % 8) * 8)) & 0xFF; + _str_inst_1.buf[_i] = _data_ptr[_i] ^ _key_byte; + } + _str_inst_1.buf[13] = 0; + _str_inst_1; +}).buf))); +``` + +This approach allows encrypted strings to be stored in the `.text` section and decoded at runtime. The method reasoning and other methods are described in the accompanying blog post: https://tmpest.dev/enc_pic_str.html + +Note that there are some cases where using the `pic_str` function may be unsafe. Please refer to `pic_str.h` in this repository for details and always conduct your own testing. For a good template for bare bones PIC/shellcode, see [`enc_pic_str/examples/04-advanced-peb`](examples/04-advanced-peb). + +## Build + +### Arch Linux + +```bash +sudo pacman -S go clang llvm lld mingw-w64-gcc make +``` + +### Debian/Ubuntu + +```bash +sudo apt update +sudo apt install golang clang llvm lld mingw-w64 make +``` + +After installing the dependencies, run `make` to build the project. Independent makefiles also exist in the example directory. + +## Disclaimer + +This was vibe coded but method itself has been used and tested. diff --git a/examples/01-simple-printf/Makefile b/examples/01-simple-printf/Makefile new file mode 100644 index 0000000..d9162c4 --- /dev/null +++ b/examples/01-simple-printf/Makefile @@ -0,0 +1,31 @@ +# Example 1: Simple printf with stdlib +# ============================================================================= + +CC = clang +TARGET = x86_64-w64-mingw32 +OBFUSCATOR = ../../obfuscator/enc_pic_str +CFLAGS = --target=$(TARGET) -I../.. -O2 + +BUILD_DIR = build +SOURCE = main.c +OBFUSCATED = $(BUILD_DIR)/obfuscated.c +OUTPUT = $(BUILD_DIR)/example1.exe + +.PHONY: all clean + +all: clean $(OUTPUT) + +$(BUILD_DIR): + @mkdir -p $(BUILD_DIR) + +$(OBFUSCATED): $(SOURCE) | $(BUILD_DIR) + @echo "[*] Obfuscating source..." + $(OBFUSCATOR) -i $(SOURCE) -o $(OBFUSCATED) + +$(OUTPUT): $(OBFUSCATED) + @echo "[*] Compiling..." + $(CC) $(OBFUSCATED) $(CFLAGS) -o $(OUTPUT) + @echo "[+] Success: $(OUTPUT)" + +clean: + @rm -rf $(BUILD_DIR) diff --git a/examples/01-simple-printf/main.c b/examples/01-simple-printf/main.c new file mode 100644 index 0000000..bd010ac --- /dev/null +++ b/examples/01-simple-printf/main.c @@ -0,0 +1,15 @@ +/* + * Example 1: Simple printf with stdlib + * + * This example demonstrates basic string obfuscation with standard library functions. + */ + +#include "pic_str.h" +#include + +int main() { + // Basic string output + puts(pic_str("Hello, World!")); + + return 0; +} diff --git a/examples/02-messagebox/Makefile b/examples/02-messagebox/Makefile new file mode 100644 index 0000000..0cb0cca --- /dev/null +++ b/examples/02-messagebox/Makefile @@ -0,0 +1,32 @@ +# Example 2: MessageBox with regular strings (ANSI) +# ============================================================================= + +CC = clang +TARGET = x86_64-w64-mingw32 +OBFUSCATOR = ../../obfuscator/enc_pic_str +CFLAGS = --target=$(TARGET) -I../.. -O2 +LDFLAGS = -luser32 + +BUILD_DIR = build +SOURCE = main.c +OBFUSCATED = $(BUILD_DIR)/obfuscated.c +OUTPUT = $(BUILD_DIR)/example2.exe + +.PHONY: all clean + +all: clean $(OUTPUT) + +$(BUILD_DIR): + @mkdir -p $(BUILD_DIR) + +$(OBFUSCATED): $(SOURCE) | $(BUILD_DIR) + @echo "[*] Obfuscating source..." + $(OBFUSCATOR) -i $(SOURCE) -o $(OBFUSCATED) + +$(OUTPUT): $(OBFUSCATED) + @echo "[*] Compiling..." + $(CC) $(OBFUSCATED) $(CFLAGS) $(LDFLAGS) -o $(OUTPUT) + @echo "[+] Success: $(OUTPUT)" + +clean: + @rm -rf $(BUILD_DIR) diff --git a/examples/02-messagebox/main.c b/examples/02-messagebox/main.c new file mode 100644 index 0000000..5f4d566 --- /dev/null +++ b/examples/02-messagebox/main.c @@ -0,0 +1,29 @@ +/* + * Example 2: MessageBox with regular strings (ANSI) + * + * This example demonstrates obfuscating strings used in Windows MessageBoxA calls. + */ + +#include "pic_str.h" +#include + +int main() { + // Simple message box + MessageBoxA(NULL, pic_str("This is an obfuscated message!"), pic_str("Example 2"), MB_OK | MB_ICONINFORMATION); + + // Message box with different icon + MessageBoxA(NULL, pic_str("This string is hidden from static analysis."), pic_str("String Obfuscation"), + MB_OK | MB_ICONEXCLAMATION); + + // Yes/No dialog + int result = + MessageBoxA(NULL, pic_str("Do you want to continue?"), pic_str("Confirmation"), MB_YESNO | MB_ICONQUESTION); + + if (result == IDYES) { + MessageBoxA(NULL, pic_str("You clicked Yes!"), pic_str("Result"), MB_OK); + } else { + MessageBoxA(NULL, pic_str("You clicked No!"), pic_str("Result"), MB_OK); + } + + return 0; +} diff --git a/examples/03-messagebox-wide/Makefile b/examples/03-messagebox-wide/Makefile new file mode 100644 index 0000000..333c8c7 --- /dev/null +++ b/examples/03-messagebox-wide/Makefile @@ -0,0 +1,32 @@ +# Example 3: MessageBox with wide strings (Unicode) +# ============================================================================= + +CC = clang +TARGET = x86_64-w64-mingw32 +OBFUSCATOR = ../../obfuscator/enc_pic_str +CFLAGS = --target=$(TARGET) -I../.. -O2 +LDFLAGS = -luser32 + +BUILD_DIR = build +SOURCE = main.c +OBFUSCATED = $(BUILD_DIR)/obfuscated.c +OUTPUT = $(BUILD_DIR)/example3.exe + +.PHONY: all clean + +all: clean $(OUTPUT) + +$(BUILD_DIR): + @mkdir -p $(BUILD_DIR) + +$(OBFUSCATED): $(SOURCE) | $(BUILD_DIR) + @echo "[*] Obfuscating source..." + $(OBFUSCATOR) -i $(SOURCE) -o $(OBFUSCATED) + +$(OUTPUT): $(OBFUSCATED) + @echo "[*] Compiling..." + $(CC) $(OBFUSCATED) $(CFLAGS) $(LDFLAGS) -o $(OUTPUT) + @echo "[+] Success: $(OUTPUT)" + +clean: + @rm -rf $(BUILD_DIR) diff --git a/examples/03-messagebox-wide/main.c b/examples/03-messagebox-wide/main.c new file mode 100644 index 0000000..c77cc60 --- /dev/null +++ b/examples/03-messagebox-wide/main.c @@ -0,0 +1,30 @@ +/* + * Example 3: MessageBox with wide strings (Unicode) + * + * This example demonstrates obfuscating wide character strings (wchar_t) + * used in Windows MessageBoxW calls. Wide strings support full Unicode. + */ + +#include "pic_str.h" +#include + +int main() { + // Simple wide string message box + MessageBoxW(NULL, pic_str(L"This is a wide character string!"), pic_str(L"Example 3"), MB_OK | MB_ICONINFORMATION); + + // Unicode characters (emoji, symbols) + MessageBoxW(NULL, pic_str(L"Unicode support: \u2764 \u263A \u2605 \u2602"), pic_str(L"Unicode Demo"), + MB_OK | MB_ICONINFORMATION); + + // Chinese characters + MessageBoxW(NULL, + pic_str(L"\u4F60\u597D\u4E16\u754C"), // "Hello World" in Chinese + pic_str(L"\u6D4B\u8BD5"), // "Test" in Chinese + MB_OK); + + // Mixed content + MessageBoxW(NULL, pic_str(L"Mixed: ABC \u2022 \u4E2D\u6587 \u2022 123"), pic_str(L"Wide String Obfuscation"), + MB_OK | MB_ICONEXCLAMATION); + + return 0; +} diff --git a/examples/04-advanced-peb/Makefile b/examples/04-advanced-peb/Makefile new file mode 100644 index 0000000..36fa093 --- /dev/null +++ b/examples/04-advanced-peb/Makefile @@ -0,0 +1,70 @@ +# Example 4: Advanced PEB/LDR with Position Independent Code +# ============================================================================= + +CC = clang +AS = clang +LD = clang +OBJDUMP = llvm-objdump +OBJCOPY = llvm-objcopy +PYTHON = python3 +TARGET = x86_64-w64-mingw32 +OBFUSCATOR = ../../obfuscator/enc_pic_str + +# PIC-safe compiler flags +COMMON_FLAGS = --target=$(TARGET) -fno-ident -fno-asynchronous-unwind-tables \ + -fno-vectorize -fno-slp-vectorize -fno-unwind-tables -fno-stack-protector \ + -fno-builtin -nostdlib -fdata-sections -ffunction-sections \ + -fomit-frame-pointer -fno-jump-tables -falign-functions=1 -mno-stack-arg-probe \ + -fno-builtin-memcpy -I../.. + +OPT_FLAGS = -Os + +BUILD_DIR = build +SOURCE = main.c +OBFUSCATED = $(BUILD_DIR)/obfuscated.c +OUTPUT = $(BUILD_DIR)/example4.exe + +.PHONY: all clean + +all: clean $(OUTPUT) + +$(BUILD_DIR): + @mkdir -p $(BUILD_DIR) + +$(OBFUSCATED): $(SOURCE) | $(BUILD_DIR) + @echo "[*] Step 1: Obfuscating source..." + $(OBFUSCATOR) -i $(SOURCE) -o $(OBFUSCATED) + +$(BUILD_DIR)/pic.exe: $(OBFUSCATED) + @echo "[*] Step 2: Compiling PIC Image..." + @echo "WinMainCRTStartup" > $(BUILD_DIR)/order.txt + $(CC) $(OBFUSCATED) $(OPT_FLAGS) $(COMMON_FLAGS) \ + -fuse-ld=lld \ + -Wl,-s \ + -Wl,-eWinMainCRTStartup \ + -Wl,--subsystem,windows \ + -Wl,/order:@$(BUILD_DIR)/order.txt \ + -Wl,/map:$(BUILD_DIR)/build.map \ + -o $(BUILD_DIR)/pic.exe + +$(BUILD_DIR)/shellcode.bin: $(BUILD_DIR)/pic.exe + @echo "[*] Step 3: Extracting Shellcode..." + $(OBJCOPY) --dump-section .text=$(BUILD_DIR)/shellcode.bin $(BUILD_DIR)/pic.exe + +$(BUILD_DIR)/stub.exe: minimal.s + @echo "[*] Step 4: Creating Stub..." + $(AS) --target=$(TARGET) -c minimal.s -o $(BUILD_DIR)/stub.o + $(LD) --target=$(TARGET) $(BUILD_DIR)/stub.o -o $(BUILD_DIR)/stub.exe \ + -fuse-ld=lld -nostdlib \ + -Wl,--subsystem=windows \ + -Wl,--image-base=0x140000000 \ + -Wl,-s \ + -Wl,-e_mainCRTStartup + +$(OUTPUT): $(BUILD_DIR)/shellcode.bin $(BUILD_DIR)/stub.exe + @echo "[*] Step 5: Patching Shellcode into Stub..." + $(PYTHON) patcher.py $(BUILD_DIR)/stub.exe $(BUILD_DIR)/shellcode.bin $(OUTPUT) + @echo "[+] Success: $(OUTPUT)" + +clean: + @rm -rf $(BUILD_DIR) diff --git a/examples/04-advanced-peb/main.c b/examples/04-advanced-peb/main.c new file mode 100644 index 0000000..80771a7 --- /dev/null +++ b/examples/04-advanced-peb/main.c @@ -0,0 +1,264 @@ +/* + * Example 4: Advanced PEB/LDR Manual Function Resolution + * + * This example demonstrates advanced techniques for obfuscated string usage: + * - Manual DLL resolution via PEB (Process Environment Block) + * - Manual function resolution without Import Address Table + * - Custom CRT entry point (no standard library dependencies) + * - Position-independent code suitable for shellcode + */ + +#include "pic_str.h" +#include +#include +#include + +// ============================================================================ +// PEB/LDR Structures (Required for Manual Resolution) +// ============================================================================ + +typedef struct _UNICODE_STR { + USHORT Length; + USHORT MaximumLength; + PWSTR pBuffer; +} UNICODE_STR, *PUNICODE_STR; + +typedef struct _LDR_DATA_TABLE_ENTRY { + LIST_ENTRY InMemoryOrderModuleList; + LIST_ENTRY InInitializationOrderModuleList; + PVOID DllBase; + PVOID EntryPoint; + ULONG SizeOfImage; + UNICODE_STR FullDllName; + UNICODE_STR BaseDllName; + ULONG Flags; + SHORT LoadCount; + SHORT TlsIndex; + LIST_ENTRY HashTableEntry; + ULONG TimeDateStamp; +} LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY; + +typedef struct _PEB_LDR_DATA { + DWORD dwLength; + DWORD dwInitialized; + LPVOID lpSsHandle; + LIST_ENTRY InLoadOrderModuleList; + LIST_ENTRY InMemoryOrderModuleList; + LIST_ENTRY InInitializationOrderModuleList; + LPVOID lpEntryInProgress; +} PEB_LDR_DATA, *PPEB_LDR_DATA; + +typedef struct __PEB { + BYTE bInheritedAddressSpace; + BYTE bReadImageFileExecOptions; + BYTE bBeingDebugged; + BYTE bSpareBool; + LPVOID lpMutant; + LPVOID lpImageBaseAddress; + PPEB_LDR_DATA pLdr; + LPVOID lpProcessParameters; + LPVOID lpSubSystemData; + LPVOID lpProcessHeap; + PRTL_CRITICAL_SECTION pFastPebLock; + LPVOID lpFastPebLockRoutine; + LPVOID lpFastPebUnlockRoutine; + DWORD dwEnvironmentUpdateCount; + LPVOID lpKernelCallbackTable; + DWORD dwSystemReserved; + DWORD dwAtlThunkSListPtr32; +} _PEB, *_PPEB; + +#define PTR_OFFSET(x, y) ((void *)(x) + (ULONG)(y)) + +typedef struct { + IMAGE_DOS_HEADER *DosHeader; + IMAGE_NT_HEADERS *NtHeaders; + IMAGE_OPTIONAL_HEADER *OptionalHeader; +} DLLDATA; + +// ============================================================================ +// Function Prototypes +// ============================================================================ + +typedef VOID(WINAPI *pOutputDebugStringA)(LPCSTR lpOutputString); +typedef int(WINAPI *pMessageBoxA)(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType); +typedef int(WINAPI *pMessageBoxW)(HWND hWnd, LPCWSTR lpText, LPCWSTR lpCaption, UINT uType); +typedef HMODULE(WINAPI *pLoadLibraryA)(LPCSTR lpLibFileName); + +// ============================================================================ +// String Helpers +// ============================================================================ + +static inline int my_stricmp(const char *s1, const char *s2) { + while (*s1 && *s2) { + char c1 = *s1; + char c2 = *s2; + if (c1 >= 'a' && c1 <= 'z') + c1 -= 32; + if (c2 >= 'a' && c2 <= 'z') + c2 -= 32; + if (c1 != c2) + return c1 - c2; + s1++; + s2++; + } + return *s1 - *s2; +} + +static inline int my_strlen(const char *s) { + int len = 0; + while (*s++) + len++; + return len; +} + +static inline int my_wcslen(const wchar_t *s) { + int len = 0; + while (*s++) + len++; + return len; +} + +// ============================================================================ +// Resolution Logic +// ============================================================================ + +HANDLE findModuleByName(const char *moduleName) { + _PEB *pPEB; + LDR_DATA_TABLE_ENTRY *pEntry; + LDR_DATA_TABLE_ENTRY *pFirstEntry; + char ansiName[256]; + USHORT counter; + +#if defined(_WIN64) + pPEB = (_PEB *)__readgsqword(0x60); +#elif defined(_WIN32) + pPEB = (_PEB *)__readfsdword(0x30); +#endif + + if (!pPEB || !pPEB->pLdr) + return NULL; + + pEntry = (LDR_DATA_TABLE_ENTRY *)pPEB->pLdr->InMemoryOrderModuleList.Flink; + pFirstEntry = pEntry; + + while (pEntry) { + if (!pEntry->BaseDllName.pBuffer || pEntry->BaseDllName.Length == 0) + goto next_entry; + + char *name = (char *)pEntry->BaseDllName.pBuffer; + counter = pEntry->BaseDllName.Length / 2; + if (counter > 255) + counter = 255; + + for (USHORT i = 0; i < counter; i++) + ansiName[i] = name[i * 2]; + ansiName[counter] = 0; + + if (my_stricmp(ansiName, moduleName) == 0) + return (HANDLE)pEntry->DllBase; + + next_entry: + pEntry = (LDR_DATA_TABLE_ENTRY *)pEntry->InMemoryOrderModuleList.Flink; + if (pEntry == pFirstEntry) + break; + } + return NULL; +} + +void ParseDLL(char *src, DLLDATA *data) { + data->DosHeader = (IMAGE_DOS_HEADER *)src; + data->NtHeaders = (IMAGE_NT_HEADERS *)(src + data->DosHeader->e_lfanew); + data->OptionalHeader = (IMAGE_OPTIONAL_HEADER *)&(data->NtHeaders->OptionalHeader); +} + +FARPROC findFunctionByName(HANDLE src, const char *wantedFunction) { + DLLDATA data; + ParseDLL((char *)src, &data); + + IMAGE_DATA_DIRECTORY *exportTableHdr = data.OptionalHeader->DataDirectory + IMAGE_DIRECTORY_ENTRY_EXPORT; + IMAGE_EXPORT_DIRECTORY *exportDir = (IMAGE_EXPORT_DIRECTORY *)PTR_OFFSET(src, exportTableHdr->VirtualAddress); + + DWORD *exportName = (DWORD *)PTR_OFFSET(src, exportDir->AddressOfNames); + WORD *exportOrdinal = (WORD *)PTR_OFFSET(src, exportDir->AddressOfNameOrdinals); + + for (DWORD i = 0; i < exportDir->NumberOfNames; i++) { + char *funcName = (char *)PTR_OFFSET(src, exportName[i]); + if (my_stricmp(funcName, wantedFunction) == 0) { + DWORD *exportAddress = PTR_OFFSET(src, exportDir->AddressOfFunctions); + return (FARPROC)PTR_OFFSET(src, *(exportAddress + exportOrdinal[i])); + } + } + return NULL; +} + +// ============================================================================ +// Test Helper +// ============================================================================ + +void test_log(pOutputDebugStringA dbgStr, const char *msg) { + if (dbgStr) { + dbgStr(msg); + } +} + +// ============================================================================ +// Main Test Execution +// ============================================================================ + +__attribute__((noinline)) static inline void RealMain(); + +int WinMainCRTStartup() { + __asm__ __volatile__("and $0xFFFFFFFFFFFFFFF0, %%rsp \n\t" + "sub $0x20, %%rsp \n\t" + "call %P0 \n\t" + "add $0x20, %%rsp \n\t" + : + : "i"(RealMain) + : "memory", "cc"); + return 0; +} + +__attribute__((noinline)) static inline void RealMain() { + // Resolve functions + HANDLE kernel32 = findModuleByName(pic_str("KERNEL32.DLL")); + if (!kernel32) + return; + + pOutputDebugStringA dbgStr = (pOutputDebugStringA)findFunctionByName(kernel32, pic_str("OutputDebugStringA")); + pLoadLibraryA LoadLib = (pLoadLibraryA)findFunctionByName(kernel32, pic_str("LoadLibraryA")); + + HANDLE user32 = findModuleByName(pic_str("USER32.DLL")); + if (!user32 && LoadLib) { + user32 = (HANDLE)LoadLib(pic_str("USER32.DLL")); + } + + if (!user32) { + test_log(dbgStr, pic_str("[ERROR] Cannot load User32")); + return; + } + + pMessageBoxA MsgBoxA = (pMessageBoxA)findFunctionByName(user32, pic_str("MessageBoxA")); + pMessageBoxW MsgBoxW = (pMessageBoxW)findFunctionByName(user32, pic_str("MessageBoxW")); + + // ========================================================================== + // TEST SUITE + // ========================================================================== + + test_log(dbgStr, pic_str("=== PIC String Obfuscator Advanced Test ===\n")); + + if (MsgBoxA) { + MsgBoxA(NULL, pic_str("This example uses manual DLL/function resolution!"), pic_str("Advanced Example"), + MB_OK | MB_ICONINFORMATION); + } + + if (MsgBoxW) { + MsgBoxW(NULL, pic_str(L"Wide strings with manual resolution \u2764"), pic_str(L"Unicode Support"), + MB_OK | MB_ICONINFORMATION); + } + + if (MsgBoxA) { + MsgBoxA(NULL, pic_str("All strings are obfuscated and resolved at runtime!"), pic_str("Complete"), + MB_OK | MB_ICONINFORMATION); + } +} diff --git a/examples/04-advanced-peb/minimal.s b/examples/04-advanced-peb/minimal.s new file mode 100644 index 0000000..1d1abf9 --- /dev/null +++ b/examples/04-advanced-peb/minimal.s @@ -0,0 +1,14 @@ +# GNU AS (AT&T syntax) - Minimal PE with 100KB .text section and int3 +# This creates a freestanding executable that doesn't link with msvcrt +# Save this as: minimal.s + .section .text + .globl _mainCRTStartup + +_mainCRTStartup: + int3 # First instruction - breakpoint (0xCC) + ret + + # Pad the rest of the 100KB section with ret instructions + # 100KB = 0x19000 = 102400 bytes, minus 2 bytes for int3 and ret + # ret instruction is 0xC3 + .fill 3023980, 1, 0xC3 diff --git a/examples/04-advanced-peb/patcher.py b/examples/04-advanced-peb/patcher.py new file mode 100644 index 0000000..39cd146 --- /dev/null +++ b/examples/04-advanced-peb/patcher.py @@ -0,0 +1,58 @@ +import sys +import os + +def patch_pe_section(stub_path, payload_path, output_path): + if not os.path.exists(stub_path) or not os.path.exists(payload_path): + print("[-] Error: Input files missing.") + sys.exit(1) + + with open(stub_path, "rb") as f: + stub = bytearray(f.read()) + with open(payload_path, "rb") as f: + payload = f.read() + + # Verify MZ Header + if stub[:2] != b'MZ': + print("[-] Error: Stub is not a valid PE file.") + sys.exit(1) + + # Get PE Header Offset + e_lfanew = int.from_bytes(stub[0x3C:0x40], "little") + + # Section Table Info + num_sections = int.from_bytes(stub[e_lfanew+6:e_lfanew+8], "little") + size_of_optional_header = int.from_bytes(stub[e_lfanew+20:e_lfanew+22], "little") + section_table_offset = e_lfanew + 24 + size_of_optional_header + + # Search for .text section + for i in range(num_sections): + offset = section_table_offset + (i * 40) + section_name = stub[offset:offset+8].decode('ascii').rstrip('\x00') + + if section_name == ".text": + # PointerToRawData (Offset 20), SizeOfRawData (Offset 16) + raw_size = int.from_bytes(stub[offset+16:offset+20], "little") + raw_ptr = int.from_bytes(stub[offset+20:offset+24], "little") + + if len(payload) > raw_size: + print(f"[-] Error: Payload ({len(payload)} bytes) is larger than .text section ({raw_size} bytes).") + print("[!] Increase the padding in minimal.s") + sys.exit(1) + + # Patch the bytes + stub[raw_ptr:raw_ptr+len(payload)] = payload + + with open(output_path, "wb") as f: + f.write(stub) + + print(f"[+] Successfully patched {len(payload)} bytes into {section_name} at offset {hex(raw_ptr)}") + return + + print("[-] Error: Could not find .text section in stub.") + sys.exit(1) + +if __name__ == "__main__": + if len(sys.argv) != 4: + print("Usage: python patcher.py ") + sys.exit(1) + patch_pe_section(sys.argv[1], sys.argv[2], sys.argv[3]) diff --git a/examples/05-non-obfuscated/Makefile b/examples/05-non-obfuscated/Makefile new file mode 100644 index 0000000..1bcadef --- /dev/null +++ b/examples/05-non-obfuscated/Makefile @@ -0,0 +1,26 @@ +# Example 5: Non-obfuscated (header passthrough) +# ============================================================================= + +CC = clang +TARGET = x86_64-w64-mingw32 +CFLAGS = --target=$(TARGET) -I../.. -O2 + +BUILD_DIR = build +SOURCE = main.c +OUTPUT = $(BUILD_DIR)/example5.exe + +.PHONY: all clean + +all: clean $(OUTPUT) + +$(BUILD_DIR): + @mkdir -p $(BUILD_DIR) + +$(OUTPUT): $(SOURCE) | $(BUILD_DIR) + @echo "[*] Compiling WITHOUT obfuscation..." + @echo "[*] (Demonstrating non-invasive header)" + $(CC) $(SOURCE) $(CFLAGS) -o $(OUTPUT) + @echo "[+] Success: $(OUTPUT)" + +clean: + @rm -rf $(BUILD_DIR) diff --git a/examples/05-non-obfuscated/main.c b/examples/05-non-obfuscated/main.c new file mode 100644 index 0000000..3087444 --- /dev/null +++ b/examples/05-non-obfuscated/main.c @@ -0,0 +1,18 @@ +/* + * Example 5: Non-obfuscated code (header passthrough) + * + * This example demonstrates that the header is non-invasive. + * The code compiles and runs WITHOUT obfuscation because + * pic_str() simply passes strings through when not obfuscated. + */ + +#include "pic_str.h" +#include + +int main() { + printf("%s\n", pic_str("This is NOT obfuscated!")); + printf("%s\n", pic_str("The header just passes strings through.")); + printf("%s\n", pic_str("No obfuscator needed - still works!")); + + return 0; +} diff --git a/examples/06-batch-mode/Makefile b/examples/06-batch-mode/Makefile new file mode 100644 index 0000000..2d8a4f4 --- /dev/null +++ b/examples/06-batch-mode/Makefile @@ -0,0 +1,37 @@ +# Example 6: Batch mode - multiple files obfuscated together +# ============================================================================= + +CC = clang +TARGET = x86_64-w64-mingw32 +OBFUSCATOR = ../../obfuscator/enc_pic_str +CFLAGS = --target=$(TARGET) -I../.. -O2 + +BUILD_DIR = build +SOURCES = main.c greet.c info.c +OBFUSCATED = $(addprefix $(BUILD_DIR)/,$(SOURCES)) +OUTPUT = $(BUILD_DIR)/example6.exe + +.PHONY: all clean + +all: clean $(OUTPUT) + +$(BUILD_DIR): + @mkdir -p $(BUILD_DIR) + +$(OBFUSCATOR): + @echo "[*] Building obfuscator..." + @cd ../../obfuscator && go build -o enc_pic_str + +$(OBFUSCATED): $(SOURCES) $(OBFUSCATOR) | $(BUILD_DIR) + @echo "[*] Copying header file to build directory..." + @cp ../../pic_str.h $(BUILD_DIR)/ + @echo "[*] Obfuscating multiple C files in batch mode..." + $(OBFUSCATOR) -mode batch -i $(shell echo $(SOURCES) | tr ' ' ',') -o $(BUILD_DIR) + +$(OUTPUT): $(OBFUSCATED) + @echo "[*] Compiling..." + cd $(BUILD_DIR) && $(CC) $(SOURCES) $(CFLAGS) -o $(notdir $(OUTPUT)) + @echo "[+] Success: $(OUTPUT)" + +clean: + @rm -rf $(BUILD_DIR) diff --git a/examples/06-batch-mode/greet.c b/examples/06-batch-mode/greet.c new file mode 100644 index 0000000..7956d1e --- /dev/null +++ b/examples/06-batch-mode/greet.c @@ -0,0 +1,7 @@ +#include +#include "pic_str.h" + +void greet_user(void) { + printf("%s", pic_str("Hello from greet.c!\n")); + printf("%s", pic_str("This file contains greeting functionality.\n")); +} diff --git a/examples/06-batch-mode/info.c b/examples/06-batch-mode/info.c new file mode 100644 index 0000000..53faa99 --- /dev/null +++ b/examples/06-batch-mode/info.c @@ -0,0 +1,9 @@ +#include +#include "pic_str.h" + +void show_info(void) { + printf("%s", pic_str("Information from info.c:\n")); + printf("%s", pic_str(" - Multiple files processed in batch mode\n")); + printf("%s", pic_str(" - All strings are obfuscated\n")); + printf("%s", pic_str(" - Compiled together into one binary\n")); +} diff --git a/examples/06-batch-mode/main.c b/examples/06-batch-mode/main.c new file mode 100644 index 0000000..a640636 --- /dev/null +++ b/examples/06-batch-mode/main.c @@ -0,0 +1,17 @@ +#include +#include "pic_str.h" + +// External functions from other files +extern void greet_user(void); +extern void show_info(void); + +int main() { + printf("%s", pic_str("=== Batch Mode Demo ===\n")); + + greet_user(); + show_info(); + + printf("%s", pic_str("Program complete!\n")); + + return 0; +} diff --git a/obfuscator/go.mod b/obfuscator/go.mod new file mode 100644 index 0000000..bf1d637 --- /dev/null +++ b/obfuscator/go.mod @@ -0,0 +1,3 @@ +module enc_pic_str + +go 1.25.5 diff --git a/obfuscator/main.go b/obfuscator/main.go new file mode 100644 index 0000000..cf36913 --- /dev/null +++ b/obfuscator/main.go @@ -0,0 +1,496 @@ +package main + +import ( + "crypto/rand" + "encoding/binary" + "flag" + "fmt" + "os" + "path/filepath" + "regexp" + "strings" +) + +var ( + mode = flag.String("mode", "single", "Mode: single or batch") + inputFile = flag.String("i", "", "Input C file(s) - single file for single mode, comma-separated list for batch mode") + outputFile = flag.String("o", "", "Output C file (single mode) or output directory (batch mode)") + counter = 0 +) + +func main() { + flag.Usage = func() { + fmt.Fprintf(os.Stderr, "String Obfuscator - Obfuscate C string literals\n\n") + fmt.Fprintf(os.Stderr, "Usage:\n") + fmt.Fprintf(os.Stderr, " Single file mode:\n") + fmt.Fprintf(os.Stderr, " %s -mode single -i -o \n\n", os.Args[0]) + fmt.Fprintf(os.Stderr, " Batch mode:\n") + fmt.Fprintf(os.Stderr, " %s -mode batch -i -o \n\n", os.Args[0]) + fmt.Fprintf(os.Stderr, "Flags:\n") + flag.PrintDefaults() + } + + flag.Parse() + + switch *mode { + case "single": + processSingleMode() + case "batch": + processBatchMode() + default: + fmt.Fprintf(os.Stderr, "Error: Invalid mode '%s'. Use 'single' or 'batch'\n", *mode) + flag.Usage() + os.Exit(1) + } +} + +func processSingleMode() { + if *inputFile == "" || *outputFile == "" { + fmt.Fprintln(os.Stderr, "Error: Single mode requires both -i and -o flags") + flag.Usage() + os.Exit(1) + } + + counter = 0 + data, err := os.ReadFile(*inputFile) + if err != nil { + fmt.Fprintf(os.Stderr, "Error reading input file: %v\n", err) + os.Exit(1) + } + + content := string(data) + obfuscated := obfuscateStrings(content) + + err = os.WriteFile(*outputFile, []byte(obfuscated), 0644) + if err != nil { + fmt.Fprintf(os.Stderr, "Error writing output file: %v\n", err) + os.Exit(1) + } + + fmt.Printf("[+] Obfuscated %d strings in %s -> %s\n", counter, *inputFile, *outputFile) +} + +func processBatchMode() { + if *inputFile == "" || *outputFile == "" { + fmt.Fprintln(os.Stderr, "Error: Batch mode requires both -i and -o flags") + flag.Usage() + os.Exit(1) + } + + // Parse comma-separated input files + inputFiles := strings.Split(*inputFile, ",") + for i := range inputFiles { + inputFiles[i] = strings.TrimSpace(inputFiles[i]) + } + + if len(inputFiles) == 0 { + fmt.Fprintln(os.Stderr, "Error: Batch mode requires at least one input file") + flag.Usage() + os.Exit(1) + } + + // Check if output path exists and is a directory, or can be created + info, err := os.Stat(*outputFile) + if err == nil { + // Path exists, check if it's a directory + if !info.IsDir() { + fmt.Fprintf(os.Stderr, "Error: Output path '%s' exists but is not a directory\n", *outputFile) + os.Exit(1) + } + } else if os.IsNotExist(err) { + // Directory doesn't exist, create it + err = os.MkdirAll(*outputFile, 0755) + if err != nil { + fmt.Fprintf(os.Stderr, "Error creating output directory: %v\n", err) + os.Exit(1) + } + } else { + // Some other error + fmt.Fprintf(os.Stderr, "Error checking output directory: %v\n", err) + os.Exit(1) + } + + totalStrings := 0 + successCount := 0 + + for _, inputPath := range inputFiles { + counter = 0 + + data, err := os.ReadFile(inputPath) + if err != nil { + fmt.Fprintf(os.Stderr, "[!] Error reading %s: %v\n", inputPath, err) + continue + } + + content := string(data) + obfuscated := obfuscateStrings(content) + + // Generate output filename preserving the original name + outputPath := filepath.Join(*outputFile, filepath.Base(inputPath)) + + err = os.WriteFile(outputPath, []byte(obfuscated), 0644) + if err != nil { + fmt.Fprintf(os.Stderr, "[!] Error writing %s: %v\n", outputPath, err) + continue + } + + fmt.Printf("[+] %s -> %s (%d strings)\n", inputPath, outputPath, counter) + totalStrings += counter + successCount++ + } + + fmt.Printf("\n[+] Batch complete: %d/%d files processed, %d total strings obfuscated\n", + successCount, len(inputFiles), totalStrings) +} + +func obfuscateStrings(content string) string { + // Inject memcpy implementation after includes + memcpyImpl := ` +// Provide memcpy implementation for -nostdlib builds +#if !defined(memcpy) && !defined(__memcpy_defined) +#define __memcpy_defined + +__attribute__((weak)) +void *memcpy(void *dst, const void *src, size_t n) { + unsigned char *d = (unsigned char *)dst; + const unsigned char *s = (const unsigned char *)src; + while (n--) { + *d++ = *s++; + } + return dst; +} +#endif + +` + + // Find the last #include or the first non-preprocessor line + lines := strings.Split(content, "\n") + insertIdx := 0 + for i, line := range lines { + trimmed := strings.TrimSpace(line) + if strings.HasPrefix(trimmed, "#include") { + insertIdx = i + 1 + } else if insertIdx > 0 && !strings.HasPrefix(trimmed, "#") && trimmed != "" && !strings.HasPrefix(trimmed, "//") { + break + } + } + + // Insert memcpy implementation + if insertIdx > 0 && insertIdx < len(lines) { + before := strings.Join(lines[:insertIdx], "\n") + after := strings.Join(lines[insertIdx:], "\n") + content = before + "\n" + memcpyImpl + after + } else { + content = memcpyImpl + content + } + + // Regex to match pic_str("...") or pic_str(L"...") + // Updated regex to capture content including escape sequences + reChar := regexp.MustCompile(`pic_str\s*\(\s*"((?:[^"\\]|\\.)*)"\s*\)`) + reWide := regexp.MustCompile(`pic_str\s*\(\s*L"((?:[^"\\]|\\.)*)"\s*\)`) + + // Replace wide strings first + content = reWide.ReplaceAllStringFunc(content, func(match string) string { + submatches := reWide.FindStringSubmatch(match) + if len(submatches) > 1 { + return obfuscateWideString(submatches[1]) + } + return match + }) + + // Then replace regular strings + content = reChar.ReplaceAllStringFunc(content, func(match string) string { + submatches := reChar.FindStringSubmatch(match) + if len(submatches) > 1 { + return obfuscateCharString(submatches[1]) + } + return match + }) + + return content +} + +// unescapeString converts C escape sequences to actual bytes +// It properly handles Unicode escapes (\u and \U) by converting them to UTF-8 +func unescapeString(s string) []byte { + result := make([]byte, 0, len(s)) + i := 0 + for i < len(s) { + if s[i] == '\\' && i+1 < len(s) { + switch s[i+1] { + case 'n': + result = append(result, '\n') + i += 2 + case 'r': + result = append(result, '\r') + i += 2 + case 't': + result = append(result, '\t') + i += 2 + case '\\': + result = append(result, '\\') + i += 2 + case '"': + result = append(result, '"') + i += 2 + case '\'': + result = append(result, '\'') + i += 2 + case '0': + result = append(result, 0) + i += 2 + case 'a': + result = append(result, '\a') + i += 2 + case 'b': + result = append(result, '\b') + i += 2 + case 'f': + result = append(result, '\f') + i += 2 + case 'v': + result = append(result, '\v') + i += 2 + case 'x': + // Hex escape sequence \xHH + if i+3 < len(s) { + var val byte + fmt.Sscanf(s[i+2:i+4], "%02x", &val) + result = append(result, val) + i += 4 + } else { + result = append(result, s[i]) + i++ + } + case 'u': + // Unicode escape sequence \uHHHH (4 hex digits) + if i+5 < len(s) { + var val uint32 + _, err := fmt.Sscanf(s[i+2:i+6], "%04x", &val) + if err == nil { + // Convert to UTF-8 + utf8Bytes := []byte(string(rune(val))) + result = append(result, utf8Bytes...) + i += 6 + } else { + result = append(result, s[i]) + i++ + } + } else { + result = append(result, s[i]) + i++ + } + case 'U': + // Unicode escape sequence \UHHHHHHHH (8 hex digits) + if i+9 < len(s) { + var val uint32 + _, err := fmt.Sscanf(s[i+2:i+10], "%08x", &val) + if err == nil { + // Convert to UTF-8 + utf8Bytes := []byte(string(rune(val))) + result = append(result, utf8Bytes...) + i += 10 + } else { + result = append(result, s[i]) + i++ + } + } else { + result = append(result, s[i]) + i++ + } + default: + // Unknown escape, keep the backslash + result = append(result, s[i]) + i++ + } + } else { + result = append(result, s[i]) + i++ + } + } + return result +} + +func randomKey() uint64 { + buf := make([]byte, 8) + if _, err := rand.Read(buf); err != nil { + panic(err) + } + return binary.LittleEndian.Uint64(buf) +} + +func obfuscateCharString(s string) string { + counter++ + // Unescape the string first + plaintext := unescapeString(s) + length := len(plaintext) + + // Generate random 64-bit XOR key + key := randomKey() + + // XOR encrypt + encrypted := make([]byte, length) + for i := 0; i < length; i++ { + keyByte := byte((key >> ((i % 8) * 8)) & 0xFF) + encrypted[i] = plaintext[i] ^ keyByte + } + + // Generate unique variable name using counter + varName := fmt.Sprintf("_str_struct_%d", counter) + instanceName := fmt.Sprintf("_str_inst_%d", counter) + + // Build byte array + byteArray := "" + for i, b := range encrypted { + if i > 0 { + byteArray += ", " + } + byteArray += fmt.Sprintf("0x%02x", b) + } + + // Generate the inline assembly obfuscation with multiline formatting + code := fmt.Sprintf(`((const char*)(({ + struct %s { char buf[%d]; } %s; + unsigned char *_data_ptr; + volatile uint64_t _key_val; + volatile size_t _len = %d; + __asm__ volatile( + "jmp skip_str_%%=\n" + "str_data_%%=:\n" + ".byte %s\n" + "str_key_%%=:\n" + ".quad 0x%016x\n" + "skip_str_%%=:\n" + "lea str_data_%%=(%%%%rip), %%0\n" + "movq str_key_%%=(%%%%rip), %%1\n" + : "=r"(_data_ptr), "=r"(_key_val) : : + ); + for (volatile size_t _i = 0; _i < _len; _i++) { + volatile unsigned char _key_byte = (_key_val >> ((_i %% 8) * 8)) & 0xFF; + %s.buf[_i] = _data_ptr[_i] ^ _key_byte; + } + %s.buf[%d] = 0; + %s; +}).buf))`, + varName, + length+1, + instanceName, + length, + byteArray, + key, + instanceName, + instanceName, + length, + instanceName, + ) + + return code +} + +func obfuscateWideString(s string) string { + counter++ + // Unescape the string first - this converts \uXXXX to UTF-8 bytes + unescaped := unescapeString(s) + + // Convert UTF-8 bytes to UTF-16LE (wide char) + // First convert bytes to string, then to runes, then to UTF-16LE + utf8String := string(unescaped) + wideBytes := toUTF16LE(utf8String) + length := len(wideBytes) + + // Generate random 64-bit XOR key + key := randomKey() + + // XOR encrypt + encrypted := make([]byte, length) + for i := 0; i < length; i++ { + keyByte := byte((key >> ((i % 8) * 8)) & 0xFF) + encrypted[i] = wideBytes[i] ^ keyByte + } + + // Generate unique variable name using counter + varName := fmt.Sprintf("_wstr_struct_%d", counter) + instanceName := fmt.Sprintf("_wstr_inst_%d", counter) + + // Build byte array + byteArray := "" + for i, b := range encrypted { + if i > 0 { + byteArray += ", " + } + byteArray += fmt.Sprintf("0x%02x", b) + } + + // Generate the inline assembly obfuscation with multiline formatting (wide string termination - 2 null bytes) + code := fmt.Sprintf(`((const wchar_t*)(({ + struct %s { char buf[%d]; } %s; + unsigned char *_data_ptr; + volatile uint64_t _key_val; + volatile size_t _len = %d; + __asm__ volatile( + "jmp skip_str_%%=\n" + "str_data_%%=:\n" + ".byte %s\n" + "str_key_%%=:\n" + ".quad 0x%016x\n" + "skip_str_%%=:\n" + "lea str_data_%%=(%%%%rip), %%0\n" + "movq str_key_%%=(%%%%rip), %%1\n" + : "=r"(_data_ptr), "=r"(_key_val) : : + ); + for (volatile size_t _i = 0; _i < _len; _i++) { + volatile unsigned char _key_byte = (_key_val >> ((_i %% 8) * 8)) & 0xFF; + %s.buf[_i] = _data_ptr[_i] ^ _key_byte; + } + %s.buf[%d] = 0; + %s.buf[%d] = 0; + %s; +}).buf))`, + varName, + length+2, + instanceName, + length, + byteArray, + key, + instanceName, + instanceName, + length, + instanceName, + length+1, + instanceName, + ) + + return code +} + +// toUTF16LE converts a UTF-8 string to UTF-16LE bytes +// This properly handles all Unicode characters including those outside BMP +func toUTF16LE(s string) []byte { + runes := []rune(s) + result := make([]byte, 0, len(runes)*2) + + for _, r := range runes { + if r <= 0xFFFF { + // Basic Multilingual Plane (BMP) - single UTF-16 code unit + result = append(result, byte(r&0xFF)) + result = append(result, byte((r>>8)&0xFF)) + } else { + // Supplementary Planes - surrogate pair needed + // Subtract 0x10000 and split into high/low surrogates + r -= 0x10000 + high := 0xD800 + ((r >> 10) & 0x3FF) + low := 0xDC00 + (r & 0x3FF) + + // Write high surrogate + result = append(result, byte(high&0xFF)) + result = append(result, byte((high>>8)&0xFF)) + + // Write low surrogate + result = append(result, byte(low&0xFF)) + result = append(result, byte((low>>8)&0xFF)) + } + } + + return result +} + diff --git a/pic_str.h b/pic_str.h new file mode 100644 index 0000000..e53b697 --- /dev/null +++ b/pic_str.h @@ -0,0 +1,67 @@ +#ifndef PIC_STR_H +#define PIC_STR_H + +#ifdef _MSC_VER +#error "pic_str requires GNU extensions (statement expressions). Please use Clang or MinGW on Windows instead of MSVC." +#endif + +#include +#include + +/* + * pic_str() - Position Independent Code String Obfuscator + * + * SAFE USAGE CONTEXTS (Immediate Consumption): + * The deobfuscated string must be consumed by a function or expression + * before the statement ends. + * - Function arguments: printf(pic_str("Hello %s"), name); + * - Windows API calls: MessageBoxW(NULL, pic_str(L"Text"), pic_str(L"Title"), 0); + * - Comparisons: if (strcmp(input, pic_str("password")) == 0) + * - Conditionals: if (pic_str("is_enabled")[0] == '1') + * + * UNSAFE USAGE CONTEXTS (Leads to dangling pointers or memory corruption): + * Do NOT store the pointer returned by this macro for use in later statements. + * - Pointer assignments: const char *msg = pic_str("test"); + * (The pointer 'msg' is dangling and points to invalid memory on the next line) + * - Return values: return pic_str("error"); + * (Returns an address to a local stack frame that is destroyed upon return) + * - Struct member initialization: s.msg = pic_str("data"); + * (The member points to memory that will be overwritten by the next function call) + * - Array initialization: const char *arr[] = {pic_str("a"), pic_str("b")}; + * (The array elements become invalid pointers as soon as the line completes) + * + * INVALID COMPILE-TIME CONTEXTS: + * - Static/global initialization: static const char *msg = pic_str("x"); + * - Compile-time constants: #if pic_str("x") + * - String literal concatenation: pic_str("hello") "world" + * + * SUPPORTED STRING TYPES: + * - Regular strings: pic_str("hello") -> const char* + * - Wide strings (Windows): pic_str(L"hello") -> const wchar_t* + */ + +#ifdef __cplusplus +extern "C" { +#endif + +// Declare functions that return const char* - this provides type checking +// and autocomplete, and will cause errors for invalid usage (like initializing +// fixed-size char arrays, static initialization, etc.) +static inline const char *pic_str_impl(const char *s) { return s; } + +static inline const wchar_t *pic_str_impl_w(const wchar_t *s) { return s; } + +// Use _Generic to dispatch to the right function based on string type +// This handles char* (regular strings) and wchar_t* (Windows wide strings) +#define pic_str(x) \ + _Generic((x), \ + char *: pic_str_impl, \ + const char *: pic_str_impl, \ + wchar_t *: pic_str_impl_w, \ + const wchar_t *: pic_str_impl_w)(x) + +#ifdef __cplusplus +} +#endif + +#endif // PIC_STR_H