问询:生成调用Windows API的自包含汇编子程序及C/C++函数独立执行方案
Got it, let's tackle this problem head-on—you've already got shellcode execution working via thread creation or hijacking, but hand-writing shellcode is a massive time sink. Here's how to convert your C/C++ functions into fully self-contained, runnable machine code, plus a self-contained assembly routine to call Windows APIs without relying on import tables:
1. Compile C/C++ Functions to Standalone Shellcode
Instead of writing bytes by hand, we can compile your functions directly into dependency-free machine code. Here's the step-by-step:
Step 1: Write a Minimal CRT-Free C Function
Avoid using standard library functions that pull in C Runtime (CRT) dependencies. For example, a simple message box function (we'll handle API resolution manually later):
void MessageBoxShellcode() { typedef int(WINAPI* MessageBoxAPtr)(void*, const char*, const char*, unsigned int); MessageBoxAPtr pMessageBoxA; // We'll fill pMessageBoxA with our assembly routine below }
Step 2: Compile with No Dependencies
Use MinGW (or Clang) to compile without CRT, exceptions, or unwind tables—this ensures the output has no external dependencies:
gcc -c -O2 -fno-asynchronous-unwind-tables -fno-exceptions -fno-rtti -nostdlib your_function.c -o your_function.o
Step 3: Extract Pure Machine Code
Use objcopy to pull out just the .text (code) section from the object file—this gives you raw, self-contained machine code:
objcopy -O binary -j .text your_function.o your_function.bin
This .bin file is ready to write into target process memory, just like your hand-written shellcode.
Note: If your function needs Windows APIs, the compiled code won't have import table entries (which is what we want for self-containment). We need to dynamically resolve API addresses at runtime, which brings us to the assembly routine.
2. Self-Contained Assembly Routine: Resolve Windows APIs via PEB
To call Windows APIs without an import table, we'll traverse the Process Environment Block (PEB) to find kernel32.dll's base address, then walk its export table to get our target API's address. Here's a x86 assembly implementation:
; Input: EBX = pointer to ASCII API name string ; Output: EAX = address of the API function GetProcAddressByPEB: push ebp mov ebp, esp push ebx ecx edx esi edi ; Grab PEB address from FS register mov eax, fs:[0x30] ; Get PEB_LDR_DATA structure mov eax, [eax + 0xC] ; Walk to kernel32.dll in the module list mov eax, [eax + 0x14] ; InMemoryOrderModuleList mov eax, [eax] ; Next entry (ntdll.dll) mov eax, [eax] ; Next entry (kernel32.dll) mov ebx, [eax + 0x10] ; kernel32.dll base address ; Locate PE header mov edx, [ebx + 0x3C] ; e_lfanew (offset to PE header) add edx, ebx ; Get export table RVA mov edx, [edx + 0x78] ; Export table offset add edx, ebx ; Pull export table details mov ecx, [edx + 0x18] ; Number of exported functions mov esi, [edx + 0x20] ; RVA of function names add esi, ebx mov edi, [edx + 0x24] ; RVA of function addresses add edi, ebx FindAPI: dec ecx mov eax, [esi + ecx*4] ; Get current function name RVA add eax, ebx ; Convert to absolute address push ecx ; Save counter mov ecx, ebx ; ECX = pointer to our target API name CompareName: mov dl, [eax] ; Current char from export name cmp dl, [ecx] ; Compare to target name char jne NextName ; Mismatch, move to next test dl, dl ; Check if we hit null terminator jz FoundAPI ; Match found! inc eax inc ecx jmp CompareName NextName: pop ecx jnz FindAPI ; Keep searching if we haven't hit 0 xor eax, eax ; Return 0 if API not found jmp End FoundAPI: pop ecx mov eax, [edi + ecx*4] ; Get function address RVA add eax, ebx ; Convert to absolute address End: pop edi esi edx ecx ebx pop ebp ret
Integrate This with Your C/C++ Function
Link this assembly routine into your C code, then use it to resolve API addresses at runtime:
// Declare the assembly function extern void* GetProcAddressByPEB(const char* apiName); void SelfContainedMessageBox() { typedef int(WINAPI* MessageBoxAPtr)(void*, const char*, const char*, unsigned int); MessageBoxAPtr pMessageBoxA = (MessageBoxAPtr)GetProcAddressByPEB("MessageBoxA"); if (pMessageBoxA) { pMessageBoxA(NULL, "Self-contained API call success!", "Win", MB_OK); } }
Recompile this with the same no-dependency flags, extract the machine code, and you've got a fully self-contained function that calls Windows APIs without imports.
3. Execute the Code Like Your Manual Shellcode
This compiled machine code works exactly like your hand-written shellcode:
- Use
VirtualAllocExto allocate executable memory in the target process (setPAGE_EXECUTE_READWRITE). - Write the
.bincontent into that memory withWriteProcessMemory. - Either start a new thread with
CreateRemoteThreador hijack an existing thread by modifying its context (setEIP/RIPto the memory address of your code).
内容的提问来源于stack exchange,提问作者Pierre Ciholas

