Snapshot of current state

This commit is contained in:
tmpest
2026-01-29 14:33:44 +04:00
commit bf35f9731d
23 changed files with 1435 additions and 0 deletions
+32
View File
@@ -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
+21
View File
@@ -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.
+56
View File
@@ -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."
+71
View File
@@ -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.
+31
View File
@@ -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)
+15
View File
@@ -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;
}
+32
View File
@@ -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)
+29
View File
@@ -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;
}
+32
View File
@@ -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)
+30
View File
@@ -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;
}
+70
View File
@@ -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)
+264
View File
@@ -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);
}
}
+14
View File
@@ -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
+58
View File
@@ -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])
+26
View File
@@ -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)
+18
View File
@@ -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;
}
+37
View File
@@ -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)
+7
View File
@@ -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"));
}
+9
View File
@@ -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"));
}
+17
View File
@@ -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;
}
+3
View File
@@ -0,0 +1,3 @@
module enc_pic_str
go 1.25.5
+496
View File
@@ -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
}
+67
View File
@@ -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