首页
社区
课程
招聘
反逆向工程揭密
发表于: 2005-4-6 22:49 19146

反逆向工程揭密

nbw 活跃值
24
2005-4-6 22:49
19146

Anti Reverse Engineering Uncovered

简译:nbw  
      
      nbw.07cn.com

作者:Nicolas Brulez*
E-Mail: 0x90@Rstack.org
Received: 07. Mar. 2005, Accepted: 12. Mar. 2005, Published: 13. Mar. 2005

译注:作者写的不错。我翻译的很垃圾,也没翻译完,大家凑和着看,最好看原文更好。

Abstract
概述

相比在二进制形式分析文件,我更喜欢展示一些更具挑战性的技术,以及如何去实践他们。这篇文章站在软件保护的角度说明问题。你可学到一些关于保护/反保护数据的方法。很多保护方面的弱点都在这里被说明。
关键词:软件保护;逆向代码工程;Linx;反调试;反反调试

1. 介绍

本次的目标是通过逆向项工程,分析一个未知文件,并且改进一些从安全社区学来的方法,工具和曾经用到的分析过程。这看起来有点像SotM32,但是这些需要被分析的文件被处理过,以增加分析困难,防止被逆向分析。

技术水平:高级/专业水准

关于这个被分析的文件,目前可以说的是该文件在WInxp系统中建立,然后交给你去分析。你应该尽可能分析出来文件的信息和他的内部工作原理,以及该文件要完成的功能。而我的目标就是教会你去分析这些被加了很多保护的文件。这些技术也可以利用在其他方面。

2.  列举和解释一些反逆向和文件保护技术

很多技术被用来减慢或者中断逆向分析工具的工作。

修改PE头

很多文件的PE头被修改以欺扰分析工具。下面说一下一些比较重要的修改:

->Optional Header
Magic: 0x010B (HDR32_MAGIC)
MajorLinkerVersion: 0x02
MinorLinkerVersion: 0x19 -> 2.25
SizeOfCode: 0x00000200
SizeOfInitializedData: 0x00045400
SizeOfUninitializedData: 0x00000000
AddressOfEntryPoint: 0x00002000
BaseOfCode: 0x00001000
BaseOfData: 0x00002000
ImageBase: 0x00DE0000 <--- "Non Standard" ImageBase
SectionAlignment: 0x00001000
FileAlignment: 0x00001000
MajorOperatingSystemVersion: 0x0001
MinorOperatingSystemVersion: 0x0000 -> 1.00
MajorImageVersion: 0x0000
MinorImageVersion: 0x0000 -> 0.00
MajorSubsystemVersion: 0x0004
MinorSubsystemVersion: 0x0000 -> 4.00
Win32VersionValue: 0x00000000
SizeOfImage: 0x00049000
SizeOfHeaders: 0x00001000
CheckSum: 0x00000000
Subsystem: 0x0003 (WINDOWS_CUI)
DllCharacteristics: 0x0000
SizeOfStackReserve: 0x00100000
SizeOfStackCommit: 0x00002000
SizeOfHeapReserve: 0x00100000
SizeOfHeapCommit: 0x00001000
LoaderFlags: 0xABDBFFDE <--- Bogus Value
NumberOfRvaAndSizes: 0xDFFFDDDE <--- Bogus Value

标准的win32程序文件基址通常是400000,并且逆向工程也采用这个默认值来分析。虽然这并不是程序自身的保护,但是这个简单的修改的确可以混淆逆向分析。

反 OllyDbg:

修改LoaderFlags 和 NumberOfRvaAndSizes..我逆向分析了OllDBG和SoftICE,发现了一些可以减缓他们工作的技巧。更改了上面说的2个标记,可以导致OD认为文件基址错误,然后直接运行该文件而不是中断在入口点。有时候这是很糟糕的事情。

反Soft ICe:蓝屏死机

修改NumberOfRvaAndSizes 字段可以让现在版本的Soft ICE重起计算机。反汇编Soft ICE的PE加载器,我发现一个很严重的漏洞可以导致运行Soft ICE不报错而直接是系统崩溃。这个漏洞已经被报告给软件公司,并且下个版本将被修正。

