反病毒攻防研究第011篇:DLL注入(下)——无DLL的注入

一、前言

一般来说,想要将自己编写的程序注入到其它进程中,是必须要使用DLL文件的,这种方法已经在上一篇文章中讨论过了。但是事实上,可以不依赖于DLL文件来实现注入的。只不过这种方法不具有通用性,没有DLL注入那样灵活,因为它需要把代码写入“注入程序”中,一旦想要注入的内容发生了变化,就需要重写整个“注入程序”。而不像DLL注入那样,只要修改DLL程序即可。即便如此,无DLL进行注入的方式,也是一种值得讨论的方法。

二、无DLL注入的基本原理

在注入与卸载方面,无论是否有DLL文件,都是需要让远程线程执行LoadLibrary()或者FreeLibrary(),都是需要使用WriteProcessMemory()函数,不同的的是,无DLL注入是要把代码直接写入目标进程的。

我们如果想在目标进程中实现我们的目的,就需要找到相关的API函数。Kernel32.dll文件在每个进程中的地址都是一样的,但是这样不代表其它的DLL文件在每个进程中的地址都一样。如此一来,就需要在目标进程中找到想要使用的API函数的地址。而获取API函数的地址所要使用的函数是LoadLibrary()以及GetProcAddress(),在目标进程中,利用这两个函数就可以获取任何一个想要使用的API函数的地址了。把想要使用的API函数及API所在的DLL都封装到一个结构体中,直接写入到目标进程的空间中,或者直接把要在远程执行的代码写入到目标进程空间中,然后使用CreateRemoteThread()运行即可。

三、界面的制作

这次的程序依旧使用MFC制作,界面比上一次还要简单,只需要在对话框上添加一个“Static Text”、一个“Edit Box”和一个“Button”即可,如下图所示:

图1 界面的设计

四、代码的编写

这里先在文件NoDllInjectDlg.cpp中定义一个结构体,将接下来要使用的函数的地址都包含进去,以方便接下来的一系列操作:

#define STRLEN 40

typedef struct _DATA
{
        DWORD dwLoadLibrary;
        DWORD dwGetProcAddress;
        DWORD dwGetModuleHandle;
        DWORD dwGetModuleFileName;

        char User32Dll[STRLEN];
        char MessageBox[STRLEN];
        char Text[STRLEN];
        char Caption[STRLEN];
}DATA, *PDATA;

结构体中保存了LoadLibraryA()、GetProcAddress()、GetModuleHandle()以及GetModuleFileName()这四个函数的地址。它们都属于Kernel32.dll,因此可以提前提取。User32Dll保存“User32.dll”字符串,因为我们用于病毒模拟的对话框函数——MessageBoxA()就存在于User32.dll中。而Text和Caption则分别表示对话框的内容以及标题。

接下来编写注入代码:

void CNoDllInjectDlg::InjectCode(DWORD dwPid)
{
        //利用PID值,获取欲注入的进程句柄
        HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPid);
        if ( hProcess == NULL )
        {
                AfxMessageBox("进程打开失败!");
                return ;
        }

        DATA Data = { 0 };
        //获取欲使用的API函数的句柄
        Data.dwLoadLibrary = (DWORD)GetProcAddress(
                             GetModuleHandle("kernel32.dll"),
                             "LoadLibraryA");
        Data.dwGetProcAddress = (DWORD)GetProcAddress(
                             GetModuleHandle("kernel32.dll"),
                             "GetProcAddress");
        Data.dwGetModuleHandle = (DWORD)GetProcAddress(
                             GetModuleHandle("kernel32.dll"),
                             "GetModuleHandleA");
        Data.dwGetModuleFileName = (DWORD)GetProcAddress(
                             GetModuleHandleA("kernel32.dll"),
                             "GetModuleFileNameA");
        //对话框定义
        lstrcpy(Data.User32Dll, "user32.dll");
        lstrcpy(Data.MessageBox, "MessageBoxA");
        lstrcpy(Data.Text, "You have been hacked! (by J.Y.)");
        lstrcpy(Data.Caption, "Warning");
        //申请数据结构的内存空间
        LPVOID lpData = VirtualAllocEx(hProcess,    //process to allocate memory
                          NULL,                     //desired starting address
                          sizeof(DATA),             //size of region to allocate
                          MEM_COMMIT | MEM_RESERVE, //type of allocation
                          PAGE_READWRITE);          //type of access protection
        if(lpData == NULL)
        {
                AfxMessageBox("申请数据区域失败!");
                CloseHandle(hProcess);
                return;
        }  

        DWORD dwWriteNum = 0;
        if (!WriteProcessMemory(hProcess,lpData,&Data,sizeof(DATA),&dwWriteNum))
        {
                AfxMessageBox("数据结构写入进程失败!");
                //失败就释放原先申请的内存区域,撤销内存页的提交状态
                VirtualFreeEx(hProcess, lpData, sizeof(DATA), MEM_DECOMMIT);
                CloseHandle(hProcess);
                return;
        }  

        //申请线程函数的内存空间
        DWORD dwFunSize = 0x2000;
        LPVOID lpCode = VirtualAllocEx(hProcess,
                                       NULL,
                                       dwFunSize,
                                       MEM_COMMIT,
                                       PAGE_EXECUTE_READWRITE);
        if(lpCode == NULL)
        {
                AfxMessageBox("申请函数区域失败!");
                //失败就释放原先申请的内存区域 撤销内存页的提交状态
                VirtualFreeEx(hProcess, lpCode, dwFunSize, MEM_DECOMMIT);
                CloseHandle(hProcess);
                return;
        }
        if (!WriteProcessMemory(hProcess,lpCode,RemoteThreadProc,dwFunSize,&dwWriteNum))
        {
                AfxMessageBox("线程函数写入进程失败!");
                //失败就释放原先申请的内存区域,撤销内存页的提交状态
                VirtualFreeEx(hProcess, lpData, sizeof(DATA), MEM_DECOMMIT);
                VirtualFreeEx(hProcess, lpCode, dwFunSize, MEM_DECOMMIT);
                CloseHandle(hProcess);
                return;
        }
        HANDLE hRemoteThread = CreateRemoteThread(hProcess,
                                                  NULL,
                                                  0,
                                                  (LPTHREAD_START_ROUTINE)lpCode,
                                                  lpData,
                                                  0,
                                                  NULL);
        if (hRemoteThread == NULL)
        {
                AfxMessageBox("创建远程线程失败!");
                //释放原先申请的内存区域,撤销内存页的提交状态
                VirtualFreeEx(hProcess, lpData, sizeof(DATA), MEM_DECOMMIT);
                VirtualFreeEx(hProcess, lpCode, dwFunSize, MEM_DECOMMIT);
                CloseHandle(hProcess);
                return;
        }  

        AfxMessageBox("成功注入!");
        //等待线程退出
        WaitForSingleObject(hRemoteThread, INFINITE);
        //释放原先申请的内存区域,撤销内存页的提交状态
        VirtualFreeEx(hProcess, lpData, sizeof(DATA), MEM_DECOMMIT);
        VirtualFreeEx(hProcess, lpCode, dwFunSize, MEM_DECOMMIT); 

        CloseHandle(hRemoteThread);
        CloseHandle(hProcess);
}

上述代码稍长,但事实上,它的原理还是非常简单的,这里我依旧每一步都要判断是否执行成功,若是执行失败,则要释放掉之前所申请的资源。所以上述代码还是非常简单的。

卸载代码和注入代码没什么差别,关键是要把线程函数也写入目标进程中,这样在使用CreateRemoteThread()时,直接给出线程函数在目标进程中的地址即可。

线程函数的代码如下:

DWORD WINAPI RemoteThreadProc(LPVOID lpParam)
{
        PDATA pData = (PDATA)lpParam;

        // 定义API函数原型
        HMODULE (__stdcall *MyLoadLibrary)(LPCTSTR);
        FARPROC (__stdcall *MyGetProcAddress)(HMODULE, LPCSTR);
        HMODULE (__stdcall *MyGetModuleHandle)(LPCTSTR);
        int (__stdcall *MyMessageBox)(HWND, LPCTSTR, LPCTSTR, UINT);
        DWORD (__stdcall *MyGetModuleFileName)(HMODULE, LPTSTR, DWORD);

        MyLoadLibrary = (HMODULE (__stdcall *)(LPCTSTR))pData->dwLoadLibrary;
        MyGetProcAddress = (FARPROC (__stdcall *)(HMODULE,LPCSTR))pData->dwGetProcAddress;
        MyGetModuleHandle = (HMODULE (__stdcall *)(LPCSTR))pData->dwGetModuleHandle;
        MyGetModuleFileName = (DWORD (__stdcall *)(HMODULE,LPTSTR,DWORD nSize))pData->dwGetModuleFileName;

        HMODULE hModule = MyLoadLibrary(pData->User32Dll);
        MyMessageBox = (int (__stdcall *)(HWND,LPCTSTR,LPCTSTR,UINT))
        MyGetProcAddress(hModule, pData->MessageBox);
        char szModuleName[MAX_PATH] = { 0 };
        MyGetModuleFileName(NULL, szModuleName, MAX_PATH);

        MyMessageBox(0, pData->Text, pData->Caption, 0);

        return 0;
}

