★我要吧★

 找回密码
 注册[Register]
搜索
qq空间相册密码查看为什么登陆后需要激活无法注册?

《杀不死的秘密》杀软与免杀的战争【经典】

[复制链接]
发表于 2011-2-24 13:30:07 | 显示全部楼层 |阅读模式
《杀不死的秘密》这本书,不得不说是个经典,我发现我有搜藏,在这里给大家分享下。希望看来以后不要对主流杀软失去信心。

                   转《杀不死的秘密》第一章





一、免杀技术的发展

        所谓“免杀”,就是逃避杀毒软件的查杀,目前用得比较多的免杀方法有加壳、修改特征码和加花指令三种,通常黑客们会针对不同的情况来运用不同的免杀方法。
        第一款杀毒软件Mcafee是于1989年诞生的。
        1989年:第一款杀毒软件Mcafee诞生,标志着反病毒与反查杀时代的到来。
        1997年:国内出现了第一个可以自动变异的千面人病毒(Polymorphic/Mutation Virus)。
        2002年7月31日:国内第一个真正意义上的变种病毒“中国黑客II”出现。2004年:在黑客圈子内部,免杀技术由IT工程师之家团队在这一年首先公开提出。
        2005年1月:大名鼎鼎的免杀工具CCL的软件作者tankaia在杂志上发表了一篇文章,藉此推广了CCL,从此国内黑客界才有了自己第一个专门用于免杀的工具。
        2005年2月至7月:通过各方面有意或无意的宣传,黑客爱好者们开始逐渐重视免杀。
        2005年8月:第一个可查的关于免杀的动画由IT工程师之家团队完成。
        2005年9月:免杀技术开始真正的火起来。
        [国内免杀技术的起源时间:2002年7月31日]

        小提示:毫无疑问,“免杀”技术是目前最火热的技术之一。“免杀”是可用性很强,应用范围非常广的一门黑客技术。免杀往往是脚本入侵技术、病毒攻击技术等一些其他黑客技术的准备工作。举个简单的例子,当一个黑客发现并利用了一个网站所存在的脚本漏洞后,经过提权最终得到了服务器的管理权限。为了方便和巩固控制,黑客往往需要将后门传至对方网站服务器上(可能是脚本后门,也可能是PE后门),或者是rootkit,然而对方服务器上有很厉害的杀毒软件,当这些后门被黑客上传到对方服务器之后,服务器上的杀毒软件识别并查杀出黑客上传的后门或rootkit,使黑客的工具不能发挥作用。这在很大程序上影响了黑客对漏洞的利用。但是如果黑客上传的工具是经过免杀技术处理过的,那么黑客就可以通过简单有效的工具来巩固控制。显然,这只是一个很片面的例子,在真正的Hacking过程中,免杀技术几乎是无处不在,所以学好免杀技术对于一个信息安全技术爱好者是非常重要的。

免杀技术的定义

        从前面可以看出,免杀指的就是让原本被杀毒软件或其它计算机安全工具捕获并查杀的文件,经过处理后变得不被捕获和查杀,这种技术就是免杀技术,这种处理过程就称为“免杀”,通常也称之为病毒免杀(因为被杀毒软件或其它计算机安全工具查杀的文件一般都称为病毒)。

杀毒软件的查杀原理

1、特征码法
        杀毒软件运用特征码扫描确定某文件为病毒时,这个文件需要满足两个条件:
        (1)该文件中的某一位置与杀毒软件病毒库的某一位置相对应。
        (2)该位置上存放的代码与病毒库中定义的该位置上的代码相同。

特征码法的特点:
        A.速度慢。
        B.误报警率低。
        C.不能检查多态性病毒。
        D.不能对付隐蔽性病毒。

2、校验和法
        运用校验和法查病毒采用三种方式:
        (1)在检测病毒工具中纳入校验和法,对被查的对象文件计算正常状态的校验和,将校验和值写入被查文件中或检测工具中,而后进行比较。
        (2)在应用程序中,放入校验和法自我检查功能,将文件正常状态的校验和写入文件笨身中,每当应用程序启动时,比较现行校验和与原校验和值。实现应用程序的自检测。
        (3)将校验和检查程序常驻内存,每当应用程序开始运行时,自动比较检查应用程序内部或别的文件中预先保存的校验和。

校验和法的特点:
        优点:方法简单、能发现未知病毒、被查文件的细微变化也能发现。缺点:发布通行记录正常态的校验和、会误报警、不能识别病毒名称、不能对什隐蔽型病毒。
3、行为监测法
        利用病毒的特有行为特征来监测病毒的方法。

监测病毒的行为特征:
        A.占有INT 13H
        B.改DOS系统为数据区的内存总量
        C.对COM、EXE文件做写入动作
        D.病毒程序与宿主程序的切换

行为监测法的特点:
        行为监测法的长处:可发现未知病毒、可相当准确地预报未知的多数病毒。行为监测法的短处:可能误报警、不能识别病毒名称、实现时有一定难度。

4、软件模拟法
        在虚拟机中综合运用多种查杀方法就是通常所说的软件模拟法。

软件模拟法的特点:
        软件模拟法的长处:对病毒的判定能力最强(因为综合了多种查毒方法)。
        软件模拟法的短处:扫描速度慢,查毒往往不准确。

最终结论:
        目前杀毒软件最主要还是依赖特征码识别技术来检测病毒。也就是说,特征码就是杀毒软件为了判定病毒而从病毒本身提取出来的有特点的代码,我们的免杀就是专门针对这些特征码的。也就是说,让杀毒软件找不到这些特征码就可以达到免杀的目的了,不过还有一点要强高速的是,文件本身的功能是不能损坏的,也就是说免杀之后的文件和原文件在功能上要做到完全相同。只有这样才算是真正有效的免杀。
=======================================================================

二、常见杀毒软件及其引擎特点
1、卡巴斯基
        俄罗斯出品、扫描未知病毒非常厉害、病毒库很大、扫描速度特别慢、非常占用资源。

2、瑞星
        国产、杀毒能力和卡巴斯基差距很大,主动防御功能模块和卡巴斯基也没办法比、优势就是速度快、系统占用资源相对较少。

3、江民
        杀毒能力不错,耗费资源一般,主动防御功能也很不错。

4、诺顿
        这个杀软是诺顿公司的,性能卓越。病毒库很大,扫描速度慢。不同版本的诺顿占用系统资源的程度相差很大。这个杀软的免杀非常好做。是著名杀毒软件中实力最弱的。

5、金山
        杀毒能力很差,占用资源一般,速度快。

6、NOD32
        这个的查杀机制和其他几个差别很大,针对它的免杀比较难做。如今新版的NOD32加入了主动防御功能,功能上的提升使它成为目前最能对付的杀毒软件。系统占用资源一般,扫描速度非常快。启动扫描特别厉害,查杀未知病毒的性能和卡巴斯基旗鼓相当。

7、麦咖啡
        国外杀毒软件,功能一般,查杀能力一般,占用系统资源一般。高级版对未知病毒的查杀能力很强。

8、小红伞
        启发式扫描很厉害,可以说和NOD32的启发扫描不分伯仲,各有各的特点。

9、F-Prot Antivirus:这是一款来自冰岛的杀毒软件。该杀毒软件的查杀能力绝对一流,对未知病毒的查杀能力远在卡巴和NOD32之上。

10、微软杀毒
        依仗着微软在操作系统的占有率,其杀毒软件也有一定的占有率。几乎所有的木马都不能过微软的杀毒软件。对微软杀毒研究相对深入之后,不难发现,目前的微软杀毒引擎和NOD32的杀毒引擎很像,基本都是使用启发式扫描对API函数的调用进行判定和加权。      

=======================================================================

三、免杀技术的分类

1、内部免杀和外部免杀
        处部免杀又通常称为PE免杀。内部免杀指的是从病毒的源代码入手,通过修改病毒的源代码实现病毒的隐蔽性变种。外部免杀指的是将已经编译连接后的病毒文件通过加密等手段将病毒的代码复杂化,从而干扰杀毒软件对其的判定,使之免杀。
        内部免杀有一个必要的前提,就是免杀制作者必须要有病毒的源代码,这一条件非常苛刻。外部免杀需要一定的反汇编基础和PE结构知识。
        内部免杀和外部免杀属同一技术层次,没有高低之分。

2、特征码免杀和大范围免杀
        先进的杀毒引擎使用的杀毒技术依然是围绕特征码展开的,所以修改病毒的特征码在可预见的很长一段时间里还会是免杀技术的中流砥柱。大范围免杀通常又被称为“无特征码免杀”,指的是使用一些例如加花加壳这种可使病毒代码复杂化的方法对病毒进行处理,处理过的病毒很可能会同时免杀掉多种杀毒软件。大范围指的不是直接针对特征码的修改,只是指通过其它方法使代码复杂化,从而间接影响杀毒软件对病毒特征码的识别。
3、文件免杀、内存免杀和行为免杀
        文件免杀指的是计算机硬盘上存放的被某杀毒软件查杀的病毒,经过免杀处理后,再对病毒进行静态的病毒扫描时,该杀毒软件不将这个原本被自己查杀的文件判定为病毒。这种将硬盘上的病毒文件中特定PE区段载入到内存并进行病毒鉴定的方法,被称为对病毒的动态文件扫描,这并不等同于杀毒软件的内存扫描。
        内存查杀是指在病毒运行的时候,病毒被载入计算机的内存后,杀毒软件在内存中侦测到病毒的运行,并将病毒在内存中击杀。内存免杀指的就是在上述例子中的病毒被载入到内存后系统无法通过杀毒软件的内存扫描功能从内存中找出该病毒。
        行为免杀的概念出现在文件免杀和内存免杀之后,是一个相对比较新的概念。病毒程序大都有很多共性,如:在系统文件中释放一些动态链接库文件并添加启动项或服务等。杀毒软件可以抓住病毒的这一特性,监控计算机运行的程序,控制程序可能产生的对计算机系统有危害的行为,杀毒软件的这种监控方式就是行为监控,而突破这种监控方式的工作就称为病毒的行为免杀。

4、盲免技术
        盲免技术又被简称为“盲免”,盲免可以说得上是一个隐藏在圈内很久未被公开的免杀技术。
        盲免指的就是在不使用杀毒软件定位特征码也不需要杀毒软件测试免杀的效果,直接制作出最终的免杀样本。在这看似投机的结果中,其实有很多技术细节和经验在里面,盲免并不是瞎搞,与特征码免杀一样,盲免也是有很强的针对性的。
        能熟练盲免的免杀制作者,他们对各种杀毒引擎都非常了解,这些都是靠平时做一般性免杀的过程中得到的,有了这个前提,才能在不借助杀毒软件的情况下,判断出杀毒软件对特征码识别的弱点。
        针对不尽相同的杀毒引擎,修改特征码的方法也各种不同,所以在研究查毒引擎之前,需要掌握好PE结构、反汇编等基础知识。
 楼主| 发表于 2011-2-24 13:30:30 | 显示全部楼层
第二章开始讲的是免杀环境的搭建和虚拟机的安装以及使用,这里我就不打上来了。我只说一些常用的工具。工具下载区-暗组2010里面应该全都有。
常用免杀工具
1、程序名:exeScope
        这是一个典型的资源工具。我们可以能过它来修改被载入文件的资源。
2、程序名:LordPe
        这个工具被称为“最好的PE工具”。通过这个工具,我们可以对PE文件进行修改并查看PE文件的详细信息,如:入口点、区段等。
3、程序名:UltraEdit-32
        这是一个非常好的十六进制编辑工具。功能很强大,免杀常用。
4、程序名:Restorator 2007文件资源编辑工具,操作非常简单,界面也很友好,所以被广泛的应用于免杀过程中的导入、导出资源工作中。不过这个工具的综合性能并不好。
5、程序名:ZeroAdd v1.0
        通过这个程序,我们可以对指定PE文件进行加入0区段的工作。并且可以自定义所加区段的区段名和区段大小。这个工具可以说的上是免杀必备工具了。
6、程序名:WinHex
        一款简介而又强大的十六进制编辑工具。
7、程序名:Peditor 1.7
        一款PE编辑工具,虽然没有LordPe功能强大,但是操作十分简单,甚是好用。
8、程序名:FreeRes 0.94
        严格来说,它不是脱壳工具,因为它只是把资源释放出来,并没有对被压缩的文件进行解压的反向工程,只要你用这个软件为被压缩的软件建立起一个覆盖资源,即可用任何的资源编辑软件对其进行资源的编辑了。
9、程序名:MyCCl
        这是一个查找文件特征码的工具,免杀必备。
10、程序名:multiCCL f
        这也是一个定位文件特征码的工具。相对于MyCCl来说,这个工具的定位更准确,精度高,但是操作麻烦。
11、程序名:ASProtect.2.3.6.26.Modified
        一款强壳,几乎是所有壳的综合,功能十分强大,ASPM is short for ASProtect.2.3.6.26.Modified.
12、程序名:Mask PE 2.0
        曾经辉煌一时的免杀工具,功能简单,但是免杀效果极其的好,尤其是它内存免杀方面的作用,更是突出。
13、程序名:北斗壳 v3.7
        中国人自己的壳,又名nspack 3.7,这款壳也被国外的cracker认可。
14、程序名:PeLock 1.06
        通常在破解界,很多人通过来脱这个壳来证明自己的实力。可见这个壳的加壳效果。著名的黑防灰鸽子的控制端就加的这个壳。
15、程度名:Themida
        功能非常强大的壳。著名的黑洞控制端就是加的这个壳。
16、程序名:c32asm
        界面很漂亮的十六进制和静态编辑工具。通过这个工具我们可以修改SYS驱动文件,做免杀必备。
17、程序名:OllyICE
        动态调试工具,我们通常用其来修改特征码,不过这个工具不能加载SYS驱动文件,令人感到遗憾。
18、程序名:OllyDbg汉化原版
        之所以把这个工具和上面的OllyICE分开,是因为这个工具具备OllyICE不具备的功能,那就是在手工查找内存特征码的时候,OllyICE是不能定位准确的,而我们的原版则可以准确定位,不过OllyICE可以调试ring0,而OllyDbg则不可以。
