我在上一篇文章中所讨论的DLL利用方法,对于DLL文件本身来说是十分被动的,它需要等待程序的调用才可以发挥作用。而这次我打算主动出击,编写DLL注入与卸载器,这样就可以主动地对进程进行注入的操作了,从而更好地模拟现实中恶意代码的行为。
如果想让DLL文件强制注入某个进程,那么就需要通过创建远程线程来实现。这里需要注意的是,所谓的“远程线程”,并不是跨计算机的,而是跨进程的。举例来说,进程A在进程B中创建一个线程,这就叫做远程线程。从根本上说,DLL注入技术要求目标进程中的一个线程调用LoadLibrary来载入我们想要的DLL。由于我们不能轻易地控制别人进程中的线程,因此这种方法要求我们在目标进程中创建一个新的线程。由于这个线程是我们自己创建的,因此我们可以对它执行的代码加以控制。远程线程不但在木马、外挂方面应用广泛,而且在反病毒软件方面也有着广泛的应用。
现在让我们来归纳一下DLL注入与卸载必须采取的步骤:
(1)用VirtualAllocEx函数在远程进程的地址空间中分配一块内存。
(2)用WriteProcessMemory函数把DLL的路径名复制到第1步分配的内存中。
(3)用GetProcAddress函数来得到LoadLibraryW或LoadLibraryA函数(在Kernel32.dll中)的实际地址。
(4)用CreateRemoteThread函数在远程进程中创建一个线程,让新线程调用正确的LoadLibrary函数并在参数中传入第1步分配的内存地址。这时,DLL已经被注入到远程进程的地址空间中,DLL的DllMain函数会收到DLL_PROCESS_ATTACH通知并且可以执行我们想要执行的代码。当DllMain返回的时候,远程线程会从LoadLibraryW/A调用返回到BaseThreadStart函数。BaseThreadStart然后调用ExitThread,使远程线程终止。
现在远程进程中有一块内存,它是我们在第1步分配的,DLL也还在远程进程的地址空间中。为了对它们进行清理,我们需要在远程线程退出之后执行后续步骤。
(5)用VirtualFreeEx来释放第1步分配的内存。
(6)用GetProcAddress来得到FreeLibrary函数(在Kernel32.dll中)的实际地址。
(7)用CreateRemoteThread函数在远程进程中创建一个线程,让该线程调用FreeLibrary函数并在参数中传入远程DLL的HMODULE。
可以说,DLL的注入与卸载操作也是遵循着一个模板的,所以我们在编程的时候,往往考验的不是个人的记忆力如何,而是遇到问题知不知道如何去寻找答案,如何去查找,这才是最重要的。代码有些时候可能会很长,这时无需去死记硬背,只需知道其原理,知道该怎么查,久而久之,想记不住都难。
这个程序我用MFC来实现,首先制作程序的界面:
图1 界面外观
然后编写DLL注入文件的代码:
void CInjectDLLDlg::InjectDll(DWORD dwPid, char *szDllName)
{
if(dwPid == 0 || strlen(szDllName) == 0)
{
AfxMessageBox("输入信息不全!");
return;
}
//利用PID值,获取进程的句柄
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPid);
if(hProcess == NULL)
{
AfxMessageBox("进程打开失败!");
return;
}
//长度为进程名称的长度加上字符终止符
int nDllLen = strlen(szDllName) + sizeof(char);
//申请内存空间
PVOID pDllAddr = VirtualAllocEx( hProcess, //process to allocate memory
NULL, //desired starting address
nDllLen, //size of region to allocate
MEM_COMMIT, //type of allocation
PAGE_READWRITE); //type of access protection
if(pDllAddr == NULL)
{
AfxMessageBox("申请内存区域失败!");
CloseHandle(hProcess);
return;
}
DWORD dwWriteNum = 0;
if (!WriteProcessMemory(hProcess,pDllAddr,szDllName,nDllLen,&dwWriteNum))
{
AfxMessageBox("进程写入失败!");
//失败就释放原先申请的内存区域 撤销内存页的提交状态
VirtualFreeEx(hProcess, pDllAddr, nDllLen, MEM_DECOMMIT);
return;
}
//获取LoadLibraryA的地址
FARPROC pFunAddr = GetProcAddress(GetModuleHandle("kernel32.dll"), "LoadLibraryA");
HANDLE hThread = CreateRemoteThread(hProcess, //handle to process
NULL, //SD
0, //initial stack size
(LPTHREAD_START_ROUTINE)pFunAddr, //thread function
pDllAddr, //thread argument
0, //creation option
NULL); //thread identifier
if (hThread == NULL)
{
AfxMessageBox("创建远程线程失败!");
//释放原先申请的内存区域 撤销内存页的提交状态
VirtualFreeEx(hProcess, pDllAddr, nDllLen, MEM_DECOMMIT);
return;
}
AfxMessageBox("成功注入!");
//等待线程退出
WaitForSingleObject(hThread,INFINITE);
//释放原先申请的内存区域 撤销内存页的提交状态
VirtualFreeEx(hProcess, pDllAddr, nDllLen, MEM_DECOMMIT);
//关闭句柄
CloseHandle(hThread);
CloseHandle(hProcess);
}
上述代码我已经添加了足够的注释,就不再进行分析。这里我想要强调的是,良好的代码风格有助于我们及时发现程序中的错误。具体来讲,每创建一个进程或是分配一块内存空间,都对其是否成功创建进行判断,特别对于类似上述过程较多的程序而言,出问题时可以让我们知道程序运行到了哪一步。
不要忘记在InjectDLLDlg.h文件中的classCInjectDLLDlg : public Cdialog下添加如下声明:void InjectDll(DWORD dwPid, char *szDllName);最后添加“注入”按键的代码:
void CInjectDLLDlg::OnBtnInject()
{
char szPath[MAX_PATH] = { 0 };
DWORD pid;
GetDlgItemText(IDC_EDIT_DLLPATH, szPath, MAX_PATH);
pid = GetDlgItemInt(IDC_EDIT_PID, NULL, TRUE);
InjectDll(pid, szPath);
} 编译成功后运行,这里我以记事本(notepad)程序为例。启动记事本程序,查看其PID值(可以在cmd中输入tasklist查看),然后启动注入程序,将我上次编写的HackedDll.dll文件的完整路径填入程序的“注入/卸载DLL”输入框中,再将记事本的PID值填入“注入/卸载PID值”输入框中,单击“注入”,用于模拟病毒的对话框自动启动,说明已经成功注入:
DLL注入技术如果用在木马方面,那么它的危害就会很大。接下来讨论的是如何卸载程序中的DLL。卸载DLL程序的思路和注入的思路差不多,代码改动非常小。而对于卸载的讨论,也相当于讨论了如何通过编程手段来对抗DLL注入。
DLL卸载的代码如下:void CInjectDLLDlg::UnInjectDll(DWORD dwPid, char *szDllName)
{
BOOL flag = FALSE;
if ( dwPid == 0 || strlen(szDllName) == 0 )
{
return;
}
//获取系统运行进程、线程等的列表
HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, dwPid);
MODULEENTRY32 Me32 = { 0 };
Me32.dwSize = sizeof(MODULEENTRY32);
//检索与进程相关联的第一个模块的信息
BOOL bRet = Module32First(hSnap, &Me32);
while ( bRet )
{
//查找所注入的DLL
if ( strcmp(Me32.szModule, szDllName) == 0 )
{
flag = TRUE;
break;
}
//检索下一个模块信息
bRet = Module32Next(hSnap, &Me32);
}
if (flag == FALSE)
{
AfxMessageBox("找不到相应的模块!");
return;
}
CloseHandle(hSnap);
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPid);
if ( hProcess == NULL )
{
AfxMessageBox("进程打开失败!");
return ;
}
FARPROC pFunAddr = GetProcAddress(GetModuleHandle("kernel32.dll"),"FreeLibrary");
HANDLE hThread = CreateRemoteThread(hProcess,
NULL,
0,
(LPTHREAD_START_ROUTINE)pFunAddr,
Me32.hModule,
0,
NULL);
if (hThread == NULL)
{
AfxMessageBox("创建远程线程失败!");
return;
}
AfxMessageBox("成功卸载!");
//等待线程退出
WaitForSingleObject(hThread, INFINITE);
CloseHandle(hThread);
CloseHandle(hProcess);
} 由于程序中使用了CreateToolhelp32Snapshot()函数,所以需要添加头文件:#include <Tlhelp32.h>之后在InjectDLLDlg.h文件中的classCInjectDLLDlg : public Cdialog下添加如下声明:
void UnInjectDll(DWORD dwPid, char* szDllName);最后添加“卸载”按键的代码:
void CInjectDLLDlg::OnBtnUnInject()
{
char szDllName[MAX_PATH] = { 0 };
DWORD pid = 0;
GetDlgItemText(IDC_EDIT_DLLPATH, szDllName, MAX_PATH);
pid = GetDlgItemInt(IDC_EDIT_PID, NULL, TRUE);
UnInjectDll(pid, szDllName);
} 编译成功后,执行程序,只要我们知道目标进程的PID值以及DLL的名称,就能够实现DLL卸载的操作。注意这里的“注入/卸载DLL”输入框,只需要输入 DLL的名称即可,无需输入完整的路径名。经实际测试,程序可行,这里不再赘述。它也可以当做是我们的安全工具,用于DLL注入类木马病毒的清除。DLL注入到一个进程中后,只要进程不结束,那么DLL就会一直附加在进程中。比如我的这个DLL对话框程序,当对话框弹出,即便是单击了“确定”,尽管对话框关闭了,但是用“冰刃”进行检查,发现DLL并没有消除,只有关闭了进程(记事本程序),才会消除DLL。或者需要通过我们文章中所编写的DLL卸载器进行卸载。一般来说,恶意程序总会对系统进程(如svchost.exe)进行DLL注入的操作,对于这种情况来说,想要进行查杀,我们自身平时就要多多积累,要清楚这些系统敏感进程在正常情况下会包含哪些DLL,或者实在不知道,可以通过上网查找或者运用相关软件的校验功能对DLL进行检测。需要说明的是,如果要对系统进程进行注入的话,由于有一个OpenProcess()的权限问题,可能会导致无法获得系统进程的句柄。但是这个问题有些敏感,我不打算讨论。
反病毒攻防研究第010篇:DLL注入(中)——DLL注入与卸载器的编写
原文地址:http://blog.csdn.net/ioio_jy/article/details/39404359