这里是SoftICE的PE加载器的反汇编结果,可以从中找到为什么会重启系统:

.text:000A79FE
.text:000A79FE loc_A79FE: ; CODE XREF:
sub_A79B9+31j
.text:000A79FE ; sub_A79B9+3Cj
.text:000A79FE ; DATA XREF:
.text:00012F9Bo
.text:000A79FE sti
.text:000A79FF mov esi, ecx
.text:000A7A01 mov ax, [esi]
.text:000A7A04 cmp ax, 'ZM'
.text:000A7A08 jnz not_PE_file
.text:000A7A08
.text:000A7A0E mov edi, [esi+_IMAGE_DOS_HEADER.e_lfanew]
.text:000A7A11 add edi, esi
.text:000A7A13 mov ax, [edi]
.text:000A7A16 cmp ax, 'EP'
.text:000A7A1A jnz not_PE_file
.text:000A7A1A
.text:000A7A20 movzx ecx,
[edi+IMAGE_NT_HEADERS.FileHeader.NumberOfSections]
.text:000A7A24 or ecx, ecx
.text:000A7A26 jz not_PE_file
.text:000A7A26
.text:000A7A2C mov eax,
[edi+IMAGE_NT_HEADERS.OptionalHeader.NumberOfRvaAndSizes]
.text:000A7A2F lea edi,
[edi+eax*8+IMAGE_NT_HEADERS.OptionalHeader.DataDirectory]
.text:000A7A33 mov eax, ecx
.text:000A7A35 imul eax, 28h
.text:000A7A38 mov al, [eax+edi] ; 很严重的BUG,别人可以让EAX+EDI指向0,在Ring 0权限里面读取地址0处的内容可不是一件好事。;-)
.text:000A7A3B
.text:000A7A3B loc_A7A3B: ; DATA XREF:
.text:00012FA5o
.text:000A7A3B cli
.text:000A7A3C call sub_15C08
.text:000A7A3C
.text:000A7A41 mov byte_FA259, 0
.text:000A7A48 push eax                                                        ; 保存EAX
.text:000A7A49 mov eax, dword_16B56F                ; 将一个保存的数据放入EAX
.text:000A7A4E mov dr7, eax                                        ;  dr7 = eax
.text:000A7A51 pop eax                                                        ; 恢复EAX
.text:000A7A52 mov dword_FC6CC, esp
.text:000A7A58 mov esp, offset unk_FBABC
.text:000A7A5D and esp, 0FFFFFFFCh
.text:000A7A60 xor al, al                                                ; AL设定为0么?那么上面的 mov al, [eax+edi] 有什么用呢?我没找到其他合理解释
.text:000A7A62 call sub_4D2EB
.text:000A7A62
.text:000A7A67 call sub_36AC1
.text:000A7A67
.text:000A7A6C xor edx, edx
.text:000A7A6E
.text:000A7A6E loc_A7A6E: ; CODE XREF:
sub_A79B9+124j
.text:000A7A6E call sub_74916
.text:000A7A6E

根据上面的分析,可以让SoftICE读取内存地址0处的数据,或者修改PE结构,让它读取我们设定的地址。如果程序中对这个地方需要读取的正确内容进行验证,那么可以觉察到正在被分析。我不会说如何修改文件中的那个字段,因为这里毕竟只是说明原理,并且我不想别人利用这个做坏事。

你只需要修改一下PE头,就可以修正这个技巧。NumberOfRvaAndSizes 标准的数据为0x10。将这个字段修改正确,SoftICE就不会崩溃。对于OD,把上面说的LoaderFlags 和 NumberOfRvaAndSizes字段修改正确就可以,也可以将他们改成0。

修改区段以终止一些工具

