Files
go_pwner/USAGE_RU.md
T
2026-07-18 21:37:15 +03:00

396 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 .
```