19、程序名:UPX
        一款非常著名的壳程序。因为范围极广。
20、程序名:超级巡警自动脱壳机
        我们可以通过这个简单的工具完成很多脱壳工作。
21、程序名:OC
        一款将被载入文件的RVA转换成实际内存地址的小工具。
22、程序名:驱动加载工具(InstDrv-中文版)
        这个工具可以很容易的查看被载入驱动文件是否工作正常,有了各款工具,可以大大提高我们免杀SYS文件的效率。
23、程序名:Resource Binder 2.6
        一款效果非常好,而且操作很简单的资源重建工具。
24、程序名:VMProtect.exe v1.55
        这是一个非常方便的加密工具,可以加密几乎所有的PE文件,我们在做SYS文件的免杀时常用。
25、程序名:Exelnfo PE ver.0.0.1.7.c
        一款很实用的壳程序。
26、程序名:ExeStealth2.75a
        非常好用的壳,保护措施很多,脱这个壳也很有意思。27、程序名:PPC-Protect 1.1
        这个算得上是个比较新的壳了,免杀效果不错。建议配合加花技术使用。
28、程序名:VMProtect 1.21
        相比于前面的1.55版本,这个版本更适合一些精确的特征码加密。非常好的一款工具,值得收藏。
29、程序名:ImportREC_fix
        Windows XP下修复Import表的时候,ImportREC会将ntdll!RtlRestoreLastWin32Error重定位到kernel32!RestoreLastError,而这个函数在以前的win-dows系统下是不存在的,而RestoreLastError和SetLastError实际上是同一个函数,都被转移到对ntdll!RtlRestoreLastWin32Error的调用。这个ImoprtREC_Fix.exe修正版可以用SetLastError代替RestoreLastError函数。
30、程序名:花指令伪装器
        一款加花指令的工具,支持多次加花。
31、程序名:PE-Armor
        一款压缩加密壳,功能很多,单独使用就有很好的免杀效果。
32、程序名:超级加花器
        这也是一款加花工具,介面非常漂亮,功能也很实用,而且这个工具的作者很勤快,工具总是更新很快。
33、程序名:svkp v1.43a
        纯加密壳,强度高,但是兼容性极差。
34、程序名:ASP代码加密大师
        一款N年前出品的加密ASP木马后门的加密工具。
35、程序名:吾芝道ASP木马加密免杀工具GUI版
        这个是个效果特别好的ASP加密工具,也是一款早期的ASP加密工具。
36、程序名:HTML混淆器
        这是GOLDSUN写的一款HTML格式文件处理工具,这个工具可以重复对HTML文件进行处理,而且经过这个工具处理过的HTML文件的体积会比处理之前小很多。37、程序名:加密最终4.0
        这是最好用的ASP后门加密工具,操作简单,效果极好。重点推荐。
38、Webshell免杀工具
        这个是一款专门对webshell进行免杀的工具。
39、蓝星网页加密专家v1.0
        这个工具是一款可以批量加密脚本文件的免杀工具。
40、phpencode
        这个是一款加密PHP后门木马的工具,命令行下使用。
回复 支持 反对

使用道具 举报

 楼主| 发表于 2011-2-24 13:30:55 | 显示全部楼层
第三章

一、PE结构简单介绍
        PE究竟是什么,通俗的说,PE是一种格式。PE格式,是微软Win32环境可执行文件的标准格式(所谓可执行文件不光是.exe文件,还包括.DLL/.VXD/.SYS/.VDM等)。PE就是Portable Executable的缩写。Portable是指对于不同的Windows版本和不同的CPU类型上PE文件的格式是一样的,当然CPU不一样了,CPU指令的二进制编码是不一样的,只是文件中各种东西的布局是一样的。
PE文件的主要结构:
        .text        是在编译成汇编结束时产生的一种块,它的内容全是指令代码
        .rdata        是运行期只读数据
.data        是初始化的数据块
        .idata        包含其它外来DLL的函数及数据信息,即输入表
        .rarc        包含模块的全部资源:如图标、菜单、位图等
      
        在PE文件中,代码、资源以及重定向定位信息等数据按照属性分类放到不同的区段中(Section),而且每个区段的属性和位置等信息用一个IMAGE_SECTION_HEADER结构来表示,所有的IMAGE_SECTION_HEADER结构组成一个区段表(Section Table),区段表被放在所有区段的前面,用于标志所有区段的属性(如:可读、可写、可执行等属性)。区段通常又被称为节,区段表也通常被称为节表。

        CPU之所以可以准确的读取内存中的指令和数据,是因为CPU把内存解成了成千上万份,把每一份都标上一个编号,而每一份的编号都是唯一并且有次序的。这个编号被称作内存地址,有了这个唯一的地址,CPU当然就可以准确地定位内存中的指令和数据了。

PE文件具有如下地址:
        1、入口点                Entry Point
        2、文件偏移地址                File offset
        3、虚拟地址                Virtual Address 简称:VA
        4、基地址                ImageBase
        5、相对虚拟地址                Relative Virual Address 简称:RVA
公式:RVA(相对虚拟地址)=VA(虚拟地址)-ImageBase(基地址)

二、DOS文件头和DOS块
        一个标准的DOS可执行文件部分包含两个结构,分别是DOS文件头和DOS块。
DOS文件头是一个IMAGE_DOS_HEADER结构,该结构定义如下:
IMAGE_DOS_HEADER  STRUCT
        e_magic                WORD  ?                :魔术数字
        e_cblp                WORD  ?                :文件最后页的字节数
        e_cp                WORD  ?                :文件页数
        e_crlc                WORD  ?                :重定义元素个数
        e_cparhdr        WORD  ?                :头部尺寸.以段落为单位
        e_minalloc        WORD  ?                :所需的最小附加段
        e_maxalloc        WORD  ?                :所需的最大附加段
        e_ss                WORD  ?                :初始的SS值(相对偏移量)
        e_sp                WORD  ?                :初始的SP值
        e_csum                WORD  ?                :校验和
        e_ip                WORD  ?                :初始的IP值
        e_cs                WORD  ?                :初始的CS值(相对偏移量)
        e_lfaric        WORD  ?                :重分配表文件地址
        e_ovno                WORD  ?                :覆盖号
        e_res                WORD        4 dup (?)        :保留字
        e_oemid                WORD  ?                :OEM标识符(相对e_oeminfo)
        e_oeminfo        WORD  ?                :OEM信息
        e_res2                WORD        10 dup (?)        :保留字
        e_lfanew        WORD  ?                E头部的文件地址
        IMAGE_DOS_HEADER  END

结构体(struct)是由一系列具有相同类型或不同类型的数据构成的数据集合,也叫结构。在汇编语言中,结构体的定义往往如下:
        结构体名称        STRUCT
        结构体字段1        结构体字段类型                初始值
        结构体字段2        结构体字段类型                初始值
        ……
        结构体字段N        结构体字段类型                初始值
        结构体名称        ENDS
      
        这里还需要进行一下说明,在Win32汇编中,分号(;)为注释符号,注释掉该行为分后面的语句。而“初始值”处的问号(?)表示没有初始值。因为仅仅是为了表明IMAGE_DOS_HEADER结构体的字段,所以所有字段的值都有“?”来表示。其中e_res字段的初始值我用 4  dup(?)表示,这表明该字段占有4个WORD宽度,也就是占有4个该结构体字段对应的结构体字段类型宽度。那么不难理解e_res2字段就是占有10个WORD宽度了。
        这里我们有用的只有两上字段,他们是IMAGE_DOS_HEADER结构中的第一个和最后一个字段(e_magic和e_lfanew)。
        e_lfanew字段占一个双字的宽度。其值为一个偏移地址,该地址为DOS块后面的PE头的文件偏移。

        DOS块就是DOS部分的可执行代码。
二、PE文件头
        PE文件头的结构定义如下:
        IMAGE_NT_HEADERS        STRUCT
        Signature        DWORD                        ?
        FileHeader        IMAGE_FILE_HEADER        <>
        OptionalHeader        IMAGE_OPTIONAL_HEADER32        <>
        IMAGE_NT_HEADERS        ENDS
      
        而PE文件大部分的文件属性是由Signature字段后面的FileHeader字段和OptionalHeader字段来设定的。
        FileHeader字段也是由一个结构体定义的,其具体结构如下:
        IMAGE_FILE_HEADER        STRUCT
        Machine          WORD        ?        :0004h        -运行平台
        NumberOfSections        DWORD        ?        :0006h        -文件的区段数量
        TimeDateStamp        DWORD        ?        :0008h        - 文件创建日期和时间
        PointerToSymbolTable        DWORD        ?        :000ch        -指向符号表(调试时使用)
        NumberOfSymbols        DWORD        ?        :0010h        -符号表中的符号数量
        SizeOfOptionalHeadder        WORD        ?        :0014h        -IMAGE_OPTIONAL_HEADER32结构长度
        Characteristics                WORD        ?        :0016h        -文件属性
        IMAGE_FILE_HEADER        ENDS

        在IMAGE_FILE_HEADER结构中各个字段的注释中,“-”前面的部分是该字段相对于PE文件头的偏移,“-”后面的部分是字人面功能注释。

Machine字段
        该字段相对于PE文件头的偏移为0004h,占有一个字的宽度,用于指定文件的执行平台。

NumberOfSections字段
该字段们于Machine字段后,相对于PE文件头的偏移0006h,占一个字的宽度,用于指字本PE文件的区段数量。

TimeDateStamp字段
        该字段指定编译器创建此文件的时间,它的数值是从1969年12月31日下午4:00开始创建时间为此的总秒数。

PointerToSymbolTable字段和NumberOfSymbols字段
        这两个字段与调试有关,这里不讲。

SizeOfOptionalHeadder字段
        SizeOfOptionalHeadder字段指定OptionalHeader字段的长度,其值为0x e0 00。

Characteristics字段
        Characteristics字段可谓是非常重要的字段。它定义了文件的属性,而文件的属性直接影响系统对文件的装入方式。

        OptionalHeader字段也是一个IMAGE_OPTIONAL_HEADER32结构体,其结构的定义如下:
        IMAGE_OPTIONAL_HEADER32                STRUCT
        Magic                        WORD                ?  :0018h
        MajorLinkerVersion        BYTE                ?  :001ah
        MinorLinkerVersion        BYTE                ?  :001bh
        SizeOfCode                DWORD                ?  :001ch
        SizeOfInitializedData        DWORD                ?  :0020h
        SizeOfUninitializedData        DWORD                ?  :0024h
        AddressOfEntryPoint        DWORD                ?  :0028h
        BaseOfCode                DWORD                ?  :002ch
        BaseOfData DWORD                ?  :0030h
        ImageBase                DWORD                ?  :0034h
        SectionAlignment        DWORD                ?  :0038h
        FileAlignment                DWORD                ?  :003ch
        MajorOperatingSystemVersion        WORD        ?  :0040h
        MinorOperatingSystemVersion        WORD        ?  :0042h
        MajorImageVersion                WORD        ?  :0044h
        MinorImageVersion                WORD        ?  :0046h
        MajorSubsystemVersion                WORD        ?  :0048h
        MinorSubsystemVersion                WORD        ?  :004ah
        Win32VersionValue                DWORD        ?  :004ch
        SizeOfImage                        DWORD        ?  :0050h
        SizeOfHeaders                        DWORD        ?  :0054h
        CheckSum                        DWORD        ?  :0058h
        Subsystem                        WORD        ?  :005ch
        DllCharacteristics                WORD        ?  :005eh
        SizeOfStackReserve                DWORD        ?  :0060h
        SizeOfStackCommit                DWORD        ?  :0064h
        SizeOfHeapReserve                DWORD        ?  :0068h
        SizeOfHeapCommit                DWORD        ?  :006ch
        LoaderFlags                        DWORD        ?  :0070h
        NumberOfRvaAndSizes                DWORD        ?  :0074h
        DataDirectory                IMAGE_DATA_DIRECTORY  16dup(<>)          ;0078h
        IMAGE_OPTIONAL_HEADER32                ENDS

IMAGE_OPTIONAL_HEADER32结构中的字段实在太多了,但是并不需要了解全部的字段,这里只讲解其中对免杀制作最紧密相关的几个字段。

AddressOfEntryPoint字段
        AddressOfEntryPoint字段的功能就像它的名字,也就是通常所说的入口点。什么是入口点呢?入口点指的是一个入口地址,指向一个RVA地址,RVA地址处的指令会在文件执行的时候最选被执行。
ImageBase字段
        该字段的功能就像它的名字,优先装入地址,当文件执行的时候,操作系统将会优先在ImageBase字段指定的地址中装入文件,只有指定的地址已经被占用时,才把文件装入到其它的地址中。

SectionAlignment字段和FileAlignment字段
        SectionAlignment字段指定了区段被装入内存后的对齐单位。也就是说,第个节被装入的地址必定是该字段指定数值的整数倍;FileAlignment字段指定了区段储在磁盘文件中的对齐单位。

DataDirectory字段
        DataDirectory字段是一个重点,它是由16个相同的IMAGE_DATA_DIRECTORY结构组成的。位于PE文件各个区段中的数据按照用途的不同,可以被分为输入表、输出表、资源、重定位表等数据块,这16个IMAGE_DATA_DIRECTORY结构就是用来定义这些不同的数据块的。其结构如下:
        IMAGE_DATA_DIRECTORY        STRUCT
        VirtualAddress                DWORD        ?        :数据起始RVA
        isize                        DWORD        ?        :数据块的长度
        IMAGE_DATA_DIRECTORY        ENDS