->区段
1. item:
Name: CODE
VirtualSize: 0x00001000
VirtualAddress: 0x00001000
SizeOfRawData: 0x00001000
PointerToRawData: 0x00001000
PointerToRelocations: 0x00000000
PointerToLinenumbers: 0x00000000
NumberOfRelocations: 0x0000
NumberOfLinenumbers: 0x0000
Characteristics: 0xE0000020
(CODE, EXECUTE, READ, WRITE)
2. item:
Name: DATA
VirtualSize: 0x00045000
VirtualAddress: 0x00002000
SizeOfRawData: 0x00045000
PointerToRawData: 0x00002000
PointerToRelocations: 0x00000000
PointerToLinenumbers: 0x00000000
NumberOfRelocations: 0x0000
NumberOfLinenumbers: 0x0000
Characteristics: 0xC0000040
(INITIALIZED_DATA, READ, WRITE)
3. item:
Name: NicolasB
VirtualSize: 0x00001000
VirtualAddress: 0x00047000
SizeOfRawData: 0xEFEFADFF <---                        非常大的数据
PointerToRawData: 0x00047000
PointerToRelocations: 0x00000000
PointerToLinenumbers: 0x00000000
NumberOfRelocations: 0x0000
NumberOfLinenumbers: 0x0000
Characteristics: 0xC0000040
(INITIALIZED_DATA, READ, WRITE)
4. item:
Name: .idata
VirtualSize: 0x00001000
VirtualAddress: 0x00048000
SizeOfRawData: 0x00001000
PointerToRawData: 0x00047000
PointerToRelocations: 0x00000000
PointerToLinenumbers: 0x00000000
NumberOfRelocations: 0x0000
NumberOfLinenumbers: 0x0000
Characteristics: 0xC0000040
(INITIALIZED_DATA, READ, WRITE)
根据上面的数据,可以得出一些结论。首先,看起来文件没有被压缩,因为NicolasB 区段的文件地址和大小是相符合的。但是这个非常大的数据可以导致一些工具崩溃或者降低分析效率。

由于IDA认为这个区段有这么大,所以会试图申请一个这么大的内存,导致系统变的非常慢 -)。最终,他会加载文件,或者导致内存溢出,这些取决于你的电脑配置。

这种修改方法也会让其他一些工具带来分析麻烦,例如Objdump,PE editor,以及一些内存转存工具等等。这种手段也很容易被修复,你只需要把区段的大小更正过来。通过观察程序加载后区段的大小和这些字段的关系,可以发现有些区段是多余的,那么可以把那些区段的大小属性填充为0。

发现手段

在写程序时候,我知道有些人会更改我的文件的PE头,那么我就把这些PE头的字段作为文件保护的密钥,如果改动了这些字段,文件就无法运行。

另外我还修改了PE头的其他一些字段,但是并没有真正利用。(好像化妆品一样)

冗余代码

为了加强分析难度,我在在文件的真正的有用代码之间添加了一些垃圾代码。这些垃圾代码很长,但是实际上并没有真正做什么事情,只是干扰分析,尤其是静态分析。这些垃圾代码用一些工具生成,并且每段都不一样。

Here is how it looks inside a disassembler:
这里是一段垃圾代码的反汇编:

                                                        图1

这段垃圾代码开头是 pushad指令,用于将所有的寄存器状态保存到堆栈中,然后结束时候是popad,用于恢复这些寄存器状态,下面是代码结尾:

                                                        图2

对付这手段的方法:

这个保护错误并不完美,至少我上面所说的那种方法是这种情况。由于这种垃圾代码有明显的pushad/popad标记,因此很容易就可以发现。我也注意到了这一点,毕竟这只是一个演示而已。这种情况可以用IDA/OD脚本来处理。这些脚本很有趣,可以自己编写出来剔除垃圾代码的功能。如果你没写过,我建议你用一下。我写这些的时候,已经有了一个更加完善的垃圾代码生成器,他生成的代码不是以pushad/popad作为起始与结束。

SEH - 结构异常处理

SEH也可以被利用。它可以获取当前进程的内容结构,以此来读取或者修改调试寄存器。这些寄存器被用于硬件断点(BPM),如果可以设置他们,就可以取消硬件断点。

利用SEH检测时间

