StdAfx自我欣赏文章:在同一个进程空间运行两个程序(进程隐藏)

StdAfx 2003-02-26 03:27:24
在同一个进程空间运行两个程序(进程隐藏)

在同一个进程空间运行两个程序
运行环境:Windows NT4.0/Windows 2000
关键字:进程隐藏,API截获,映像加载
众所周知,bo2k可以在一个指定的进程空间(比如explorer.exe进程)做为一个线程运行。本文试图找出一种方法,使得任意exe都可以在其他进程中以线程运行(当然,这里说的"任意"是有条件的,下面会讲到)。
为行文简单起见,我把先加载的exe称为宿主,后加载的exe称为客户。对于上面的例子,explorer.exe为宿主,bo2k.exe为客户。

基本知识
每一个exe都有一个缺省加载基址,一般都是0x400000。如果实际加载基址和缺省基址相同,程序中的重定位表就不需要修正(fixup),否则,就必须修正重定位表;
如果一个程序没有重定位表,而且如果程序不能在缺省基址处加载,那么程序将不能运行。举个例子,Windows95的最低加载基址是0x400000,你在
Windows NT上开发了一个exe,指定其加载基址为0x10000,如果连接时让连接器剥离重定位表,那么他将无法在Windows95下运行。

bo2k为了避免和普通程序冲突,选了一个极其特殊的基址:0x03140000,这个地址一般不会有程序用到。这样bo2k启动后,用WriteProcessMemory将自身复制到宿主进程的
0x03140000地址处,再用CreateRemoteThread远程启动一个线程,从入口点开始执行。
bo2k能够在其他进程空间正常运行,关键有两点:
1)实际加载基址和缺省基址相同,这样就无需修正重定位表。
2)与bo2k隐性联接(implicitly link)的动态联接库在目标进程中的加载基址和bo2k启动时的加载基址一致,这样就无需修改导入函数表。除非只用到ntdll.dll和kernel32.dll两个dll,
否则这点很难保证。bo2k的解决办法是,远程运行的代码不用隐性调用,所有用到API都在远程代码运行后再动态确定(用LoadLibrary和GetProcAddress)

我的目标是让"所有"的程序都能在其他进程空间跑。在这里,"所有"的含义是所有那些"重定位表没有被剥?quot;的32位pe格式的可执行程序。
对于Visual C++,这包括所有Debug版程序和以"/FIXED:NO"选项链接的Release版程序。
对于一般的程序,上面两点都很难满足:
1)绝大多数程序的加载基址都是0x400000,这样,客户exe就很难保证加载到其缺省基址。解决办法只能是修正重定位表。如果,很不幸,这个exe的重定位表被剥离,这个exe就没法在其他进程空间跑。
对于Visual C++,剥离重定位表是Release版exe的缺省设置。可以在工程文件的连接选项中加入"/FIXED:NO"来防止连接器剥离重定位表。
2)很多程序都用隐性联接调用Windows API,而只用到kernel32.dll导出API的程序很少,因此这一点也很难保证。解决办法是重填导入表(import table)。

另外,对于有界面的程序,光修正重定位表和导入表还不够。因为他们都会直接或间接用到GetModuleHandle和LoadResource这些函数。
GetModuleHandle有个特点,如果传递给他的ModuleName为NULL,则返回宿主exe的模块句柄。LoadResource也类似,如果传递给他的模块句柄为NULL,则认为是宿主exe模块,类似的API还有一些,不一一列举。
客户exe调用这些API显然会得到错误的结果。因此必须截获这些API做特殊处理。

综合上面分析,要让两个程序共享一份进程空间,要做的工作有:
1)打开进程边界:用WriteProcessMemory向宿主进程注入代码,用CreateRemoteThread启动远程代码;
2)在远程代码中,加载客户exe,必要时修正重定位表和填充dll导入表。
3)截获GetModuleHandle,LoadResource等API,在客户exe以缺省参数调用时返回客户exe的模块句柄,而不是宿主句柄。


根据以上思路,我写了remote.dll,导出三个函数:RemoteRunA,RemoteRunW,和RemoteCall。
原型分别为:
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 );


RemoteRunA用于在宿主进程中加载执行客户exe;
RemoteRunW是RemoteRunA的unicode版本;
RemoteCall实现远程注入并运行代码。

