mirror of
https://github.com/tmpest127/enc_pic_str
synced 2026-06-08 17:51:05 +00:00
Snapshot of current state
This commit is contained in:
+32
@@ -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
|
||||
@@ -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.
|
||||
@@ -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."
|
||||
@@ -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.
|
||||
@@ -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)
|
||||
@@ -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 <stdio.h>
|
||||
|
||||
int main() {
|
||||
// Basic string output
|
||||
puts(pic_str("Hello, World!"));
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -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)
|
||||
@@ -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 <windows.h>
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -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)
|
||||
@@ -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 <windows.h>
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -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)
|
||||
@@ -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 <windows.h>
|
||||
#include <stdint.h>
|
||||
#include <intrin.h>
|
||||
|
||||
// ============================================================================
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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 <stub.exe> <payload.bin> <output.exe>")
|
||||
sys.exit(1)
|
||||
patch_pe_section(sys.argv[1], sys.argv[2], sys.argv[3])
|
||||
@@ -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)
|
||||
@@ -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 <stdio.h>
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -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)
|
||||
@@ -0,0 +1,7 @@
|
||||
#include <stdio.h>
|
||||
#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"));
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
#include <stdio.h>
|
||||
#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"));
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
#include <stdio.h>
|
||||
#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;
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
module enc_pic_str
|
||||
|
||||
go 1.25.5
|
||||
@@ -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 <input.c> -o <output.c>\n\n", os.Args[0])
|
||||
fmt.Fprintf(os.Stderr, " Batch mode:\n")
|
||||
fmt.Fprintf(os.Stderr, " %s -mode batch -i <file1.c,file2.c,file3.c> -o <output_dir>\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
|
||||
}
|
||||
|
||||
@@ -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 <stdint.h>
|
||||
#include <wchar.h>
|
||||
|
||||
/*
|
||||
* 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
|
||||
Reference in New Issue
Block a user