v0.1 - initial commit

This commit is contained in:
2026-07-18 21:37:15 +03:00
commit 9b89f4cb8e
153 changed files with 22887 additions and 0 deletions
+108
View File
@@ -0,0 +1,108 @@
//go:build windows
package winpwn
import (
"testing"
"time"
)
// TestDebuggerBreakpointAtEntry composes SpawnSuspended + Attach +
// ResumeMainThread (the pattern documented on Debugger) to break at the
// real entry point of the checked-in fixture binary, confirming against a
// live process that: the loader/CreateProcess/LoadDll/CreateThread events
// decode without error, ResolveModuleBase eventually succeeds once the
// loader has run, SetBreakpoint+the breakpoint-hit path leaves the
// thread's Rip exactly equal to the breakpoint address (the INT3
// rewind-by-one fixup), and Continue can step back past a still-armed
// breakpoint without hanging.
//
// This exercises the exact sequence validated manually against a live
// process while building debugger_windows.go (see ROADMAP.md's Phase 4
// notes) -- a hermetic version of that same proof, run by `go test`.
func TestDebuggerBreakpointAtEntry(t *testing.T) {
requireFixturePE(t)
diskPE, err := OpenPE(testFixturePE)
if err != nil {
t.Fatal(err)
}
diskBase, err := diskPE.ImageBase()
if err != nil {
diskPE.Close()
t.Fatal(err)
}
diskEntry, err := diskPE.EntryPoint()
if err != nil {
diskPE.Close()
t.Fatal(err)
}
diskPE.Close()
entryRVA := diskEntry - diskBase
tube, pid, err := SpawnSuspended(testFixturePE)
if err != nil {
t.Fatalf("SpawnSuspended: %v", err)
}
defer tube.Close()
dbg, err := Attach(pid)
if err != nil {
t.Fatalf("Attach: %v", err)
}
defer dbg.Close()
if err := ResumeMainThread(pid); err != nil {
t.Fatalf("ResumeMainThread: %v", err)
}
var bpAddr uintptr
var entrySet, hit bool
deadline := time.After(10 * time.Second)
for !hit {
select {
case ev, ok := <-dbg.Events():
if !ok {
t.Fatal("debugger event channel closed before hitting the entry breakpoint")
}
if !entrySet {
if base, rerr := ResolveModuleBase(pid, "bof_win.c.exe"); rerr == nil {
bpAddr = uintptr(base) + uintptr(entryRVA)
if err := dbg.SetBreakpoint(bpAddr); err != nil {
t.Fatalf("SetBreakpoint(0x%x): %v", bpAddr, err)
}
entrySet = true
}
// Not yet resolvable (loader hasn't populated PEB.Ldr yet) --
// expected on the first couple of events, see
// ResolveModuleBase's doc comment in procmem_windows.go.
}
if ev.Kind == EventBreakpoint {
regs, gerr := dbg.GetContext(ev.ThreadID)
if gerr != nil {
t.Fatalf("GetContext(%d): %v", ev.ThreadID, gerr)
}
if regs.Rip != uint64(ev.Addr) {
t.Errorf("Rip = 0x%x, want 0x%x (breakpoint address, after the INT3 rewind)", regs.Rip, ev.Addr)
}
if ev.Addr != bpAddr {
t.Errorf("breakpoint fired at 0x%x, want entry point 0x%x", ev.Addr, bpAddr)
}
hit = true
}
// Continue every event, including the still-armed breakpoint --
// proves Continue's internal restore/rewind/single-step/re-arm
// dance (stepPastBreakpoint) completes without hanging.
if err := dbg.Continue(ev); err != nil {
t.Fatalf("Continue: %v", err)
}
case <-deadline:
t.Fatal("timed out waiting for the entry breakpoint to fire")
}
}
}