WINDOWS] HEVD - 2) 드라이버 소스코드 분석

노션으로 옮김·2020년 3월 2일
1

skills

목록 보기
5/37
post-thumbnail

github page - hevd를 보며 분석하겠다.

먼저 드라이버에 해당하는 코드인 HackSysExtremeVulnerableDriver.c를 살펴본다.

HEVD 드라이버

DriverEntry

먼저 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를 함수를 확인해본다.

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)

BufferOverflowStackIoctlHandler

등록되는 IRP 콜백함수 중, 스택오버플로우를 일으키는 핸들러를 확인해보겠다.

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

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

irp가 요청되면, 유저랜드에서 송신한 버퍼의 값을 TriggerBufferOverflowStack()의 인자로 전달하여 호출한다.

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);

0개의 댓글