线程函数的代码稍显复杂,但事实上这些都是基本知识,不再赘述。然后编写“注入”按钮事件:

void CNoDllInjectDlg::OnBtnInject()
{
        // TODO: Add your control notification handler code here
        DWORD dwPid = GetDlgItemInt(IDC_EDIT_PID, FALSE, FALSE);
        InjectCode(dwPid);
}

最后在NoDllInjectDlg.h文件中加入:

void InjectCode(DWORD dwPid);

至此,所有代码编写完毕,经实际测试可行(Release版),效果与上一篇文章所论述的情况是相同的。

五、补充说明

如果以Debug方式编译,VC++会在程序中加入很多与调试相关的内容,而这些内容的地址是相对于当前进程的地址,这些代码到了别的进程就有可能会出错。因此这里应该使用Release方式编译,因为Release方式编译不会由于插入与调试相关的代码而导致注入的代码到别的进程会出错。

另外,如果采用无DLL的注入方式,那么用“冰刃”来查看所注入代码的进程的模块信息,注入前后,模块的数量是不会有任何变化的,只是在DLL注入的时候,模块数量才会增加。所以针对于这种无DLL的注入方式,想要结束注入,那么只要直接关闭用于注入程序就可以了。比如本例,注入后弹出对话框,那么此时直接结束“NoDllInject.exe”的进程,对话框也就关闭了,说明注入失效。

六、小结

本篇文章是DLL注入研究的最后一篇,相信这三篇文章会让大家对于DLL注入有了较为全面的了解。利用在第二篇文章中所编写的DLL注入与卸载工具,可以为后续的研究提供遍利的工具支持,以后也会继续使用。在此,我也欢迎大家提出宝贵意见,针对文章中的论述,多多交流,可能会发现我也没有发现的问题,毕竟当局者迷旁观者清,这样可以共同提高。

时间: 2024-12-17 11:06:31

反病毒攻防研究第011篇:DLL注入(下)——无DLL的注入的相关文章

反病毒攻防研究第012篇:利用Inline HOOK实现主动防御

一.前言 之前文章中所讨论的恶意程序的应对方法,都是十分被动的,即只有当恶意程序被执行后,才考虑补救措施.这样,我们就会一直处于后手状态,而如果说病毒的危害性极大,那么即便我们完美地修复了诸如注册表项,服务项等敏感位置,并且删除了病毒本身,但是它依旧可能已经破坏了系统中非常重要的文件,造成了不可逆的损伤.因此这篇文章就来简单讨论一下利用Inline HOOK技术实现主动防御,在病毒执行前,就主动将危险函数劫持,如同一道防火墙,保护我们计算机的安全. 二.Inline HOOK原理 我们平时所使用

反病毒攻防研究第015篇:病毒感染标志的添加

一.前言 对于感染型病毒而言,如果对同一个目标文件多次进行感染,有可能导致目标文件损坏,使得无法执行.所以病毒程序往往会在第一次感染时对目标文件写进一个感染标志,这样在第二次遇到该文件时,首先判断一下该文件中是否包含有感染标志,如果有,则不再感染,如果没有感染标志则进行感染(关于文件的感染,可参见<反病毒攻防研究第004篇:利用缝隙实现代码的植入>和<反病毒攻防研究第005篇:添加节区实现代码的植入>).所谓的感染标志其实就是在PE文件中无关紧要的位置写入的一个字符串,所以感染标志

反病毒攻防研究第009篇:DLL注入(上)——DLL文件的编写

一.前言 我之前所编写的用于模拟计算机病毒的对话框程序都是exe文件,所以运行时必将会产生一个进程,产生进程就非常容易被发现.而为了不被发现,可以选择将对话框程序创建为DLL文件.这种文件会加载到已有进程的地址空间中,这样就不会再次创建出进程,隐蔽性相对较好,DLL注入也是恶意程序总会使用的手段.这次我带算用几篇文章的篇幅来论述DLL注入的问题,而这篇文章就首先来讨论一下如何把我之前的对话框程序改写为DLL文件. 二.编写对话框DLL程序 这里我依旧使用VC++6.0,创建一个简单的Win32

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