这16个IMAGE_DATA_DIRECTORY结构公别表示0到15一共16个不同的数据块,在这16个数据块中,要求务必掌握0、1、2、5、12号IMAGE_DATA_DIRECTORY结构中指定的数据块。几个数据块如下:
        0        IMAGE_DIRECTORY_ENTRY_EXPORT        :输出表
        1        IMAGE_DIRECTORY_ENTRY_IMPORT        :输入表
        2        IMAGE_DIRECTORY_ENTRY_RESOURCE        :资源
        5        IMAGE_DIRECTORY_ENTRY_BASERELOC        :重定位表
        12        IMAGE_DIRECTORY_ENTRY_IAT        :导入函数地址表

三、区段表和区段
        区段表是由一个名为IMAGE_SECTION_HEADER的结构体定义的。其结构如下:
        IMAGE_SECTION_HEADER        STRUCT
        Name1  db  IMAGE_SIZEOF_SHORT_NAME  dup(?)        8字节的区段名称
        union  Misc
        PhysicalAddress  dd        ?
        VirtualSize  dd                 ?        ;区段的尺寸
        ends
        VirtualAddress  dd        ?        ;区段的起始RVA地址
        SizeOfRawData  dd        ?        ;在文件中对齐后的尺寸
        PointerToRawData  dd          ?        ;在文件中的偏移
        PoniterToReIocations  dd ?
        PointerToLinenumbers  dd  ?
        NumberOfRelocations  dw  ?
        NumberOfLinenumbers  dw   ?
        Characteristics  dd     ?        ;区段的属性IMAGE_SECTION_HEADER         ENDS

IMAGE_SECTION_HEADER结构的数量先等于PE文件中区段的数量,而这个值是由FileHeader->NumberOfSections定义的。
这里着重讲一下Characteristics字段,该字段指定了区段的属性,可用一下值或其组合值。
        IMAGE_SCN_CNT_CODE或00000020h        ;区段中包含代码
        IMAGE_SCN_CNT_INITIALIZED_DATA或00000040h        ;区段中包含已初始化数据
        IMAGE_SCN_CNT_UNINITIALIZED_DATA或00000080h        ;区段中包含未初始化数据
        IMAGE_SCN_MEM_DISCARDABLE或02000000h        ;区段中的数据在进程开始后被丢弃,一般重定位表的区段就是这个属性
        IMAGE_SCN_MEM_NOT_CACHED或04000000h        ;区段中的数据不会经过缓存
        IMAGE_SCN_MEM_NOT_PAGED或08000000h        ;区段中的数据不会被交换到磁盘
        IMAGE_SCN_MEM_SHARED或10000000h        ;区段中的数据将被不同的进程共享
        IMAGE_SCN_MEM_EXECUTE或20000000h        ;可执行属性
        IMAGE_SCN_MEM_READ或40000000h        ;可读属性
        IMAGE_SCN_MEM_WRITE或80000000h        ;可写属性

四、输出表和输入表
        输出表又被称为“导出表”,当PE文件被执行的时候,windows装载器将文件装入内存并将输入表中登记的DLL文件一并装入,再根据DLL文件中的函数输出信息对被执行文件的IAT进行修正。一般来说,EXE文件不存在输出表,DLL文件大部分都包含输出表,不过这不是绝对的。
        输入表是PE文件结构中不可或缺的部分,输入表也称之为“导入表”。DLL格式的文件,它们是“动态链接库文件”,这些文件中有很多的导入函数,这些函数不会直接被执行,当一个程序(EXE)动行时,导入函数是被程序调用执行的,其执行的代码是不在主程序(EXE)中的一小部分函数,其真正的代码却在DLL文件中。输入表就相当于EXE文件与DLL文件沟通的钥匙,形象的可以比喻成两个城市之间交流的高速公路,所有的导入函数信息都会写入输入表中,在PE文件映射到内存后,windows将相应的DLL文件装入,EXE文件通过“输入表”找到相应的DLL中的导入函数,从而完成程序的正常运行,这一动态链接的过程都是由“输入表”参与的。
回复 支持 反对

使用道具 举报

 楼主| 发表于 2011-2-24 13:31:58 | 显示全部楼层
五、加壳免杀
        从PE结构入手的免杀方法主要有三种,分别是加壳免杀,加花免杀和修改特征码免杀。

1、加壳免杀的概念
        加壳免杀就是通过壳程序对原本被杀毒软件查杀的文件进行加壳处理,使之能穿越杀毒软件的监控和检测,成为不被查杀的文件。什么是“壳程序”,这里的“壳”指的就是一种可以利用特殊的算法将EXE、DLL等文件重新压缩、加密的程序。

2、壳程序的分类
        壳程序大体上可以分为两类。第一类是压缩壳:这种壳可以将被处理文件的体积减小,举几个著名的此类壳程序,UPX、ASPACK、NSPACK,一般来说,这种壳的免杀效果相对较差,但是在做组合免杀的时候特别有用,因为在这些壳程序中有很多壳程序的兼容性是很强的,支持多重加壳。第二关是加密壳:这种壳可以对被处理文件进行加密处理,免杀效果相对较好,通常会使被处理文件的体积增大,比较常见的比类工具有ASPRM、EXEStealth等,一般来说,加密壳也都有一定的压缩功能(不过有的也没有),所以也有人把壳程序分成三类:加密壳、压缩壳和压缩加密壳。
        被了压缩壳和加密壳,还有一种叫做捆绑壳的壳程序,这类壳可以有些属于压缩壳有些属于加密壳。
        按照免杀效果,壳程序大体上可以为分三大类。第一类是“文件免杀壳”,这类壳可以将木马加密并使其做到对应某些杀毒软件的文件免杀。第二类是“内存免杀壳”,这类壳程序可以将木马加密并使其做到内存免杀,与文件免杀壳类似,此类壳也往往都是加密壳。第三类是“行为免杀壳”,这种壳程序是近几年才出现的,此类壳的目的就是便被加壳程序可以做到针对某些行为检测工具的免杀。

《杀不死的秘密》第四章 I


一、免杀技术与汇编级反汇编的关系
1.1、机器语言
        电子计算机所使用的是由“0”和“1”组成的二进制数,二进制是计算机的语言的基础。机器语言是第一代计算机语言。

1.2、汇编语言
        为了减轻使用机器语言编程的痛苦,人们进行了一种有益的改进:用一些简洁的英文字母、符号串来替代一个特定的指令的二进制串,比如:用“ADD”代表加法,“MOV”代表数据传递等等,这样一来,人们很容易读懂并理解程序在干什么,纠错级维护都变得方便了,这种程序设计语言就称为汇编语言,即第二代计算机语言。而专门负责将这些符号翻译成二进制数的机器语言,这种翻译程序被称为汇编程序。

1.3、高级语言
        1954年,第一个完全脱离机器硬件的高级语言—Fortran问世了,40多年来,共有几百种高级语言出现,有重要意义的有几十种,影响较大,使用较普遍的有Fortran、Algol、Cobol、Basic、Lisp、Snobol、PL/1、Pascal、C、Prolog、Ada、C++、VC、VB、Delphi、JAVA等。

1.4、反汇编
        反汇编是把目标代码转为汇编代码过程。反汇编即是将这些执行文件反编译还原成汇编语言或其它高级语言。
        静态反汇编是从反汇编出来的程序清单上分析,就是将硬盘上的一个程序的机器码转换成编编助记符,再把这些汇编助记符按照地址的顺序显示出来,从而使得我们可以通过这些汇编指令了解程序的一些信息。而我们反汇编要做的就是在硬盘上读取程序的机器码,然后将这些机器码转换成与之对应的汇编指令并显示出来。这样就可以让我们更直观的了解程序的功能和执行过程。常用的静态分析工具是W32DASM、IDA和HIEW等。
        动态反汇编与静态反汇编最大的不同之处在于:动态反汇编是将程序载入内存后,将程序在内存中的机器码转换为汇编助记符并显示出来,因为是在内存中进行的,所以我们可以控制程序中一些汇编指令的执行流程。动态反汇编工具的另一个名称是动态调试工具,常用的动态调试工具有OllyDBG、OllyICE等。
        用两句话概括静态反汇编和动态反汇编的区别:
        静态反汇编:程序未执行时,反汇编可执行文件
        动态反汇编:程序执行时,反汇编内存中的数据

1.5、汇编与反汇编
        汇编是一种编程语言,在程序编写的时候,程序员一般不需要知道程序运行后会在哪一段内存地址处执行,更不必知道每次程序执行的指令所在的内存地址。所以在使用汇编语言编写程序的时候,遇到循环或者是跳转,都用标号来表示,汇编是编译器将汇编助记符转换成机器码然后进行编译的过程;反汇编是将已经编译好的程序用汇编指令来表示,内存地址是反汇编中必顺出现的元素,反汇编是利用反汇编工具将已经编译好的程序机器码反向解析成汇编助记符。
        既然免杀过程中经常要接触反汇编,那为什么我们还要在这里学习汇编指令呢?
        汇编和反汇编的概念虽然不同,但是“反汇编指令”这一概念是没有的,反汇编过程中将机器码翻译成的汇编指令被称为“反汇编得到的汇编指令”或“反汇编得到的汇编代码”。因为在反汇编代码中看到的指令和编程时所使用的汇编指令基本相同,只有在遇到跳转等转移情况进产生了一些很小的差异,不过一名免杀技术爱好者并不需要了解更多关于汇编与反汇编区别的知识,只需要知道在在学习的汇编指令和在实际免杀过程中遇到的反汇编代码中的汇编指令是相同形式的就可以了。
        反汇编得到的汇编指令往往指的是汇编指令本身;而反汇编得到的汇编代码一般指的是包括内存地址、机器码以及汇编指令本身的字符串信息。
        最后得出一个与免杀密切相关的结论——无论待免杀应用程序是用哪种高级编程语言写的,都可以使用一些调试工具查看或修改其反汇编代码,从而达到修改程序的目的。
=======================================================================

二、寄存器和堆栈
2.1、寄存器
        一个典型的CPU由运算器、控制器、寄存器(CPU工作原理)等器件构成,这些器件靠内部总线相连。通常所说的内存总线指的是外部总线,而这里所说的说线则是内部总线。内部总线实现CPU内部各个器件之间的联系,外部总线实现CPU和主板上其它器件的联系。简单地说,在CPU中:
        (1)运算器进行信息处理
        (2)寄存器进行信息存储
        (3)控制器控制各器件进行工作
        (4)内部总线连接各种器件,在它们之间进行数据的传送
寄存器是CPU中程序员可以用指令读写的部件。我们目前80386的CPU的每个寄存器是4个字节的,也就是32位。也就是我们CPU每次读取内存中二进制数据的时候,每次只能读取32个二进制数据。因为我们CPU是通过数据总线去读取的。
        80386有8个32位通用寄存器。这8个寄存器名为eax、ecx、edx、ebx、esp、ebp、esi、edi。

2.2、堆栈
        堆栈就是连续的内存单元,存取方式遵循“先进后出”的原则。栈是一种具有特殊的访问方式的存储空间。它的特殊就是在于最后进入这个空间的数据,最先出去。也就是说,我们先把eax压入堆栈,然后将ebx压入堆栈,放在堆栈最上边的就是后压入的,也就是ebx。

注意:堆和栈是两上不同的概念:
        1、栈区(stack)— 由编译器自动分配释放,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
        2、堆区(heap)— 一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收。注意它与数据结构中的堆是两回事分配方式倒是类似于链表。
=======================================================================

三、内存单元与央存寻址
3.1、内存单元
        我们知道电子计算机的最小信息单位是比特(bit),也就是一个二进制位。8个bit组成一个字节(Byte),也就是通常所说的一个字节。在内存中,一个内存单元能存储一个字了的信息。对于拥有64个内存单元的内存条,可以说,它的内量就是64个字节。

3.2、内存地址
        CPU访问内存单元时,要给出内存单元的地址。所有的内存单元松成的存储空间是一个一维的线性空间,每一个内存单元在这个空间中都有唯一的地址,也就是我们所说的内存地址。在80386构架的系统中,内存地址是线性地址,而这不一定是内存的物理地址,这取决于80386的内存分页机制是否被启用。
        为了防止十六进制与十进制的混淆,通常使用前缀或反缀分16进制数和10进制数。在16进制数前面加上“0x”或者在16进制数末尾加上“h”就可以表示这个数值是以16进制存在的。
        在Windows中所指的内存地址都是线性内存地址。

3.3、80386的寻址机制
        在16位CPU的年代,内存寻址需要两个寄存器来完成,这两个寄存器都是16位的。一个寄存器中记录着段地址,这也就是段寄存器;另一个寄存器记录着偏移地址,也就是相对于段地址的偏移量。内存地址=段地址×10h+偏移地址,而“段地址×10h”就是常说的基地址。
        那么16位的CPU为什么需要两个地址呢?因为一个16位的CPU只有16根地址总线,无法直接访问超过64KB的空间,所以才引入了段地址这个概念。

Win32下段寄存器的作用
        我们现在经常使用的包括Windows XP、2003、vista在内的Windows操作系统使用的都是80386指令集。而80386处理器工作的时候,可以使用全部的32根地址总线,访问4GB大的内存。因为80386所用的通用寄存器都是32位的,所以用任何一个通用寄存器来间接寻址,不必分段就已经可以访问到所有的内存地址。
        这是不是在说,段寄存器不再有用了呢?答案是否定的。实际上在这种情况下,段寄存器更有用了,虽然在寻址上不再有分段的限制问题,但是在这种情况下,一个地址空间是否可以被写入,可以被多少优先级的代码写入,是不是允许执行等设计保护的问题就出来了。要解决这些问题,必须对一个地址空间定义一些安全上的属性。段寄存器这时就派上了用途。但是涉及属性等其它参数,要表示的信息太多了,要用64位长的数据才能表示。这个64位的属性数据叫做段描述符(Segment Descriptor)。

