github page - hevd를 보며 분석하겠다.
먼저 드라이버에 해당하는 코드인 HackSysExtremeVulnerableDriver.c
를 살펴본다.
먼저 deviceobject를 생성한다.
RtlInitUnicodeString(&DeviceName, L"\\Device\\HackSysExtremeVulnerableDriver");
//
// Create the device
//
Status = IoCreateDevice(
DriverObject,
0,
&DeviceName,
FILE_DEVICE_UNKNOWN,
FILE_DEVICE_SECURE_OPEN,
FALSE,
&DeviceObject
);
IRP 콜백함수를 등록한다.
DriverObject->MajorFunction[IRP_MJ_CREATE] = IrpCreateCloseHandler;
DriverObject->MajorFunction[IRP_MJ_CLOSE] = IrpCreateCloseHandler;
DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = IrpDeviceIoCtlHandler;
IRP_MJ_DEVICE_CONTROL
이 우리가 응용프로그램으로 드라이버와 송수신할 때 사용하는 IRP 상태에 해당하므로 IrpDeviceIoCtlHandler
를 함수를 확인해본다.
switch (IoControlCode)
{
case HEVD_IOCTL_BUFFER_OVERFLOW_STACK:
DbgPrint("****** HEVD_IOCTL_BUFFER_OVERFLOW_STACK ******\n");
Status = BufferOverflowStackIoctlHandler(Irp, IrpSp);
DbgPrint("****** HEVD_IOCTL_BUFFER_OVERFLOW_STACK ******\n");
break;
case HEVD_IOCTL_BUFFER_OVERFLOW_STACK_GS:
DbgPrint("****** HEVD_IOCTL_BUFFER_OVERFLOW_STACK_GS ******\n");
Status = BufferOverflowStackGSIoctlHandler(Irp, IrpSp);
DbgPrint("****** HEVD_IOCTL_BUFFER_OVERFLOW_STACK_GS ******\n");
break;
irp 코드를 읽어서 상태에 따라 분기처리한다.
이 때의 상태값은 헤더(HackSysExtremeVulnerableDriver.c
)에 정의되어 있다.
#define HEVD_IOCTL_BUFFER_OVERFLOW_STACK IOCTL(0x800)
#define HEVD_IOCTL_BUFFER_OVERFLOW_STACK_GS IOCTL(0x801)
#define HEVD_IOCTL_ARBITRARY_WRITE IOCTL(0x802)
등록되는 IRP 콜백함수 중, 스택오버플로우를 일으키는 핸들러를 확인해보겠다.
UserBuffer = IrpSp->Parameters.DeviceIoControl.Type3InputBuffer;
Size = IrpSp->Parameters.DeviceIoControl.InputBufferLength;
if (UserBuffer)
{
Status = TriggerBufferOverflowStack(UserBuffer, Size);
}
irp가 요청되면, 유저랜드에서 송신한 버퍼의 값을 TriggerBufferOverflowStack()
의 인자로 전달하여 호출한다.
TriggerBufferOverflowStack(
_In_ PVOID UserBuffer,
_In_ SIZE_T Size
)
{
NTSTATUS Status = STATUS_SUCCESS;
ULONG KernelBuffer[BUFFER_SIZE] = { 0 };
먼저, 커널 영역에 버퍼를 생성한다.
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));
넘겨받은 유저버퍼가 정상적으로 유저영역에 속하는 버퍼인지 확인한 후 디버깅용도로 주소값을 출력해준다.
/* 참고 - ms doc* /
The ProbeForRead routine checks that a user-mode buffer actually resides in the user portion of the address space, and is correctly aligned.
그리고 유저버퍼값을 커널버퍼로 copy해주는데 secure모드일 경우 overflow되지 않도록 커널버퍼 크기만큼만, 아닐 경우 유저버퍼가 넘어온 그대로 커널에 write해준다.
#ifdef SECURE
//
// Secure Note: This is secure because the developer is passing a size
// equal to size of KernelBuffer to RtlCopyMemory()/memcpy(). Hence,
// there will be no overflow
//
RtlCopyMemory((PVOID)KernelBuffer, UserBuffer, sizeof(KernelBuffer));
#else
DbgPrint("[+] Triggering Buffer Overflow in Stack\n");
//
// Vulnerability Note: This is a vanilla Stack based Overflow vulnerability
// because the developer is passing the user supplied size directly to
// RtlCopyMemory()/memcpy() without validating if the size is greater or
// equal to the size of KernelBuffer
//
RtlCopyMemory((PVOID)KernelBuffer, UserBuffer, Size);