Chinaunix首页 | 论坛 | 博客
  • 博客访问: 531051
  • 博文数量: 576
  • 博客积分: 40000
  • 博客等级: 大将
  • 技术积分: 5020
  • 用 户 组: 普通用户
  • 注册时间: 2008-10-13 14:47
文章分类

全部博文(576)

文章存档

2011年(1)

2008年(575)

我的朋友

分类:

2008-10-14 14:54:08

定制调试诊断工具和实用程序
——摆脱DLL“地狱”(DLL Hell)的困扰(八)

原著:Christophe Nasarre
编译:

下载源代码: (588KB)
原文出处:


本文假设你熟悉 Win32,DLL


CreateRemoteThread,命令行及其它

  你已经看到如何用 Win32 调试 API 来揭示进程是怎样加载 DLLs 以及加载到哪里的。现在让我们来揭开CreateRemoteThread 的秘密,这个函数允许你使用另外一个进程在其上下文中启动函数作为线程运行:

HANDLE CreateRemoteThread(
  HANDLE hProcess,                          // 进程句柄
  LPSECURITY_ATTRIBUTES lpThreadAttributes, // SD-安全描述符
  DWORD dwStackSize,                        // 初始栈大小
  LPTHREAD_START_ROUTINE lpStartAddress,    // 线程函数
  LPVOID lpParameter,                       // 线程参数
  DWORD dwCreationFlags,                    // 创建选项
  LPDWORD lpThreadId                        // 线程标示符
);

  lpStartAddress 应该是拟在另一个进程环境中执行的线程过程地址。这个函数的窍门在于 lpStartAddress 参数必须是另一个进程地址空间中的地址,这也是它为什么难以使用的原因。如果你不想重新创建代码程序集,那么就将它拷贝到另一个进程地址空间即可, 你可以使用一个较容易的解决方法。
  如果你比较线程函数和 kernel32.dll 输出的 LoadLibrary,你会发现它们的署名相同。两者都带32位值作为参数并返回32位值。根据这种相似性。Jeffrey Richter 在《Programming Applications for Microsoft Windows》(Microsoft Press, 1999)中解释了如何用 InjectLib 将一个 DLL 注入到另一个进程地址空间,然后用 EjectLib 将它卸载(实现细节参见 Inject.cpp )。它在本文中提供的代码有改动,以便通过对应的 CPSAPIWrapper 类动态使用 PSAPI,OpenProcess 已经被 GetProcessHandleWithEnoughRights 替代。
  为什么加载这样的 DLL 会如此有趣呢?如果你正在写 DLL,这种方法可以使你的代码轻松运行在其它进程的上下文中。真正有趣的是要在调用进程和远程 DLL 代码之间建立沟通渠道。驱动应用 GrabInfo,以及注入 DLL GrabHook 是作为例子建立的。其目的是获取四个参数,它们对于其它进程应该是未知的。这些参数分别是:命令行(GetCommandLine),环境字符串(GetEnvironmentStrings),是否调试(IsDebuggerPresent)以及在其下运行的窗口站(GetProcessWindowStation 和 GetUserObjectInformation)。
  远程调用的每个函数被包装在辅助函数中,由 DLL 中输出,它们全都调用一个通用函数:Ex-ecuteRemoteAction。该函数以目标应用的进程 ID 以及与拟执行行为对应的命令 ID 为参数(参见 GrabHook.cpp 中的 RA_XXX 常量)。它们被保存在变量 s_dwProcessID 和 s_Action 中,执行的结果被保存在 s_bSuccess 中。这三个变量要被两个进程存取,同时两个进程还要共享其相同的值,于是必须把它们声明在 DLL 的共享段中,方法是使用 #pragma data_seg 和 #pragma comment,就像下面这样:

#pragma data_seg(".shared")
DWORD s_Action = 0;
DWORD s_dwProcessID = 0;
BOOL s_bSuccess = FALSE;
#pragma data_seg()

