long long ago, 传说中的 Win32 世界原本是非常美好和貌似平等的。虽然各个进程被隔离在虚拟地址空间里,也不允许随便访问内核空间。但只要你有足够强烈的愿望,以及相应的一点点技巧,还是可以通过 \Device\PhysicalMemory 自由的读写物理内存来绕过这些通向自由的限制。虽然受到管理员权限和虚拟地址到物理地址转换的限制,但好歹大家还有希望,能通过种种不懈的努力去绕过它。
可是突然有一天魔王放出了 Win2003 SP1,打着捍卫世界和平的旗号,偷偷的收回了用户态物理内存访问的自由,致使大量无辜的程序濒临灭绝,或者走上了自带驱动的不归路。好在还有 kd 和 windbg 的拿把石中剑,能拔出它的人就能拯救世界和平 -_-b
What does \Device\PhysicalMemory Object do?哈哈,yy 结束,开始说正题
在禁止用户态通过 \Device\PhysicalMemory 访问物理内存之后,Win2003 SP1 和至少是后续服务器平台版本上,唯有编写驱动一条途径来访问核心态空间。但一旦涉及到驱动的开发,稳定性和兼容性的问题就无法逃避,这是我等草民所无力承担的。就如歌中唱道:“没有枪没有炮,敌人给我们造”,这个时候如果能直接利用 MS 现有机制,以彼之矛攻彼之盾的话,是一种较为完美的解决方法。这儿的军火库,就是 MS 提供的 kd/windbg 调试环境公用的 dbgeng.dll 调试引擎。
Debugging Tools for Windows从 WinXP 开始,MS 在操作系统一级提供了 live kernel debug 的支持。在 windbg 的文件菜单中,点击 kernel debug 并选择 local 页,就能进入对内核自身进行调试的状态,进而完成对核心态空间的访问。
sysinternals 提供的
LiveKd 也能在 win2000 上提供类似的功能。虽然这种调试本身受到一定限制,但对于访问核心态空间这类需求是绰绰有余了。
作为通用调试引擎的 dbgeng.dll,本身提供了伪 COM 的控制和管理接口,使用者可以通过其 DebugCreate/DebugConnect(Wide) 函数,创建 IDebugClient 接口的指针,并进而通过其 QueryInterface 方法获得其它接口的思想。其中最常用的接口有以下几类:
IDebugClient: 提供对调试引擎自身以及运行时环境的管理
IDebugControl: 提供对调试工作的控制,如断点、异常处理等
IDebugSystemObjects: 提供对调试环境的运行时系统对象的信息获取,如进程、线程等
IDebugSymbols: 提供对调试符号的管理支持
IDebugRegisters: 提供对调试目标的寄存器、堆栈等的管理
IDebugDataSpaces: 提供对调试目标的数据空间的访问,如虚拟地址、物理地址访问等
IDebugAdvanced: 提供对调试线程上下文、源文件信息等的支持
一个典型的使用实例代码如下:
以下内容为程序代码:
void check(HRESULT hr, const char *msg, int err = -1) { if (FAILED(hr)) { std::cerr << msg << std::endl << "errno = 0x" << std::hex << std::setw(8) << std::setfill('0') << hr << std::endl;
exit(err); } }
int _tmain(int, _TCHAR *[]) { // 1.通过 DebugCreate 创建 IDebugClient 接口的实例
CComPtr<IDebugClient> spDbgClient;
check(:[img]/images/biggrin.gif[/img]ebugCreate(__uuidof(IDebugClient), (PVOID *) &spDbgClient), "Create debug client of engine failed."[img]/images/wink.gif[/img]; // 2.获得最新的 IDebugClient5 接口实例
CComPtr<IDebugClient5> spDbgClient5;
check(spDbgClient.QueryInterface(&spDbgClient5), "Query interface of IDebugClient5 failed."[img]/images/wink.gif[/img];
// 3.判断系统是否提供内核调试支持,需要在启动时通过 /debug 配置
bool enabled = S_OK == spDbgClient5->IsKernelDebuggerEnabled(); std::cout << "Kernel debugger is " << (enabled ? "enabled" : "disabled"[img]/images/wink.gif[/img] << std::endl;
// 4.启动本地内核调试模式
installPatch();
check(spDbgClient5->AttachKernelWide(DEBUG_ATTACH_LOCAL_KERNEL, NULL), "Attach debugger to kernel failed."[img]/images/wink.gif[/img];
uninstallPatch(); // 5.等待引擎初始化后的调试事件
CComPtr<IDebugControl4> spDbgControl4;
check(spDbgClient.QueryInterface(&spDbgControl4), "Query interface of IDebugControl4 failed."[img]/images/wink.gif[/img];
check(spDbgControl4->WaitForEvent(0, INFINITE), "Wait for kernel debug event failed."[img]/images/wink.gif[/img]; // 6.do something // ...
return 0; }
|
|
就代码逻辑上非常简单,但如果要让这个代码顺利跑起来,还需要做一些准备工作。
首先要从 kd.exe/windbg.exe 里面把实际的内核驱动扒出来,呵呵。这个工作可以通过 PE Explorer 或者 Borland 的 Resource Workshop 之类工具,打开 exe 文件并提取类型为 30583 名称为 17476 的资源,并另存为 kldbgdrv.sys 文件。因为这个资源的类型和名字是硬编码到 dbgeng.dll 里面,因此需要通过资源定义文件 xxx.rc 的方式,将之编译到我们的目标 exe 文件中,例如在 RC 文件中增加一行:
以下为引用: ///////////////////////////////////////////////////////////////////////////// // // RCDATA //
30583 17476 "kldbgdrv.sys"
|
此外 dbgeng.dll 为了防止被其它程序误用,在 IDebugClient5->AttachKernelWide 初始化本地内核调试模式时,对当前可执行文件的名称进行了检测,只允许名称为 kd.exe/windbg.exe 的程序加载驱动。因此我们得通过 Hook API 的方式,在 installPatch/uninstallPatch 里面绕过此检测,代码如下:
以下内容为程序代码:
DWORD (WINAPI *g_fnGetModuleFileNameW)(HMODULE hModule, LPWSTR lpFilename, DWORD nSize) = ::GetModuleFileNameW;
DWORD WINAPI MyGetModuleFileNameW(HMODULE hModule, LPWSTR lpFilename, DWORD nSize) { DWORD dwSize = g_fnGetModuleFileNameW(hModule, lpFilename, nSize);
if (!hModule) { wcscpy(wcsrchr(lpFilename, L'\\'), L"\\kd.exe"[img]/images/wink.gif[/img]; dwSize = wcslen(lpFilename); }
return dwSize; }
void installPatch(void) { DetourTransactionBegin(); DetourUpdateThread(GetCurrentThread()); DetourAttach(&(PVOID&[img]/images/wink.gif[/img]g_fnGetModuleFileNameW, MyGetModuleFileNameW); DetourTransactionCommit(); }
void uninstallPatch(void) { DetourTransactionBegin(); DetourUpdateThread(GetCurrentThread()); DetourDetach(&(PVOID&[img]/images/wink.gif[/img]g_fnGetModuleFileNameW, MyGetModuleFileNameW); DetourTransactionCommit(); }
|
|
这儿的 Hook API 操作直接使用的
Detours Express 2.1,其提供的线程级操作事务支持功能非常实用。
至此我们就可以通过 IDebugDataSpaces 接口完成对核心态空间的直接读写访问了,例如:
以下内容为程序代码:
CComPtr<IDebugDataSpaces4> spDataSpace4;
check(spDbgClient.QueryInterface(&spDataSpace4), "Query interface of IDebugDataSpaces4 failed."[img]/images/wink.gif[/img];
char buf[4096];
check(spDataSpace4->ReadVirtualUncached(0x80800000, buf, sizeof(buf), &dwSize), "Read virtual memory failed."[img]/images/wink.gif[/img];
|
|
这种模式要求发布时自带最新版本 dbgeng.dll/dbghelp.dll,并以资源方式内含 kldbgdrv.sys 驱动。估计就授权协议来说是无法用于商业产品了,呵呵,不过用来做开放的小工具还是可以容忍的,稍加改动也可应用于 x86 环境。