[旧文存档]Introduction to Remote Run Library
Posted on 2008-10-16 11:01 sting feng 阅读(493) 评论(0) 编辑 收藏 举报也是2001年在vchelp发表的一篇相关文章。原文在这里。 代码可以在vchelp下,不过比较旧。也可以直接向我要。做做这个library的原因是当时正热衷于玩代码远程注入。远程注入的代码编写的时候有很多 限制,比如说必须关掉/GZ编译选项,不能使用全局/静态变量,远程运行的代码由于没有调试符号只能做汇编级调试,等等。因此做了这么一个可以把整个 EXE模块远程注入到其他进程的东西。
再次提醒,不要再给文章最后的email地址写信。
更新:Remote Run Library的最新代码可以在这里下载。
----------------------------------------------------------------------------------------------------------------
一:简介:
Remote Run Library是一个为部分代码提供完整的远程(这里的远程指不同于本进程的其他进程空间)运行环境(包括在代码中使用隐式API调用、字符串常量、全局 /静态变量、异常保护)的工具。它允许你在任意进程(只要你有足够的权限)运行一段代码甚至一个exe,从而可以让两个或多个可执行文件在同一个进程空间 运行。
二:使用Remote Run Library的限制:
1) Remote Run Library只能工作于windows NT/2000 for intel 386 series
2) 目标exe必须是32位pe格式的exe
3) 目标exe必须支持重定位(即存在.reloc section)。对于Visual C++ complier,debug版本exe都有;release版本默认设置会剥离.reloc section,但你可以通过
4) 目标exe不能存在.tls section。这意味着你不能在程序中使用_declspec(thread) int i = 0;这样的东东
三:Remote Run Library导出的API:
BOOL WINAPI RemoteRunA( DWORD processId, LPCSTR lpszAppPath, LPCSTR lpszCmdLine, int nCmdShow );
BOOL WINAPI RemoteRunW( DWORD processId, LPCWSTR lpszAppPath, LPCWSTR lpszCmdLine, int nCmdShow );
BOOL WINAPI RemoteCall( DWORD processId, PVOID pfnAddr, PVOID pParam, DWORD cbParamSize, BOOL fSyncronize );
BOOL WINAPI RemoteRunExA( DWORD processId, LPCSTR lpszAppPath, LPCSTR lpszCmdLine, int nCmdShow, DWORD dwFlags );
BOOL WINAPI RemoteRunExW( DWORD processId, LPCWSTR lpszAppPath, LPCWSTR lpszCmdLine, int nCmdShow, DWORD dwFlags );
RemoteRunA -- 用于在宿主进程中加载执行客户exe;
RemoteRunW -- RemoteRunA的unicode版本;
RemoteRunExA -- 前几个参数和RemoteRunA相同,dwFlags用来控制remote.dll和目标exe的加载方式。
dwFlags取值:可以是 RM_DEBUG_IMAGE(=1)和RM_DEBUG_LIBRARY(=2)的组合。
RM_DEBUG_IMAGE -- 不隐藏目标exe。如果设立,则相同的exe不能加载两次。
RM_DEBUG_LIBRARY -- 不隐藏remote.dll。
RemoteRunExW -- RemoteRunExA的unicode版本;
RemoteCall -- 实现远程注入并运行代码。
四:调用例子:
a) RemoteRun
假如宿主exe为Depends.exe(我经常使用的宿主进程),pid为136。客户exe为"C:"WINNT"system32"CALC.EXE",
RemoteRunA( 136, "C:""WINNT""system32""CALC.EXE", NULL, SW_SHOW );
或,
RemoteRunW( 136, L"C:""WINNT""system32""CALC.EXE", NULL, SW_SHOW );
RemoteRun的调用例子:
void PrintUsage() {
printf( ""tUsage: rmExe "n" );
}
int main(int argc, char* argv[]) {
if( argc <= 2) {
PrintUsage();
return -1;
}
int pid = atoi( argv[1] );
if( pid != 0 ) {
HMODULE hRemote = ::LoadLibrary( "remote.dll" );
if( hRemote != NULL ) {
typedef DWORD (WINAPI *PFN_RemoteRun)( DWORD processId, LPCSTR lpszAppPath, LPSTR lpszCmdLine, int nCmdShow);
PFN_RemoteRun fnRemoteRun = (PFN_RemoteRun)::GetProcAddress( hRemote, "RemoteRunA" );
if( fnRemoteRun != NULL )
fnRemoteRun( pid, argv[2], NULL, SW_SHOW );
FreeLibrary( hRemote );
}
}
return 0;
}
b) RemoteCall
RemoteCall是一个很cool的副产品,可以在任意宿主进程运行一系列你自己精心准备的代码。
远程代码无需特殊处理,就像在本地调用一样。RemoteCall支持很多特性:
-- 可以对Windows API进行隐性调用(无需用LoadLibrary和GetProcAddress动态确定)
-- 可以使用全局/静态变量(除了不能动态初始化);
-- 可以使用编译时数据,特别是字符串常量;
-- 支持异常处理;
-- 支持源码级调试;
-- 支持同步、异步调用;
-- 对于同步调用,可以取得返回结果和错误号;
-- 对远程代码做了异常保护,代码执行错误不会使宿主进程崩溃。
RemoteCall的唯一缺点是效率不高(当然,还有一个缺点,你的exe必须是可重定位的)。
RemoteCall的调用例子:
在Windows 2000中,对有密码保护风格的Edit control调用SendMessage(hwnd, WM_GETTEXT,...)试图得到密码内容时,
系统会检查调用SendMessage的进程和Edit control所在的进程是否相同,不同则返回空字符串,调用失败。
解决办法显然应该是在目标进程中调用SendMessage。
利用RemoteCall,可以很容易地实现:
typedef struct _tagGETPASS {
HWND hwndPassword; // in
char szPassText[1024]; // out
}GETPASS;
static int *_p = NULL;
BOOL NullFunction() {
// 可以用静态变量和异常保护。
__try {
*_p = 0;
}__except(EXCEPTION_EXECUTE_HANDLER){}
return TRUE;
}
// 准备在远程运行的代码
BOOL WINAPI RemoteGetPasswordText( GETPASS* pgp ) {
// 可以使用相对调用(near call),没什么用,演示一下
NullFunction();
// 隐性调用Windows API
if( SendMessageA( pgp->hwndPassword, WM_GETTEXT, sizeof(pgp->szPassText)-1, (LPARAM)pgp->szPassText ) ) ) {
MessageBoxA( NULL,
pgp->szPassText,
"Great!!", // 可以使用字符串常量
MB_OK );
return TRUE;
}
return FALSE;
}
void GetPasswordText( HWND hwnd ) {
GETPASS gp;
gp.hwndPassword = hwnd;
DWORD processId;
GetWindowThreadProcessId( hwnd, &processId );
HMODULE hLib = ::LoadLibrary( "remote.dll" );
if( hLib != NULL ) {
typedef BOOL (WINAPI *PFN_RemoteCall)( DWORD processId, PVOID pfnAddr, PVOID pParam, DWORD cbParamSize, BOOL fSyncronize );
PFN_RemoteCall fnRemoteCall = (PFN_RemoteCall)::GetProcAddress( hLib, "RemoteCall" );
if( fnRemoteCall != NULL ) {
if( fnRemoteCall( processId, RemoteGetPasswordText, &gp, sizeof(gp), TRUE ) )
MessageBoxA( NULL, gp.szPassText, "we get the password!!", MB_OK );
}
::FreeLibrary( hLib );
}
}
五:怎样编译remote Run Library:
1) 编译remote需要Detours Library,如果你手头没有,请从这里下载: http://www.research.microsoft.com/sn/detours 下载后请把Detours library的相应路径(include/lib/source directories)加到VC缺省路径中。
2) Visual C++的路径设定: "Projects" menu-->Settings...-->Option-->Include files|Library files|Source files 我习惯于把Platform SDK的include, lib路径加在VC的缺省路径前面。由于Platform SDK中一些头文件的定义和VC自带的不同,如果你把VC的include路径加在PlatformSDK的前面,或者没有安装Platform SDK,你需要手工改动一些代码,否则编译将不能通过。
3) 不能使用/GZ编译选项。
六:一些技术细节:
1) 在一个进程空间运行一份exe,关键是要解决exe的加载问题,这包括把exe映射到目标进程空间,解决IAT和重定位表,为exe加载必要的dll。remote run library中实现了多种加载exe的方法。
a) 手工映射exe到目标进程空间,手工解决IAT和重定位表。
b) 用LoadLibrary加载exe,手工解决IAT和重定位表。因为LoadLibrary不会为exe解决IAT和重定位表。
c) 修改exe的pe header中的Characteristics信息,让LoadLibrary以为这是一个dll。这样LoadLibrary会替我们解决IAT和重 定位表。由于LoadLibrary会为dll调用DllMain,但是exe没有DllMain。因此还必须准备一个伪DllMain给 LoadLibrary。与此类似,用FreeLibrary卸载exe时,也必须提供一个伪DllMain。
d) LoadLibraryEx提供一个参数用于控制dll的加载方式--dwFlags。dwFlags可取 DONT_RESOLVE_DLL_REFERENCES和LOAD_LIBRARY_AS_DATAFILE或其他值。 LOAD_LIBRARY_AS_DATAFILE让LoadLibraryEx把dll当成一个数据文件加载,不做任何调整; DONT_RESOLVE_DLL_REFERENCES让LoadLibraryEx把dll当成一个image文件加载,但是不解决IAT和重定位 表,当然也不会调用DllMain。但是,有没有一种方法,让LoadLibraryEx解决IAT和重定位表,但是又不调用DllMain呢?出于个人 兴趣,我研究了LoadLibraryEx,发现他做一些调整后,最终调用了ntdll.dll中的LdrLoadDll,LdrLoadDll又调用了 LdrLoadDllEx(这个API没有被nt/2k导出,名字也是我自己起的)。LdrLoadDllEx比LdrLoadDll多一个参数,前几个 完全相同,最后一个指明是否调用DllMain。这样我们又有了第4中加载exe的方法:修改exe的Characteristics,伪装成dll,直 接调用LdrLoadDllEx,最后一个参数为0。
DWORD WINAPI LdrLoadDllEx(LPCWSTR szSearchPath, DWORD *dwFlags, UNICODE_STRING *uszImagePath, HMODULE *pModule, BOOL fCallDllMain);
2) 大部分win32程序都有界面,因此都会用到一些像GetModuleHandle, LoadResource这样的API,如果客户exe以缺省值调用这些API,会返回宿主进程的handle,因此必须拦截这类API,做特殊处理。
3) 隐藏模块名字。
a) 先备份一个dll的完整数据到一块临时内存;
b) 调用FreeLibrary卸载dll;
c) 在dll原加载基址处分配内存,再从临时内存中复原所有数据。
遗憾的是,如果dll用了c runtime library(很不幸,remote.dll就用了),FreeLibrary在调用DllMain时,c runtime library会把所有new出来的内存统统释放掉。幸好我们有Detours Library!! 我的解决办法是用截获DllMain,不做任何事情而直接返回。
4) remote run library中有几处用到了代码本身自己释放自己的技巧,这让RemoteCall可以进行异步调用! 我最早从csdn上学到这一技巧,陆麟的主页上也有介绍,感谢他们!!
5) 关于.tls section。无论是windows 9x,nt还是2000版本的LoadLibrary都不能正确解决有.tls section的dll。目前如果客户exe存在.tls section,RemoteRun将失败。具有.tls section的dll只能和exe做隐式连接才能正常工作,这是因为在nt/2000中,初始化tls数组的工作只在 LdrInitializeThunk(在ntdll.dll中导出)中完成。LdrInitializeThunk负责进程创建时exe的初始化( 包括解决IAT和重定位表,初始化NLS、TLS,加载所有与exe隐性连接的dll并调用他们的DllMain)工作。
七:参考资料:
Matt Pietrik. Windows 95 System Programming SECRETS
Hunt Galen. Detours: Binary Interception of Win32 Functions
David A. Solomon, Mark E. Russinovich. Inside Windows 2000 Third Edition
Gary Nebbett. Windows NT/2000 Native API Reference
Jeffrey Richter. Advance Windows
Jeffrey Richter. Q&A Win32, Microsoft System Journal, March 1996
Cult Of The Dead Cow. Back Orifice 2000 (Source code)
Sting Feng. Process Hacker (Source code)
Zoltan Csizmadia. Examine Information on Windows NT System Level Primitives (Source code). http://www.codeguru.com/system/ntsysteminfo.shtml
Richard Eperjesi. Taskbar Modification to Kill Windows NT/2000 Processes (Source code). http://www.codeguru.com/system/killer.html
八:如果还有任何问题,请和我联系。