# winpwn — краткий справочник

## Установка CLI (winpwn, pwninit)

```bash
cd C:\tools\go_pwner
go install ./cmd/winpwn
go install ./cmd/pwninit
```

Кладёт `winpwn.exe`/`pwninit.exe` в `%USERPROFILE%\go\bin` (в PATH). После этого
`winpwn`/`pwninit` работают из любой директории.

Если `go build`/`go install`/`go test` внутри `go_pwner\` падают с
`directory outside module roots` — `C:\tools\go.work` не перечисляет `./go_pwner`
в `use (...)`. Отредактируй файл, не удаляй его (`workspace\` от него зависит).

Solve-скрипты (`import "winpwn"`) требуют `go.mod` с
`replace winpwn => C:/tools/go_pwner` в каждой задаче — Go не видит модуль вне
`go.work`/`replace`. `pwninit` генерирует это автоматически (см. ниже).

## Где писать solve-скрипты

Не в `go_pwner\` (исходник библиотеки). В `C:\tools\workspace\`:

```
C:\tools\
├── go_pwner\      ← библиотека
└── workspace\     ← скрипты
    ├── go.mod     ← replace winpwn => ../go_pwner
    └── mytask\
        ├── main.go
        └── chal.exe
```

```bash
cd C:\tools\workspace
mkdir mytask && cd mytask
copy path\to\chal.exe .
# main.go, затем:
go run .
```

Шаблон `main.go` — в `workspace\template\main.go`. Задачи и решения — в
`workspace\` (бинари, `src\`, solve-скрипты, флаги).

## CLI — быстрые ответы без скрипта

```
winpwn checksec chal.exe                    # митигации + секции (VA/Offset/Size/entropy) + импортированные DLL с live base
winpwn exports chal.exe                     # таблица экспортов
winpwn imports chal.exe                     # IAT
winpwn rop chal.exe -search "pop rcx ; ret" # поиск гаджетов (ret-, jmp-, call-терминированных)
winpwn rop chal.exe -regex "^pop r.* ; ret$"
winpwn bytes chal.exe ebfe                  # все VA байт-паттерна (jmp $ = EB FE); "ebfe"/"EB FE"/"\xeb\xfe"
winpwn disasm chal.exe 0x140001538 5        # дизасм N инструкций с адреса
winpwn cyclic 200                           # de Bruijn паттерн
winpwn cyclic -l aaab                       # оффсет по подстроке
winpwn hexdump chal.exe
winpwn hex                                  # stdin → hex
winpwn unhex                                # hex → raw bytes
winpwn heap <PID>                           # все кучи в живом процессе
winpwn heap <PID> -walk                     # + обход записей NT Heap
```

Гаджет-поиск (`rop`/`NewROP`) работает через `rp-win.exe` (rp++): по умолчанию
`C:\tools\rp-win\rp-win.exe`, переопределяется `RP_WIN_EXE`. Запуск с
`--allow-branches` — находит `ret`-терминированные цепочки и JOP-транзит через
`jmp reg`/`call reg`.

### pwninit — начать новую задачу

```bash
cd C:\tools\workspace
mkdir mytask && cd mytask
copy path\to\chal.exe .
pwninit                 # автоопределяет chal.exe, если он один в директории
```

Печатает recon (arch, checksec, секции R/W/X + Offset + entropy, импортированные
DLL с live base), затем пишет `go.mod` (`replace winpwn => ...`, путь из
`WINPWN_HOME` или `C:\tools\go_pwner`) и минимальный `main.go` (`Spawn` +
`Interactive`). Не перезатирает существующие файлы без `-force`.

## Tube — I/O с процессом

```go
t, err := winpwn.Spawn("chal.exe")          // локальный процесс
t, err := winpwn.Remote("host", 1337)       // TCP

t.PID()   // PID локального процесса (0 для remote)

t.Send(data)
t.SendLine(data)                             // + \n
t.SendAfter(delim, data)
t.SendLineAfter(delim, data)

