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 环境。
posted on 2006-09-06 10:55 孤独的夜 阅读(1491) 评论(0)  编辑 收藏 引用 所属分类: Driver
只有注册用户登录后才能发表评论。