码迷,mamicode.com
首页 > 其他好文 > 详细

反病毒攻防研究第010篇:DLL注入(中)——DLL注入与卸载器的编写

时间:2014-09-20 10:07:17      阅读:308      评论:0      收藏:0      [点我收藏+]

标签:姜晔   病毒   木马   安全   dll注入   

一、前言

        我在上一篇文章中所讨论的DLL利用方法,对于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的注入与卸载操作也是遵循着一个模板的,所以我们在编程的时候,往往考验的不是个人的记忆力如何,而是遇到问题知不知道如何去寻找答案,如何去查找,这才是最重要的。代码有些时候可能会很长,这时无需去死记硬背,只需知道其原理,知道该怎么查,久而久之,想记不住都难。

 

三、DLL注入的代码实现

        这个程序我用MFC来实现,首先制作程序的界面:

bubuko.com,布布扣

图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值”输入框中,单击“注入”,用于模拟病毒的对话框自动启动,说明已经成功注入:

bubuko.com,布布扣
图2 DLL注入成功

四、DLL卸载的代码实现

        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()的权限问题,可能会导致无法获得系统进程的句柄。但是这个问题有些敏感,我不打算讨论。

 

六、小结

        我自己比较喜欢建立一个“程序库”,以便于当未来遇到要编写类似的程序时能够方便查找。这样每次就无需从零开始编程,将“程序库”中的程序改动一下就可以。就比如这篇文章所讲的DLL注入/卸载的代码,就可以添加到“程序库”中。当然,建立“程序库”主要还是因为我自己的记忆力不好,加上自己并不是天天都要进行编程,难免生疏。对程序库中的程序,只要记住原理即可,并且添加上足够的注释,这样等到未来使用时,稍微看一看,就有一种“哦,原来如此”的感觉。


反病毒攻防研究第010篇:DLL注入(中)——DLL注入与卸载器的编写

标签:姜晔   病毒   木马   安全   dll注入   

原文地址:http://blog.csdn.net/ioio_jy/article/details/39404359

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!