Автор: shqnx
Специально для style="text-align: center">Вступление
Всем привет, дорогие читатели! В данной статье мы поговорим про DLL Hijacking и DLL Side-Loading и рассмотрим эти техники атак на практическом примере. Статья в первую очередь посвящается новичкам, тут будет довольно простое и разжёванное объяснение всего и вся. Пора приступать.
Начать я хотел бы с небольшого рассказа про DLL Hijacking.
И так, что же такое DLL Hijacking? DLL Hijacking - это техника атаки, при которой задача атакующего сводится к тому, чтобы разместить вредоносный DLL-файл в определённом месте, которое является более приоритетным для поиска DLL-файлов в Windows. Тем самым заставить выполнить вредоносный DLL-файл вместо легитимного. Сейчас всё объясню.
В Windows при запуске программы есть определённая очередь из тех мест, в которых будут искаться необходимые для этой программы DLL-файлы. Первый - самый приоритетный, последний - наименее приоритетный. Ничего сложного. Выглядит очередь примерно так:
1. Проверка. Не загружена ли на данный момент необходимая DLL в памяти?
2. Известные DLL (HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\KnownDLLs)
3. Каталог, в котором находится программа
4. C:\Windows\System32\
5. C:\Windows\System\
6. C:\Windows\
7. Рабочий каталог
8. Каталоги в переменной среды PATH
Теперь рассмотрим поэтапно, как именно это работает и в чём суть:
1. Определяется уязвимая программа, которая загружает DLL-файл из определенного места;
2. Атакующий размещает в более приоритетном из очереди для поиска месте вредоносный DLL-файл с тем же именем, что и легитимный;
3. Когда программа загружает DLL-файл, она загружает уже не легитимный файл, а вредоносный;
4. Успех.
Думаю с этим разобрались. Теперь перейдём к DLL Side-Loading.
И так, что же такое DLL Side-Loading? DLL Side-Loading - это техника атаки, при которой задача атакующего сводится к тому, чтобы заменить легитимный DLL-файл на вредоносный, что позволит ему выполнить произвольный код на целевой системе. На данном этапе уже должны отпасть вопросы, почему я обозвал эти две техники братьями. Цель везде одна - манипулировать загружаемыми программой DLL-файлами. Поэтому удобнее всего рассказать о них двух в одной статье.
И так, настал черёд перейти к практической части. Хотелось бы сказать о том, что практическую часть я буду показывать именно для техники DLL Side-Loading. Основная причина заключается в том, что для вас главным будет научиться работать с программой-жертвой, находить необходимую информацию для создания своего вредоностного DLL-файла и непосредственно правильно создавать его. Для обоих методов этой информации уже будет достаточно на 99%. И я просто не вижу смысла дублировать, грубо говоря, один и тот же код два раза. Для начала я создам легитимный DLL-файл и подопытную программу, загружающую этот самый DLL-файл. Затем мы создадим вредоносный DLL-файл и проверим, что у нас получилось. Поехали:
C: Скопировать в буфер обмена
И так, это код DLL, которая впоследствии будет загружаться программой-жертвой. Он максимально прост. Но для нас главное не это, а то, что в нём присутствуют функции экспорта, как и во встречающихся в живой природе примерах. В данном случае это функция для нахождения суммы двух целых чисел и возвращающая результат, а также функция, которая останавливает выполнение программы с помощью системной команды pause. Нам нужно будет научиться находить функции экспорта и работать с ними в дальнейшем. Но об этом позднее. Сейчас создадим саму программу-жертву, которая будет загружать полученный только что DLL-файл:
C: Скопировать в буфер обмена
И так, вот и она. Я специально создаю свои экземпляры, чтобы в них была исключительно нужная информация касаемо данной техники и для новичков это всё легче усваивалось. В общем, что тут происходит? Происходит стандартная загрузка DLL (в нашем случае она называется examplelib.dll). Мы объявляем новые типы (Sum и Pause), которые являются указателями на соответствующие функции. В функции main происходит загрузка библиотеки в адресное пространство текущего процесса при помощи LoadLibrary из Windows API. В случае успешной загрузки, мы получаем хэндл нашей DLL. Иначе программа завершает выполнение с кодом ошибки. Далее получаем адрес нашей экспортированной функции Pause из загруженной DLL. Если не удаётся это сделать, вызывается FreeLibrary для выгрузки DLL из памяти и программа завершает выполнение с кодом ошибки. Вызываем функцию функцию Pause через указатель exPause, полученный на предыдущем шаге. Аналогичные действия проводим и для экспортированной функции Sum. Наконец, выводим результат суммы, выгружаем DLL из памяти и завершаем программу.
И так, перемещаем полученный examplelib.dll к исполняемому файлу нашей программы example.exe для тестов и смотрим, всё ли работает:
Всё окей, мы получили рабочий экземпляр, с которым теперь будем работать. Представим, что мы ничего не знаем об example.exe. Каким образом мы можем это исправить? Правильно, с помощью соответствующих инструментов для анализа и мониторинга. А если быть точнее, то с помощью API Monitor и Process Monitor. Для начала открываем API Monitor (чувствительна к архитектуре, поэтому убедитесь, что вы открыли версию, соответствующую архитектуре программы-жертвы). Важно указать модули, для которых мы хотим отслеживать вызовы API. Поскольку в исходной программе мы используем Windows API, выберем Kernel32.dll:
Далее ставим галочку напротив System Services > Dynamic-Link Libraries, как показано на скриншоте:
Выбираем нашу программу-жертву example.exe в соседнем окне Monitored Processes:
И нажимаем OK:
И так, мы можем видеть вызовы API в нашей программе:
Здесь отображается всё, что нам необходимо, а именно: вызов функции LoadLibrary, которая загружает легитимную библиотеку examplelib.dll, вызов функции GetProcAddress для двух экспортируемых функций - Pause и Sum, а также для выгрузки DLL из памяти по завершению выполнения программы. Двигаемся дальше.
Лучший способ определить, есть ли у вас возможность выполнить DLL Side-Loading - сделать это при помощи программы Process Monitor. Открываем её. Для удобства нам нужно создать определённый фильтр. Для этого тыкаем на значок фильтра или нажимаем сочитание клавиш Ctrl+L:
В появившемся окне нам необходимо выставить следующие параметры и нажать Add:
И так, я еще раз запущу example.exe, дабы в Process Monitor появилась информация о запущенном процессе. Что мы конкретно должны найти для проверки, возможен ли сайдлоадинг - так это операции с DLL, для которых результатом является NAME NOT FOUND, вот пример:
Сейчас я перемещу examplelib.dll в другое место, например, в C:\Windows\ и еще раз запущу example.exe. В Process Monitor мы увидим следующее:
Это говорит нам о том, что у нас есть возможность выполнить данную атаку. На данном скриншоте также прослеживается информация, по которой виден порядок поиска нашей DLL. О чём я и расписывал в начале статьи про DLL Hijacking. И так, возможность для проведения атаки найдена. Давайте приступать к созданию "злой" DLL.
И так, сначала код, потом объяснение. Приступаем:
C: Скопировать в буфер обмена
Для того, чтобы example.exe не крашнулась, необходимо либо реплицировать функционал исходных функций экспорта, либо проксировать их. В данном случае показано именно проксирование. Мы объявляем директивы линковщика, которые используются для управления экспортом функций из легитимной DLL, которая в данном случае должна называться _examplelib.dll. Также мы указываем порядковые номера функций в легитимном DLL при помощи @номер. Откуда взять порядковые номера функций? В этом нам поможет dumpbin. Открываем командную строку разработчика и пишем следующую команду:
Можем посмотреть на вывод данной команды и увидеть в ней порядковые номера функций:
И так, как это вообще работает? Мы экспортируем функции из легитимного DLL-файла, при этом получаем возможность добавить свой вредоносный функционал в DllMain. В данном случае это просто вывод месседж бокса, однако там может быть всё что угодно. Для проверки нам нужно поместить переименованный в _examplelib.dll легитимный DLL-файл, наш перемеименованный в examplelib.dll "злой" DLL-файл и запустить example.exe. Смотрим, что получилось:
Выполняется DllMain нашего поддельного examplelib.dll, появляется месседж бокс. После его закрытия example.exe продолжает свою работу как ни в чём не бывало и потом завершает своё выполнение:
Всё отработало корректно.
Теперь давайте рассмотрим пример с репликацией функционала исходных функций экспорта. Пишем "злую" DLL:
C: Скопировать в буфер обмена
В данном случае мы реплицируем только функцию Pause, а функцию Sum мы по прежнему проксируем. Суть в том, чтобы заменить функционал легитимной функции Pause. Думаю тут опять понятно, что вместо месседж бокса, который я использую для примера, может быть всё, что душе угодно. Проверяем результат:
Как мы видим, сначала выполняется наша злобная репликация функции Pause. То есть выводится месседж бокс вместо системной паузы. Затем, как ни в чём не бывало, выполняется функция Sum и программа завершает своё выполнение:
Специально для style="text-align: center">Вступление
Всем привет, дорогие читатели! В данной статье мы поговорим про DLL Hijacking и DLL Side-Loading и рассмотрим эти техники атак на практическом примере. Статья в первую очередь посвящается новичкам, тут будет довольно простое и разжёванное объяснение всего и вся. Пора приступать.
Теоретическая часть
Начать я хотел бы с небольшого рассказа про DLL Hijacking.
И так, что же такое DLL Hijacking? DLL Hijacking - это техника атаки, при которой задача атакующего сводится к тому, чтобы разместить вредоносный DLL-файл в определённом месте, которое является более приоритетным для поиска DLL-файлов в Windows. Тем самым заставить выполнить вредоносный DLL-файл вместо легитимного. Сейчас всё объясню.
В Windows при запуске программы есть определённая очередь из тех мест, в которых будут искаться необходимые для этой программы DLL-файлы. Первый - самый приоритетный, последний - наименее приоритетный. Ничего сложного. Выглядит очередь примерно так:
1. Проверка. Не загружена ли на данный момент необходимая DLL в памяти?
2. Известные DLL (HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\KnownDLLs)
3. Каталог, в котором находится программа
4. C:\Windows\System32\
5. C:\Windows\System\
6. C:\Windows\
7. Рабочий каталог
8. Каталоги в переменной среды PATH
Теперь рассмотрим поэтапно, как именно это работает и в чём суть:
1. Определяется уязвимая программа, которая загружает DLL-файл из определенного места;
2. Атакующий размещает в более приоритетном из очереди для поиска месте вредоносный DLL-файл с тем же именем, что и легитимный;
3. Когда программа загружает DLL-файл, она загружает уже не легитимный файл, а вредоносный;
4. Успех.
Думаю с этим разобрались. Теперь перейдём к DLL Side-Loading.
И так, что же такое DLL Side-Loading? DLL Side-Loading - это техника атаки, при которой задача атакующего сводится к тому, чтобы заменить легитимный DLL-файл на вредоносный, что позволит ему выполнить произвольный код на целевой системе. На данном этапе уже должны отпасть вопросы, почему я обозвал эти две техники братьями. Цель везде одна - манипулировать загружаемыми программой DLL-файлами. Поэтому удобнее всего рассказать о них двух в одной статье.
Практическая часть
И так, настал черёд перейти к практической части. Хотелось бы сказать о том, что практическую часть я буду показывать именно для техники DLL Side-Loading. Основная причина заключается в том, что для вас главным будет научиться работать с программой-жертвой, находить необходимую информацию для создания своего вредоностного DLL-файла и непосредственно правильно создавать его. Для обоих методов этой информации уже будет достаточно на 99%. И я просто не вижу смысла дублировать, грубо говоря, один и тот же код два раза. Для начала я создам легитимный DLL-файл и подопытную программу, загружающую этот самый DLL-файл. Затем мы создадим вредоносный DLL-файл и проверим, что у нас получилось. Поехали:
C: Скопировать в буфер обмена
Code:
#include <windows.h>
__declspec(dllexport) void Pause()
{
system("pause");
}
__declspec(dllexport) int Sum(int a, int b)
{
return a + b;
}
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
return TRUE;
}
И так, это код DLL, которая впоследствии будет загружаться программой-жертвой. Он максимально прост. Но для нас главное не это, а то, что в нём присутствуют функции экспорта, как и во встречающихся в живой природе примерах. В данном случае это функция для нахождения суммы двух целых чисел и возвращающая результат, а также функция, которая останавливает выполнение программы с помощью системной команды pause. Нам нужно будет научиться находить функции экспорта и работать с ними в дальнейшем. Но об этом позднее. Сейчас создадим саму программу-жертву, которая будет загружать полученный только что DLL-файл:
C: Скопировать в буфер обмена
Code:
#include <windows.h>
#include <stdio.h>
typedef int (*Sum)(int, int);
typedef void (*Pause)();
int main() {
HINSTANCE hExampleLib = LoadLibrary(L"examplelib.dll");
if (hExampleLib == NULL) {
return EXIT_FAILURE;
}
Pause exPause = (Pause)GetProcAddress(hExampleLib, "Pause");
if (exPause == NULL) {
FreeLibrary(hExampleLib);
return EXIT_FAILURE;
}
exPause();
Sum exSum = (Sum)GetProcAddress(hExampleLib, "Sum");
if (exSum == NULL) {
FreeLibrary(hExampleLib);
return EXIT_FAILURE;
}
int sumRes = exSum(5, 3);
printf("Sum: %i\n", sumRes);
FreeLibrary(hExampleLib);
return EXIT_SUCCESS;
}
И так, вот и она. Я специально создаю свои экземпляры, чтобы в них была исключительно нужная информация касаемо данной техники и для новичков это всё легче усваивалось. В общем, что тут происходит? Происходит стандартная загрузка DLL (в нашем случае она называется examplelib.dll). Мы объявляем новые типы (Sum и Pause), которые являются указателями на соответствующие функции. В функции main происходит загрузка библиотеки в адресное пространство текущего процесса при помощи LoadLibrary из Windows API. В случае успешной загрузки, мы получаем хэндл нашей DLL. Иначе программа завершает выполнение с кодом ошибки. Далее получаем адрес нашей экспортированной функции Pause из загруженной DLL. Если не удаётся это сделать, вызывается FreeLibrary для выгрузки DLL из памяти и программа завершает выполнение с кодом ошибки. Вызываем функцию функцию Pause через указатель exPause, полученный на предыдущем шаге. Аналогичные действия проводим и для экспортированной функции Sum. Наконец, выводим результат суммы, выгружаем DLL из памяти и завершаем программу.
И так, перемещаем полученный examplelib.dll к исполняемому файлу нашей программы example.exe для тестов и смотрим, всё ли работает:
Поиск необходимых данных
Всё окей, мы получили рабочий экземпляр, с которым теперь будем работать. Представим, что мы ничего не знаем об example.exe. Каким образом мы можем это исправить? Правильно, с помощью соответствующих инструментов для анализа и мониторинга. А если быть точнее, то с помощью API Monitor и Process Monitor. Для начала открываем API Monitor (чувствительна к архитектуре, поэтому убедитесь, что вы открыли версию, соответствующую архитектуре программы-жертвы). Важно указать модули, для которых мы хотим отслеживать вызовы API. Поскольку в исходной программе мы используем Windows API, выберем Kernel32.dll:
Далее ставим галочку напротив System Services > Dynamic-Link Libraries, как показано на скриншоте:
Выбираем нашу программу-жертву example.exe в соседнем окне Monitored Processes:
И нажимаем OK:
И так, мы можем видеть вызовы API в нашей программе:
Здесь отображается всё, что нам необходимо, а именно: вызов функции LoadLibrary, которая загружает легитимную библиотеку examplelib.dll, вызов функции GetProcAddress для двух экспортируемых функций - Pause и Sum, а также для выгрузки DLL из памяти по завершению выполнения программы. Двигаемся дальше.
Лучший способ определить, есть ли у вас возможность выполнить DLL Side-Loading - сделать это при помощи программы Process Monitor. Открываем её. Для удобства нам нужно создать определённый фильтр. Для этого тыкаем на значок фильтра или нажимаем сочитание клавиш Ctrl+L:
В появившемся окне нам необходимо выставить следующие параметры и нажать Add:
И так, я еще раз запущу example.exe, дабы в Process Monitor появилась информация о запущенном процессе. Что мы конкретно должны найти для проверки, возможен ли сайдлоадинг - так это операции с DLL, для которых результатом является NAME NOT FOUND, вот пример:
Сейчас я перемещу examplelib.dll в другое место, например, в C:\Windows\ и еще раз запущу example.exe. В Process Monitor мы увидим следующее:
Это говорит нам о том, что у нас есть возможность выполнить данную атаку. На данном скриншоте также прослеживается информация, по которой виден порядок поиска нашей DLL. О чём я и расписывал в начале статьи про DLL Hijacking. И так, возможность для проведения атаки найдена. Давайте приступать к созданию "злой" DLL.
Создаём нашу "злую" DLL. Проксирование функций.
И так, сначала код, потом объяснение. Приступаем:
C: Скопировать в буфер обмена
Code:
#include <windows.h>
#pragma comment(linker, "/export:Pause=_examplelib.Pause,@1")
#pragma comment(linker, "/export:Sum=_examplelib.Sum,@2")
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
switch (fdwReason)
{
case DLL_PROCESS_ATTACH:
MessageBoxA(NULL, "Evil DllMain", , MB_OK);
}
return TRUE;
}
Для того, чтобы example.exe не крашнулась, необходимо либо реплицировать функционал исходных функций экспорта, либо проксировать их. В данном случае показано именно проксирование. Мы объявляем директивы линковщика, которые используются для управления экспортом функций из легитимной DLL, которая в данном случае должна называться _examplelib.dll. Также мы указываем порядковые номера функций в легитимном DLL при помощи @номер. Откуда взять порядковые номера функций? В этом нам поможет dumpbin. Открываем командную строку разработчика и пишем следующую команду:
dumpbin /EXPORTS C:\Windows\examplelib.dll
Нажмите, чтобы раскрыть...
Можем посмотреть на вывод данной команды и увидеть в ней порядковые номера функций:
И так, как это вообще работает? Мы экспортируем функции из легитимного DLL-файла, при этом получаем возможность добавить свой вредоносный функционал в DllMain. В данном случае это просто вывод месседж бокса, однако там может быть всё что угодно. Для проверки нам нужно поместить переименованный в _examplelib.dll легитимный DLL-файл, наш перемеименованный в examplelib.dll "злой" DLL-файл и запустить example.exe. Смотрим, что получилось:
Выполняется DllMain нашего поддельного examplelib.dll, появляется месседж бокс. После его закрытия example.exe продолжает свою работу как ни в чём не бывало и потом завершает своё выполнение:
Всё отработало корректно.
Создаём нашу "злую" DLL. Репликация функций.
Теперь давайте рассмотрим пример с репликацией функционала исходных функций экспорта. Пишем "злую" DLL:
C: Скопировать в буфер обмена
Code:
#include <windows.h>
#pragma comment(linker, "/export:Sum=_examplelib.Sum,@2")
__declspec(dllexport) void Pause()
{
MessageBoxA(NULL, "Evil Pause", , MB_OK);
}
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
return TRUE;
}
В данном случае мы реплицируем только функцию Pause, а функцию Sum мы по прежнему проксируем. Суть в том, чтобы заменить функционал легитимной функции Pause. Думаю тут опять понятно, что вместо месседж бокса, который я использую для примера, может быть всё, что душе угодно. Проверяем результат:
Как мы видим, сначала выполняется наша злобная репликация функции Pause. То есть выводится месседж бокс вместо системной паузы. Затем, как ни в чём не бывало, выполняется функция Sum и программа завершает своё выполнение:
Заключение
Всех благодарю за внимание. Я постарался сделать материал максимально полезным и максимально приятным в чтении для новичков. Желаю всем успехов!
Ученье — свет, а неученье — тьма.
Всех благодарю за внимание. Я постарался сделать материал максимально полезным и максимально приятным в чтении для новичков. Желаю всем успехов!
Ученье — свет, а неученье — тьма.