# winpwn roadmap

Status and planned work, organized by feature area. Each item names the pwntools
feature it mirrors, where one exists.

Scope: winpwn is built for a local Windows-only CTF. The goal is to let a player
who understands an exploit technique (overflow, UAF, IOCTL abuse, token stealing)
work at that level instead of on WinAPI struct layouts and Go plumbing.

Legend: ✅ done · 🚧 partial · ⬜ planned.

## Core ergonomics — ✅

- `Context` ([context.go](context.go)): `Arch` (x86/x64), `LogLevel`, `Timeout`,
  `Newline`. Note: `P16/P32/P64` are little-endian only and do not read
  endianness from `Context.Arch` — every Windows target is little-endian.
- Logging ([log.go](log.go)): `Info`/`Success`/`Warn`/`Error`, gated by
  `Context.LogLevel`. Every `Tube` method returns `error` rather than exiting.
- Timeouts ([tube.go](tube.go)): `(*Tube).SetTimeout` overrides `Context.Timeout`
  (0 = block forever). `Recv*`/`Send*` race the call against the deadline via
  goroutine + `select`. A timed-out call's goroutine is not killed; it runs until
  the underlying blocking I/O completes (Go has no portable deadline for an
  arbitrary pipe/socket reader).
- `cyclic` ([cyclic.go](cyclic.go)): `Cyclic`/`CyclicN`, `CyclicFind`/
  `CyclicFindN`. Generator is lazy (`deBruijnEach`) and stops at the requested
  length. Verified against pwntools' `cyclic(20)` output.
- `fiddling` ([fiddling.go](fiddling.go)): `Hexdump`, `Enhex`/`Unhex`, `Xor`.
- Packing ([packing.go](packing.go)): `P16`/`P32`/`P64`, `U16`/`U32`/`U64`.
- Tube API ([tube.go](tube.go)): `Send*`/`Recv*`/`RecvRegex`/`RecvPred`/
  `Interactive`/`Close`.

## PE tooling — ✅

- Parsing ([pe.go](pe.go), [sections.go](sections.go)): `ImageBase`,
  `EntryPoint`, PE32 and PE32+ via a unified `header()` helper, per-section
  `Entropy`/`IsLikelyPacked`, `IsReadable`/`Writable`/`Executable`/`IsRWX`.
- Checksec ([checksec.go](checksec.go)): ASLR/`DYNAMIC_BASE`, `HighEntropyVA`,
  DEP/`NX_COMPAT`, CFG (cross-checked against Load Config `GuardFlags`), SafeSEH
  (x86 only, flagged `SEHApplicable`), GS heuristic, Authenticode presence, .NET.
  Signature verification via `WinVerifyTrust` in
  [authenticode_windows.go](authenticode_windows.go).
- IAT/EAT ([exports.go](exports.go), [imports.go](imports.go)): `ListExports`/
  `GetExport` with forwarder resolution, `ListImports`/`FindImport` walking the
  thunk arrays (ordinal-or-name, 32/64-bit thunk width).
- ROP ([gadgets.go](gadgets.go), [rop.go](rop.go)): `NewROP` shells out to
  `rp-win.exe` (rp++ build), resolved from `RP_WIN_EXE` or
  `C:\tools\rp-win\rp-win.exe`, run with `--allow-branches`. `NewROPExternal`
  takes an explicit tool path. `Find`/`Search`/`SearchRegex` filter the results;
  `Disassemble` decodes natively via `x86asm` for chain verification.
- Patching ([patch.go](patch.go)): `OpenPEForWrite`,
  `SetSectionCharacteristics`/`MakeSectionExecutable`/`MakeSectionWritable`,
  `DisableTLSCallbacks`, `PatchBytes`/`PatchBytesAtOffset`, `RecalculateChecksum`.

