v0.1 - initial commit
This commit is contained in:
+395
@@ -0,0 +1,395 @@
|
||||
# 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 .
|
||||
```
|
||||
Reference in New Issue
Block a user