段的基址的获取
        80386中引入两个新的寄存器来管理段描述符表。一个48位的全局描述符表寄存器GDTR,一个是16位的全局描述符表寄存器LDTR。
        GDTR指向描述表为全局描述符表GDT(Global Descriptor Table)。它包含系统中所有任务都可用的段描述符,通常包含描述操作系统所使用的代码段、数据段和堆栈段的描述符及各任务的LDT段等;全局描述符只有一个。
        LDTR则指向局部描述符表LDT(Local Descriptor Table)。80386处理器设计成每个任务都有一个独立的LDT。它包含有每个任务私有的代码段、数据段和堆栈段的描述符,也包含该任务所使用的一些门描述符,如任务门和调用门描述符等。
        和GDTR直接指向内存地址不同,LDTR选择器只存放索引值,指向局部描述符表内存段对应的描述符表中的位置。
        16位的段选择器中只有高13位表示索引值。剩下的3个数据位中,第0,1位表示程序的当前优先级RPL;第2位TI位用来表示在段描述符的位置;TI=0表示在GDT中,TI=1表示在LDT中。

最终确定的基址
        如果TI位为0,则先从GDTR寄存器中获取GDT的基址,然后在GDT中以段选择器高13位当做位置索引得到段描述符。
        如果段选择器的TI位为1,表示段描述符在LDT中,这时第一步的操作还是从GDTR寄存器中获取GDT的基址,并且要从LDTR中获取LDT所在段的位置索引。
        一个程序的基地址又叫做镜像基地址。好比一个程序的基地址是00040000,镜像大小为FFFF。那么在Windows的内存中,从00040000到0004FFFF就是该程序的领空,Windows可以通过对程序基地址的识别判断程序的领空,从而控制一些权限问题。

3.4、大尾与小尾
        CPU对字节顺序的处理方式有两种:大尾方式(Big Endian)和小尾方式(little Endian)。在大尾方式中,数据的高字节被放置在连续储存区的首位,而小尾方式与大尾方式相反,是将字节数据倒序存放在内存中。
=======================================================================
      
四、jmp指令与EIP寄存器

4.1、        JMP指令作为免杀过程中出现频率非常高的汇编指令,是我们首先要学习的汇编指令。

JMP内存地址
        当程序执行到这条指令的时候,会直接转到跟在JMP后面的内存地址处继续执行。一般来说,JMP指令分两种情况,一种是绝对跳转;加一种是近转移,也叫相对跳转。

4.2、        EIP寄存器
前面讲到过,在Win32下,寄存器有EAX、EBX、ECX、EDX、EDI、ESI、ESP、EBP等。
        EAX是Win32 API默认的返回值存放处;
        ECX是计数寄存器,可用于计算loop的循环次数;
        ESP是堆栈指针;
        EBP通常用来作为子程序局部变量指针;
        ESI用于存放存储单元在段内的偏移量;
而我们这次要讲的EIP指针则是80386的指令寄存器,对应8086中的IP寄存器。指指令和数据在存储介质上都是以二进制信息存放的,计算机把有些二进制信息看成是指令,而把另一部分二进制信息看成是数据。那么计算机是如何做到将指令和数据区分开来的呢。是因为有一个EIP寄存器,EIP寄存器中存储着CPU下次将要执行的指令所在的内存地址。所有执行过的代码都被EIP寄存器指向过。
CPU执行指令的过程如下:
        1、从EIP寄存器指向的内存单元读取指令,读取的指令进入指令缓冲器;
        2、EIP=EIP+所读取指令的长度,从而指向下一条指令;
        3、执行指令,转到步骤1,重复这一过程。

        修改EIP寄存器
    如果EIP寄存器只会按照前面介绍的步骤逐步增加其值,CPU就只会按照顺序执行程序的代码。而事实肯定并非如此,因为EIP寄存器的值并不是只会按照前面的过程变化。
    如果执行了JMP指令,就会跳转到JMP所指向的内存地址处执行该内存地址处的指令,也就是说,在执行JMP指令后,EIP的值会变成JMP指向的内存地址,只有这样才能使得在CPU执行JMP指令后,直接转到JMP所指向的内存地址处继续执行该位置的指令。
    总的来说就是一句话,当执行JMP指令时,EIP寄存器的值变为JMP所指向的内存地址。
=======================================================================

五、常用传送指令
        传送指令有很多,在免杀过程中遇到的最多的就是PUSH、POP、MOV、LEA四条传送指令。其中前三条指令为数据传送指令,而最的的LEA指令为目的地址传送指令。

5.1、PUHS指令
        Windows为每个程序安排了堆栈段,它是从高地址向低地址延伸的,之所以采用这种方式,是因为这样可以使堆栈指针始终指向最近入栈的元素的起始地址,这样的话,为访问这个元素提供了非常便利的方式。
        反汇编的时候看到的PUSH指令一般有如下格式:
        PUSH  数值
        PUSH  寄存器
        第一种格式表示将“数值”压入栈中;第二种格式表示将“寄存器”中的数据压入栈中。
        前面讲过,最先被压入堆栈中的数据存放在堆栈的最下面;最后被压入堆栈中的数据存在于堆栈的最顶端。寄存器ESP始终指向堆栈的栈顶,其值会随着堆栈的变化而变化,当然我们使用PUSH指令就会改变ESP的值。下面就从一个例子中了解PUSH指令压栈的基本过程:
        当前ESP内的值为栈顶,当执行PUSH EAX指令时,执行以下两个步骤:
        1、ESP=ESP-4,ESP指向当前栈顶前面的单元,以当前栈顶前面的单元为新的栈顶;
        2、将EAX中的内容送入EIP所指向的内存单元处。

5.2、POP指令
        POP指令与PUSH指令相反,POP指令负责将堆栈中的数据弹出堆栈。

在反汇编中经常看到的POP指令格式如下:
        POP  寄存器
        加入执的指令是:pop eax
        那么该指令执行过程如下所示:
        1、将ESP所指向的内存单元处的数据送入EAX中;
        2、ESP=ESP+4,ESP指向当前栈顶下面的元素,以当前栈顶下面的单元为新的栈顶。
      
5.3、MOV指令
        MOV指令是最典型的数据传送指令,很多免杀爱好者将其称为赋值指令。MOV指令有两个参数,所以我们在反汇编代码中看到的MOV指令常以如下格式存在:
        MOV  寄存器,数值
        MOV  寄存器,寄存器
        MOV  寄存器,[]
        MOV  [],数值
        以上所有形式都可以表述成“MOV  a,b”的形式,表示把b的值赋a,其中逗号前面、MOV后面的部分叫做目的操作数;逗号后面的部分叫做源操作数,以后遇到用逗号分隔的两个操作数都可以按照这种形式分解。举几个简单的例子来说明这个问题。
        MOV  eax,ebx
        将寄存器ebx的值存放到寄存器eax中
        mov  eax,11223344
        将11223344H这个数值保存到eax寄存器中
  默认情况下,出现在汇编指令中的立即数都是16进制数,后面讲到的也是默认如此。
  上面还给出了“[]”,关于这个中括号是什么含义呢?依然用实例进行说明。
        mov eax,dword ptr [10000005]
        将10000005作为内存地址,然后将从这个内存地址开始4个字节的数据赋给eax寄存器。为什么是将4个字节的数据赋给eax中呢?这不仅仅是因为eax是一个32位的寄存器,一次只能存放4个字节的数据。上面有这相一个标识“dword ptr”,“dword”也就是“双字”,也就是4个字节,这表明提取的是4个字节的数据。下面再来看一个例子:
        mov ax,word ptr [ebp-06]
        将ebp中的数值减6得出一个新的数值,将这个新的数值作为内存地址。将这个内存地址中2个字节的数据存放在寄存器ax中。因为“word ptr”中的“word”标识此次传输1个字的数据量,也就是2个字节的数据。
        下面是对MOV指令的一些规定(其中所说的“立即数”就是上面例子中的“数值”,如:mov 寄存器,数值):
        1、两个操作数的数据类型要相同,要同为8位、16位或32位;如:MOV  BL, AX等是不正确的;
        2、两个操作数不能同时为段寄存器,如:MOV  ES, DS等;
        3、代码段寄存器CS不能为目的操作数,但可作为源操作数,如:指令MOV  CS, AX等不正确,钽指令MOV  AX,CS等是正确的;
        4、立即数不能直接传给段寄存器,如:MOV  DS, 100H等;
        5、立即数不能作为目的操作数,如:MOV  100H, AX 等;
        6、指令指针IP,不能作为MOV指令的操作数;
        7、两个操作数不能同时为存储单元,如:MOV  VARA, VARB等,其中VARA和VARB是同数据类型的内存变量。

5.4、LEA指令
        意思是“装入有效地址(Load Effective Address)”,它的操作数就是地址,而不是具体的数据。这也就是LEA指令表面上与MOV指令的区别。在反汇编代码中LEA常以下列形式出现:
        lea 寄存器,[]
        此指令是将源操作数(也就是存储单元)的有效地址传送到目的操作数。举一个简单的例子:
        lea  eax, [ebp-4C]
        这里有两个注意的地方:
         1、源操作数必须是一个存储单元。不能是立即数。
         2、目的操作数必须是8个通用寄存器:eax,ebx,ecx,edx,esp,ebp,esi,edi。
        就是把ebp中的值减去4C再送入eax,而不是把由[ebp-4C]指明的内存地址上的值放到eax,其结果是[eax]和[ebp-4C]都表示同一个地址。
=======================================================================

六、算术运算指令I
6.1、ADD指令
    ADD在英文中是相加的意思,在汇编指令中,ADD也是加的意思。反汇编代码中常见到的ADD指令的格式如下:
        ADD  操作对象1,操作对象2
        功能:操作对象1=操作对象1+操作对象2
        来看一个例子:
        mov ebp,00000001
        add ebp,4
        第一行是将ebp的值改为00000001H;第二行是给ebp中的值00000001H加上4H。执行这两条指令后,ebp的值为00000005H。

6.2、SUB指令
    SUB拽令是与ADD指令相对的汇编指令,也就是减指令。格式如下:
        SUB  操作对象1,操作对象2
        功能:操作对象1=操作对象1+操作对象2
        举个例子:
        mov ebp,00000005
        sub ebp,4
        第一行是将ebp的值改为00000005H;第二行是给ebp中的值00000005H减去4H。执行这两条指令后,ebp的值变为00000001H。
=======================================================================
回复 支持 反对

使用道具 举报

 楼主| 发表于 2011-2-24 13:32:36 | 显示全部楼层
七、标志寄存器
        在CPU内部的寄存器中,有一种特殊的寄存器(对于不同的处理机,个数和结构都可能不同)具有以下3种功能。
        1、用来存储相关指令某些执行结果;
        2、用来为CPU执行相关指令提供行为依据;
        3、用来控制CPU的相关工作方式。
    标志寄存器又叫EFlags寄存器,它与其它寄存器不一样,其它寄存器是用来存放搂据的,都是整个寄存器具有一个含义。而EFlags寄存器是按位起作用的,也就是说,它是每一位都有专门含义,记录特定的信息。

7.1、ZF标志位
        ZF标志位记录相关指令执行后,其结果是否为0,如果结果为0,那么ZF=1;如果结果不为0,那么ZF=0。
        举个例子,执行如下指令;
         mov eax,1
         sub eax,1
        mov eax,1指令执行后,eax的值为00000001H;sub eax,1 指令执行后,eax的值为00000000H,应为执行结果为0,所以ZF=1。
         mov eax,2
         sub eax,1
        mov eax,2指令执行后,eax的值为00000002H;sub eax,1指令执行后,eax的值为00000001H,因为执行结果不为0,所以ZF=0。

7.2、PF标志位
        PF标志是奇偶标志位。它记录相关指令执行后,其结果的所有bit位中1的个数是否为偶数。如是1的个数为偶数,PF=1,如果为奇数,那么PF=0。
        举个例子,执行指令:
         mov ebx,1
         add ebx,10
        mov ebx,1指令执行后,ebx的值为100000011H。将100000011H转换成2进制为10001B(在计算机中,用后缀B表示二进制数,如001B就是一个二进制数,而001H则是十六进制数)。10001B中有2个1。2为偶数,所以PF=1。
         mov ebx,1
         add ebx,12
        mov ebx,1指令执行完成后,ebx的值为00000001H;add ebx,12指令执行后,ebx的值为00000013H。将00000013H转换成2进制为10011B。10011B中有3个1。3为奇数,所以PF=0。
        那么如果指行指令:sub eax,eax
        eax-eax的值肯定为0,其中有0个1,而0在这里作为偶数来识别,所以PF=1。

7.3、SF标志位
        SF标志位是符号标志。它记录相关批蛉 执行后,其结果是否为负。如果结果为负,SF=1;如果非负,SF=0        。
        计算机中通常用补码来表示有符号的数据。计算机中的一个数据可以看做是有符号数,也可以看成是无符号数。如:00000001B,可以看作是无符号数1,或者是有符号数+1;10000001B,可以看作为无符号数129,也可以看作是有符号数-127。
        上面的例子告诉我们,对于同一个二进制数,计算机可以将它当作无符号数据来运算,也可以当作有符号数据来运算。比如;
          mov al,10000001B
          add al,1
        执行结果:al=10000001B
        可以将ADD指令进行的运算当作无符号数的运算,那么ADD指令相当于计算,那么ADD指令相当于计算129+1,结果为130(10000010B);也可以将ADD指令进行的运算当作有符号数的运算,那么ADD指令相当于算-127+1,结果为-126(10000001B)。
        SF标志,就是CPU对符号数运算结果的一种记录,它记录数据的正负。在我们将数据当作有符号数来运算的时候,可以通过它来得知结果的正负。如果我们将数据当作无符号数来运算,SF的值则没有意义,显然相关的指令影响了它的值。
        执行如下指令:
          mov al,10000001B
          add al,1
        执行后,结果为10000010,SF=1,表示:如果指令进行的是有符号数运算,那么结果为负;
          mov al ,10000001B
          add al,01111111B
        执行后,结果为0,SF=0,表示:如果指令进行的是有符号数运算,那么结果为非负。

