Обходим Application Control через NTVDM
В рамках развития своей пентестерской деятельности заинтересовался такой областью, как защищенность банкоматов. Как все, наверное, знают, в большинстве случаев на сколько-нибудь современных банкоматах, стоят те или иные версии ОС Windows: от XP до 10 (в том числе Embedded/IoT редакции). В ОС запущен некий набор банковских приложений, а также реализованы дополнительные меры защиты, как, например, контроль работы приложений (Application Control). Об одном из способов обхода этой защиты и будет этот пост.
Способов обхода контроля запускаемых приложений существует большое количество и их применимость/не применимость зависит от грамотности произведенной настройки и используемых средств. Прежде тем перечислить основные способы обхода, я перечислю основные признаки, которые используются для контроля легитимности запускаемого приложения:
— полный/частичный путь к файлу приложения или директории;
— контрольная сумма файла (MD5, SHA-1 и т.д.);
— криптографическая подпись файла.
Вышеперечисленные методы используются в комбинации с белыми/черными списками приложений, а также доступных для запуска расширений. Теперь перейдем к способам обхода:
— выполнение в контексте доверенных приложений (cmd, NTVDM и т.д.) / интерпретаторов (Java, PHP и т.д.);
— коллизии хэш-функций;
— DLL hijacking.
Также можно отметить обход черного списка расширений (редкие расширения, про которые не все помнят, вроде .scr, .com) и использование уязвимостей в самих защищающих продуктах (не AppLocker'ом едины) / в установленном ПО. Подробнее о вышеперечисленных вещах можно узнать из тематических докладов со всевозможных конференций или обратиться к yarbabin, как к автору одного из материалов по теме.
Вернемся к сути статьи: рассмотрим подробнее способ обхода через выполнение в контексте доверенного приложения, а конкретнее - NTVDM. В контексте NTVDM в современных x86-версиях Windows (а нештатным образом и в x64 - https://github.com/leecher1337/ntvdmx64) исполняются 16-разрядные приложения.
Если вы думаете, что 16-разрядные приложения - это исключительно прерывания, ассемблер и примитивные операции времен MS-DOS, то это не так. Во времена Windows 3.11 и позднее можно было создавать 16-разрядные приложения с использованием WinAPI, причем из такого приложения никто не мешает загрузить 32-разрядную динамическую библиотеку, которая будет исполнять произвольный код, разработанный в привычной для современного разработчика среде. Такой вот тривиальный и относительно известный способ обхода.
Ниже я приведу пример кода для старой среды разработки Microsoft Visual C 1.52 (последняя версия, которая позволяла собирать 16-разрядные файлы, если верить Wiki) реализуюший вышеописанную задачу.
Итак, нам понадобятся: загрузочная дискета Windows 95, образ Windows 95 и Microsoft Visual C 1.52c. После установки ОС и среды разработки на виртуальную машину, запускаем среду и копируем туда следующий код:
#include <Windows.h> DECLARE_HANDLE32(HPROC32); DECLARE_HANDLE32(HINSTANCE32); int main(int argc, char** argv) { HMODULE hModule; HINSTANCE32 hDll; HINSTANCE32 (WINAPI *LoadLibraryEx32W) (LPCSTR, DWORD, DWORD); BOOL (WINAPI *FreeLibrary32W) (HINSTANCE32); printf("begin" "\n"); if(argc != 2) { printf("Usage: loader.exe my.dll" "\n"); return 1; } hModule = GetModuleHandle("KERNEL"); if(!hModule) { printf("GetModuleHandle - %08X" "\n", hModule); return 1; } (FARPROC) LoadLibraryEx32W = GetProcAddress(hModule, "LoadLibraryEx32W"); (FARPROC) FreeLibrary32W = GetProcAddress(hModule, "FreeLibrary32W"); if(!LoadLibraryEx32W || !FreeLibrary32W) { printf("GetProcAddress - %08X, %08X" "\n", LoadLibraryEx32W,FreeLibrary32W); return 1; } hDll = LoadLibraryEx32W(argv[1], 0, 0); if(!hDll) { printf("LoadLibraryEx32W - %08X" "\n", hDll); return 1; } getchar(); FreeLibrary32W(hDll); printf("end" "\n"); return 0; }
Код довольно примитивен: мы просто вызываем штатные, но не слишком известные модификации методов LoadLibrary
и FreeLibrary
для 32-разрядных приложений из 16-разрядного, а также используем макрос DECLARE_HANDLE32
. Что он из себя представляет? Да в общем-то ничего особенного:
#ifdef STRICT #define DECLARE_HANDLE32(name) struct name##__ { int unused; }; \ typedef const struct name##__ _far* name #else /* STRICT */ #define DECLARE_HANDLE32(name) typedef DWORD name #endif /* !STRICT */ #endif /* !DECLARE_HANDLE32 */
Стандартный легаси-макрос, который знаком только бородатым программистам. Кстати, вы можете спросить, зачем я вызываю FreeLibrary32W в конце кода, хотя казалось бы, после завершения процесса динамическая библиотека и так будет выгружена - отнюдь. В данном случае все работает несколько иначе. После запуска вышеописанного кода, библиотека подгружается в контексте процесса ntvdm.exe, однако, после завершения работы она не выгружается.
Скорее всего это связанно либо с тем, что разработчики не захотели заморачиваться, либо с организацией памяти системы в старых версиях Windows, которая существовала вплоть до Windows NT, аутентичная эмуляция так сказать.
Теперь давайте скомпилируем наше приложение и проверим его работоспособность. Для этого просто выберем из меню среды разработки Options->Project->QuickWin application (.EXE), а также Build Mode - Release.
После этого нажмем Shift+F8 или через меню Project->Build ***. Формат QuickWin application был выбран из-за простоты и отсутствия необходимости создавать проект, хоть и добавляет солидный по тем временам оверхед: результирующий файл получился размером ~ 43 КБ.
Перенесем полученный файл на целевую систему и попробуем его запустить, указав в качестве аргумента имя 32-разрядной динамической библиотеки, расположенной в той же директории, что и запускаемый файл (DLL, которая выводит Hello World, можно взять отсюда или собрать самому).
Ожидаемый результат получен, DLL была подгружена в процесс ntvdm.exe, а процесс с именем нашего исполняемого файла (кстати, файлу можно изменить расширение, например, на .com) технически так и не был создан. Успех.