分享
 
 
 

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

王朝vc·作者佚名  2006-05-23
窄屏简体版  字體: |||超大  

定制调试诊断工具和实用程序

——摆脱DLL“地狱”(DLL Hell)的困扰(八)

原著:Christophe Nasarre

编译:NorthTibet

下载源代码:EscapeFromDLLHell.exe (588KB)

原文出处:Windows XP:Escape from DLL Hell with Custom Debugging and Instrumentation Tools and Utilities,Part 2

本文假设你熟悉 Win32,DLL

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

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

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

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

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

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

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

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

工具(参见

Figure 14)。有关如何从控制台获取输出的讨论可以参知识库文章

Q190351

所有繁琐的细节工作都被包装在 CConsoleAppDriver 类中,使用时你要从其中派生。你只需要通过 StartApp

指出所执行的命令行,对于产生的每一行,重写的 OnNewLine 方法都会被调用。如果有错误,只要把 FALSE 赋给 m_bParsingOk。如果你已经捕获到所需的信息而想停止解析,返回

FALSE 即可。

诀窍是创建一个管道,专门用来接收应用程序输出,而不是让 Windows 来做这项工作。该管道和新进程之间连接是通过传递给 CreateProcess

的结构 PROCESS_INFORMATION 实现的。为了获取进程输出,该类 hStdOutput 域包含类读取的管道信息(实现细节参见 ConsoleAppDriver.cpp)。

对于一个给定的 DLL,DllSpy 列出使用该 DLL 的进程。对于任何给定的 DLL,我的工具提供了同样的特性。正像你在

Figure 14 中所看到的,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

你应该用来自 http://www.sysinternals.com

的 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”。

Figure 16 是对 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偏移量处找到命令行。参见

Figure 16,你能看到四个 DWORD 偏移(0x10)到 ProcessParameter

指针。

下一步是找到 _RTL_USER_PROCESS_PARAMETERS 结构指向的定义,再次用 strct 扩展命令获得答案,如

Figure 17 所示。填充 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 命令轻松译解这些域。

Figure 18 所示,如果你加载某个进程(如 Resource Kit 中的 oh.exe ),!peb

命令列出了一些有趣的字段的值,比如:ProcessParameters.CommandLine。借助这些命令,你可以进一步深入钻研到

Windows NT、Windows 2000 和 Windows XP 的内幕。

(全文完)

参考资料

The Win32 Debugging Application Programming Interface

Bugslayer: Windows 2000 and LDR Messages, A COM Symbol Engine, Finding Bloated Functions, and MoreDEB Sample: Debug Event Browser

Spawn Console Processes with Redirected Standard Handles

GetWindowModuleFileName & GetModuleFileName Work Only with the Calling Process

作者简介

Christophe Nasarre 是法国 Business Objects 公司的技术经理(technical

manager)。他在 Windows 平台上(3.0 以后的版本)编写了若干个低级工具。他的联系方式:cnasarre@montataire.net.

.

本文出自 MSDN Magazine

August 2002 期刊,可通过当地报摊获得,或者最好是

订阅

本文由 VCKBASE MTT 翻译

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有