Фаззинг библиотек

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

Ещё недавно, как я начал изучать веб хакинг, я счёл интересным занятие исследовать Linux и Windows на предмет бинарных уязвимостей. Хотя легально заработать в одиночку хакером у нас в России я думаю можно только веб хакингом, я всё равно хочу изучать все интересующие аспекты атакующей и защищающей стороны. Кто знает, вдруг я когда-нибудь буду в red team. Ну а пока я просто грызу гранит науки.

Слегка поразмыслив над решением задачи, я определил что нужно для осуществления моей проблемы. Я не знаю как другие проводят фаззинг библиотек, у которых нет исходных текстов, но додумался до одного варианта. Далее будут два примера для Linux и Windows.

Linux

Первым делом я занялся разработкой заготовки для linux. Нужно было определить все пункты, с которыми мне нужно будет столкнуться. Эти пункты составляли такой список:

  1. Библиотека не имеет исходных кодов

  2. На каком ассемблере писать код

  3. Как вызывать функции из динамической библиотеки

Да, 3 вариант похож на очень глупый вопрос. Но давайте объясню по подробней почему я задумался о нём. Я не знал как линкуется динамическая библиотека с программой на ассемблере. Понятное дело, если мы в сишной программе используем dlopen, dlsym, но тут нужен функционал, который позволял бы использовать c++ классы. В такие дебри я не заходил ни разу для ассемблера.

Я выбрал ассемблер nasm. Этот ассемблер полюбился больше, чем fasm, хотя и fasm я использовал раньше. Nasm кроссплатформенный, и как вы убедитесь позже, он подошел и для Windows разработки.

Библиотеку, которую нужно проверить на ошибки, код которой я написал от балды, я не стал приводить исполняющую часть, только заголовок.

#ifndef TE_H
#define TE_H
#include <cstdio>
#include <string>

class Handler {
	public:
		Handler ();
	private:
		FILE *fp = {nullptr};
};

class V8 {
	public:
		V8 ();
		int parse_string (Handler& handle, std::string& code);
};

Handler *create_handler ();
V8 *create_js ();

#endif

Нам нужно передавать в V8::parse_string строки кода и ждать ответа в виде правильного, неправильного или segfault.

Также привожу заголовок фаззера.

#ifndef GETTER_H
#define GETTER_H
#include <string>

std::string *getter_string ();

#endif

В данном случае библиотека при каждом вызове передаёт указатель на std::string. Удобней было передавать именно указать, который не несёт за собой ничего, кроме хранения указателя в памяти.

Следующим шагом было собрать библиотеки и посмотреть с помощью radare2 названия связующих функций. Ими стали.

extern _Z14create_handlerv
extern _Z9create_jsv
extern _Z13getter_stringB5cxx11v
extern _ZN2V812parse_stringER7HandlerRNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE

За их зашифрованными символами скрывались их определения и только названия функций давали понимания, что это именно то, что я ищу.

Далее остается только написать программу, которая принимает очередную строку и отправляет её в класс другой библиотеки.

section .text

extern _Z14create_handlerv
extern _Z9create_jsv
extern _Z13getter_stringB5cxx11v
extern _ZN2V812parse_stringER7HandlerRNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE

global main

main:
	sub rsp, 8 + 8 + 8
	call _Z13getter_stringB5cxx11v
	mov [rsp + 16], rax
	call _Z14create_handlerv
	mov [rsp + 0], rax
	call _Z9create_jsv
	mov [rsp + 8], rax
	mov rdi, [rsp + 8]
	mov rsi, [rsp + 0];
	mov rsi, [rsi]
	lea rdx, [rsp + 16];
	mov rdx, [rdx]
	call _ZN2V812parse_stringER7HandlerRNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE
	mov rax, 60
	mov rbx, 0
	syscall

Мой любимый ассемблер. Его я люблю за то, что он не требует от нас проверять типы данных. Для строгого C++ будет очень трудно восстанавливать класс, чтобы его можно было использовать в чужой библиотеке. Ассемблерная программа же даёт нам преимущество. Если C++ класс в библиотеке занимает 120 байт, то мы просто либо в стеке выделяем 120 байт, либо держим 8 байт памяти для хранения указателя.

Остается только собрать это всё и вот как это выглядит.

