mirror of
https://github.com/youssefnoob003/SindriKit
synced 2026-07-07 21:57:09 +00:00
Release v1.1.0: Process Injection & Architecture Hardening
- Implement complete cross-process injection engine via snd_inj_ctx_t - Track explicit remote_entry_point for safe thread hijacking and PE execution - Refactor standalone syscall resolvers into a unified pipeline (scan/sort) - Perfect status code system by purging generic fallbacks in favor of highly-specific, domain-aware error codes - Fix minor pointer validation and parsing bugs in the reflective loader
This commit is contained in:
@@ -1,6 +1,54 @@
|
||||
# Injection Domain
|
||||
|
||||
This directory is reserved for future capabilities focused on remote process targeting, thread hijacking, and inter-process payload injection.
|
||||
Remote process injection: open target, allocate remote memory, write payload, set protections, create a remote thread. Cross-process operations route through an injected `snd_process_api_t` table.
|
||||
|
||||
## Shared context vs loader contexts
|
||||
|
||||
Unlike loaders, **all injection techniques share** `snd_inj_ctx_t` (`sindri/injection/context.h`). Stage machine, handles, remote fields, and `proc_api` are identical across classic and future techniques.
|
||||
|
||||
Each technique adds engine functions and chains under a subdirectory but mutates the same context:
|
||||
|
||||
```
|
||||
include/sindri/injection/
|
||||
├── context.h ← shared across ALL techniques
|
||||
└── classic/
|
||||
├── engine.h ← per-stage classic engine
|
||||
└── chain.h ← snd_inj_classic_shell, snd_inj_classic_pe
|
||||
```
|
||||
|
||||
Loader contexts are **per-technique** (`snd_ldr_pe_ctx_t` today).
|
||||
|
||||
## Header map
|
||||
|
||||
| Header | Role |
|
||||
|---|---|
|
||||
| `sindri/injection.h` | Umbrella include |
|
||||
| `sindri/injection/context.h` | `snd_inj_ctx_t`, stages, `snd_inj_cleanup` |
|
||||
| `sindri/injection/classic.h` | Classic technique umbrella |
|
||||
| `sindri/injection/classic/engine.h` | Per-stage engine functions |
|
||||
| `sindri/injection/classic/chain.h` | `snd_inj_classic_shell`, `snd_inj_classic_pe` |
|
||||
|
||||
## Implemented techniques
|
||||
|
||||
| Technique | Chain | Payload |
|
||||
|---|---|---|
|
||||
| Classic shellcode | `snd_inj_classic_shell` | Raw buffer in `inj_ctx.payload` |
|
||||
| Classic PE | `snd_inj_classic_pe` | Local bake + remote execute (requires `snd_ldr_pe_ctx_t`) |
|
||||
|
||||
## PoCs
|
||||
|
||||
| PoC | Chain | Profile |
|
||||
|---|---|---|
|
||||
| `pocs/inject_shell/main.c` | `snd_inj_classic_shell` | KnownDlls bootstrap + `snd_proc_win` |
|
||||
| `pocs/inject_pe/main.c` | `snd_inj_classic_pe` | `snd_mem_sys`, `snd_mod_nt`, `snd_proc_sys` |
|
||||
|
||||
## Table of Contents
|
||||
- *No documentation currently exists. Check back later.*
|
||||
|
||||
- [techniques.md](techniques.md) — classic pipelines, stage machine, loader interop
|
||||
- [api_reference.md](api_reference.md) — context, engine, and chain API
|
||||
|
||||
## Related documentation
|
||||
|
||||
- [Loaders domain](../loaders/README.md) — local PE bake for classic PE path
|
||||
- [Process primitives](../primitives/process/README.md)
|
||||
- [Examples](../../examples/README.md)
|
||||
|
||||
@@ -1,6 +1,165 @@
|
||||
# Injection API Reference
|
||||
# Injection: API Reference
|
||||
|
||||
> [!NOTE]
|
||||
> The injection domain is planned for a future release of SindriKit. This page will be populated when the injection context (`snd_injector_ctx_t`) and its associated primitives are implemented.
|
||||
Public injection API under `include/sindri/injection/`. Include `sindri/injection.h` or `sindri.h` for the full surface.
|
||||
|
||||
See [techniques.md](techniques.md) for a high-level overview of planned capabilities.
|
||||
---
|
||||
|
||||
## Shared Context (`sindri/injection/context.h`)
|
||||
|
||||
All injection techniques operate on `snd_inj_ctx_t`. Defined in `context.h`, not in technique-specific headers.
|
||||
|
||||
### `snd_inj_ctx_t`
|
||||
|
||||
| Field | Type | Description |
|
||||
|---|---|---|
|
||||
| `target_pid` | `DWORD` | PID of the target process |
|
||||
| `target_process` | `HANDLE` | Handle opened via `proc_api->open_process` |
|
||||
| `remote_base` | `PVOID` | Remote allocation base |
|
||||
| `remote_entry_point` | `PVOID` | Optional. If set, thread starts here instead of `remote_base` (PE path) |
|
||||
| `remote_size` | `SIZE_T` | Size of the remote allocation |
|
||||
| `remote_thread` | `HANDLE` | Handle from `proc_api->create_remote_thread` |
|
||||
| `stage` | `snd_inj_stage_t` | Current pipeline stage |
|
||||
| `payload` | `const snd_buffer_t *` | Source buffer (shellcode or locally baked PE image) |
|
||||
| `proc_api` | `const snd_process_api_t *` | Injected remote process API |
|
||||
|
||||
**Required before any chain:** `target_pid`, `proc_api`, and (for alloc/write) a valid `payload`.
|
||||
|
||||
---
|
||||
|
||||
### `snd_inj_stage_t`
|
||||
|
||||
| Constant | Value | Description |
|
||||
|---|---|---|
|
||||
| `SND_INJ_STAGE_UNINITIALIZED` | 0 | Not started |
|
||||
| `SND_INJ_STAGE_TARGET_ACQUIRED` | 1 | Process handle obtained |
|
||||
| `SND_INJ_STAGE_MEMORY_ALLOCATED` | 2 | Remote memory allocated (RW) |
|
||||
| `SND_INJ_STAGE_PAYLOAD_WRITTEN` | 3 | Payload written remotely |
|
||||
| `SND_INJ_STAGE_PROTECTIONS_SET` | 4 | Remote memory set to RX |
|
||||
| `SND_INJ_STAGE_EXECUTED` | 5 | Remote thread created |
|
||||
|
||||
---
|
||||
|
||||
### `snd_inj_stage_to_string`
|
||||
|
||||
Returns a human-readable stage name for logging and error context.
|
||||
|
||||
```c
|
||||
const char *snd_inj_stage_to_string(snd_inj_stage_t stage);
|
||||
```
|
||||
|
||||
**Source:** `src/injection/context.c`
|
||||
|
||||
---
|
||||
|
||||
### `snd_inj_cleanup`
|
||||
|
||||
Closes `remote_thread` and `target_process` through `proc_api->close_handle`, clears remote fields, resets stage to `UNINITIALIZED`.
|
||||
|
||||
```c
|
||||
void snd_inj_cleanup(snd_inj_ctx_t *ctx);
|
||||
```
|
||||
|
||||
Does not free loader-local mappings. No-op if `ctx` or `proc_api` is NULL.
|
||||
|
||||
---
|
||||
|
||||
## Classic Engine (`sindri/injection/classic/engine.h`)
|
||||
|
||||
Per-stage functions with strict stage validation. Each advances `ctx->stage` on success.
|
||||
|
||||
| Function | Required stage | `proc_api` callback | Advances to |
|
||||
|---|---|---|---|
|
||||
| `snd_inj_classic_open_target` | `UNINITIALIZED` | `open_process` | `TARGET_ACQUIRED` |
|
||||
| `snd_inj_classic_alloc_remote` | `TARGET_ACQUIRED` | `alloc_remote` | `MEMORY_ALLOCATED` |
|
||||
| `snd_inj_classic_write_payload` | `MEMORY_ALLOCATED` | `write_remote` | `PAYLOAD_WRITTEN` |
|
||||
| `snd_inj_classic_set_protections` | `PAYLOAD_WRITTEN` | `protect_remote` | `PROTECTIONS_SET` |
|
||||
| `snd_inj_classic_execute` | `PROTECTIONS_SET` | `create_remote_thread` | `EXECUTED` |
|
||||
|
||||
### `snd_inj_classic_open_target`
|
||||
|
||||
Opens the target with `PROCESS_ALL_ACCESS` (`0x001FFFFF`). Requires non-zero `target_pid`.
|
||||
|
||||
**Returns:** `SND_OK`, `SND_STATUS_PROCESS_OPEN_FAILED`, `SND_STATUS_INVALID_STAGE_SEQUENCE`, `SND_STATUS_INVALID_PARAMETER`
|
||||
|
||||
---
|
||||
|
||||
### `snd_inj_classic_alloc_remote`
|
||||
|
||||
Allocates `payload->size` bytes remotely with `MEM_COMMIT | MEM_RESERVE` and `PAGE_READWRITE`. Sets `remote_size = payload->size`.
|
||||
|
||||
**Returns:** `SND_OK`, allocation errors from `proc_api`, `SND_STATUS_INVALID_STAGE_SEQUENCE`
|
||||
|
||||
---
|
||||
|
||||
### `snd_inj_classic_write_payload`
|
||||
|
||||
Writes `payload->data` (`remote_size` bytes) to `remote_base`.
|
||||
|
||||
**Returns:** `SND_OK`, `SND_STATUS_VIRTUAL_WRITE_FAILED`, `SND_STATUS_INVALID_STAGE_SEQUENCE`
|
||||
|
||||
---
|
||||
|
||||
### `snd_inj_classic_set_protections`
|
||||
|
||||
Transitions the full remote region to `PAGE_EXECUTE_READ`.
|
||||
|
||||
**Returns:** `SND_OK`, `SND_STATUS_VIRTUAL_PROTECT_FAILED`, `SND_STATUS_INVALID_STAGE_SEQUENCE`
|
||||
|
||||
---
|
||||
|
||||
### `snd_inj_classic_execute`
|
||||
|
||||
Creates a remote thread at `remote_base` with parameter `NULL`. Stores handle in `remote_thread`.
|
||||
|
||||
**Returns:** `SND_OK`, `SND_STATUS_THREAD_CREATE_FAILED`, `SND_STATUS_INVALID_STAGE_SEQUENCE`
|
||||
|
||||
**Source:** `src/injection/classic/engine.c`
|
||||
|
||||
---
|
||||
|
||||
## Classic Chain (`sindri/injection/classic/chain.h`)
|
||||
|
||||
### `snd_inj_classic_shell`
|
||||
|
||||
Runs the full shellcode pipeline: open → alloc → write → protect → execute.
|
||||
|
||||
```c
|
||||
snd_status_t snd_inj_classic_shell(snd_inj_ctx_t *ctx);
|
||||
```
|
||||
|
||||
| Parameter | Description |
|
||||
|---|---|
|
||||
| `ctx` | Context with `target_pid`, `payload`, and `proc_api` set |
|
||||
|
||||
**Returns:** `SND_OK` or the failing stage's status
|
||||
|
||||
**Source:** `src/injection/classic/chain.c`
|
||||
|
||||
---
|
||||
|
||||
### `snd_inj_classic_pe`
|
||||
|
||||
Orchestrates local PE preparation via `snd_ldr_pe_ctx_t` and remote injection via `snd_inj_ctx_t`. See [techniques.md](techniques.md) for the interleaved step order.
|
||||
|
||||
```c
|
||||
snd_status_t snd_inj_classic_pe(snd_ldr_pe_ctx_t *ldr_ctx, snd_inj_ctx_t *inj_ctx);
|
||||
```
|
||||
|
||||
| Parameter | Description |
|
||||
|---|---|
|
||||
| `ldr_ctx` | Loader context with `raw_source`, `mem_api`, and `mod_api` set |
|
||||
| `inj_ctx` | Injection context with `target_pid` and `proc_api` set |
|
||||
|
||||
On success, `inj_ctx` reaches `SND_INJ_STAGE_EXECUTED` with `remote_entry_point` pointing at the remote entry point and `remote_base` pointing at the allocation base.
|
||||
|
||||
**Returns:** `SND_OK` or the failing loader/injection stage status
|
||||
|
||||
**Source:** `src/injection/classic/chain.c`
|
||||
|
||||
---
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- [Loader API](../loaders/api_reference.md) — `snd_ldr_pe_ctx_t` used by `snd_inj_classic_pe`
|
||||
- [Process primitives](../primitives/process/api_reference.md) — `snd_process_api_t` backends
|
||||
- [State machines](../../architecture/state_machines.md) — global stage pattern
|
||||
|
||||
@@ -1,17 +1,157 @@
|
||||
# Injection Techniques
|
||||
|
||||
> [!NOTE]
|
||||
> The injection domain is planned for a future release of SindriKit. This page will document process injection capabilities as they are implemented.
|
||||
Injection operations follow the same Dependency Injection and state machine patterns used throughout SindriKit. Every technique advances a shared `snd_inj_ctx_t` through discrete stages and delegates all cross-process work to `ctx->proc_api`.
|
||||
|
||||
## Overview
|
||||
**Prerequisite reading:** [Process primitives](../primitives/process/techniques.md), [Dependency Injection](../../architecture/dependency_injection.md)
|
||||
|
||||
The injection domain will expose process injection capabilities that follow the same State Machine and Dependency Injection patterns used throughout the toolkit. A `snd_injector_ctx_t` context will manage the progression of injection operations through discrete, trackable stages.
|
||||
---
|
||||
|
||||
Planned capabilities include:
|
||||
## Architecture: One Context, Many Techniques
|
||||
|
||||
- **Classic Remote Injection** — Standard `VirtualAllocEx` / `WriteProcessMemory` / `CreateRemoteThread` injection, exposed through the injected `mem_api` so the allocation step can be transparently replaced with a direct `NtAllocateVirtualMemory` syscall.
|
||||
- **Process Hollowing** — Unmapping a legitimate process's image and replacing it with a payload image, applying relocations and resolving imports in the remote process context.
|
||||
- **Thread Hijacking** — Suspending a target thread, overwriting its context to redirect execution to a shellcode or reflectively-loaded payload, then resuming.
|
||||
- **APC Queue Injection** — Queuing an Asynchronous Procedure Call to an alertable thread in the target process using `NtQueueApcThread`.
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph shared ["Shared (all techniques)"]
|
||||
CTX["snd_inj_ctx_t"]
|
||||
PROC["proc_api → snd_process_api_t"]
|
||||
end
|
||||
|
||||
Each technique will rely on the existing PE parser and reflective loading pipeline from the loaders domain, reusing all established primitives rather than re-implementing them.
|
||||
subgraph classic ["Classic technique (implemented)"]
|
||||
ENG["snd_inj_classic_* engine"]
|
||||
SH["snd_inj_classic_shell"]
|
||||
PE["snd_inj_classic_pe"]
|
||||
end
|
||||
|
||||
subgraph future ["Future techniques"]
|
||||
APC["APC queue …"]
|
||||
HIJ["Thread hijack …"]
|
||||
end
|
||||
|
||||
CTX --> ENG
|
||||
ENG --> SH
|
||||
ENG --> PE
|
||||
CTX -.-> APC
|
||||
CTX -.-> HIJ
|
||||
PROC --> ENG
|
||||
```
|
||||
|
||||
Future techniques will add their own engine headers (e.g. `injection/apc/engine.h`) but continue to mutate the same `snd_inj_ctx_t`. Technique-specific metadata, if ever needed, lives in technique-local structures passed alongside the shared context — not in a forked injection context type.
|
||||
|
||||
---
|
||||
|
||||
## Shared Stage Machine (`snd_inj_stage_t`)
|
||||
|
||||
| Stage | Set by | Meaning |
|
||||
|---|---|---|
|
||||
| `SND_INJ_STAGE_UNINITIALIZED` | — | Context created, not started |
|
||||
| `SND_INJ_STAGE_TARGET_ACQUIRED` | `snd_inj_classic_open_target` | Handle to target process |
|
||||
| `SND_INJ_STAGE_MEMORY_ALLOCATED` | `snd_inj_classic_alloc_remote` | RW region reserved in remote process |
|
||||
| `SND_INJ_STAGE_PAYLOAD_WRITTEN` | `snd_inj_classic_write_payload` | Payload bytes copied remotely |
|
||||
| `SND_INJ_STAGE_PROTECTIONS_SET` | `snd_inj_classic_set_protections` | Remote region transitioned to RX |
|
||||
| `SND_INJ_STAGE_EXECUTED` | `snd_inj_classic_execute` | Remote thread created |
|
||||
|
||||
Each engine function validates the current stage and returns `SND_STATUS_INVALID_STAGE_SEQUENCE` on mismatch. This ordering is enforced for all classic paths and will be reused by future techniques that build on the same remote write/execute primitives.
|
||||
|
||||
---
|
||||
|
||||
## Classic Technique: Shellcode (`snd_inj_classic_shell`)
|
||||
|
||||
The baseline **Alloc → Write → Protect → Execute** pattern. The payload buffer is treated as opaque shellcode — the thread starts at `remote_base` (allocation base), not at an PE entry point.
|
||||
|
||||
### Pipeline
|
||||
|
||||
1. **Open target** — `proc_api->open_process(target_pid, PROCESS_ALL_ACCESS, …)`
|
||||
2. **Allocate remote** — `payload->size` bytes, `MEM_COMMIT | MEM_RESERVE`, `PAGE_READWRITE`
|
||||
3. **Write payload** — `proc_api->write_remote` copies the full shellcode buffer
|
||||
4. **Protect** — single `proc_api->protect_remote` call: `PAGE_READWRITE` → `PAGE_EXECUTE_READ` over the entire allocation
|
||||
5. **Execute** — `proc_api->create_remote_thread` at `remote_entry_point` (or `remote_base` if NULL), parameter `NULL`
|
||||
|
||||
### OpSec notes
|
||||
|
||||
- Avoids allocating `PAGE_EXECUTE_READWRITE` directly (RW then RX is a common evasion pattern).
|
||||
- The shellcode path does not parse PE structures or touch the loader domain.
|
||||
- Backend choice (`snd_proc_win` / `_nt` / `_sys`) determines telemetry surface — see [process primitives](../primitives/process/techniques.md).
|
||||
|
||||
### Example (`pocs/inject_shell/main.c`)
|
||||
|
||||
```c
|
||||
snd_inj_ctx_t inj_ctx = {0};
|
||||
inj_ctx.target_pid = target_pid;
|
||||
inj_ctx.payload = &shellcode_buf;
|
||||
inj_ctx.proc_api = &snd_proc_win; // or snd_proc_sys after syscall bootstrap
|
||||
|
||||
snd_status_t status = snd_inj_classic_shell(&inj_ctx);
|
||||
snd_inj_cleanup(&inj_ctx);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Classic Technique: PE (`snd_inj_classic_pe`)
|
||||
|
||||
High-level orchestrator linking a **reflective loader context** (`snd_ldr_pe_ctx_t`) with the **shared injection context** (`snd_inj_ctx_t`). The PE is parsed, mapped, relocated, and import-fixed **locally**, then the fixed image bytes are written into the remote process. Execution starts at the remote entry point (`remote_base + AddressOfEntryPoint`).
|
||||
|
||||
This is not a full in-remote reflective load — fixups happen in local memory using the loader engine, then the baked image is marshaled cross-process.
|
||||
|
||||
### Interleaved pipeline
|
||||
|
||||
The PE chain deliberately interleaves loader and injection stages so relocations use the **remote base** as the execution address:
|
||||
|
||||
| Step | Component | Action |
|
||||
|---|---|---|
|
||||
| 1 | Loader | `snd_pe_parse(ldr_ctx->raw_source, FALSE, &ldr_ctx->pe)` → `SND_STAGE_PARSED` |
|
||||
| 2 | Loader | `snd_ldr_pe_compatibility_check` |
|
||||
| 3 | Loader | `snd_ldr_pe_allocate_and_copy_image` — local RW mapping |
|
||||
| 4 | Injection | `inj_ctx->payload` ← local mapped buffer (`local_base`, `allocated_size`) |
|
||||
| 5 | Injection | `snd_inj_classic_open_target` |
|
||||
| 6 | Injection | `snd_inj_classic_alloc_remote` — remote RW region sized to `allocated_size` |
|
||||
| 7 | Loader | `ldr_ctx->target.execution_base = inj_ctx->remote_base` |
|
||||
| 8 | Loader | `snd_ldr_pe_apply_relocations` — delta = remote_base − ImageBase |
|
||||
| 9 | Loader | `snd_ldr_pe_resolve_imports` — IAT patched locally |
|
||||
| 10 | Injection | `snd_inj_classic_write_payload` — writes baked image to remote |
|
||||
| 11 | Injection | `snd_inj_classic_set_protections` — flat `PAGE_EXECUTE_READ` on remote region |
|
||||
| 12 | Injection | `remote_entry_point = remote_base + ep_rva`; `snd_inj_classic_execute` |
|
||||
|
||||
**Key behaviors:**
|
||||
|
||||
- **`execution_base`** on the loader context is set to the remote allocation address *before* relocations so the delta matches where the image will run.
|
||||
- **Per-section protections** (`snd_ldr_pe_apply_memory_protections`) are **not** called — the injection path applies a single RX protection over the entire remote allocation.
|
||||
- **TLS callbacks** are **not** invoked in the current PE injection chain.
|
||||
- **Local execution** (`snd_ldr_pe_execute_image`) is blocked when `local_base != execution_base`; detach/free similarly refuse remote-prepared images.
|
||||
|
||||
For DLL payloads, the remote thread starts at `AddressOfEntryPoint` (the DLL entry symbol, typically `DllMain`) with **`NULL` thread parameter** — not a typed `DllMain(hinst, DLL_PROCESS_ATTACH, NULL)` call. Do not assume `DLL_PROCESS_ATTACH` semantics; this differs from local `snd_ldr_pe_execute_image` in `chain.c`.
|
||||
|
||||
### Example (`pocs/inject_pe/main.c`)
|
||||
|
||||
```c
|
||||
snd_ldr_pe_ctx_t ldr_ctx = {0};
|
||||
snd_inj_ctx_t inj_ctx = {0};
|
||||
|
||||
ldr_ctx.mem_api = &snd_mem_sys;
|
||||
ldr_ctx.mod_api = &snd_mod_nt;
|
||||
ldr_ctx.raw_source = &file_buf;
|
||||
|
||||
inj_ctx.target_pid = target_pid;
|
||||
inj_ctx.proc_api = &snd_proc_sys;
|
||||
|
||||
snd_status_t status = snd_inj_classic_pe(&ldr_ctx, &inj_ctx);
|
||||
snd_inj_cleanup(&inj_ctx);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Cleanup
|
||||
|
||||
`snd_inj_cleanup` closes `remote_thread` and `target_process` via `proc_api->close_handle`, clears remote fields, and resets stage to `UNINITIALIZED`. It does not free the local loader mapping — callers manage `snd_ldr_pe_free_mapped_image` separately if a local `snd_ldr_pe_ctx_t` was used.
|
||||
|
||||
---
|
||||
|
||||
## Planned Techniques
|
||||
|
||||
Future injection techniques will reuse `snd_inj_ctx_t` and `proc_api`:
|
||||
|
||||
| Technique | Description |
|
||||
|---|---|
|
||||
| **APC queue** | Queue user APC to an alertable thread via `NtQueueApcThread` |
|
||||
| **Thread hijack** | Suspend thread, rewrite context, resume |
|
||||
| **Process hollowing** | Replace remote image in situ (loader + injection coordination) |
|
||||
|
||||
Each will add technique-specific engine headers under `include/sindri/injection/<technique>/` without forking the shared context type.
|
||||
|
||||
Reference in New Issue
Block a user