反向进程注入及隐藏--动手做一个最简单的PELoader
作者:Luke0314 (msfocus_at_hotmail.com)
来源:安全焦点
一.废话
最近因为公司的项目需要,顺带的学习了一点和PELoader相关的东西,恰见网上正在沸沸扬扬的谈论虚拟脱壳。本人不才,实在是没能力也没精力去写一个真正意义上的虚拟机,因此尝试做了一个简单而偷懒的PE加载器。
这个PE加载器也可以看做是VM的前身吧。我想它可以成为一个简易脱壳工具或者用户态的进程内调试器基础。
二.做这个东西干嘛?
1.公司的项目需要实现但进程内多插件并发运行,也就是说,1个PID需要同时给n个进程使用,这牵扯到更麻烦的进程内内存切换工作。
2.实现反向进程注入,隐藏进程,这样做的RK更不容易被发现。
3.自从离开了安全的伤心地之后,一直堕落于做IM软件的Server,很久没有碰windows了,需要活动一下大脑
三.PELoader完成了什么工作?
这个PELoader写得很乱很粗糙,全部代码+调试基本上是在两天之内堆完的。由于时间关系,我只实现了它如下几个特性:
1. 在普通用户权限下实现用户态的PE文件启动执行。
2. 被启动的程序无进程,而嵌与宿主(PELoader)程序体内,与宿主共享一个进程ID
3. 实现了大部分资源文件的加载
4. 实现了进程内API调试,跟踪
5. 最基础的内存Dump脱壳(没做OEP等的修正工作)
6. 宿主可在程序被加载启动后继续执行
7. 支持console和gui程序执行
四.目前可支持的程序
1.cmd.exe 经过测试,我发现存一些显示资源的小问题,但仍然可以比较健壮的运行
2.Excel.exe(Office10) 这个程序在PELoader里运行的非常好,但还是有一个小Bug,就是窗体资源图标不是很对劲…问题还没找到
五.目前的问题
还是因为时间的问题,好多东西我没处理好,如果有朋友能改出一个不错的版本,希望可以mail我一份:msfocus@hotmail.com
1.目前我正在做多进程共享的问题,在进程间切换的时候,如果完全切换所有被使用的内存,程序将异常的慢。如果仅切换部分需要使用的内存,将牵扯到复杂的虚拟页表切换,搞得很头大
2.由于进程自身资源错位,因此需要拦截非常多的API,写到手酸,还是没有写全,不知道哪里能有个完整的需要拦截的API的列表。
3.在debug状态下运行,经常崩溃,烦躁…
4.很多程序在加载时候会失败或者启动之后崩溃,我一直没功夫检查这个错误
六.技术原理说明
正常情况下,一个PE文件被系统加载后,系统会自动处理好IAT和IID表,然后找到OEP开始执行代码,一般情况下call OEP后的第一条API为GetVersion。
我们要动手做一个PELoader则必须先将进程代码注入自己的内存空间,并手工解决IAT,定位OEP,如果你以为仅仅如此,那么我保证你的代码最多运行你自己写的一小段shellcode
要运行一个真正的进程,还需要做类似资源管理,句柄管理等很多的工作。
没关系,我们一个一个来:
首先解决加载问题。
加载可以有很多方式如:alloc一块内存保存或利用LoadLiarbryEx作为一个数据文件加载。但无论是哪种方法都没有解决IAT的问题,如果我们自己手工解决IAT,可能需要比较多的计算过程,这个过程可以参考kanxue的Linxer写的PE重定位函数。但本文所要提的并不是这种方法。也许有人说,有文章提到过ntdll.dll中自己搜索并导出LdrLoadDllEx就可以实现加载并解决IAT,但实际上这种方法至少在我的winxp系统上是失败的。所以我采用了另外一种方式用LoadLibrary来加载:
A. 修改PE头中的Characteristics属性,为其增加IMAGE_FILE_DLL属性,此外我还为其增加了一个非必须的 IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP属性。让LoadLibrary误认为这是一个DLL文件。
B. 将PE选项部分的AddressOfEntryPoint设置为空,至于原因不要奇怪,msdn是这样描述的:
AddressOfEntryPoint
Pointer to the entry point function, relative to the image base address. The entry point function is optional for DLLs. When no entry point is present, this member is zero.
我们这样做只是为了让LoadLibrary认为这个假DLL文件没有dll_main入口点。因为实际上,我们的EXE文件只有main入口点。
C. HOOK API,我们需要HOOK非常多的API来保证这个PE文件能够正确的获取自身句柄,资源等。确保启动过程中不会发生资源找不到之类的问题。因为本人太懒的原因,HOOK的方法参考了eyas的一个程序。这里和我们的何大侠讨论了很久,发现还是有很多问题要处理的。
D. 这是非必须的一步—恢复入口点及Characteristics属性,确保个别BT一点的进程也能够正常运行。
E. Call 原始的EntryPoint,这实际上会调用到目标程序的main函数,此时程序就已经正常运行起来了。
F. 扩展VUE。这一步也不是必要的,但如果你想试试,可以考虑用我提供的dumpfile函数在执行GetVersion的时候,或者是第一个API的时候,尝试目标程序的dump内存。当然,修复工作还是需要自己去完成的。
G. 在宿主内继续执行代码,包括可以设置断点对目标程序进行调试。
H. 编译时候,记得设置编译选项,把代码放到0x0f400000的地方,把0x400000等常用地址让出给目标程序,因为我实在太懒了,这样我就可以偷懒解决没有IID的问题了。当然这个是不稳妥的解决方案。
I. 没了,哪位兄弟改出漂亮的代码mail我一份:msfocus@hotmail.com
后面给出一个bug重重的代码:
/*********************************************************/
//PELoader.exe v1.0
//Luke msn:msfocus@hotmail.com
//2007.7.25
/*********************************************************/
#include <windows.h>
#include <stdio.h>
#include <imagehlp.h>
#include <psapi.h>
#pragma pack(1)
#pragma comment(lib, "imagehlp.lib")
#pragma comment(lib, "psapi.lib")
#pragma comment(lib, "user32.lib")
HMODULE hmod = NULL;
char *lpNewBaseOfDll = NULL;
char *lpNewBaseOfDll1 = NULL;
MODULEINFO mi;
MODULEINFO mi1;
HMODULE OldKernel32Address = NULL;
HMODULE OldUser32Address = NULL;
char PEFile[MAX_PATH] = {0};
unsigned long OEP = 0;
char *addr_GetModuleHandleExA = NULL;
char *addr_GetModuleHandleExW = NULL;
unsigned long LoadPEFile(char *FileName, char **Buffer)
{
FILE *fp = fopen(FileName, "rb");
fseek(fp, 0, SEEK_END);
unsigned long len = ftell(fp);
fseek(fp, 0, SEEK_SET);
*Buffer = new char[len + 4];
memset(*Buffer, 0x0, len + 4);
unsigned long i = 0;
while(i < len)
{
fread(*Buffer + i, 4, 1, fp);
i+=4;
}
fclose(fp);
return len;
}
void SaveAs(char *FileName, char *Buffer, unsigned long len)
{
FILE *fp = fopen(FileName, "wb");
unsigned long i = 0;
while(i < len)
{
fwrite(Buffer + i, 4, 1, fp);
fflush(fp);
i+=4;
}
fclose(fp);
}
void WINAPI DumpFile(char *FileName)
{
MODULEINFO dumpinfo;
DWORD dw = 0;
GetModuleInformation(GetCurrentProcess(), hmod, &dumpinfo, sizeof MODULEINFO);
printf("dump size:%d
", dumpinfo.SizeOfImage);
SaveAs(FileName, (char *)dumpinfo.lpBaseOfDll, dumpinfo.SizeOfImage);
}
BOOL WINAPI MyGetModuleHandleExA(DWORD dwFlags, LPCSTR lpModuleName, HMODULE *phModule)
{
printf("in MyGetModuleHandleExA
");
BOOL realbool = false;
char *lpm = new char[MAX_PATH];
memset(lpm, 0x0, MAX_PATH);
if(lpModuleName == NULL)
strcpy(lpm, PEFile);
else
strcpy(lpm, lpModuleName);
DWORD pNewFunc = (DWORD)addr_GetModuleHandleExA - (DWORD)mi.lpBaseOfDll + (DWORD)lpNewBaseOfDll;
__asm push phModule
__asm push lpm
__asm push dwFlags
__asm call pNewFunc
__asm mov realbool, eax
printf("realbool:%d
", realbool);
delete []lpm;
// if(*phModule == OldKernel32Address)
// *phModule = (HMODULE)lpNewBaseOfDll;
printf("out MyGetModuleHandleExA
");
return realbool;
}
BOOL WINAPI MyGetModuleHandleExW(DWORD dwFlags, LPCWSTR lpModuleName, HMODULE *phModule)
{
printf("in MyGetModuleHandleExW
");
BOOL realbool = false;
WCHAR *lpm = new WCHAR[MAX_PATH];
memset(lpm, 0x0, sizeof(WCHAR) * MAX_PATH);
if(lpModuleName == NULL)
{
swprintf(lpm, L"%s", PEFile);
// wcscpy(lpm, L"c:\a.exe");
}
else
wcscpy(lpm, lpModuleName);
DWORD pNewFunc = (DWORD)addr_GetModuleHandleExW - (DWORD)mi.lpBaseOfDll + (DWORD)lpNewBaseOfDll;
__asm push phModule
__asm push lpm
__asm push dwFlags
__asm call pNewFunc
__asm mov realbool, eax
printf("realbool:%d
", realbool);
delete []lpm;
// if(*phModule == OldKernel32Address)
// *phModule = (HMODULE)lpNewBaseOf
补充:综合编程 , 安全编程 ,