7.4、CF标志位
        CF标志位是进位标志位。一般情况下,在进行无符号数运算的时候,它记录了运算结果的最高有效位向更高位的进位值,或从更高位的借位值。
        举个例子来说明CF标志位的作用:
          mov eax,80000000
          add eax ,eax
          add eax, eax
         第一行指令执后,eax的值为80000000H;当执行eax+eax这一运算后,要向最高位进位,此时的值为00000000H,CF标志位为1;再次执行eax+eax运算,此时不能存在进位与借位的问题,CF寄存器的值变为0。
        某些指令将影响标志寄存器的多个标记位,这些被影响的标记位比较全面地记录了指令的执行结果,为相关的处理提供了所需的依据。比如指令sub eax,eax执行后,ZF、PF、SF等标志位都要受到影响,它们分别为:1、1、0。
=======================================================================

八、算术运算指令II
8.1、ADC指令
        ADC指令类似于ADD指令,都是加法运算,但它们之间也有秀大的区别。DC指令被称为带进位加法,在反汇编代码中经常看到以下格式显示的ADC指令:
        ADC  操作对象1,操作对象2
        功能:操作对象=操作对象1+操作对象2+CF
        执行如下指令:
          mov ebx,2
          mov eax,1
          sub eax,ebx
          adc ebx,1
        mov ebx,2指令执行后,寄存器ebx的值为00000002H;mov eax,1指执行后,寄存器eax的值为00000001H;sub eax,ebx指令执行后,寄存器eax的值为FFFFFFFFH,也就是十进制的-1,CF的值变为1;adc ebx,1指令执行后,ebx=ebx+1+1=00000004H。而如果把上例中的adc ebx,1换成add ebx,1,  ebx的最终结果为3H,因为ADD指令不加CF标志位的数值。

8.2、SBB指令
        SBB类似于SUB指令,都是减法指令。SBB指令与SUB指令的关系类似于ADC指令与ADD指令的关系。SBB指令在运算的时候要使CF标志位加入到运算中来,而SUB指令则没有将CF标志位的值加入到运算中。反汇编代码中常见到的SBB指令的格式如下:
        SBB  操作对象1,操作对象2
        功能:操作对象1=操作对象1-操作对象2-CF
        执行如下指令:
          mov ebx,2
          mov eax,1
          mov ecx,2
          sub eax,ebx
          sbb ecx,1
        mov ebx,2指令执行后,寄存器ebx的值为00000002H;
        mov eax,1指令执行后,寄存器eax的值为00000001H;
        mov ecx,2指令执行后,寄存器ecx的值为00000002H;
        sub eax,ebx指令执行后,寄存器eax的值为FFFFFFFFH,也就是十进制的-1,CF的值变为1;
        sbb ecx,1指令执行后,ecx=2-1-1=00000000H。

8.3、INC指令
        INC指令蛔一指令,用于给操作对象进行加一操作。INC指令的格式如下:
        INC 操作对象
        功能:操作对象=操作对象+1      
        INC指令中的操作数可以是寄存器的存储器作操数,但不能是立即数和段寄存器。INC指令影响标志位OF,SF,ZF,PF和AF,不影响CF。
        执行如下指令:
         mov ebx,1
         inc ebx
        执行结果为ebx=00000002H。

8.4、DEC指令
        DEC指令是减一指令,与INC指令相对。DEC指令与INC指令使用方法类似,都只有一个操作对象。
        DEC 操作对象
        功能:操作对象=操作对象-1

8.5、CMP指令
        CMP指令是比较指令,CMP指令有两个操作数,在执行CMP指令时,类似于执行SUB指令,只不过CMP指令是不保存结果的,而只影响标志寄存器的相关标志位。在CMP之后的指令往往会通过识别标志寄存器的相关标志位来进行下一步操作。
        CMP指令的格式也类似于SUB指令,其格式如下:
        CMP 操作对象1,操作对象2
        功能:计算机操作以象1-操作对象2,但是不会将计算结果保存到任何一个操作对象中,但是计算过后,会对标志寄存器的相关标志位进行设置。
        举个例子,执行指令cmp eax,eax ,结果为0,但是并不把这个结果保存到eax中。可是标志寄存器中的相关标志位的值却受到了影响,指令执行后:ZF=1,PF=1,SF=0,CF=0

九、逻辑运算指令
        逻辑运算又称布尔运算

9.1、AND指令
        汇编中的AND指令是逻辑按位与运算批令,首先让我们来看一下二进制数的运算。
        现在有两个二进制数,它们分别是100000001B和110000000B,这里将其记作数A和数B。为了方便理解,这里纵向放置。
        100000001B
        110000000B
        如果相同位置上,两个数的值都是1则计算结果中该位置的数值也为1,否则为0。在这个例子中,数A的第7位是1,数B的第7位也是1,而其它位置上,不存在数A与数B都为1的情况,所以这两个数与运算的结果为10000000B。在数学和一些计算机高级编程语言上,逻辑与运算的运算符是“&”,那么上面的运算可以表示成:
        100000001B & 110000000B
        反汇编中看到的AND指令总是以如下形式存在的:
        AND  操作对象1,操作对象2
        功能:操作对象1=操作对象1 & 操作对象2

9.2、OR指令
        OR在汇编中是按位逻辑或运算指令,其用法与AND指令相似。
        或运算中,两个操作对象在相同位置上的数值有一个1,结果中该位置的数值就为1,否则为0。
        或运算的运算符是“||”,举个例子,这里操作的还是上例中的数A和数B,将数A与数B进行与运算。
        数A中第0位和第7位为1;数B中第7位和第6位为1。所以10000001B || 11000000B=11000001B。
        在汇编中,逻辑按位或运算指令的格式如下:
        OR  操作对象1,操作对象2
        功能:操作对象1=操作对象1 || 操作对象2

9.3、XOR指令
        XOR是按位异或运算指令。按位异或的数学运算符是“^”。如果把两个数进行异或运算,在相同位上数值相同为0,相异为1。举个例子:
        10001B XOR 10101B就是将10001B与10101B进行异或运算。
        来看最前边的一位,10001B最前边的一位是1;10101B最前边的一位也是1。二者相同,所以该位运算后的结果为0。
        第二位也都是0,所以该位运算结果为0。第三位分别为0和1,二者不同,所以该位运算结果为1。第四位运算结果为0。第五位运算结果也为0。这样最终的运算结果就是00100B。
        下面再进行一个异或运算:00100B ^ 10101B
        第一位数值不同,所以该位运算结果为1;
        第二位数值相同,所以该位运算结果为0;
        第三位数值相同,所以该位运算结果为0;
        第四位数值相同,所以该位运算结果为0;
        第五位数值不同,所以该位运算结果为1。
        最终的运算结果就是10001B。


        XOR指令的格式如下:
        XOR  操作对象1,操作对象2
        功能:操作对象1=操作对象1 ^ 操作对象2

9.4、TEST指令
        TEST指令的运算操作与AND指令类似,而功能却与CMP指令类似。
        TEST指令的格式如下:
        TEST  操作对象1,操作对象2
        功能:操作对象1 & 操作对象2,这一点与AND指令相似,都是进行与运算,但是不同于AND指令的是,TEST运结果不保存。说它与CMP指令也很类似是因为,它影响标志寄存器中相关的标志位。跟在TEST后面的指令根据TEST运算后标志寄存器中相关标志位的数值来进行下一步操作。
=======================================================================

十、程序转移指令
        汇编语言中的程序转移指令有很多,我们要学习的是CALL、RETN/RETF、JE、JNE、JB、JNB、JA、JNA、LOOP、NOP指令
        其中CALL和RETN/RETF是无条件转移指令;JE、JNE、JB、JNB、JA、JNA是条件转移指令;LOOP是循环控制指令;NOP是处理器控制指令。

10.1、CALL指令
        CALL指令可不是如唤指令,而是子程序调用指令。那么汇编语言中的子程序是什么呢?子程序能被其它程序调用,在实现某种功能后能自动返回到调用程序去的程序。其最后一条指令一定是返回指令,故能保证得新返回到调用它的程序中去。也可调用其它子程序,甚至可自身调用。
        我们可以暂时把子程序理解为一个代码段,是一个模块化的代码面。这个代码段可以完成某一特定功能,当程序在执行过程中需要用到这一功能,将会进入这个代码段。这块代码段执行完毕后,会跳出这块代码段。而进入代码段这一过程就是子程序的调用,也就是这里所说的CALL指令所要完成的工作。
        反汇编经常看到的CALL指令的基本格式如下:
        CALL  地址1
        功能:调用地址1处的子程序
        CALL指令分为两种情况,一种是段内转移;另一种是段间转移。这两种情况类似于JMP指令的相对跳转和绝对跳转(只不过相对跳转的JMP指令会有short标识)。
        在CALL指令进行的是段内转移的情况时,跟在CALL后面的地址1为一个相对位移;而CALL指令进行的是段间转移的情况时,跟在CALL后面的地址1为一个绝对内存地址。
        段内转移的CALL指令等价于两条指令:
        push eip
        jmp  目的位置
        也就是说,执行段内转移的CALL指令时,相当于先后执行以上两条指令。
        段间转移的CALL指令等价于三条指令:
        push CS
        push eip
        jmp  目的位置


10.2、RETN/RETF指令
        按照前面讲CALL指令举的那个例子,CALL指令是进入子程序的指令,而例子中所说的跳出子程序这一过程也需要2条指令,它们是RETN/RETF。
        RETN/RETF是跳出子程序的指令,被称为返回指令。RETN指令用于从段内转移CALL进的子程序中返回;RETF指令用于从段间转移CALL进的子程序中返回。
        RETN/RETF在反汇编代码中呈现的形式如下:
        RETN
        RETN  操作数1


        RETF
        RETF  操作数1

        RETN等价于一条指令:POP   eip
        RETF等价于两条指令:
        POP  eip
        POP  CS
        而带有操作数的RETN/RETF指令则是在POP之后,执行ESP=ESP+操作数1。

10.3、条件转移指令
        既然在汇编语言中存在无条件转移指令,那么与之相对的条件转移指令也一定存在。它们分别是JE、JNE、JB、JNB、JA、JNA。
        上述指令中的字母“J”都是“JUMP”的意思;字母“E”是“equal”;字母“N”表示“NOT”;字母“B”表示“below”;字母“A”表示“above”。
        这些条件转移指令往往跟在“test”和“cmp”指令后,按照“TEST”和“CMP”对标志寄存器相关标志位的影响来进行相应的条件转移。具体如下:

        指令                含义                检测的相关标志位
        je             等于则转移                      zf=1
        jne          不等于则转移              zf=0
        jb             低于则转移                       cf=1
        jnb             不低于则转移              cf=0
        ja             高于则转移                   cf=0且zf=0
        jna          不高于则转移          cf=1或zf=1

        在反汇编中,以上这几条条件转移指令的使用格式都相同,如下:
        条件转移指令        内存地址1
        判断条件,若符合跳转条件,则跳转到内存地址1处;若不符合跳转条件,则继续执行该指令的下一行指令。

10.4、LOOP指令
        LOOP指令是汇编中的循环控制指令,这条指令可是重点中的重点。
        LOOP指令需要一个寄存器来做计数器,通常这个寄存器是ecx。在反汇编代码中经常看到如下形式的LOOP指令:
        mov  ecx,数值1
        一段指令
        loop 内存地址1

        功能:执行内存地址1到loop指令之间的代码数值1次。内存地址1到loop指令之间的代码被称为“循环体”。
        举个简单的例子:
        1000C6B6  mov        ebx,1
        1000C6BB  mov        ecx,8
        1000C6C0  add        ebx,ebx
        1000C6C2  loopd            short  1000C6C0

        可以看到loop后面的内存地址就是指令add  ebx,ebx所在的内存地址,那么循环体就是add   ebx,ebx ,执行次数是8次。总结起来就是,执行8次指令 add  ebx,ebx ,执行结果为ebx=00000100H。

10.5、NOP指令
        NOP指令是最简单的汇编指令之一,它表示不进行任何操作。使用格式也非常简单,只需要一个NOP就可以了,如下:

        NOP

        当程序执行到这句代码,程序什么都不会做,而直接执行下一条指令。
=======================================================================

十一、环境保存
        环境保存又叫做环境保护,这个概念在反汇编中经常看到。 在调用子程序之前,程序当前的很多信息保存在各个寄存器当中,当进入子程序之后,各寄存器的值可能被子程序修改。那么,程序从子程序中跳出并返回到主程序后,使用被子程序修改过的寄存器,程序会出现很多错误。为什么会出现这种情况呢?我们从ESP寄存器讲起。

11.1、变化的ESP寄存器
        我们知道ESP寄存器始终指向栈顶,当程序CALL进子程序后,子程序中的很多指令都可以改变ESP寄存器的值。当使用RETN/RETF指令跳出子程序回到主程序后,主程序需要接着CALL进子程序之前的状态继续运行。而此时ESP寄存器的值已经变化,如果程序需要用到CALL进子程序之前压入堆栈的某一个值,需要使用POP这样的指令将堆栈中的值存放到某一寄存器中。但是此时ESP寄存器的值已经变化了,指向的不一定是之前压入堆栈那个所需要的数据。一旦使用类似POP这样的指令将现在ESP所指向的栈顶的数据提取出来,这极有可能将错误的数据提出来,从而导致程序执行错误。

11.2、LEAVE指令
        在跳出子程序之前,需要将ebp的值恢复给esp。我们知道,跳出子程序的指令是RETN/RETF,所以要将esp恢复过程放在RETN/RETF指令之前。LEAVE就是放在RETN/RETF之前的恢复指令。LEAVE指令相当于先后执行如下两条指令:
        mov esp,ebp
        pop ebp

        mov esp,bep指令执行后,将之前保存到ebp中的esp值还原给esp。但这时的寄存器esp并不是CALL进子程序之前的状态。
        pop ebp指令执行后,将esp的值加上4,使此时的esp还原为CALL进子程序之前的状态。
        在子程序中,ebp寄存器保存有原始的esp,并随时用作存取局部变量的指针地址,所以在任何时刻,不要尝试把ebp用于别的用途,否则可能会出现意想不到的结果。
        这里还需要注意一点,往往调用子程序不需要保存所有的寄存器,那样很多子程序会失去意义。