Deferred:
- ⬜ `AddSection`: append a new section for payload injection. Requires
  growing the header area and recomputing `SizeOfImage`/`SizeOfHeaders`; needs
  round-trip tests against real binaries.
- ⬜ `DynPE` (analogue of `DynELF`): given one memory-read primitive, walk a
  loaded module's export directory at runtime to resolve symbols.

## Live-process introspection — ✅

Reads a PE inside a running process, resolving a module base via the PEB.

- `PEFile` is backed by `io.ReaderAt`/`io.WriterAt` ([pe.go](pe.go)), so a
  `ReadProcessMemory`-backed implementation gets every PE accessor for free.
- `OpenPEFromProcess(pid, base)` ([procmem_windows.go](procmem_windows.go)):
  live-memory entry point over `ProcessMemory`. In this mode `RVAToFileOffset`
  is the identity function (a loaded RVA is a read offset from the module base).
- `ResolveModuleBase(pid, name)`: walks `PEB->Ldr->InMemoryOrderModuleList`,
  matched case-insensitively by base name.
- `SpawnSuspended`/`ResumeMainThread`: launch with `CREATE_SUSPENDED`.
  `ResolveModuleBase` returns null on a still-suspended process — the loader
  (`ntdll!LdrInitializeThunk`) has not populated `PEB->Ldr` yet.
  `SpawnSuspended` is for attaching a debugger before the loader/entry point run,
  not for pre-resume base resolution.
- `ProcessSymbols` ([symbols_windows.go](symbols_windows.go)): `Base`, `Symbol`,
  `Modules`, `AllSymbols`; caches one `PEFile` per DLL. `SymbolVA`/
  `ListLoadedModules` for one-off lookups. (Analogue of `p.libs`/`p.symbols`.)

## Minidump — ✅

[minidump.go](minidump.go): `OpenMinidump` parses `MINIDUMP_HEADER`/
`MINIDUMP_DIRECTORY` and decodes `ModuleListStream` (`Modules()`) and
`ExceptionStream` (`Exception()`) from the public struct layouts, without
`dbghelp.dll`. `RawStream(type)` returns any other stream undecoded. Register
context (`CONTEXT` blob) is not decoded — its layout is arch-specific with XSAVE
padding; use `RawStream`. Works without `GOOS=windows`.

## Shellcode & encoding — 🚧

- ✅ `ShellcodeWinExec(cmd)` ([shellcraft.go](shellcraft.go),
  [shellcode/asm/winexec_x64.asm](shellcode/asm/winexec_x64.asm)):
  position-independent x64, resolves kernel32 via the PEB
  (`shellcode/asm/resolver.inc`), calls `WinExec` by name. Assembled ahead of
  time with NASM; `.asm` source kept alongside the `.bin`.
- ✅ `ExecuteShellcode` ([shellcode_exec_windows.go](shellcode_exec_windows.go)):
  runs a template locally for validation.
- ⬜ More templates on the same resolver base: `MessageBoxA`, reverse shell via
  raw `WS2_32`, a standalone `LoadLibraryA`+`GetProcAddress` primitive (current
  `find_export` only walks an already-loaded module).
- ⬜ Token-stealing shellcode (see Driver/LPE).
- ⬜ Encoders: alphanumeric and XOR bad-character avoidance.
- ⬜ `asm`/assemble direction. Disassembly exists (`ROP.Disassemble` via
  `x86asm`); assembling would use cgo bindings to the `keystone/` engine
  (walled off by its own `go.mod`, [keystone/go.mod](keystone/go.mod)).

## Debugger — ✅

[debugger_windows.go](debugger_windows.go) wraps the Windows Debug API
(`DebugActiveProcess`/`WaitForDebugEvent`/`ContinueDebugEvent`/
`Get/SetThreadContext`), resolved via `LazyDLL`. No GUI-debugger integration.