一.前言 我在上一篇文章中所讨论的DLL利用方法,对于DLL文件本身来说是十分被动的,它需要等待程序的调用才可以发挥作用.而这次我打算主动出击,编写DLL注入与卸载器,这样就可以主动地对进程进行注入的操作了,从而更好地模拟现实中恶意代码的行为. 二.DLL注入的原理 如果想让DLL文件强制注入某个进程,那么就需要通过创建远程线程来实现.这里需要注意的是,所谓的"远程线程",并不是跨计算机的,而是跨进程的.举例来说,进程A在进程B中创建一个线程,这就叫做远程线程.从根本上说,DLL注入技

反病毒攻防研究第004篇:利用缝隙实现代码的植入

一.前言 现在很多网站都提供各式各样软件的下载,这就为黑客提供了植入病毒木马的良机.黑客可以将自己的恶意程序植入到正常的程序中,之后发布到网站上,这样当用户下载并运行了植入病毒的程序后,计算机就会中毒,而且病毒可能会接着感染计算机中的其他程序,甚至通过网络或者U盘,使得传播面积不断扩大.而本篇文章就来剖析病毒感染的实现原理,首先需要搜索正常程序中的缝隙用于"病毒"(用对话框模拟)的植入,之后感染目标程序以实现"病毒"的启动.当然,讨论完这些,我依旧会分析如何应对这种

反病毒攻防研究第007篇:简单木马分析与防范part1

一.前言 病毒与木马技术发展到今天,由于二者总是相辅相成,你中有我,我中有你,所以它们之间的界限往往已经不再那么明显,相互之间往往都会采用对方的一些技术以达到自己的目的,所以现在很多时候也就将二者直接统称为"恶意代码".这次我打算用两篇文章的篇幅来讨论病毒与简单的木马相互结合的分析与防范方法.本篇也就是第一篇,讨论的是利用只有服务器端的木马程序实现"病毒"的启动.而在下一篇中,我会讨论既有服务器端又有客户端的木马程序与"病毒"相结合的分析与防范.

反病毒攻防研究第008篇:简单木马分析与防范part2

一.前言 一般来说,木马是既有客户端也有服务器端的.上次讨论的不过是一种特殊情况,毕竟不是人人都懂得DOS命令,因此现在木马的客户端也都是做成非常直观的界面形式,方便操作.本篇文章会从客户端与服务器端两个方面进行讨论,与上次的讨论不同的是,这次我会直接把用来模拟病毒的对话框程序放入服务器端,这样只要成功连接,那么就可以通过由客户端所发出的命令来让服务器端直接执行对话框程序.用这种思想,可以给服务器端增加很多功能,但是在这里仅仅讨论对话框的打开. 二.服务器端的实现 这里所讨论的木马依旧是命令行下

反病毒攻防研究第005篇:添加节区实现代码的植入

一.前言 上一篇文章所讨论的利用缝隙实现代码的植入有一个很大的问题,就是我们想要植入的代码的长度不能够比缝隙大,否则需要把自身的代码截成几个部分,再分别插入不同的缝隙中.而这次所讨论的方法是增加一个节区,这个节区完全可以达到私人订制的效果,其大小完全由我们自己来决定,这样的话,即便是代码较长,也不用担心.而这种方式最大的缺陷就是不利于恶意代码自身的隐藏,因此在现实中可能并不常用.其实,我在这里讨论节区的添加,是为了以后更加深入的讨论打下基础,因为在加壳以及免杀技术中,经常会对PE文件添加节区.这

网络攻防研究第001篇:尝试暴力破解某高校研究生管理系统学生密码

前言 如果你是在校大学生,而且还对网络攻防比较感兴趣的话,相信你最开始尝试渗透的莫过于所在院校的学生管理系统.因为一般来说这样的系统往往比较薄弱,拿来练手那是再合适不过的了.作为本系列的第一篇文章,我将会利用暴力破解的方式,尝试对某高校的研究生管理系统的学生密码进行破解.由于这个管理系统的网站属于该高校的内网资源,外网是无法访问的,因此大家就不要尝试按照文中的内容来对文中出现的网址进行访问了.利用本文所论述的暴力破解思想,可以帮助大家更好地认识我们的网络,也有助于了解目标网站是否安全.那么在这里