data, err := t.Recv(64)
data, err := t.RecvUntil(delim)             // включая delim
data, err := t.RecvLine()
data, err := t.RecvRegex(re)

t.Interactive()   // передать stdin/stdout в терминал
t.Close()
```

## PE — анализ бинаря

```go
pf, _ := winpwn.OpenPE("chal.exe")
defer pf.Close()

base, _ := pf.ImageBase()
entry, _ := pf.EntryPoint()
r, _ := pf.Checksec()
// r.ASLR, r.DEP, r.CFG, r.SafeSEH, r.GSHeuristic

winRVA, _ := pf.GetProcAddress("win")
winAddr := base + winRVA

exports, _ := pf.ListExports()   // []Export{Name, Ordinal, RVA}
imports, _ := pf.ListImports()   // []Import{DLL, Name, Ordinal, IATRVA}

// sec.VirtualAddress (RVA в памяти) и sec.Offset (PointerToRawData, оффсет в
// файле) — разные числа, оба нужны
for _, sec := range pf.Sections() {
    _ = sec.VirtualAddress
    _ = sec.Offset
}

// image base импортированных DLL в этом процессе (LoadLibrary). Системные DLL
// рандомизируются раз за перезагрузку, не на процесс — значение верно для любого
// процесса до ребута, без запуска таргета.
libs, _ := pf.ImportedLibs()  // []ImportedLib{Name, Base, Err}

offsets, _ := pf.SearchBytes([]byte("cmd.exe\x00"))

// PE из памяти живого процесса
memBase, _ := winpwn.ResolveModuleBase(pid, "kernel32.dll")
pf2, _ := winpwn.OpenPEFromProcess(pid, memBase)
```

## Символы живого процесса

Аналог pwntools: `p.libs["kernel32.dll"]` и `p.symbols["kernel32.dll"]["WinExec"]`.

```go
tube, _ := winpwn.Spawn("chal.exe")
sym := winpwn.NewProcessSymbols(tube.PID())
defer sym.Close()

// база DLL (p.libs["..."])
k32, _ := sym.Base("kernel32.dll")
ntdll, _ := sym.Base("ntdll.dll")

// абсолютный VA экспорта (p.symbols["..."]["..."])
winexec, _ := sym.Symbol("kernel32.dll", "WinExec")
ntAlloc, _ := sym.Symbol("ntdll.dll", "NtAllocateVirtualMemory")

// все загруженные модули (p.libs целиком)
mods, _ := sym.Modules()   // map[string]uint64

// все экспорты одной DLL
all, _ := sym.AllSymbols("kernel32.dll")  // map[string]uint64

// один вызов без struct
va, _ := winpwn.SymbolVA(pid, "kernel32.dll", "WinExec")
libs, _ := winpwn.ListLoadedModules(pid)  // map[string]uintptr
```

`ProcessSymbols` кеширует PEFile на DLL — повторные `Symbol` для одной DLL не
переоткрывают файл.

## ROP

Бэкенд — `rp-win.exe` (по умолчанию `C:\tools\rp-win\rp-win.exe`, переопределяется
через `RP_WIN_EXE`). `NewROPExternal(path, toolPath)` — явный путь до другого
билда rp++.

```go
rop, _ := winpwn.NewROP("chal.exe")
defer rop.Close()

// find["pop rcx ; ret"][0] в pwntools-стиле: одно выражение. Ранжировано так,
// что [0] — самый чистый вариант (точное совпадение и более короткие инструкции
// приоритетнее гаджета с той же подстрокой по меньшему адресу с мусорным
// префиксом вроде "ror ... ; pop rcx ; ret").
addr := rop.Find("pop rcx ; ret")[0].Address

gadgets, _ := rop.Search("pop rcx ; ret")        // тот же поиск, (results, error)
gadgets, _ := rop.SearchRegex(`^pop r\w+ ; ret$`)
lines, _ := rop.Disassemble(gadgets[0].Address, 3) // ["pop rcx", "ret"]
```

## Packing

```go
winpwn.P64(addr)    // uint64 → []byte LE
winpwn.P32(addr)
winpwn.P16(addr)
winpwn.U64(buf)     // []byte LE → uint64
winpwn.U32(buf)