#pragma comment(linker, "-section:.shared,rws")

  s_Action 和 s_dwProcessID 的值在调用进程中发生了什么变化?被调用进程又是如何给 s_bSuccess 赋值的呢,Figure 13 例举了远程调用的过程。


Figure 13 远程执行

  一旦 DLL 被加载到其它进程的地址空间,DllMain 入口点以 DLL_PROCESS_ATTACH 为参数被调用。作为安全检查,s_dwProcessID 与运行进程的 ID 进行比较。用 GrabHook_SharedBuffer 作为名称创建内存映像文件,对应的共享内存指针被保存在 g_lpvMem。该缓冲被用于在远程进程中运行的 DLL 代码和调用进程自身之间交换大块数据。如果你需要分配一块缓冲,其大小只为远端执行代码所知,你只要用 VirtualAlloc 分配一块内存即可,而不使用内存映像文件,其指针地址被保存在一个共享变量中。然后,你在调用进程中用 ReadProcessMemory 读取缓冲内容。最后用 VirtualFreeEx 来释放它以避免溢出。
  接下来的工作就是缓冲拷贝问题以及从远程进程卸载 DLL。使用与 CreateRemoteThread 一样的技术,因为 FreeLibrary 具备与线程过程同样的署名。唯一的窍门是要找到 DLL 的加载地址。Toolhelp32 已被 PSAPI 取而代之,以便代码也能在 Windows NT 中运行。这个注入技术有一点局限。对于 某些进程来说,比如那些以其他用户身份运行的进程,CreateRemoteThread 调用会失败。在这种情况下,内核会有如下的出错记录:

SE: Warning, new thread does not have
SET_THREAD_TOKEN for itself
SE: Check that thread 468.2ec isn''t in
some weird state at the kernel level

  但是该 SET_THREAD_TOKEN 看起来并不像任何 SE 特权。相反,它看起来像 THREAD_SET_THREAD_TOKEN,它是线程专用的存取权限。由于传递给 CreateRemoteThread 的参数中安全描述符为 NULL,这似乎就是调用失败的原因。
  你不应该在 注入 DLL 中使用 MFC,因为你可能会得到一些未定义的结果,比如,要是目标进程为 csrss.exe (Win32 子系统)的话,则会崩溃。
  在本文的第一部分,我提出了三个不同的获得某个远程进程命令行的解决方案。我还简要地提到另一个方案,也就是我所说的输出重用。思路是从控制台程序获得输出,对之进行解析,然后获得需要的描述。TLIST 是一个非常好的命令行例子。
  很多以控制台模式运行的工具都揭示了未在正式文档中披露的大量 Windows 内部运行机制,尤其是资源包和 Platform SDK 工具(参见 )。有关如何从控制台获取输出的讨论可以参知识库文章 。
  所有繁琐的细节工作都被包装在 CConsoleAppDriver 类中,使用时你要从其中派生。你只需要通过 StartApp 指出所执行的命令行,对于产生的每一行,重写的 OnNewLine 方法都会被调用。如果有错误,只要把 FALSE 赋给 m_bParsingOk。如果你已经捕获到所需的信息而想停止解析,返回 FALSE 即可。
  诀窍是创建一个管道,专门用来接收应用程序输出,而不是让 Windows 来做这项工作。该管道和新进程之间连接是通过传递给 CreateProcess 的结构 PROCESS_INFORMATION 实现的。为了获取进程输出,该类 hStdOutput 域包含类读取的管道信息(实现细节参见 ConsoleAppDriver.cpp)。
  对于一个给定的 DLL,DllSpy 列出使用该 DLL 的进程。对于任何给定的 DLL,我的工具提供了同样的特性。正像你在 中所看到的,OH 是一个控制台工具,它列出每个进程正在使用的内核对象。该程序的主要缺点是不完善的命名规范。请看:

000000A0 csrss.exe File 03bc \WINNT\system32\ega.cpi

这一行意思是 CSRSS 正在使用文件 \WINNT\system32\ega.cpi。但是正如你所见到的,没有驱动器规范说明,同样的问题存在于注册表键值中,对于 Win32 来说,其命名对用户实在是不友好。