调用例子:
假如宿主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 );


RemoteCall是一个很cool的副产品,可以在任意宿主进程运行一系列你自己精心准备的代码。
远程代码无需特殊处理,就像在本地调用一样。RemoteCall支持很多特性:
可以对Windows API进行隐性调用(无需用LoadLibrary和GetProcAddress动态确定)
可以使用全局/静态变量(除了不能动态初始化);
可以使用编译时数据,特别是字符串常量;
支持异常处理;
支持源码级调试;
支持同步、异步调用;
对于同步调用,可以取得返回结果和错误号;
对远程代码做了异常保护,代码执行错误不会使宿主进程崩溃。 RemoteCall的唯一缺点是效率不高(当然,还有一个缺点,你的exe必须是可重定位的)。

调用例子:
在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 );
}
}


RemoteRun的调用例子:

void PrintUsage() {
printf( "\tUsage: rmExe <target process id> <Exe file path>\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;
}


应该注意的问题:
1)最困难的部分是加载客户exe,简单的调用LoadLibrary根本不能解决问题,他不会替你修改重定位表和导入表。
另外对于.tls section(用于支持线程本地存储)和.bss section(用于为初始化数据),我目前还不是很清楚如何处理;希望有人和我一起探讨;
2)目前remote.dll还不能支持在一个进程空间运行三个或更多程序。问题出在我在remote.dll中维护着一个客户exe的thread列表,
用于判断谁调用了GetModuleHandle等API,目前只能处理一个客户exe。这个问题不难解决;

3)有一些工具可以查看进程中加载的模块列表,如果想做进程彻底隐藏,不想让这些工具检测到我们的模块,在我看来,至少有两种解决办法:
一,不用LoadLibrary,自己写LoadDLL,这看起来似乎很困难,幸运的是,在bo2k的源代码中提供了一套这样的工具(在dll_load.cpp中实现)。
remote.dll中修改重定位表和导入表基本上用的都是dll_load.cpp里的代码。值得注意的是,dll_load.cpp原来的实现中有一点bug,他不能正确处理有Borland的tlink32生成的exe。
具体原因请仔细阅读Matt pietrek的"Windows 95 system programming secrets",
或msdn文章:"Peering Inside the PE: A Tour of the Win32 Portable Executable File Format",里面讲到了ms linker和borland linker的区别。
二,我自己实现了一种模块剥离技术,可以让进程脱离.exe文件和.dll文件运行。其思想是先对要剥离的exe或dll模块的所有数据做好备份,然后用FreeLibrary或者UnmapViewOfFile卸掉模块,
再把备份的模块数据恢复回来。我以前在csdn上贴过代码的,自己找吧。

4)截获API用的是MS Detours Package 1.3。我不打算附上它的源代码,自己去下载吧:http://research.microsoft.com/sn/detours

5)在截获API时必须挂起其他线程。我用了两个未公开的接口:NtQuerySystemInformation用
...全文
45 6 打赏 收藏 转发到动态 举报
AI 作业
写回复
用AI写文章
6 条回复
切换为时间正序
请发表友善的回复…
发表回复
idAnts 2003-03-01
  • 打赏
  • 举报
回复
有点意思,收藏.
niboah 2003-03-01
  • 打赏
  • 举报
回复
收藏先!!!
yaotang 2003-02-28
  • 打赏
  • 举报
回复
好文,虽然看不太懂;谢谢楼主
NowCan 2003-02-28
  • 打赏
  • 举报
回复
够难!
winthegame 2003-02-27
  • 打赏
  • 举报
回复
Mark,回去好好看看。
elabs 2003-02-27
  • 打赏
  • 举报
回复
不错,有点深奥

给个代码例子如何,也好研究一下。

