NtSystemDebugControl是Windows NT系列操作系统上实现的一个系统调用,在不同系统上的调用号分别为:
Windows NT
0xba
Windows 2000
0xde
Windows XP
0xff
Windows 2003
0x108
这是一个未文档化的 API,《Windows NT/2000 Native API Reference》中有相关介绍。官方定义可以在一个微软的private头文件ntexapi.h中找到。该文件中还包含很多其它内部数据结构。可能Windows NT 4的SDK中还曾经有过这个文件(至少NT4ResourceKit的支持文档里面是这样说的),但现在似乎微软只提供给它的合作伙伴。好在NTKernel新闻组上有一个“very kind person”共享了这个头文件,你可以从参考资源[2]的两个链接中得到它。
这就是ntexapi.h中的定义:
typedef enum _SYSDBG_COMMAND {
SysDbgQueryTraceInformation = 1,
//KdGetTraceInformation()
SysDbgSetTracepoint = 2,
//KdSetInternalBreakpoint()
SysDbgSetSpecialCall = 3,
//KdSetSpecialCall()
SysDbgClearSpecialCalls = 4,
//KdClearSpecialCalls()
SysDbgQuerySpecialCalls = 5,
//KdQuerySpecialCalls()
SysDbgQueryModuleInformation
//ntexapi.h中有,但实际上未实现
} SYSDBG_COMMAND, *PSYSDBG_COMMAND;
NTSYSAPI
NTSTATUS
NTAPI
NtSystemDebugControl (
IN SYSDBG_COMMAND Command,
IN PVOID InputBuffer,
IN ULONG InputBufferLength,
OUT PVOID OutputBuffer,
IN ULONG OutputBufferLength,
OUT PULONG ReturnLength
);
从上面可以看出,Windows NT和Windows 2000上的NtSystemDebugControl通过不同的第一形参可调用五个内核函数,实现相关功能。
NtSystemDebugControl在Windows NT和Windows 2000上的功能还是比较简陋的,《Windows NT/2000 Native API Reference》一书对这些已经介绍的很详细了,本文不再赘述。
从Windows NT 5.1内核(Windows XP)开始,NtSystemDebugControl的功能被极大扩增了。根据逆向工程的结果来看,在Windows XP上NtSystemDebugControl的第一形参可接受 20个不同的功能调用,在Windows 2003上则有28个。
关于NtSystemDebugControl在Windows NT 5.1以上的实现,互联网上唯一能找到的资料是BUGTRAQ ID 9694关于该 API的一个漏洞报告(参考资源[1]),事实上,这个所谓漏洞是不能称之为漏洞的,因为调用这个API需要SeDebugPrivilege 特权,普通用户根本执行不了,也就谈不上权限提升。
下面的enum是我逆向工程的结果,绝大部分经过测试:
typedef enum _SYSDBG_COMMAND {
//以下5个在Windows NT各个版本上都有
SysDbgGetTraceInformation = 1,
SysDbgSetInternalBreakpoint = 2,
SysDbgSetSpecialCall = 3,
SysDbgClearSpecialCalls = 4,
SysDbgQuerySpecialCalls = 5,
// 以下是NT 5.1 新增的
SysDbgDbgBreakPointWithStatus = 6,
//获取KdVersionBlock
SysDbgSysGetVersion = 7,
//从内核空间拷贝到用户空间,或者从用户空间拷贝到用户空间
//但是不能从用户空间拷贝到内核空间
SysDbgCopyMemoryChunks_0 = 8,
//SysDbgReadVirtualMemory = 8,
//从用户空间拷贝到内核空间,或者从用户空间拷贝到用户空间
//但是不能从内核空间拷贝到用户空间
SysDbgCopyMemoryChunks_1 = 9,
//SysDbgWriteVirtualMemory = 9,
//从物理地址拷贝到用户空间,不能写到内核空间
SysDbgCopyMemoryChunks_2 = 10,
//SysDbgReadVirtualMemory = 10,
//从用户空间拷贝到物理地址,不能读取内核空间
SysDbgCopyMemoryChunks_3 = 11,
//SysDbgWriteVirtualMemory = 11,
//读写处理器相关控制块
SysDbgSysReadControlSpace = 12,
SysDbgSysWriteControlSpace = 13,
//读写端口
SysDbgSysReadIoSpace = 14,
SysDbgSysWriteIoSpace = 15,
//分别调用RDMSR@4和_WRMSR@12
SysDbgSysReadMsr = 16,
SysDbgSysWriteMsr = 17,
//读写总线数据
SysDbgSysReadBusData = 18,
SysDbgSysWriteBusData = 19,
SysDbgSysCheckLowMemory = 20,
// 以下是NT 5.2 新增的
//分别调用_KdEnableDebugger@0和_KdDisableDebugger@0
SysDbgEnableDebugger = 21,
SysDbgDisableDebugger = 22,
//获取和设置一些调试相关的变量
SysDbgGetAutoEnableOnEvent = 23,
SysDbgSetAutoEnableOnEvent = 24,
SysDbgGetPitchDebugger = 25,
SysDbgSetDbgPrintBufferSize = 26,
SysDbgGetIgnoreUmExceptions = 27,
SysDbgSetIgnoreUmExceptions = 28
} SYSDBG_COMMAND, *PSYSDBG_COMMAND;
从上面可以看出,在Windows NT 5.1以上的NtSystemDebugControl可以实现读写内核线性空间数据、读写物理内存、读写端口、读写总线数据、读写MSR 等功能;在Windows NT 5.2以上还可以在系统运行状态下使能、禁用内核调试以及获取、设置一些相关变量等。
显然,从Windows XP开始,我们再次获得了MS DOS时代直接操纵系统的权杖,戴着桂冠,重新回到了奥林匹斯山之巅。
下面举几个具体应用的例子。
例子1:
下面代码演示读取KdVersionBlock:
//------------------------------------------------------------------------
typedef struct _DBGKD_GET_VERSION64 {
USHORT
MajorVersion;
USHORT
MinorVersion;
USHORT
ProtocolVersion;
USHORT
Flags;
USHORT
MachineType;
UCHAR
MaxPacketType;
UCHAR
MaxStateChange;
UCHAR
MaxManipulate;
UCHAR
Simulation;
USHORT
Unused[1];
ULONG64 KernBase;
ULONG64 PsLoadedModuleList;
ULONG64 DebuggerDataList;
} DBGKD_GET_VERSION64, *PDBGKD_GET_VERSION64;
DBGKD_GET_VERSION64 KdVersionBlock;
EnablePrivilege(SE_DEBUG_NAME);
ZwSystemDebugControl
(
SysDbgSysGetVersion,
NULL,
0,
&KdVersionBlock,
sizeof(KdVersionBlock), //必须是0x28
NULL
);
printf ("KernBase:
0x%.8x\n",KdVersionBlock.KernBase);
printf ("PsLoadedModuleList: 0x%.8x\n",KdVersionBlock.PsLoadedModuleList);
printf ("DebuggerDataList:
0x%.8x\n",KdVersionBlock.DebuggerDataList);
//------------------------------------------------------------------------
例子2:
下面代码演示读取内核空间数据的操作,这里读取的是Windows 2003内核映像的头两个字节,也就是“MZ”。
//------------------------------------------------------------------------
typedef struct _MEMORY_CHUNKS {
ULONG Address;
PVOID Data;
ULONG Length;
}MEMORY_CHUNKS, *PMEMORY_CHUNKS;
MEMORY_CHUNKS QueryBuff;
ULONG ReturnLength;
char Buff[0x2] = {0};
QueryBuff.Address = 0x804e0000; //Windows 2003的KernBase
QueryBuff.Data = Buff;
//在此是读出缓冲
QueryBuff.Length = sizeof(Buff);
EnablePrivilege(SE_DEBUG_NAME);
ZwSystemDebugControl
(
SysDbgCopyMemoryChunks_0,
&QueryBuff,
sizeof(MEMORY_CHUNKS),
//必须是0x0C
NULL,
0,
&ReturnLength
);
printf ("\"MZ\":
%s\n",Buff);
//------------------------------------------------------------------------
例子3:
下面是一个使用NtSystemDebugControl的SysDbgCopyMemoryChunks_1功能实现的Patch内核的ShellCode,把0x80580e66由原来的8a450c改为90b001:
修改前:
nt!SeSinglePrivilegeCheck+0x5c:
80580e66 8a450c
mov
al,[ebp+0xc]
80580e69 c9
leave
80580e6a c20c00
ret
0xc
修改后:
nt!SeSinglePrivilegeCheck+0x5c:
80580e66 90
nop
80580e67 b001
mov
al,0x1
80580e69 c9
leave
80580e6a c20c00
ret
0xc
这样,SeSinglePrivilege