winpwn.Enhex(data)       // []byte → "aabbcc..."
winpwn.Unhex(hexstr)     // "aabbcc..." → []byte, error
winpwn.Xor(data, key)    // XOR, ключ циклится
winpwn.Hexdump(data)     // hex+ASCII дамп, строка
```

## Cyclic patterns

```go
pattern := winpwn.Cyclic(200)          // de Bruijn n=4
pattern8 := winpwn.CyclicN(200, 8)      // n=8 для 64-битных указателей

offset := winpwn.CyclicFind(crashedRIPBytes)
offset8 := winpwn.CyclicFindN(crashedRIPBytes, 8)
```

## Heap API

Все функции работают с любым `io.ReaderAt` (`*ProcessMemory`, `*Debugger`, буфер
в тесте). Только `ListProcessHeaps` требует живой процесс.

### Найти кучи

```go
heaps, _ := winpwn.ListProcessHeaps(pid)         // читает PEB.ProcessHeaps
mem, _ := winpwn.OpenProcessMemory(pid, 0)
kind, _ := winpwn.DetectHeapKind(mem, heapAddr)  // HeapKindNT / HeapKindSegment
```

### NT Heap

```go
h, _ := winpwn.ReadHeap(mem, heapAddr)
// h.Flags, h.FrontEndHeapType, h.FrontEndHeap, h.EncodingActive()

entries, _ := h.WalkAllHeapEntries(mem)   // все сегменты
entries, _ := h.WalkSegment0(mem)         // только Segment0

// Поля записи
e.Addr; e.BlockSize(); e.UserSize(); e.UserData()
e.Busy(); e.LastEntry()

// Анализ
stats := winpwn.SummariseEntries(entries)
// stats.BusyEntries, stats.FreeEntries, stats.BusyBytes, stats.FreeBytes
pairs := winpwn.AdjacentBusyPairs(entries)    // [][2]HeapEntry — смежные busy записи
hits := winpwn.EntriesWithUserData(entries, leakedAddr)  // обратный поиск по адресу
```

### NT Heap LFH

```go
buckets, _ := winpwn.ReadLFHBuckets(mem, h.FrontEndHeap)
bucket, _ := winpwn.FindLFHBucket(buckets, 32)
// ищет BlockSize >= 32+16 (LFH блоки включают 16-байтный заголовок)

subsegAddr, _ := winpwn.ActiveSubsegment(mem, h.FrontEndHeap, bucket.Index)
subseg, _ := winpwn.ReadLFHSubsegment(mem, subsegAddr)
// subseg.BlockSize, subseg.BlockCount
// subseg.Busy — []bool, Busy[i] == true → слот занят

// Адрес слота по индексу — нужен один известный адрес для калибровки
off := winpwn.CalibrateLFHFirstBlockOffset(subseg, knownAddr)
addr := subseg.BlockAddress(off, i)
idx, ok := subseg.SlotOf(off, knownAddr)
```

Build 10.0.26100:
- LFH активируется на бакет после ≈19 аллокаций одного размера.
- `_HEAP_USERDATA_HEADER.EncodedOffsets` зашифрован — используй калибровку.
- XOR-ключ LFH-блоков не восстановлен (нужен отдельный проход).

### Segment Heap

```go
sh, _ := winpwn.ReadSegmentHeap(mem, heapAddr)
// sh.VS.CommittedUnits, sh.VS.FreeUnits, sh.VS.SubsegmentCount
// sh.LFH.ActiveBuckets — []SegmentLFHBucket{Index, TotalBlockCount}

// Смежная пара из набора утечённых адресов (без парсинга заголовков)
lo, hi, found := winpwn.FindAdjacentPair(addrs, 32)     // первая пара
pairs := winpwn.AdjacentAddressPairs(addrs, 32)         // все пары
```

`heap_handle + 0x2c0` → адрес внутри ntdll (build 10.0.26100). Leak ntdll base из
утечённого адреса кучи.

### Обход памяти процесса

```go
mem, _ := winpwn.OpenProcessMemory(pid, 0)
defer mem.Close()

