Образцы серьезной малвари и вымогателей часто содержат интересные методики заражения, скрытия активности и нестандартные отладочные приемы. В вирусах типа Potato или вымогателях вроде SynAsk используется простая, но мощная техника скрытия вызовов 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
),/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 явно читаются в коде и видны в таблице импорта приложения.
Теперь давай создадим модуль, который поможет скрывать от любопытных глаз используемые нами функции 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
. Вот его структура:
Материалы из последних выпусков можно покупать отдельно только через два месяца после публикации. Чтобы продолжить чтение, необходимо купить подписку.
Подписка позволит тебе в течение указанного срока читать ВСЕ платные материалы сайта. Мы принимаем оплату банковскими картами, электронными деньгами и переводами со счетов мобильных операторов. Подробнее о подписке
1 год7190 р. Экономия 1400 рублей! |
1 месяц720 р. 25-30 статей в месяц |
Уже подписан?
Читайте также
Последние новости