这里是我发现的一些检测调试器的方法。将SEH上下文结构和一些时间检测技术结合起来,可以检测到一些Ring 3级的调试器和跟踪器。具体方法就是采用RDTSC指令(CPU基本指令周期)读取CPU运行周期,然后根据产生的异常来判断是否被调试。

在异常处理时,我们可以在上下文结构中从eax读取RDTSC指令执行结果,从而获取TSC。然后再次使用一下RDTSC命令,获取当前TSC值。比较2次TSC值,就可以判断出来是否被调试或者跟踪。如果是被调试,这2个值的差距很大。发现这种情况,可以修改EIP值,指向上下文结构。程序会返回错误地址,然后就会崩溃。但是由于不同的系统中CPUID指令有差别,返回的值并非存放在eax,而是放在ecx。

这个"bug"导致这种方法有时候会被检测到,但是这个事情目前来说还不是太严重。很多人会问为什么在TDTSC指令前面用CPUID指令。因为在有些CPU中,比如P4,需要用这条指令来强制CPU执行顺序。该指令是指定CPU同步的指令。如果不告诉CPU不使用000执行,就无法确定CPU执行代码的顺序,导致跟程序中原来的意思发生不同。有时候甚至会无故导致程序崩溃。

下面是这种检测方法的代码:

                pusha
                call        install_seh
                mov        ecx, [esp+40h+var_34]
                add        [ecx+context.eip], 2
                xor        eax, eax
                mov        [ecx+context.Dr0], eax
                mov        [ecx+context.Dr1], eax
                mov        [ecx+context.Dr2], eax
                mov        [ecx+context.Dr3], eax
                mov        [ecx+context.Dr4], eax
                mov        [ecx+context.Dr5], eax
                mov        [ecx+context.Dr6], eax
                mov        [ecx+context.Dr7], eax
                mov        eax, [ecx+context.eax]
                push        eax
                cpuid                                                                                ;保证CPU执行序
                rdtsc                                                                                ;获取Tsc
                sub        eax, [esp+44h+var_44]
                add        esp, 4
                cmp        eax, 0E0000h                                        ;比较返回值
                ja                short bad_reverse_enginer
                xor        eax, eax
                retn

bad_reverse_enginer:
                add        [ecx+context.eip], 63h
                xor        eax, eax
                retn

install_seh        proc near
               
                xor        eax, eax
                push        Dowrd ptr fs:[eax]
                mov        fs:[eax], esp
                cpuid
                rdtsc
                xor        ebx, ebx
                pop        dword ptr [ebx]
                pop        dword ptr fs:0
                add        esp, 0
                popa
               

E0000h 是设定的最大检测标记。如果这里得到的数据大于这个数值,说明有可能处于被调试状态。

发现手段

我这里采用了一个固定的数据进行检测: E0000h。一般也可以采用一个随机数而不是常数,这样可以防止一些人通过搜索特征码来发现这些地方。我也可以采用一些手段让每处SEH指令都看来不同。不过这种方法最大的缺点是每次使用都无法避免使用一些相同的指令。当然,检测者也可以通过写一个内核模型驱动来捕获每次RDTS异常,从而发现我们做的手脚,具体可以参考Intel手册。

BPX检测

由于我们要使用API函数,所以必须防止这些函数被BPX断点拦截到。除了可以利用GetProceAddress函数获取API地址然后检测该处的int 3的机器码(0xCC)外,我还有另外一种方法。我可以直接读取Import表获取API函数的地址,然后检查该处是否有断点。

逆向人员都对int 3了如指掌。为了更有迷惑性,我检测断点的时候,利用"SHR"指令来产生检测值:  0x66 shr 3 = 0xCC。然后程序检测API函数入口处是否有int3断点。如果发现被段下,我用一种很巧妙的方法让程序崩溃:采用RDTSC指令产生一个随机数,把这个随机数入栈,利用RET指令,达到修改EIP到这个随机的地址。那么程序就会随机崩溃在任意一个地方,这个地方会离检测代码很远,并且Soft ICE的Fault on 指令无法捕获到。(译:真恶毒阿~~~)

对付这种anti的方法:

首先,导入函数是不被保护的,所以别人可以察看导入函数,看到printf, GetCommandLineA 和 ExitProcess函数都被使用。别人可以在这些函数上下断点,或者至少猜测到这些函数会被使用。然后别人会猜测到这里会传来一个command参数。对付这种人,可以通过手工加载Import表来解决。

手工加载Import表,可以利用一个自己写的GetProceAddress函数查找需要导入的函数所在的dll的导出表。由于Kernel32的地址总是被存放在堆栈中,我们可以获取他的ImageBase(或者用PEB,SEH链表等等。。。),然后获取LoadLibaray的地址,以便加载所有函数。这样,有了自己写的GetProceAddress,加上获得的LoadLibrary地址,就可以加载任何别的函数。这样就可以利用import表来引用别的库函数。

但实际上还需要做一点。对于win2k系统,必须给自己的文件做一个import表,里面至少有一个引入函数,哪怕从Kernel32导入也可以。因为Windows 2000的PE Loader和WindowsXP不一样,XP系统允许PE文件没有导入表,而Windows2000不允许。

自己做的小的Import表用来进行欺骗。真正的引入表被加密,并在运行时候才被解压缩。

然后我们只需要模拟操作系统自己加载Import表。我们需要把API地址放到Import表中,逆向者只有追踪引入表的解压代码时候才可以获取我们的函数地址。

这种BPX保护也有自己的弱点。如果这个API函数有很多指令,我只需要检查API入口的开始4个字节,就可以知道是否有断点。如果别人把断点下在这4个字节后面,我可以采用一个用于检测每条指令长度的反汇编引擎(LDE)来区分每条指令的长度,从而避免检查的是错误的地点,具体见下。

由于一般的正常指令也可能含有0xCC字节,比如: Mov eax, 0x4010CC。因为这条指令的机器码也含有0xCC,因此或许会误把这条指令检查成断点。为此,我们可以用一个LDE判断这条指令的长度(5字节),但是int 3指令只有1、2个字节(0xCC或者 0xCD 0x03)。这样就可以正确越过这条mov指令而不检查他。

一旦检测到有BPX断点,我们可以不抱错,而把断点转设在其他API函数中。这样或许会导致别的系统错误,但是却隐藏了我们自己。

未完无续

图1:



图2:



原文:
http://www.codebreakers-journal.com/include/getdoc.php?id=124&article=57&mode=pdf