\REGISTRY\USER\S-1-5-21-1021013165-1664506389-1469997231-1938Control Panel\International

上面这行被转化为:

HKCU\Control Panel\International

  你应该用来自 的 HANDLE 工具取代 OH。它提供了更好的格式化输出。FileUsage 工具如 Figure 15 所示,利用了本文上一部分介绍的类,并实现了从一个从 CConsoleAppDriver 派生的类来解析在 OnNewLine 方法中的 HANDLE 输出(解析细节参见 FileInfo.cpp)。这两者都必须用资源包中的 GFlags 来启用“Maintain a list of objects for each type”标志。


Figure 15 FileUsage 工具

文档

  在 Win32 这一层,Windows 用来管理进程、线程和模块的一个有趣的数据结构是 PEB。你在 Platform SDK 或 DDK 中找不到任何关于该结构的文档。但是,如果你下载 Windows 的调试工具(Debugging Tools for Windows),其中有一个工具是 WinDbg。用这个工具你可以钻研 PEB 以及更多其它信息。
  WinDbg 除了是一个内核模式的调试器外,它还带有几个非常有用的扩展 DLLs。kdex2x86 实现了一个 strct 命令,这个命令使你能找到几个有趣的无正式文档的 C 式样数据结构,包括:PEB、EPROCESS、KPROCESS 和 KTHREAD。确保遵循下列简单规则:总是使用与你所需的 Windows 版本一致的扩展;否则,这些内部结构可能会与你的应用程序实际试图存取的扩展版本不一致。为了使用该命令,你只须借助对任何进程的调试,比如:NotePad;在命令行随时调用 kdex2x86.strct 即可。对一个给定的进程,用 PROCESS_VM_READ 作为希望的进程句柄访问方式,用 ReadProcessMemory 存取其 PEB 内容并不难。这是因为其 PEB 总是位于地址 0x7ffdf000 处(或者用 NtQueryInformationProcess 得到地址)。不幸的是,后面的三个结构属于内核,因此,从一般的 Win32 应用程序无法对它们进行存取。如果你想跨越雷池,请首先阅读 James Finnegan在 1998 年 3 月的 MSJ 文章:“op Open a Privileged Set of APIs with Windows NT Kernel Mode Drivers”。
   是对 Windows 2000 PEB 的剖析,本文 2002 年 6 月第一部分里那个神秘的辅助函数 GetProcessCmdLine 的源代码中用到两个很相似的但不太透明的数据结构:

typedef struct
{
    DWORD Filler[4];
    DWORD InfoBlockAddress;
} __PEB;

typedef struct
{
    DWORD Filler[17];
    DWORD wszCmdLineAddress;
} __INFOBLOCK;

  命令行应该被存储在 __INFOBLOCK 中的某个域指向的一个内存块中,借助此域可以在 PEB 开始位置到四个DWORD偏移量处找到命令行。参见 ,你能看到四个 DWORD 偏移(0x10)到 ProcessParameter 指针。
  下一步是找到 _RTL_USER_PROCESS_PARAMETERS 结构指向的定义,再次用 strct 扩展命令获得答案,如 所示。填充 17 个 DWORD 到 _RTL_USER_PROCESS_PARAMETERS 的 0x44 偏移处,并直接跳至 _UNICODE_STRING CommandLine 域指针,未使用 Length 域。4

+040 struct _UNICODE_STRING CommandLine
+040 uint16 Length
+042 uint16 MaximumLength
+044 uint16 *Buffer

除了列出每个 PEB 域之外,你还可以用 WinDbg 的 !peb 命令轻松译解这些域。
  如 所示,如果你加载某个进程(如 Resource Kit 中的 oh.exe ),!peb 命令列出了一些有趣的字段的值,比如:ProcessParameters.CommandLine。借助这些命令,你可以进一步深入钻研到 Windows NT、Windows 2000 和 Windows XP 的内幕。


(全文完)

参考资料

  •  
    --------------------next---------------------


    阅读(209) | 评论(0) | 转发(0) |
    给主人留下些什么吧!~~