diff --git a/ROADMAP.md b/ROADMAP.md index 78a865f..a533bd5 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -7,9 +7,9 @@ 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. +Legend: [+] done ยท [+-] partial ยท [-] planned. -## Core ergonomics โ€” โœ… +## 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 @@ -29,7 +29,7 @@ Legend: โœ… done ยท ๐Ÿšง partial ยท โฌœ planned. - Tube API ([tube.go](tube.go)): `Send*`/`Recv*`/`RecvRegex`/`RecvPred`/ `Interactive`/`Close`. -## PE tooling โ€” โœ… +## PE tooling โ€” [+] - Parsing ([pe.go](pe.go), [sections.go](sections.go)): `ImageBase`, `EntryPoint`, PE32 and PE32+ via a unified `header()` helper, per-section @@ -52,13 +52,13 @@ Legend: โœ… done ยท ๐Ÿšง partial ยท โฌœ planned. `DisableTLSCallbacks`, `PatchBytes`/`PatchBytesAtOffset`, `RecalculateChecksum`. Deferred: -- โฌœ `AddSection`: append a new section for payload injection. Requires +- [-] `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 +- [-] `DynPE` (analogue of `DynELF`): given one memory-read primitive, walk a loaded module's export directory at runtime to resolve symbols. -## Live-process introspection โ€” โœ… +## Live-process introspection โ€” [+] Reads a PE inside a running process, resolving a module base via the PEB. @@ -78,7 +78,7 @@ Reads a PE inside a running process, resolving a module base via the PEB. `Modules`, `AllSymbols`; caches one `PEFile` per DLL. `SymbolVA`/ `ListLoadedModules` for one-off lookups. (Analogue of `p.libs`/`p.symbols`.) -## Minidump โ€” โœ… +## Minidump โ€” [+] [minidump.go](minidump.go): `OpenMinidump` parses `MINIDUMP_HEADER`/ `MINIDUMP_DIRECTORY` and decodes `ModuleListStream` (`Modules()`) and @@ -87,25 +87,25 @@ Reads a PE inside a running process, resolving a module base via the PEB. context (`CONTEXT` blob) is not decoded โ€” its layout is arch-specific with XSAVE padding; use `RawStream`. Works without `GOOS=windows`. -## Shellcode & encoding โ€” ๐Ÿšง +## Shellcode & encoding โ€” [+-] -- โœ… `ShellcodeWinExec(cmd)` ([shellcraft.go](shellcraft.go), +- [+] `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)): +- [+] `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 +- [-] 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 +- [-] 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 โ€” [+] [debugger_windows.go](debugger_windows.go) wraps the Windows Debug API (`DebugActiveProcess`/`WaitForDebugEvent`/`ContinueDebugEvent`/ @@ -128,21 +128,21 @@ padding; use `RawStream`. Works without `GOOS=windows`. - `Step()` in response to an `EventBreakpoint` behaves like `Continue` (resuming past a software breakpoint already requires an internal single-step). -## Networking & transports โ€” ๐Ÿšง +## Networking & transports โ€” [+-] -- โœ… Named pipes ([pipe_windows.go](pipe_windows.go)): `ServePipe(name)`/ +- [+] 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 +- [-] `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 โ€” ๐Ÿšง +## CLI โ€” [+-] [cmd/winpwn/main.go](cmd/winpwn/main.go), additive to the library. @@ -151,25 +151,25 @@ 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 +- [-] `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) โ€” โฌœ +## Driver / LPE (Ring 0) โ€” [-] -- โฌœ Device handle + IOCTL wrapper: `OpenDevice(name)` over `CreateFileA`, +- [-] 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 +- [-] Token-stealing shellcode (x86 and x64), parameterized by a `KernelOffsets{Process, ActiveProcessLinks, Token}` struct. -- โฌœ Kernel info-leak helpers over the common `NtQuerySystemInformation` classes. +- [-] Kernel info-leak helpers over the common `NtQuerySystemInformation` classes. -## Heap exploitation โ€” ๐Ÿšง +## 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`/ +- [+] 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`). @@ -177,9 +177,9 @@ finding a heap address in a live process needs Windows syscalls. `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 +- [+] `ListProcessHeaps(pid)` ([heap_windows.go](heap_windows.go)): PEB walk via `NtQueryInformationProcess(ProcessBasicInformation)`. -- โœ… NT Heap LFH ([heap_lfh.go](heap_lfh.go)): `ReadLFHBuckets`, +- [+] 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`, @@ -187,22 +187,22 @@ finding a heap address in a live process needs Windows syscalls. `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)): +- [+] 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 [-walk]` CLI subcommand. -- โฌœ BSTR/client-spray generator (lower priority; `SprayAndFind` covers the +- [+] `winpwn heap [-walk]` CLI subcommand. +- [-] BSTR/client-spray generator (lower priority; `SprayAndFind` covers the generic retry/search loop). -## Go-native additions โ€” โฌœ +## Go-native additions โ€” [-] -- โฌœ `Pool`: fan out one exploit across N parallel connections via a bounded +- [-] `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 +- [-] 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