- `Attach(pid)`: the only entry point. The OS ties the debug session to the
  thread that called `DebugActiveProcess`, so `Attach` pins a dedicated goroutine
  with `runtime.LockOSThread` and runs the entire event loop there. Compose with
  `SpawnSuspended`/`ResumeMainThread` to debug from the first instruction.
- `Events() <-chan DebugEvent`: decodes `EXCEPTION_DEBUG_EVENT`/
  `CREATE_PROCESS_DEBUG_EVENT`/`LOAD_DLL_DEBUG_EVENT`/`EXIT_PROCESS_DEBUG_EVENT`/
  etc. `DEBUG_EVENT` union payloads are read via `unsafe.Pointer`.
- `SetBreakpoint`/`RemoveBreakpoint`: software INT3. `Continue` restores the
  original byte, single-steps, and re-arms so a breakpoint persists across hits.
  `Rip` is rewound past the trap before the event reaches the caller.
- `GetContext`/`SetContext`: a `Registers` struct (Rax..R15/Rip/EFlags) over the
  x64 `CONTEXT`. `ReadMemory`/`WriteMemory` over the debuggee's address space.
- Hardware breakpoints (debug registers) are not implemented; software
  breakpoints plus `Step`/`GetContext`/`SetContext` cover the common case.
- `Step()` in response to an `EventBreakpoint` behaves like `Continue` (resuming
  past a software breakpoint already requires an internal single-step).

## Networking & transports — 🚧

- ✅ Named pipes ([pipe_windows.go](pipe_windows.go)): `ServePipe(name)`/
  `DialPipe(name)`, both returning a `*Tube`. `ServePipe` uses
  `PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED`; each `ReadFile`/`WriteFile` blocks
  on `GetOverlappedResult`, so the handle behaves as a blocking
  `io.ReadWriteCloser`. `pipeConn.Close()` is idempotent (`sync.Once`) because
  the duplex handle is used as both `Tube.stdin` and `Tube.stdout`.
- ⬜ `Listen`: TCP listener tube for reverse shells (`net.Listen`).
- ⬜ TLS transport (`Remote` with `tls.Dial`).
- ⬜ SSH transport (`golang.org/x/crypto/ssh`).
- ⬜ Process-tree cleanup: `Spawn` should kill the whole tree on `Close()` via a
  Windows Job Object.

## CLI — 🚧

[cmd/winpwn/main.go](cmd/winpwn/main.go), additive to the library.

Done: `checksec`, `cyclic` (`-l` accepts literal bytes or a `0x...` packed
integer), `hex`/`unhex`/`hexdump`, `rop` (`-search`/`-regex`; no unfiltered dump
mode), `disasm`, `exports`, `imports`, `heap`.

Open:
- ⬜ `winpwn asm`: blocked on the Keystone integration.
- ⬜ `winpwn template`: scaffold a new solve script (partly covered by
  [cmd/pwninit](cmd/pwninit/main.go)).

## Driver / LPE (Ring 0) — ⬜

- ⬜ Device handle + IOCTL wrapper: `OpenDevice(name)` over `CreateFileA`,
  `(*Device).IOCTL(code, in)` over `DeviceIoControl`, handling output-buffer
  sizing and error mapping.
- ⬜ Token-stealing shellcode (x86 and x64), parameterized by a
  `KernelOffsets{Process, ActiveProcessLinks, Token}` struct.
- ⬜ Kernel info-leak helpers over the common `NtQuerySystemInformation` classes.

## Heap exploitation — 🚧

Heap struct-parsing layer. All decoders work against any `io.ReaderAt`; only
finding a heap address in a live process needs Windows syscalls.