elabs@sohu.com
因文件超过20M不能上传,所以拆分为两个文件分次上传 第1章 COM背景知识 1.1 COM的起源 1.1.1 软件业面临的挑战 1.1.2 传统解决方案 1.1.3 面向对象程序设计方法 1.1.4 最终解决方案:组件软件 1.1.5 面向对象的组件模型——COM 1.2 COM的发展历程 1.2.1 COM以前的对象技术:DDE、OLE 1、VBX控件 1.2.2 COM首次亮相:OLE2 1.2.3 Microsoft拥抱Internet:ActiveX 1.2.4 更多的新名词:Windows DNA和COM+ 1.2.5 远程对象:ORBs和DCOM 1.2.6 COM的最新版本:COM+ 1.3 COM技术现状 1.3.1 COM与CORBA 1.3.2 COM与Enterprise Java Beans 1.3.3 Windows之外的COM 小结 第2章 从C++到COM 2.1 C++客户重用C++对象——例程DB 2.1.1 C++对象 2.1.2 客户程序 2.2 将C++对象移进DLL中——例程DB_cppdll 2.2.1 成员函数的引出 2.2.2 内存分配 2.2.3 Unicode/ASCII兼容 2.2.4 例程实现 2.2.4.1 修改接口文件 2.2.4.2 修改对象程序 2.2.4.3 修改客户程序 2.3 C++对象使用抽象基类——例程DB_vtbl 2.3.1 问题:私有数据成员被暴露 2.3.2 解决方案:抽象基类 2.3.2.1 什么是抽象基类(Abstract Base Class) 2.3.2.2 实现秘诀:虚函数(Virtual Functions) 2.3.3 使用抽象基类 2.3.4 例程实现 2.3.4.1 修改接口文件 2.3.4.2 修改对象程序 2.3.4.3 修改客户程序 2.4 改由COM库装载C++对象——例程dbalmostcom 2.4.1 COM库 2.4.2 对象创建的标准入口点 2.4.3 标准对象创建API 2.4.4 标准对象注册 2.4.5 例程实现 2.4.5.1 修改接口文件 2.4.5.2 修改对象程序 2.4.5.3 修改客户程序 2.5 将C++对象变成COM对象 2.5.1 引用计数 2.5.2 多接口 2.5.3 IUnknown接口 2.5.4 标准类厂接口:IClassFactory 2.5.5 对象代码的动态卸载 2.5.6 自动注册 2.5.7 例程实现 2.5.7.1 修改接口文件 2.5.7.2 修改对象程序 2.5.7.3 修改客户程序 2.6 为COM对象添加多接口支持 2.6.1 多接口 2.6.2 DEFINE_GUID 2.6.3 例程实现 2.6.3.1 修改接口文件 2.6.3.2 修改对象程序 2.6.3.3 修改客户程序 小结 第3章 COM基础知识 3.1 对象与接口 3.1.1 COM对象 3.1.2 COM接口 3.1.3 IUnknown接口 3.1.3.1 生存期控制:AddRef和Release 3.1.3.2 接口查询:QueryInterface 3.1.4 全球唯一标识符GUID 3.1.5 COM接口定义 3.1.6 接口描述语言IDL 3.2 COM应用模型 3.2.1 客户/服务器模型 3.2.2 进程内组件 3.2.3 进程外组件 3.2.4 COM库 3.2.5 HRESULT返回值 3.2.6 COM与注册表 3.3 COM组件 3.3.1 实现类厂对象 3.3.2 类厂对象的创建 3.3.3 实现自动注册 3.3.4 实现自动卸载 3.4 COM客户 3.4.1 COM对象创建函数 3.4.1.1 CoGetClassObject 3.4.1.2 CoCreateInstance 3.4.1.3 CoCreateInstanceEx 3.4.2 如何调用进程内组件 3.4.3 COM客户调用进程外组件 3.5 进一步认识COM 3.5.1 可重用机制:包容和聚合 3.5.2 进程透明性 3.5.3 安全性机制 小结 第4章 COM扩展技术 4.1 可连接对象机制 4.1.1 客户、接收器与可连接对象 4.1.1.1 接收器 4.1.1.2 可连接对象 4.1.1.3 客户 4.1.2 实现可连接对象 4.1.3 实现接收器 4.1.4 建立接收器与连接点的连接 4.1.5 获得出接口的类型信息 4.2 结构化存储 4.2.1 什么叫结构化存储和复合文件 4.2.2 存储对象和IStorage接口 4.2.2.1 IStorage接口 4.2.2.2 获得IStorage指针 4.2.2.3 释放STATSTG内存 4.2.2.4 枚举存储对象中的元

2,644

社区成员

发帖
与我相关
我的任务
社区描述
VC/MFC 硬件/系统
社区管理员
  • 硬件/系统社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