=======================================================================

十二、手工加花免杀
12.1、加花免杀的原理
        说到加花,要先说说花指令。花指令(junk code)指的是一段无用的指令,执行花指令前后,程序的各项参数都没有变化。加花免杀指的是在程序的头部加上一段花指令,当程序执行的时候,先执行花指令。花指令执行完毕后,执行程序原本的指令。因为执行花指令做的是无用功,所以这不会影响程序的功能。但是因为在程序的头部多了这些花指令代码,程序代码被复杂化了,很多时候,杀毒软件就不能再查杀这个病毒了。

12.2、一个典型的花指令
        看下面这段汇编代码:
        push ebp
        mov ebp,esp
        add esp,-OC
        add esp,OC
        push eax
        jmp入口

        这就是一段非常简单的花指令了,我们一行一行地看:
        push ebp就是反ebp压入堆栈
        mov ebp,esp就是把esp的值传递给ebp
        add esp,-OC就是把esp加上-OC
        add esp,OC就是把esp加上OC
        push eax就是把eax压入堆栈
        jmp入口 就是跳转到程序首先运行的地址

        从上边的
        add esp,-OC
        add esp,OC
        这两行可以看出,这两得是无用代码,先给esp加上了“-OC”,然后又加上了“OC”。这两个值,一个正的,一个负的,总的看来,就相当于什么也没做。
        这就是典型的花指令了,因为它什么也没做,但是可以使原程序的代码复杂化。

12.3、为什么要编写花指令
        我们并没有免杀效果好的花指令,因为那些广为流传的花指令是很容易被杀毒软件识别的。这样就使我们的加花免杀效果大打折扣。所我这里我们就来学习自己编写花指令。

  12.3.1、堆栈平衡
        关于花指令的编写,网上有这样的比喻:我们把一段花指令比喻成一道数学运算题,把汇编指令(push pop等)比喻成加减乘除,把寄存器或数据(eax,ebx,1等)比喻成数字(1,2,3等),那么要保持花指令堆栈的平衡,等于保持这道数学题的结果是0。

  12.3.2、pushad和popad
        前面讲过环境保存的相关,在编写花指令的时候也需要注意环境保存。否则,可能导致程序执行出错。
        这里给出两条非常实用的花指令,pushad和popad指令。pushad指令是将8个通用寄存器压入堆栈;popad指令则是与pushad相反。pushad将8个通用寄存器压入堆栈的顺序和popad将堆栈中的数据返回到8个通用寄存器的顺序相对,不会产生寄存器恢复错位的问题。使用这两条指令就可以轻松的实现环境的保存和恢复。

  12.3.3、常用平衡指令
        这里给出一些简单的平衡指令,所谓平衡指令就是实现花指令无效这一特性成对的指令。


         push ebp——把基址指针寄存器压入堆栈
         pop ebp ——把基址指针寄存器弹出堆栈
         push eax——把数据寄存器压入堆栈
         pop eax——把数据寄存器弹出堆栈
         nop ——不执行
         add esp,1——指针寄存器加1
         sub esp,1——指针寄存器减1
         inc ecx——计数器加1
         dec ecx——计数器减1
         sub esp,1——指针寄存器减1
         sub esp,-1——指针寄存器加-1
         jmp 入口地址——跳到程序入口地址
         push 入口地址——把入口地址压入堆栈
         retn ——返回到入口地址,效果与jmp入口地址一样
         mov eax,入口地址——把入口地址转送到数据寄存器中
         jmp eax ——跳到程序入口地址
         jb 入口地址
         jnb 入口地址 ——效果和jmp入口地址一样,直接跳到程序入口地址

注:以后编写花指令,都可以参考以下灵活组合,快速写出自己的花指令。
        push ebp
        pop ebp


        puh eax
        pop eax

        push esp
        pop esp

        push 0
        push 0

        push 10 ——其中数字可以任意,注意与下面对应
        push -10

        nop ——可任意在中间添加
        mov edi,edi ——效果与nop一样

        add esp,1 ——其中数字可以任意,注意与下面对应
        add esp,-1

        add esp,1 ——其中数字可以任意,注意与下面对应
        sub esp,1

        inc ecx
        dec ecx

        sub eax,2 ——其中数字可以任意,注意与dec的个数对应
        dec eax
        dec eax

        add eax,-2 ——其中数字可以任意,注意与inc的个数对应
        inc eax
        inc eax

        jmp 下一个jmp地址
        jmp 下一个地址

        push ebp
        mov ebp,esp ——可做为花指令的开头句

        jmp 入口地址 ——跳到程序入口地址
        与它效果一样的还有(以下三个):

        push  入口地址
        retn

        jb 入口地址
        jnb 入口地址

        mov eax,入口地址
        jmp eax
回复 支持 反对

使用道具 举报

 楼主| 发表于 2011-2-24 13:33:42 | 显示全部楼层
《杀不死的秘密》第五章和第六章


一、脱壳基础知识
1、脱壳的概念
        在第三章中讲了加壳的概念,与加壳技术相对的就是脱壳技术了。脱壳就是将已经加壳的程序从壳中剥离出来。既然能给程序进行加壳,那也会有相应的脱壳方法。尽管理在有些壳很难脱掉,但是脱壳技术也在不断的进步,而且在不断竞争中发展状大。

2、OEP
        OPE的意思就像它的名字一样容易理解。OEP就是原程序的入口点,也就是真正的入口点。
        当被加壳的程序运行后,首先运行的是壳程序,壳程序会将原程序还原到内存中并将控制权返还。OEP总会在这期间被脱壳者找到。只要找到这个OEP,就可以将原程序从内存中dump出来,而后针对dump出的程序进行处理,最终得到原程序。
        一般来说,脱壳就是要找程序的OEP,得到这个OEP,脱壳就完成一半了,剩下的就是修复工作了。

3、脱壳的用处
        脱壳用处很大,可以破除壳程序对程序的保护。从而使我们更接近程序代码,了解这个程序。而对于免杀制作者来说,脱壳的用处也很大,有些木马生成的服务端默认就被加壳。壳把这个木马服务端保护起来了,这或多或少对我们的免杀造也了阻碍。所以免杀制作者往往先将木马服务端的壳脱掉,而后再按照平时免杀的方法对木马服务端进行免杀处理。由此可见,基础的脱壳技能是免杀技术爱好者的技能。
=======================================================================

二、单步跟踪法
        脱壳的方法有很多,先来讲脱壳方法中最基础的单步跟踪法。单步跟踪法就是利用OD的单条指令执行功能,从壳的入口一直执行到OEP,最终通过这个OEP将原程序dump出来。然当,在单步跟踪的时候需要跳过一些不能执行到的指令。

使用单步跟踪法追踪OEP的常见步骤:
        1、用OD载入待脱壳文件,如果出现压缩提示,选择“不分析代码”;
        2、向下单步跟踪,实现向下的跳转;
        3、遇到程序往上跳转的时候(包括循环),在回跳的下一句代码上单击并按键盘上的“F4”键跳过回跳指令;
        4、OD中的绿色线条表示跳转没有实理,不必理会,红色线条表示跳转已经实现;
        5、如果刚载入程序的时候,在附近有一个CALL指令,那么就要按键盘上的“F7”键跟进这个CALL内,不然程序很容易运行起来;
        6、在跟踪的时候,如果执行某个CALL指令后就运行,一定要按键盘上的“F7”键进入这个CALL之内再单步跟踪;
        7、遇到在popad指令下的远转移指令时,要格处注意,因为这个远转移指令的目的地很可能就是OEP。
=======================================================================

三、ESP定律法
        ESP定律法是脱壳的利器,是国外友人发现的。有了ESP定律,可以方便我们脱掉大多数的压缩壳。可谓是本世纪破解界中最伟大的发现之一。这里只简单的看一下狭义ESP定律的原理。

        使用ESP定律追踪OEP的常见步骤:
        1、将待脱壳程序载入到OD中,开始就按键盘上的“F8”键单步跟踪一步,这时如果看到OD右边的寄存器窗口中的ESP寄存器的值有没有变为红色,如果发现ESP寄存器的值变为红色,执行第2步;
        2、在OD的命令行窗口中执行命令hrXXXXXXXX,xxxxxxxx就是变为红色的ESP寄存器的值,在输入命令之后,一定不要忘记按键盘上的回车键;
        3、按键盘上的“F9”键让程序运行起来;
        4、使用单步跟踪的方法跟踪到OEP即可。
=======================================================================

四、二次断点法
        二次断点是有技巧的下两个断点,在两个断点之后就可以很轻松的找到OEP。
        使用二次断点法追踪OEP的常见步骤:
        1、将待脱壳程序载入到OD中,单击OD的“选项”菜单下的“调试设置”命令,在弹出的“调试选项”对话框中切换到“异常”选项卡,勾选该选项卡下的所有复选框,也就是忽略所有异常;
        2、按键盘上的“ALT+M”组合键打开OD的内存窗口;
        3、在OD的内存窗口中找到“.rsrc”区段,单击该区段后按键盘上的“F2”键在该区段上下一断点;
        4、按“Shift+F9”让程序运行到断点心处,而后再次打开OD的内存窗口,这次在“.rsrc”区段上面的“.code”区段(有的时候就是“.text”)上下一个断点;
        5、按“shift+F9”让程序运行到第二次下的断点处,然后单步跟踪既可以来到OEP。
=======================================================================

五、末次异常法
        在脱壳方法中,末次异常法又被称为最后一次异常法,这是最基础的脱壳技术之一。末次异常法脱壳很是简单,但就是这简单的脱壳方法可以挑掉曾经风靡一时的强壳。

        使用末次异常法追踪OEP的常见步骤:
        1、将待脱壳程序载入到OD中,单击OD的“选项”菜单,在弹出的菜单中单击“调试设置”命令,在随后弹出的“调试选项”对话框中切换到“异常”选项卡,清除该选项卡下所有复选框,也就是不忽略任何异常;
        2、连续按键盘上的“Shift+F9”组合键让程序运行起来,记录按键的次数X;
        3、回到OD中,按键盘上的“Ctrl+F2”组合键重新载入程序,按X-1次“Shift+F9”组合键;
        4、在OD右下角窗口中找到“SE句柄”或是“SE处理程序”,记录此处的内存地址;
        5、在OD的反汇编窗口中跟随到上一步记录下的内存地址,并在此内存地址处下一个断点;
        6、按键盘上的“Shift+F9”组合键让程序运行到上一步下的断点处,按键盘上的“F2”键取消此处的断点;
        7、使用单步跟踪法追踪到OEP。
=======================================================================

六、模拟跟踪法
        在这章中讲到的众多脱壳方法中,我们首先讲了单步跟踪法脱壳,因为单步跟踪脱壳法是脱壳技术中最基础的方法,在后面其它的一些脱壳方法中总会或多或少的配合单步跟踪法才能顺利完成脱壳工作。便是始终是一次次的按“F8”键来单步跟踪程序,偶尔遇到回跳就跳过执行,这样机械性的操作很是烦人,那么能不能让机器来代替人力,让工具帮我们单步跟踪呢?答案是肯定的,这也就是这节讲的内容——模拟跟踪法。模拟脱壳法就是模拟单步跟踪来进行查找OEP。
        模拟跟踪法的常见步骤:
        1、将待脱壳程序载入OD中,先简单的跟踪一下程序,看看有没有SEH暗桩;
        2、按键盘上的“ALT+F9”打开OD的内存窗口,找到“SFX,输入表,资源”的行,并记录此行的内存地址;
        3、在OD的命令行窗口执行命令“tc   eip<上一步中记录下的地址”,命令执行后就会跟踪到OEP。
=======================================================================

七、SFX自动脱壳法
        在上一节,我们使用模拟跟踪法代替手动单步跟踪法进行脱壳。在OD中,不但可以利用模拟跟踪来代替单步跟踪进行脱壳,从而节省劳动力,还有一种SFX自动脱壳的方法也可以节省劳动力,并能快速有效的将程序的壳脱掉。

        使用SFX自动脱壳法脱壳的常见步骤:
        1、将OD设置为忽略所有异常;
        2、在OD的“调试选项”对话框的“SFX”选项卡中选择“字节模式跟踪实际入口”选项并确定;
        3、将待脱壳程序载入OD,待程序载入完成后,会直接停在OEP处。
=======================================================================

八、出口标志法
        前面几个脱壳方法中有一个共同点,就是在单步跟踪到popad指令后面不远处的jmp指令的时候,就可以大胆的判断这个jmp指令的目的地址就是OEP。原因很简单,popad指令用于将壳运行之前保存的环境恢复,使原程序能正常运行。有些壳的popad指令很少,我们就可以查看被这种壳加壳的程序的所有popad指令,找到后面存在jmp指令的popad指令,然后来到其后的jmp指令的目的地址,这很可能就是OEP,然后就可以进行dump脱壳了。

        使用出口标志法脱壳的常见步骤:
        1、将待脱壳程序载入OD中,在OD的反汇编客人口中单击鼠标右键,在弹出的右键菜单中单击“查找”→“所有命令”,在弹出的输入框中输入“popad”并按“查找”按钮;
        2、逐一尝试跟踪查找到的所有“popad”指令,最终达到程序的OEP。
=======================================================================

九、使用脱壳脚本辅助脱壳
        在脱壳的时候,使用模拟跟踪法可以让OD代替我们单步跟踪程序直到OEP,这样大大提高了脱壳的效率。但是模拟跟踪法并不能跟踪到一些较强的壳的OEP,这时我们可以使用高手们写的脱壳脚本来帮助我们完成脱壳工作,使用脱壳脚本来脱壳要比手动跟踪方便得多。脱壳脚本就是高手们为了方便自己或他人脱壳,把自己手动脱壳的步骤记录下来,保存的一个文本文档。虽然脱壳脚本是一个文本文档,可以使用记事本将其打开,但是轻易不要用这种方式修改脱壳脚本,因为直接修改脱壳脚本,很可能造成脱壳脚本不能正确完成对应的脱壳工作。