all:
	nasm -felf64 main.asm -o main.o
	gcc main.o -Wl,-rpath=libs -Llibs -lte -lgetter -o test
clean:
	rm main.o
	rm test

В итоге мы получаем программу, которая может получать данные из одной библиотеки и отправлять другой.

Windows

Для Windows оказалось чуточку сложнее. Над этим я провёл 2 часа решаю как это сделать. Чтобы собрать ассемблерную программу, нужно, чтобы у dll библиотеки была её связующая часть в виде dll.lib. Как я понял, она нужна, чтобы программа могла понять какие в dll библиотеке есть функции и встроить эти данные в нашу программу.

DLL заголовки я не буду приводить в пример, но могу сказать, что там нет ничего необычного. Всего лишь объявляется по правилам Windows вместе с dllspec и dllexport. Собираем обычным способом и отправляем в папку с фаззером. Для фаззинг библиотеке можно копировать dll.lib файл, а dll, ошибку в которой мы должны найти, может быть без исходников и тут нужно произвести несколько операций.

Первым делом используем dumpbin.

dumpbin /nologo /exports Dllcrackme.dll > Dllcrackme.def

Из этого файла мы можем увидеть наши функции с внутренним названием, которые могут использоваться в ассемблере. Из всего, что там было, я выделил только те функции, которые были найдены.

EXPORTS
??0Code@@QEAA@XZ = ??0Code@@QEAA@XZ @1
?check_code@Code@@QEAAHAEAV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@@Z = ?check_code@Code@@QEAAHAEAV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@@Z @2
?print@Code@@QEAAXXZ = ?print@Code@@QEAAXXZ @3

Вместо @1 к примеру были прописаны их реальные названия в C++ стиле (public __dllspec Code::Code (void))

Далее нужно использовать программу lib такой строкой.

lib /nologo /def:Dllcrackme.def /MACHINE:x64 /out:Dllcrackme.lib

Но тут возникала ошибка, когда было прописано не @1, а нормальное название функции. @1 решил эту проблему. Если мне не изменяет память, это указывает номер функции.

На выходе мы получаем файл, который будет участвовать для связывания ассемблерной программы вместе с dll. То-есть происходит только связка, а dll будет использоваться потом при каждом запуске.

Код сборки получился таким.

nasm -f win64 main.asm -o main.o
link main.o Dllcrackme.lib /entry:main /out:fuzzer.exe

А программа с ассемблерным кодом была такая.

section .text

global main

extern ?print@Code@@QEAAXXZ

main:
    call ?print@Code@@QEAAXXZ
    ret

Здесь кода мало, но это показывает, что так всё работает, и можно продолжать совершенствовать программу.

В этой статье я показал как можно фаззить библиотеки, а не как написать фаззер. Думаю, что эта статья откроет некоторым глаза на то, как это можно сделать. Если это так, то я рад, что поделился знаниями, и внёс свой вклад для становления хорошим экспертом. Ведь в дальнейшем кто-то изобретёт что-то выдающееся, и будет тоже делиться своими знаниями. Насколько полезны мои знания, я не знаю, но уверен, что в мире есть столько же много таких же исследователей как я, которые ищут разгадки на вечные вопросы о которых в интернете почти не пишут.

Источник: https://habr.com/ru/articles/798591/


Интересные статьи

Интересные статьи

С Новым Годом уважаемые читатели! Год назад я публиковал подборку из 150-ти книг для тех, кто делает игры и в ней я обещал, что буду постоянно обновлять ее и добавлять в нее новые книги. Время не заст...
Драйвер для STM32 для реализации протокола адресных светодиодов (WS2812, WS2811, SK6812, и т.д.), с рациональным использованием буферной памяти и DMA.
Давным-давно, когда не было современных гаджетов и интернетов, большинство пользователей компьютеров слушали музыку «оффлайн» при помощи различных проигрывателей. Один из самых популярных и распростра...
В данной статье решим 23-е задание с сайта pwnable.kr, узнаем, что такое stack canary и подключим libc в python. Организационная информацияСпециально для тех, кто хочет узнавать что-то новое...
Наше внимание привлёк репозиторий Electronic Arts на GitHub. Он очень маленький и из двадцати трёх проектов нас заинтересовали только несколько C++ библиотек: EASTL, EAStdC, EABase, EAThread, EAT...