- ✅ NT Heap foundation ([heap.go](heap.go)): `_HEAP`/`_HEAP_SEGMENT`/
  `_HEAP_ENTRY` + `DecodeHeapEntry` (XOR against `_HEAP.Encoding`). Offsets from
  `dt ntdll!_HEAP` on build 10.0.26100; decode validated against `!heap -a` for
  three allocations (see `TestDecodeHeapEntryMatchesLiveGroundTruth`).
  `WalkAllHeapEntries`, `SummariseEntries`, `EntriesInRange`,
  `EntriesWithUserData`, `AdjacentBusyPairs`. `UserSize` =
  `Size*HeapEntrySize - UnusedBytes` (no separate header subtraction —
  `UnusedBytes` already accounts for the header).
- ✅ `ListProcessHeaps(pid)` ([heap_windows.go](heap_windows.go)): PEB walk via
  `NtQueryInformationProcess(ProcessBasicInformation)`.
- ✅ NT Heap LFH ([heap_lfh.go](heap_lfh.go)): `ReadLFHBuckets`,
  `FindLFHBucket` (searches `BlockSize >= wantSize + 16`; LFH blocks include the
  16-byte header), `ActiveSubsegment`, `AllSubsegments`, `ReadLFHSubsegment`
  (BusyBitmap validated), `CalibrateLFHFirstBlockOffset`, `BlockAddress`,
  `SlotOf`. Caveats: a bucket warms up after ≈19 same-size requests before
  `SegmentInfoArrays[bucket]` is populated (build-specific);
  `_HEAP_USERDATA_HEADER.EncodedOffsets` is obfuscated —
  `CalibrateLFHFirstBlockOffset` uses a known-address calibration instead.
- ✅ Segment Heap outer layer ([heap_segment.go](heap_segment.go)):
  `ReadSegmentHeap` (Signature/GlobalFlags + VS/LFH summaries), VS subsegment
  walk, LFH bucket enumeration. VS `_HEAP_VS_CHUNK_HEADER.Sizes` and Segment LFH
  `BlockOffsets.EncodedData` are XOR-encoded and not decoded. Adjacency is
  handled by `AdjacentAddressPairs`/`FindAdjacentPair` on leaked addresses, which
  needs no chunk-header decode.
- ✅ `winpwn heap <pid> [-walk]` CLI subcommand.
- ⬜ BSTR/client-spray generator (lower priority; `SprayAndFind` covers the
  generic retry/search loop).

## Go-native additions — ⬜

- ⬜ `Pool`: fan out one exploit across N parallel connections via a bounded
  goroutine pool.
- ⬜ Race-condition primitives: fire N goroutines at a target behind a barrier.
- ⬜ Cancellable `Interactive`: propagate a `context.Context` into both copy
  goroutines so the stdin-forwarding goroutine does not outlive the tube.

## Testing

- Unit tests: [cyclic_test.go](cyclic_test.go), [fiddling_test.go](fiddling_test.go),
  [packing_test.go](packing_test.go) (includes a pin against pwntools'
  `cyclic(20)`).
- [tube_test.go](tube_test.go): `Send`/`Recv*`/`SendAfter`/timeouts/`Close`
  against an `io.Pipe`-backed transport.
- [gadgets_test.go](gadgets_test.go), [pe_test.go](pe_test.go): fixture-based,
  against `examples/bof_basic/bof_win.c.exe`. Skip (do not fail) when the fixture
  or `rp-win.exe` is absent.
- [heap_test.go](heap_test.go), [heap_lfh_test.go](heap_lfh_test.go),
  [heap_segment_test.go](heap_segment_test.go), [spray_test.go](spray_test.go):
  synthetic buffers and captured live-run data.

Not covered:
- Live-process paths (`OpenPEFromProcess`, `ResolveModuleBase`,
  `SpawnSuspended`/`ResumeMainThread`) — need a real target PID.
- `shellcraft.go`/`shellcode_exec_windows.go` execution.
- `patch.go` round-trip (patch a real binary, recompute checksum, compare).

Note: the fixture binary referenced by the PE/gadget tests is not present in the
tree; those tests currently skip. A green `go test` therefore does not exercise
the PE, gadget, or heap paths until the fixture is restored.
