Flier's Sky

天空,蓝色的天空,眼睛看不到的东西,眼睛看得到的东西

导航

RunDll32 的使用方法与实现原理

Posted on 2004-07-08 10:03  Flier Lu  阅读(2961)  评论(0编辑  收藏  举报
RunDll32 的使用方法与实现原理

http://www.blogcn.com/user8/flier_lu/index.html?id=1190096&run=.03463D0

    RunDll32.exe是Windows系统自带的一个直接执行DLL中导出函数的小工具,使用方式上与16位系统的RunDll.exe兼容,但提供32位的支持。

   RUNDLL32.EXE <dllname>,<entrypoint> <optional arguments>

     首先指定函数所在DLL名称,然后是函数入口名称,最后是可选的参数。RunDll32.exe会使用标准的DLL搜索策略定位DLL,但最好是指定完整路径名,避免名称冲突。此外需要注意的是,DLL名称和函数入口名称之间的逗号必须要有,因为程序在解析命令行时是依靠此标识分隔字符串的。
     具体的使用方法可以参考微软知识库提供的文档

     INFO: Windows Rundll and Rundll32 Interface

     使用这个工具可以完成很多令人意想不到的管理功能,例如在脚本中退出Windows以及调用控制面板等更多控制功能等。
   
     RUNDLL and RUNDLL32
     Using Rundll

     因为RunDll32实际上是提供了一个调用任意DLL函数的代理,所以只要此函数以名字导出,并且遵循一定的函数签名(Function Signature),就可以很方便的通过RunDll32调用。

     void CALLBACK EntryPoint(HWND hwnd, HINSTANCE hinst, LPSTR lpszCmdLine, int nCmdShow);

     而实际上RunDll32也可以应用在不符合此函数签名的函数上,例如前面提到的关闭Windows功能,调用的

     BOOL ExitWindowsEx(UINT uFlags, DWORD dwReason);

     虽然ExitWindowsEx的函数签名与RunDll32要求的不符,但实际上也可以使用,因为他在堆栈使用上可以利用RunDll32传入的参数。传入的hwnd被当成uFlags使用;hInst被当成dwReason使用。因为这两个值都是系统相关伪随机的,因此这样调用的效果伪随机(依赖窗口和模块句柄的分配算法)。这也是为什么使用此函数时往往要敲多次以尝试成功。
     详细的分析文章可以参考这个讨论What can go wrong when you mismatch the calling convention? 

     在了解了RunDll32.exe的使用方法后,我们来看看其实现原理。其源代码可以在Win2K源码包的win2kprivatewindowsshell undll32目录下找到。

     整个实现代码很简单,大概分为三个部分:ModuleEntry和WinMain前半部分完成初始化和其它薄记工作;ParseCommand完成命令解析、DLL载入以及函数入口获得,是核心部分;InitStubWindow和WinMain后半部分完成函数调用所需窗口句柄的初始化以及实际函数调用。

     ParseCommand函数首先对命令行字符串进行分解,验证传入Dll名称是否指向有效的PE映像文件,并尝试载入DLL。
     在定义了WX86符号的Alpha系统上,需要使用wx86.dll导出的Wx86LoadX86Dll函数完成实际的DLL载入,并使用Wx86ThunkProc函数构造一个Thunk块,包装实际函数的调用。
     如果定义了WINNT符号的系统,还需要从DLL中获得DOS头的和载入配置信息中的版本信息,更新PEB的相关字段模拟相应Windows版本。
     在获取函数入口地址时,RunDll32会尝试获得相应后缀为W和A的函数名入口地址。因为Win32API中凡是使用了字符串的函数一般都提供了Unicode/Ansi两个版本。这样就可以避免要求用户在命令行强行指定,RunDll32也会字段转换命令行为对应形式的字符串,传递给目标函数。
     最后的调用很简单,依次传入Stub窗口句柄、模块句柄、命令行和显示属性,并用一个try...except保护起来,处理所有的(EXCEPTION_EXECUTE_HANDLER)异常情况,弹出异常信息对话框。