n, err := mem.ReadAt(buf, int64(addr))
n, err := mem.WriteAt(data, int64(addr))
```

## Debugger

```go
tube, pid, _ := winpwn.SpawnSuspended("chal.exe")
dbg, _ := winpwn.Attach(pid)      // до ResumeMainThread
winpwn.ResumeMainThread(pid)

for ev := range dbg.Events() {
    // ev.Kind: EventBreakpoint, EventException, EventCreateProcess,
    //          EventLoadDll, EventExitProcess, ...
    if ev.Kind == winpwn.EventBreakpoint {
        regs, _ := dbg.GetContext(ev.ThreadID)
        fmt.Printf("RIP=0x%x RSP=0x%x\n", regs.Rip, regs.Rsp)
    }
    dbg.Continue(ev)   // обязательно для каждого события
}

dbg.SetBreakpoint(addr)
dbg.RemoveBreakpoint(addr)
dbg.ReadMemory(addr, 32)
dbg.WriteMemory(addr, data)
dbg.Close()   // detach без убийства процесса
```

## SprayAndFind — примитив для heap grooming

```go
older, newer, attempts, ok, err := winpwn.SprayAndFind(
    seed,       // []winpwn.SprayResult[K] — известные цели, или nil
    maxAttempts,
    func(attempt int) (winpwn.SprayResult[K], error) {
        // один шаг спрея — вернуть (id, утечённое значение)
    },
    func(a, b K) bool {
        // условие совпадения: равенство, расстояние, что угодно
    },
)
```

UAF (равенство адресов):

```go
winpwn.SprayAndFind(
    []winpwn.SprayResult[uint64]{{ID: victimID, Key: victimAddr}},
    64,
    func(i int) (winpwn.SprayResult[uint64], error) { /* один B-буфер */ },
    func(a, b uint64) bool { return a == b },
)
```

Heap overflow (смежность, расстояние ровно 32):

```go
winpwn.SprayAndFind(nil, 20,
    func(i int) (winpwn.SprayResult[uint64], error) { /* один A-объект */ },
    func(x, y uint64) bool { d := int64(y)-int64(x); return d == 32 || d == -32 },
)
```

## Shellcode

```go
code, _ := winpwn.ShellcodeWinExec("calc.exe")  // PIC x64, находит kernel32 через PEB
winpwn.ExecuteShellcode(code)                    // запустить локально для проверки
```

## Minidump

```go
m, _ := winpwn.OpenMinidump("crash.dmp")
defer m.Close()

mods, _ := m.Modules()
exc, _ := m.Exception()   // ExceptionCode, ExceptionAddress
raw, _ := m.RawStream(winpwn.StreamSystemInfo)
```

## Примеры задач (с решениями)

| Задача | Техника | ASLR | Файл |
|--------|---------|------|------|
| `task1_leak` | info leak + redirect | Нет | `workspace/task1_leak/` |
| `task2_rop` | buffer overflow + ROP + DEP | Нет | `workspace/task2_rop/` |
| `task3_fmtstr` | format string | Нет | `workspace/task3_fmtstr/` |
| `bof_basic` | stack overflow | Нет | `workspace/bof_basic/` |
| `heap_lfh` | UAF + LFH grooming | Нет | `workspace/heap_lfh/` |
| `heap_segment` | overflow + Segment Heap | Нет | `workspace/heap_segment/` |
| `heap_typemix` | UAF type confusion, без LFH | Нет | `workspace/heap_typemix/` |
| `heap_overflow` | adjacent chunk overflow, NT Heap | Нет | `workspace/heap_overflow/` |
| `heap_info_leak` | OOB read → утечка адреса → UAF | **Да** | `workspace/heap_info_leak/` |

```bash
cd C:\tools\workspace\heap_lfh
go run .
```