[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

收藏
免费 7
支持
分享
最新回复 (24)
雪    币: 200
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
作者的这篇文章是比较有难度的,而且技术性的词汇比较多,文章也很长,挺不好翻译的。
感谢nbw的译文,让我们这些不懂英文的人也能读到这么好的文章.
2005-4-7 00:36
0
雪    币: 244
活跃值: (265)
能力值: ( LV9,RANK:250 )
在线值:
发帖
回帖
粉丝
3
支持!!!!
2005-4-7 01:42
0
雪    币: 208
活跃值: (40)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
4
我也支持,收下了
2005-4-7 11:53
0
雪    币: 280
活跃值: (433)
能力值: ( LV12,RANK:450 )
在线值:
发帖
回帖
粉丝
5
不错,继续啊
2005-4-7 17:09
0
雪    币: 339
活跃值: (1510)
能力值: ( LV13,RANK:970 )
在线值:
发帖
回帖
粉丝
6
继续不了了。下面的东西超出了我的知识范围,只能自己看看,要是翻译出来,估计要误人子弟了。
2005-4-7 22:21
0
雪    币: 212
活跃值: (105)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
7
nbw别灰心,什么弄不懂的贴出来,大家讨论一下,找出解决方法.
2005-4-7 23:30
0
雪    币: 261
活跃值: (230)
能力值: ( LV8,RANK:130 )
在线值:
发帖
回帖
粉丝
8
nbw 发的必是精华

顺便说一下  那个链接打不开
2005-4-9 09:03
0
雪    币: 339
活跃值: (1510)
能力值: ( LV13,RANK:970 )
在线值:
发帖
回帖
粉丝
9
这篇文章作者写了一个Anti的程序,前半部分介绍原理,就是我上面翻译的这些。下面的就主要是他写的那个程序,我没找到程序下载。只能看那些有些缥缈的代码说明,大家有兴趣可以看看,也可以拿来讨论一下。

to:   hunter_boy  
那个连接我这里可以下阿。你重新下一下。不行的话我给你发过去也行。
2005-4-9 14:24
0
雪    币: 258
活跃值: (230)
能力值: ( LV12,RANK:770 )
在线值:
发帖
回帖
粉丝
10
学习.....
2005-4-9 15:33
0
雪    币: 390
活跃值: (707)
能力值: ( LV12,RANK:650 )
在线值:
发帖
回帖
粉丝
11
我打算翻译一下。不过需要时间。

发布日期不定。但是最后肯定会给一个完整版的。

不知NB王怎么看?
2005-4-9 18:41
0
雪    币: 339
活跃值: (1510)
能力值: ( LV13,RANK:970 )
在线值:
发帖
回帖
粉丝
12
最初由 firstrose 发布
我打算翻译一下。不过需要时间。

发布日期不定。但是最后肯定会给一个完整版的。

不知NB王怎么看?


呵呵。支持!关键是没有作者那个程序下载。要是有那个程序下载就爽了。有工夫的话可以直接跟作者联系,搞到那个程序。前段时间还看到2篇不错的anti教程,写的比这个通俗多了,也挺好。

我从来没真正搞过有anti的程序,看这些也只是在理论上瞅瞅,就算说东西,也没有底气,加上最近生活很不爽,暂且不搞了,大家多多搞,我多多支持  
2005-4-9 22:08
0
雪    币: 212
活跃值: (105)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
13
原程序在这,呵呵,支持firstrose的接手.附件:exe.rar
2005-4-10 00:39
0
雪    币: 339
活跃值: (1510)
能力值: ( LV13,RANK:970 )
在线值:
发帖
回帖
粉丝
14
Hello,

http://honeynet.org/scans/scan33/
You can find the binary here, as well as all the submissions.

Your english is easily understood, no worry.

Cheers,

--
Nicolas Brulez

Head of Software Security - Senior Software Security Engineer

The Armadillo Software Protection System
http://www.siliconrealms.com/armadillo.shtml

没想到是Armaillo

results will be released Monday, 10 January

明天就公布答案。

这个东西不错。要是不看资料把他给逆出来,那真牛人也!
2005-4-10 01:33
0
雪    币: 266
活跃值: (191)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
15
牛x
2005-4-10 02:33
0
雪    币: 390
活跃值: (707)
能力值: ( LV12,RANK:650 )
在线值:
发帖
回帖
粉丝
16
好,那我就接手

只要大家有耐心就可以了。
2005-4-10 11:49
0
雪    币: 261
活跃值: (230)
能力值: ( LV8,RANK:130 )
在线值:
发帖
回帖
粉丝
17
那个地址打开了  
不好意思呀  呵呵  是哦的问题
开了BT   感谢中
2005-4-10 14:46
0
雪    币: 263
活跃值: (10)
能力值: ( LV9,RANK:210 )
在线值:
发帖
回帖
粉丝
18
妙       
2005-4-23 23:52
0
雪    币: 538
活跃值: (32)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
19
学中文
2005-7-3 12:03
0
雪    币: 211
活跃值: (15)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
20
收下...
2005-7-10 19:08
0
雪    币: 61
活跃值: (160)
能力值: ( LV9,RANK:170 )
在线值:
发帖
回帖
粉丝
21
收下
2005-7-21 18:00
0
雪    币: 227
活跃值: (91)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
22
好东西。收藏ing。
2005-7-21 21:36
0
雪    币: 201
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
23
学习中,谢谢共享
2005-8-9 19:57
0
雪    币: 204
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
24
不错的文章收藏
2005-8-25 19:00
0
雪    币: 200
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
25
SEH - 结构异常处理

SEH也可以被利用。它可以获取当前进程的内容结构,以此来读取或者修改调试寄存器。
如果将“内容结构”翻成“上下文”结构就更好了。
2006-5-24 16:21
0
游客
登录 | 注册 方可回帖
返回
//