Кодинг

Тайный WinAPI. Как обфусцировать вызовы WinAPI в своем приложении


Образцы серьезной малвари и вымогателей часто содержат интересные методики заражения, скрытия активности и нестандартные отладочные приемы. В вирусах типа Potato или вымогателях вроде SynAsk используется простая, но мощная техника скрытия вызовов WinAPI. Об этом мы и поговорим, а заодно напишем рабочий пример скрытия WinAPI в приложении.

Итак, есть несколько способов скрытия вызовов WinAPI.

  • Виртуализация. Важный код скрывается внутри самодельной виртуальной машины.
  • Прыжок в тело функции WinAPI после ее пролога. Для этого нужен дизассемблер длин инструкций.
  • Вызов функций по их хеш-значениям.
  • Все остальные техники — это разные вариации или развитие трех этих атак. Первые две встречаются нечасто — слишком громоздкие. Как минимум приходится всюду таскать с собой дизассемблер длин и прологи функций, рассчитанные на две разные архитектуры. Вызов функций по хеш-именам прост и часто используется в более-менее видной малвари (даже кибершпионской).

    Наша задача — написать легко масштабируемый мотор для реализации скрытия вызовов WinAPI. Они не должны читаться в таблице импорта и не должны бросаться в глаза в дизассемблере. Давай напишем короткую программу для экспериментов и откомпилируем ее для x64.

    #include <Windows.h> int main() { HANDLE hFile = CreateFileA("C:\test\text.txt", GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); Sleep(5000); return 0; } 

    Как видишь, здесь используются две функции WinAPI — CreateFileA и Sleep. Функцию CreateFileA я решил привести в качестве примера не случайно — по ее аргументу "C:\test\text.txt" мы ее легко и найдем в уже обфусцированном виде.

    Давай глянем на дизассемблированный код этого приложения. Чтобы листинг на ASM был выразительнее, программу необходимо откомпилировать, избавившись от всего лишнего в коде. Откажемся от некоторых проверок безопасности и библиотеки CRT. Для оптимизации приложения необходимо выполнить следующие настройки компилятора:

    • предпочитать краткость кода (/Os),
    • отключить проверку безопасности (/Gs-),
    • отключить отладочную информацию,
    • в настройках компоновщика отключить внесение случайности в базовый адрес (/DYNAMICBASE:NO),
    • включить фиксированный базовый адрес (/FIXED),
    • обозначить самостоятельно точку входа (в нашем случае это main),
    • игнорировать все стандартные библиотеки (/NODEFAULTLIB),
    • отказаться от манифеста (/MANIFEST:NO).

    Эти действия помогут уменьшить размер программы и избавить ее от вставок неявного кода. В моем случае получилось, что программа занимает 3 Кбайт. Ниже — ее полный листинг.

    public start start proc near dwCreationDisposition= dword ptr -28h dwFlagsAndAttributes= dword ptr -20h var_18= qword ptr -18h sub rsp, 48h and [rsp+48h+var_18], 0 lea rcx, FileName ; "C:\test\text.txt" xor r9d, r9d ; lpSecurityAttributes mov [rsp+48h+dwFlagsAndAttributes], 80h ; dwFlagsAndAttributes mov edx, 80000000h ; dwDesiredAccess mov [rsp+48h+dwCreationDisposition], 3 ; dwCreationDisposition lea r8d, [r9+1] ; dwShareMode call cs:CreateFileA mov ecx, 1388h ; dwMilliseconds call cs:Sleep xor eax, eax add rsp, 48h retn start endp 

    Как видишь, функции WinAPI явно читаются в коде и видны в таблице импорта приложения.

    Приложение в программе PE-bear

    Теперь давай создадим модуль, который поможет скрывать от любопытных глаз используемые нами функции WinAPI. Напишем таблицу хешей функций.

    static DWORD hash_api_table[] = { 0xe976c80c, // CreateFileA 0xb233e4a5, // Sleep } 

    Как хешировать

    В статье нет смысла приводить алгоритм хеширования — их десятки, и они доступны в Сети, даже в Википедии. Могу посоветовать алгоритмы, с возможностью выставления вектора начальной инициализации (seed), чтобы хеши функций были уникальными. Например, подойдет алгоритм MurmurHash.

    Давай условимся, что у нас макрос хеширования будет иметь прототип HASH_API(name, name_len, seed), где name — имя функции, name_len — длина имени, seed — вектор начальной инициализации. Так что все значения хеш-функций у тебя будут другими, не как в статье!

    Поскольку мы договорились писать легко масштабируемый модуль, определимся, что функция получения WinAPI у нас будет вида

    LPVOID get_api(DWORD api_hash, LPCSTR module); 

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

    LPVOID parse_export_table(HMODULE module, DWORD api_hash) { PIMAGE_DOS_HEADER img_dos_header; PIMAGE_NT_HEADERS img_nt_header; PIMAGE_EXPORT_DIRECTORY in_export; img_dos_header = (PIMAGE_DOS_HEADER)module; img_nt_header = (PIMAGE_NT_HEADERS)((DWORD_PTR)img_dos_header + img_dos_header->e_lfanew); in_export = (PIMAGE_EXPORT_DIRECTORY)((DWORD_PTR)img_dos_header + img_nt_header->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress); 

    По ходу написания этой функции я буду пояснять, что к чему, потому что путешествие по заголовку PE-файла — дело непростое (у динамической библиотеки будет именно такой заголовок). Сначала мы объявили используемые переменные, с этим не должно было возникнуть проблем. 🙂 Далее, в первой строчке кода, мы получаем из переданного в нашу функцию модуля DLL ее IMAGE_DOS_HEADER. Вот его структура:

    Источник: xakep.ru

    Оставить комментарий

    Ваш e-mail не будет опубликован. Обязательные поля помечены *