=======================================================================

十、使用脱壳工具脱壳
        脱壳工具很多,这里只介绍最为实用的全自动脱壳机——超级巡警脱壳工具。

超级巡警脱壳工具的工作方法:
        超级巡警脱壳工具会自动侦测待脱壳程序所加的壳头,从而判断出带脱壳程序是用哪种壳程序加壳的。如果超级巡警脱壳工具支持对该壳的脱壳,就可以很方便的将程序的壳脱掉;如果不支持对该壳的脱壳,则会给我们一个简单明了的提示。
=======================================================================

第六章

1、利用加多个花指令的方法实现木马免杀
        加多个花指令和加单一花指令的原理基本相同,都是让程序先执行花指令再执行原程序从而实现程序代码的复杂化。不过稍有不同的是,加单一花指令时,最后的跳转指令是直接跳转到原程序入口点,而我们加多个花指令时,第一个花指令最后的跳转语句则是跳转到第二个花指令的起始地址,这样在第一个花指令执行完成后就会直接执行第二个花指令,然后第二个花指令的结晶尾再跳转到第三个花指令的起始位置,以此类推……最后一个花指令的结尾就直接跳转到原程序入口。原理和加单一花指令基本没什么区别,只是工作量大一些。不过加多花的免杀效果要比加单一花的免杀效果好得多。

2、利用壳外花实理木马免杀
        原理:先给木马加上一个普通的压缩壳以减小木马的体积。然后再给加过壳的木马加上一个花指令,这样就可以干扰杀毒软件对木马所加壳的判断,从而实现免杀目的。

3、利用修改壳头实现木马免杀
        我们知道,杀毒软件是通过将被检测文件的头部代码与壳特征码进行比对,以此来判断被检测文件所加的壳的种类。因为汇编指令里有很多等效指令,比如“add ebp,8”和“sub ebp,-8”就是等效的。我们大可以利用这些等效代码来实现对木马壳头的修改从而实现免杀目的。

4、利用移动PE头的方法实现木马免杀
        这里讲一下如何利用移动PE文件头来实现木马免杀。先来回顾一下第三章中讲到的内容。“DOS MZ header”是DOS头,DOS头在Windows下可以被忽略。而我们就可以将原来的PE文件头移动到DOS部分(将DOS覆盖)。因为DOS头在Windows下可以被忽略,所以我们移动PE头后的新程序也是可以正常运行的。

免杀的方法有很多,这里只是讲了几种常见的。


杀不死的秘密》第七章


一、再谈特征码
        在第一章中讲了杀毒软件对病毒的查杀原理的时候,也提到过特征码这个概念。所以,特征码查杀技术就是当今主流杀毒软件引擎的灵魂。在这节中,你会了解到关于特征码查杀技术更为深入的知识。相比在第一章对特征码查杀技术的讲解。

1、特征码查杀两要素
        当杀毒软件捕获到一个病毒样本之后,杀毒软件公司的技术人员会提取病毒样本内比较特殊的代码或数据,并记录下所提取的代码或数据在病毒样本中的文件偏移,最后将病毒样本的特征码和特征码所在的文件偏移存放到杀毒软件的病毒库中。当用户使用杀毒软件对某一个文件进行扫描时,杀毒软件的杀毒引擎会将病毒库中的特征码与被扫描文件进行比对,特征码比对的大致过程很简单,就是从病毒库中提取出第一个特征码和该特征码(记作特征码A)所对应的文件偏移(记作文件偏移A),然后比对被扫描文件在文件偏移A处的代码或数据与特征码A的一致性。如果被扫描文件在文件偏移A处的代码或数据与特征码A相同,那么被扫描文件将被杀毒软件判定为病毒;如果被扫描文件在文件偏移A处的代码或数据与特征码A不相同,则杀毒引擎会从病毒库中提取下一条特征码记录,继续进行上述操作,如果将病毒库中所有特征码记录与被扫描文件对比完毕后都没能有相同的部分,则杀毒软件判定被扫描文件不为病毒。综上所述,特征码查杀技术的两个最基本的要素就是特征码和该特征码所在的文件偏移。而这种说法并不完全正确,因为如果将这两要素中的文件偏移换做是RVA,也就是相对于镜像基址的偏移,则该特征码就是内存特征码,具体的原因很好理解,此处不再讲述。所以正确的说法是特征码查杀技术的两个最基本要素是特征码和该特征码所在的偏移。
        上面所说的特征码查杀技术是最基本、最纯粹的特征码识别技术原理,而在实际的杀毒软件中,杀毒引擎对于上述过程往往有优化措施,例如:复合特征码查杀、隐藏特征码查杀等。

2、复合特征码查杀
        当免杀技术爱好者了解到杀毒软件特征码查杀的原理之后,使用特定的办法获取杀毒软件对某病毒的特征码和该特征码所在的偏移,然后通过一些手段改掉病毒在这个偏移处的代码或数据,这个修改过的文件通常被称为该病毒的变种,至此,杀毒软件就不能判定该病毒的变种为一个病毒了。
        还有另一种情况,病毒也是程序,特征码也是二进制数据,天下的程序如此之多,很可能出现在一个非病毒程序在杀毒软件病毒库中所记录的一条偏移处存在与这条偏移相对应的特征码,如果真的发生了这种情况,杀毒软件就会将这个非病毒文件判定为病毒,从而造成通常所说的杀毒软件误杀。
        这两种情况说明了一个共同的问题,那就是杀毒软件只给病毒样本定义一处特征码是十分不安全的,既容易让病毒出现新变种,又容易造成对正常程序的误杀。为了解决这些问题,杀毒软件公司的技术人员想出了复合特征码这一概念,也就是为病毒样本提取多处特征码和特征码对应的偏移地址,在用户使用杀毒软件扫描该病毒的时候,只要该文件与代码病毒库的复合特征码中的任意2处甚至1处特征码吻合,杀毒软件就将该文件判定为病毒。而且杀毒软件公司的技术人员在提取特征码的时候也格外用心,他们将特征码定位在病毒的一些关键部位,这是为了使免杀技术爱好者不容易对特征码进行修改。有了符合特征码查杀技术,杀毒软件对正常程序的误杀率大大降低,同时也对免杀造也了更大的困难,因为面对复合特征码,免杀技术爱好者需要修改更多的特征码才能使病毒得以免杀。
        尽管复合特征码有如此权重的优点,但是复合特征码在病毒库中占用的体积很大,如果杀毒软件对所有的病毒都进行复合特征码定位的话,杀毒软件的病毒库会非常大,这不但不利于杀毒软件平时的更新,更重要的是,这会大大增加杀毒软件扫描文件的时间。所以杀毒软件公司的技术人员往往只对流行的或者是破坏性极大的病毒进行复合特征码提取,而对一般的招摇过市的小病毒则依然采取单一特征码提取。

3、隐藏特征码
        隐藏特征码又被叫做隐含特征码,是继复合特征码之后的高级特征码查杀技术之一。隐藏特征码有两种,分别是“显式隐含特征码”和“隐式隐含特征码”。
        显式隐含特征码往往和复合特征码同时出现,杀毒软件公司的技术人员对某病毒样本进行分析,从病毒样本中提取了多处特征码和这些特征码所对应的偏移,这里将其命名为第一套特征码。而后杀毒软件公司的技术人员又对同一个病毒样本进行分析,提取出了另一套特征码,这里称其为第二套特征码。这两套特征码中没有重复的部分,而且并不是并列关系。如果用该杀毒软件扫描该病毒,杀毒软件通过第一套特征码将病毒识别出来(此时第二套特征码没有用处)。然而一个免杀技术爱好者将该病毒的第一套特征码都改掉了,制作出了一个病毒变种,按照前面讲的推断,这个变种病毒应该不被该杀毒软件查杀了。然而事实恰恰相反,该变种病毒依然被该杀毒软件查杀,这就是第二套特征码在起作用了,这种使用两套或者更多套特征码进行叠加识别病毒的特征码查杀技术中的第二套和其后的特征码都被称为“显式隐含特征码”。
        隐式隐含特征码也被称为是干革命扰码。某些杀毒软件使用了隐式隐含特征码,杀毒软件公司的技术人员得到病毒样本后对这个病毒样本进行分析,从病毒样本中提取出一个特征码和与该特征码对应的偏移(记作特征码A),然后再按照一般的特征码提取原则,提取了一些特征码(特征码B)。当杀毒软件扫描该病毒时,首先检测该文件是否存在特征码A,若存在,则再检测该文件是否存在特征码B,若存在,则将该文件判定为病毒。这个特征码A就是隐式隐含特征码。

4、启发式扫描
        启发式扫描是特征码扫描的一个分持,也就是说,启发式扫描利用的也是特征码技术。但是这里指的特征码可不同于之前的特征码,因为这里的特征码是没有明确的偏移地址的。既然是特征码识别技术,却又没有偏移地址,那么启发式扫描是如何实现特征码比对的呢?说白了,启发式扫描就是给各种威胁计算机安全的代码进行加权,举一个简单的例子,杀毒软件设置了如下加权标准:


                在系统目录下释放文件得20分
                格式化分区得100分
                插入远程线程得50分
                ……
                ……

        一个新病毒,它会在系统目录下释放3个文件并且会插入到IE的进程中,之所以谈论的是新病毒,言外之意就是杀毒软件公司的技术人员还没有来得及提取这个病毒的特征码,按照常理推断,杀毒软件是不会将这个文件判定为病毒。然而并非如此,杀毒软件却将这个文件判定为病毒,就是因为杀毒软件使用了启发式扫描并定义了一个判定病毒的分数。假设这里的分数为60分,当杀毒软件对某文件的加权得分达到60分之后,就将该文件判定为病毒,不到60分则放过该文件。那么上面的情况就很好解释,按照之前定制的标准,这个病毒在系统目录下释放了文件,得到20分;而后又使用远程线程插入技术插入了IE进程,这里得到了50分,将两个值加在一起就是70分,最后得分超过了60分,所以杀毒软件判定其为病毒。
        病毒完成特定的功能需要有相应的代码,启发式扫描的特征码就是针对病毒的这些功能总结出来的一套可以判定文件行为的代码。尽管上面的例子很形象,但是真实的情况要比这里例子复杂得多。

5、狭义上的特征码与广义上的特征码
        来看这样一句话,杀毒软件对某病毒定位了一处特征码和主赢余持征码所对应的偏移。这里所说的特征码就是狭义上的特征码,是一段二进制数据。广义上的特征码则包括狭义上的特征码和狭义上的特征码所对应酬的偏移两部分。
        最后再强调一下,上面给出的例子都是最典型最直接的例子。在实际的杀毒软件中,很多杀毒软件会同时使用上述多种特征码识别技术,而且原理上也不尽相同,上面给出的例子只能说是比较有代表性的,学习的时候千万不可以偏概全,认为特征码识别技术仅限于此。
============================================================================================

二、MyCCL定位原理
        免杀技术爱好者通常使用两款工具定位某病毒针对某杀毒软件的特征码,它们是MyCCL和MultiCCL。

1、MyCCL定位复合特征码的原理
        假设现在有一个病毒,这个病毒被某杀毒软件所查杀,现在要使用MyCCL定位该病毒针对该杀毒软件的特征码。
        MyCCL首先将该病毒分为7段,并分别用数字1 ~ 7表示7段。
                1段        2段        3段        4段        5段        6段        7段
        假设,该病毒对于此杀毒软件有两处特征码,其中第一处特征码在文件的第3段中,第二处特征码在文件的第5段中。将这两处特征码分别用# 和 $ 表示,文件正常的数据用------表示。
        可以将该文件表示成如下形式:
                |------ ------ --#--- ------ ----$- ------ ------|
        MyCCL在硬盘上生成7个文件,文件1只有原程序的第1段信息,剩下的部分都被MyCCL用0x00填充;文件2只有第1段和第2段的信息,剩下的部分都被MyCCL用0x00填充了,依次类推,最终生成7个文件。可以将文件1到文件7形象的表示为以下形式:
                |------ 000000 000000 000000 000000 000000 000000|        1号文件
                |------ ------ 000000 000000 000000 000000 000000|        2号文件
                |------ ------ --#--- 000000 000000 000000 000000|        3号文件
        使用杀毒软件对这7个文件进行查杀并使用杀毒软件的病毒删除功能删除其中被杀毒软件查杀到的文件,最终从3号文件到7号文件都被删除。
        这是为什么呢,因为在3号文件和之后的文件都包含有很征码--#---,所以被杀毒软件删除。
        而后使用MyCCL识别文件的数量,MyCCL得出结论——在文件3中存在特征码。而文件2中却没有特征码,以此推断,特征码在文件的第3段。
        此时,MyCCL已经查找到了第一处特征码,MyCCL会将查找到的第一处特征码,也就是程序的第3段信息用0x00填充,然后再生成一些文件,这里列出4号文件到6号文件的示意图:
                |------ ------ 000000 ------ 000000 000000 000000|        4号文件
                |------ ------ 000000 ------ ----$- 000000 000000|        5号文件
                |------ ------ 000000 ------ ----$- ------ 000000|        6号文件
        再次使用杀毒软件查杀由MyCCL生成的文件,这时杀毒软件会查杀5号和其后的所有文件,使用杀毒软件的病毒删除功能删除这些被查杀到的文件。
        为什么5号文件和5号以后的文件都会被杀毒软件查杀到呢?原因很简单,因为从5号文件开始存在有特征码,所以这些文件被删除。而MyCCL从文件的缺失判定从5号文件开始存在特征码,所以第二处特征码在文件的第5段中。而后MyCCL会再次生成文件(新文件的第2段和第5段都会被0x00填充),不过因为我们举的这个例子中已经不存在特征码了,所以生成的新文件中没有被杀毒软件查杀的。最终MyCCL判定特征码存在于文件的第2段和第5段中。
        虽然已经查找到了这两处特征码,但是特征码定位工作还没有结束,因为这两个特征码区间都很大。换句话说,一个病毒文件可能有比较大的体积,将其分成7份,每一份也许有好几KB大,所以现在定位出的特征码区间太大了,根本无法帮助制作免杀。所以还需要精确定位特征码,也就是将现在的特征码区间再分成N份,然后重复上面的过程,最终找到非常精确的特征码区间。一般来说特征码只占1 ~ 2个字节,所以最后的特征码区间要精确到1 ~ 2个字节左右就可以了。

2、隐式隐含特征码
        隐式隐含特征码的作用是什么?关于隐式隐含特征码的原理,在上一节的文章中已经进述完了,但是杀毒软件为什么要设计这样一种特征码识别技术呢。按照MyCCL对特征码的定位原理,首先要把文件分成N份,然后使用0x00填充其中的部分数据。回到前面举的例子中,特征码用#和$来表示,隐式隐含特征码用%表示,将病毒文件表示如下;
                |------ ------ --#--- ------ ----$- ------ ---%--|
        依然是用MyCCL将这个文件分解成7份并重新生成7个文件,其中的1号到3号文件可以表示成如下形式:
                |------ 000000 000000 000000 000000 000000 000000|        1号文件
                |------ ------ 000000 000000 000000 000000 000000|        2号文件
                |------ ------ --#--- 000000 000000 000000 000000|        3号文件
        按照没有隐式隐含特征码的套路来讲,现在用杀毒软件对这7个文件进行查杀,这个3号文件肯定是会被查杀的。然而事实与猜测恰恰相反,3号文件并不被杀毒软件查杀,因为3号文件中并不被杀毒软件查杀,因为3号文件中不存在隐式隐含特征码。按照隐式隐含特征码技术的识别原理,只有文件存在隐式隐含特征码的时候才继续判定一般特征码,如果不存在隐式隐含特征码,即使发现了某病毒文件存在一般特征码,杀毒软件也不会将其杀掉。
        讲到这里,隐式隐含特征码存在的意义就显而易见了,利用隐式隐含特征码技术后,使用MyCCL工具将不能准确的定位出特征码,从而降低病毒变种的可能性。当然杀毒软件并不是对所有的病毒都提取隐式隐含特征码,因为这样会增大病毒库的体积,从而不方便用户升级,更会使杀毒软件扫描病毒的时间加长。同复合特征码技术一样,杀毒软件只会将一些流行的或者是破坏力巨大的病毒放入隐式隐含特征码技术的查杀目标之列。现在大多数杀毒软件都具备复合特征码技术,而只有极少数的杀毒软件才使用隐式隐含特征码技术,在命名用隐式隐含特征码技术的杀毒软件中,常见的有瑞星和NOD32。
        到了这里,可能会产生疑问。即使是使用了隐式隐含特征码,那么定位特征码的时候也可以定位到隐式隐含特征码本身,从而将其修改,也能达到免杀的目的,杀毒软件为何要多此一举呢。其实杀毒软件厂商的这种做法并不难理解。在一个PE文件中,有很多地方是无法修改的,杀毒软件可以直接将这种无法修改或者是难以修改的部分当做是干扰码。这样,即使免杀制作者找到了干扰码并将其误认为是一般性特征码,也很难在保证程序原功能不受影响的情况下修改掉这处特征码,从而对免杀的制作造成一定的难度,降低病毒变种的可能性。
        既然隐式隐含特征码技术能使MyCCL对特征码的定位失去原有的效果,那么免杀技术爱好者一定会制作出新的特征码定位工具来克服隐式隐含特征码技术的限制,这款工具名字就叫“MultiCCL”。
============================================================================================

三、MultiCCL定位原理
        MultiCCL是一款非常成秀的特征码定位工具,定位比MyCCL更准确,而且能应对更多的杀毒软件(前面说过,MyCCL无法应对使用隐式隐含特征码查杀技术的杀毒软件)。但是MultiCCL的操作和原理都要比MyCCL稍微复杂。本节就来简单的讲一下MultiCCL定位特征码的原理。
        首先我们假设一种比较极端的情况:
        某杀毒软件针对某病毒样本抽取了如下a  b  c  d  e的五个特征码片段,而其中的任何一个单独的片段都不构成完整的特征码。也就是说,假如文件中存在完整的a段和完整的b段,那么杀毒软件就将其判定为病毒。倘若我们将b段改掉,杀毒软件依旧会将该文件判定为病毒,因为该文件中出现了完整的a段和c段。也就是说,任意两段特征码完整的出现在文件中,杀毒软件都将会判定其为病毒。文件特征码示意如下图:
                .............................aaaaaaaaa....................
                ...................................................................
                ..........bbbbbbb..........ccc............................
                ..............ddddddddd.................eeeee........   (图1)

        在上面这个用字符表示的示意图中,“..........”表示程序的原数据或代码,而其中的“aaa”、“bbb”等五段英文字母的部分则表示杀毒软件在程序中定位的特征码。这种表示方法即能体现出特征码在程序中的位置又能体现出特征码的长度。
        针对这种情况,MultiCCL是从一端开始覆盖零(考虑到PE文件文件头的重要性,multiCCL选择了从尾端开始往前覆盖)。直到下面所示时,杀毒软件才不能识别(b片段被破坏了一个字节),这样b尾端就出来了(也就是特征码的结束偏移)。
                ..............................aaaaaaaaa...................
                ...................................................................
                ..........bbbbbb00000000000000000000000
                0000000000000000000000000000000000                        
                0000000000000000000000000000000000  (图2)

        下面是定b片段的头端了(特征码b的起始偏移)。定位b段特征码的起始位置的经过可以用下面的三个示意图表示。注意图中0区域的移动。
                ..............................aaaaaaaaa...................
                ...................................................................
                ..........bbb0bbb0000000000000000000000                     
                0000000000000000000000000000000000                        
                0000000000000000000000000000000000 (图3)
  ------  ------------------------------------------------------------------------  ------
                ..............................aaaaaaaaa...................
                ...................................................................
                ..........0bbbbbb0000000000000000000000                    
                0000000000000000000000000000000000 (图4)
   ------    ----------------------------------------------------------------------- ------
                ..............................aaaaaaaaa...................
                ...................................................................
                                0bbbbbbb00000000000000000000000000
                                0000000000000000000000000000000000
                                0000000000000000000000000000000000 (图5)
------   ------------------------------------------------------------------------ ------
        图5中的a和b两个片段都暴露了,暴露出完整的a  b 两个片段,此时杀毒软件将样本判定为病毒。这样就定位出了b段了。接下来把b段用0盖掉作为新的样本,用杀毒软件检测一下是否被杀,还杀就重复前面的步骤定c段,如下图:
                .............................aaaaaaaaa....................
                ...................................................................
                ..........0000000..........ccc............................
                ..............ddddddddd.................eeeee........   (图6)

        如果定c段盖掉后还是被杀就再定d段,如下图:
                .............................aaaaaaaaa....................
                ...................................................................
                ..........0000000..........000...........................
                ..............ddddddddd.................eeeee........       (图7)                              
        这样重复,直到片段d  e……当特征码都找到后,杀毒软件不再将文件判定为病毒就可以了。这是MultiCCL定位特征码的基本原理,便是如果编程实现起来的话,一个字节一个字节的填充文件显然是一种效率很低的做法。所以MultiCCL工具对此也做了一些优化和改进,不过这里只需要了解MultiCCL定位特征码的基本原理,不必讨论高效率的实现过程。

        补充说明:现在发现有的杀毒软件开始用检测文件的某几处是否被填充0来反定位了,一旦被检测到某几处被盖0,就把位置更先前的干扰码激活。这时就算牺牲效率一直往前盖,也只能定位出干扰码(假的特征码)。现在暂时可用随机数据串填充应付,不知道以后还会有什么招。另外,NOD32的特征法是,先普通定位,然后把找到的输入表上的特征码片段保护起来再定位,这样就能找到代码里的特征码片段了。因为MultiCCL可以保护指定代码,然后定位特征码,这样就突破了杀毒软件的隐式隐含特征码技术的限制。
============================================================================================

四、启发式扫描与主动防御
        这节主要用NOD32和卡巴斯基2010的原理讲解启发式扫描和主动防御的原理。

1、启发式扫描
        在第一节中我们已经了解了启发式扫描的评分原理。而这节对启发式扫描的讲解将直接针对NOD32这款杀毒软件的启发式扫描。如何躲过NOD32的启发式扫描可以说的上是免杀技术讨论圈子中讨论的最火热的话题了。
        NOD32启发式扫描的原理二十分简单,同其他的启发式扫描一样,也是一种评分原理。举一个简单的例子,一般的下载者程序都要用到两个API,分别是URLDownloadToFile和ShellExecute。
        URLDownloadToFile这个API用于将文件下载到本机;而后又调用了ShellExecute。即使ShellExecute执行的不是URLDownloadToFile下载的文件,NOD32也会将其列为病毒,病毒类型就是Downloader。
        但如果我们再编写一个程序,程序先调用ShellExecute,而后再调用URLDownloadToFile。那么NOD32不会判定这个程序为病毒。
        可见,NOD32是将危险的函数(病毒中常见的函数)整理出来,按照病毒的特性排列这些API的顺序,只要发现类似的API调用顺序,便将其判定为病毒。NOD32启发式扫描的基本原理就是这么简单,更深入的知识应该在黑客编程书中讲解,这里不再深入。
        对于NOD32启发式扫描还有另一个问题,这个问题因扰着很多初学免杀的朋友们。那就是在特征码定位准确的情况下,为什么移动了PE文件中的输入表中被杀的API函数,为什么PE文件还是被杀。道理很简单,因为NOD32不是按照传编的文件偏移对应特征码的进行启发式扫描,而是从PE文件头中读出输入表的位置,又从输入表的表头的相关字段找到被杀的API函数。因为移动了输入表中的API函数,要想让程序能正常执行,通常会使用LordPE这款工具修改被移动API函数的名称RVA。而LordPE这款工具也是通过修改输入表的表头结构中的相关字段来完成修改API名称RVA的功能的。所以移动输入表函数对于NOD32起不到任何免杀效果(NOD32查杀的时候照样会从输入表表头结构中读取修改后的API名称RVA)。

2、主动防御
        主动防御技术到底是怎么拦截木马的呢?关于这个问题,资料并不是很多,多数免杀制作者还是一知半解,只知道主动防御技术对木马的查杀很厉害,却并不知道主动防御拦截木马病毒的原理,这里讲解的主动防御完全针对卡巴斯基2010,其它杀毒软件的主动防御与其大同小异。
        主动防御是一种特殊的API HOOK,它HOOK了一些木马病毒中比较常用的API。举一个简单的例子,现在的木马多为远程线程插入型木马,那么一般都需要用到这样一个API——CreateRemoteThread。CreateRemoteThread用于将在远程进程中启动一个线程,并指定线程函数。
        我们对一个远程线程插入型木马定位针对卡巴斯基2010的特征码,然后成功修改掉所有的特征码,并使用卡巴斯基2010对其进行文件扫描,木马不报病毒。但是运行这个木马之后,卡巴斯基马上报告该木马有安全风险,这也就是主动防御的检测结果。
        而这个木马是一个测试程序,只有远程线程插入功能,并没有实质性的控制功能。但是程序运行一样被主动防御拦截,这就排除了其它危险API的干扰,使得我们能就CreateRemoteThread这个API进行主动防御原理的研究。
        下面在CreateRemoteThread这个API调用之前加入另一个API函数Sleep,让程序停止30秒,编译连接程序。运行这个程序,卡巴斯基没有立即报告,过了大约30秒后,卡巴斯基开始报告安全威胁,警告框和上面的那个一模一样。可见,当程序运行到CreateRemoteThread这个危险API的时候调用了卡巴斯基的检测,卡巴斯基检测CreateRemoteThread之前的环境,然后判定此时此该的CreateRemoteThread函数的调用是否会带来安全隐患,然后给出提示。
        再举一个简单的例子,如果一个程序只用URLDownloadToFile这个API而没有使用ShellExecute,也就是只完成下载工作,并不执行程序。那么NOD32的启发式扫描便会不对这个程序视而不见。便是如果这个程序是使用URLDownloadToFile将URL上的文件下载到系统目录里,这个程序便会被卡巴斯基的主动防御所拦截,而NOD32依旧对其视而不见。
        这就是前面所指的卡巴斯基主动防御对函数的监控及环境的判断了。卡巴斯基主动防御检测到该程序执行URLDownloadToFile函数,然后暂停这个程序,检测该函数执行的环境,并从中发现系统目录路径字符串,随后则判定此程序为危险程序。而如果使用URLDownloadToFile将URL下的文件下载到一些没有特点的文件夹中,比如说下载到迅雷下载文件夹中,卡巴斯基的主动防御便不会弱出任何提示。
        就拿上面举例这个只会将URL下的文件下载到系统目录下的测试程序来说。即使我们不执行这个程序,而只是用卡巴斯基扫描一下该程序,卡巴斯基也会准确的判定其为URLDownloadToFile。道理很简单,因为卡巴斯基运用了虚拟机技术,扫描文件的时候,可以将文件在一个与真实系统隔绝的环境中模拟执行,然后主动防御会判断其为危险程序,从而给出提示。
        NOD32的启发式扫描和卡巴斯基2010的主动防御模块都是其各自的重要反病毒技术,不可能将技术细节泄露出去,以上对于二者的研究均是从网上热心技术爱好者的研究结果,难免会有错误……
回复 支持 反对

使用道具 举报

 楼主| 发表于 2011-2-24 13:34:18 | 显示全部楼层
虽然非常长 当时我觉得很有用 大家耐心的看下吧
回复 支持 反对

使用道具 举报

发表于 2011-5-21 22:55:59 | 显示全部楼层
说实话我没看完
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

QQ|手机版|小黑屋|☆我要吧☆ ( 豫ICP备13016831号-1 )

GMT+8, 2024-11-24 12:43 , Processed in 0.069293 second(s), 21 queries .

Powered by abc369 X3.4

© 2001-2023 abc369.

快速回复 返回顶部 返回列表