浅谈脱壳中的附加数据问题(overlay)
Author:Lenus
From: www.popbase.net
E-mail:[email protected]
--------------------------------------------------
1.前言
最近,在论坛上看到很多人在弄附加数据overlay的问题,加上上次答应了各位兄弟所以觉得写一些着方面的废话。如果下面的内容对你有帮助那是最好。
这篇文章我们将解决以下问题:
1.什么是overlay,怎么找到overlay?
2.为什么有些壳虽然有overlay但是却不用特别处理?
3.为什么有些壳只用粘贴overlay数据就ok了,而有些壳却要定位指针?
4.如何修复文件指针?
--------------------------------------------------
2.正文
一.什么是附加数据(overlay)
1.实际当中的overlay
其实,overlay虽然大家在脱壳当中觉得很陌生,但是他离我们并不遥远。在我们平时使用的软件当中,有一些软件要处理一些数据流文件,比如winamp。当我们下载了mp3文件(数据文件),没有播放器是不可能播放的,与此相关的还有很多,比如txt文件和notepad的关系也差不多。而这些数据文件被单独的保存在硬盘上,当我们使用notepad的打开功能的时候,就可以去读取数据文件里面的东西了。
overlay又是什么意思呢?他其实真正的意思就是取消打开功能,将这些需要读取的数据放到pe文件的后面,让程序自动的运行打开的功能。这样的功能就变成了一个notepad的程序对应只能打开一个文件。
最典型的就是一些软件可以把一些数据流文件生成exe文件,比如一些mp3生成器,flash生成器,以及我们用来做动画的S-demo。他们的作用就是将数据对pe进行捆绑。(这样做的结果也就是为什么我们对这些文件用UPX等pe压缩工具却不能压缩他的原因,这是后话了)
2.技术上的overlay
在我们对pe文件的overlay进行分析之前,我们要普及一下文件映射的知识。
在pe里面,有所谓的文件偏移RA,文件偏移大小RS和与其对应的虚拟地址偏移VA,虚拟地址偏移大小VS。
我们要深刻的理解以上的概念不是我这篇文章能说清楚的,但又是搞overlay必须得弄清楚的,于是我简单的说明一下。
在我们的磁盘上的pe文件里面,排列着的数据在运行的时候将被影射到内存空间。他们将被怎么影射呢?举个例子:
例1:
一个pe文件中只有两个区段(pe head不算)
第一个区段是.text VA=401000 VS=1000 RA=200 RS=100
第二个区段是.data VA=402000 VS=1000 RA=300 RS=100
假设我们打开winhex看到在文件偏移300处的情况是这样的:
RA=2FF 处的数据是12 RA=300处的数据是34
好了,现在当pe文件装到内存中,那么会出现什么结果呢。
1.文件偏移中的200开始的100个字节将被影射到内存的401000开始的100个字节,后面的F000个字节将用0填充。
2.文件偏移中的300开始的100个字节将被影射到内存的402000开始的100个字节,后面的F000个字节将用0填充。
也就是说,在磁盘是上相邻的两个数据12和34,在内存空间中将分开得老远,12在40102FF处,而34却在402000处。
当然这只是我自己随便举的一个极端的例子,实际上由于文件的对齐机制在磁盘上每个段的结束都是填充了大量的0
为什么要讲这些呢?
因为区段是一般将会被映射到内存的,如果上面的例子中。将.data区段去掉。那么在磁盘上就将会留下从RA=300到RA=400其大小为100的数据,不会被影射到内存中。而这部分数据就将被认为是附加数据-overlay。
3.做个实验
1.用lordPE的pe editor打开一个notepad
2.打开他的senctions看看
3.把他的最后一个区段的文件大小RS修改一下(改成F00吧,少影射100的文件)如图
此主题相关图片如下:
4.用peid打开来看看吧。
PEID显示:Microsoft Visual C++ 6.0 SPx Method 1 [Overlay]
说明什么问题呢?
1.overlay只是数据他是不映射到内存的,他将被程序以打开自己的方式来读取数据
2.只要不是区段里面包括的文件的大小,将被视为overlay
好了到这里可以总结一下了:
1.附加数据是在附加在文件后面的,不被映射到内存空间中的数据,他提供他自己的程序打开自己来读取,所以dump下来的时候是没有overlay的,需要我们手动把这一部分的数据粘贴到dump下来的数据后面。
2.一般来说在区段里面我们能找到所有区段的大小,这个大小的后面就是overlay的开始,于是对于上面的实验来说,他的overlay的开始地方就是最后一个区段的RA+RS(就是C000+F00=CF00)大小是从CF00到D000的最后100。
3.现在可以回答一个简单的问题了。为什么有些壳是overlay的,但是我们却不用处理他。因为他根本没有读取后面的数据,按照我们上面的实验我们完全可以做另一个实验就是在原来的notepad后面添加一个字节的00,而这时peid也会视为是overlay。试问这样的overlay难道我们也要复制他吗?
二.如何解决附加数据的问题
win32程序对文件的操作有两中普通的文件操作和内存镜象文件,在这里我们主要是讨论普通的文件操作。
1.要回答这个问题首先就会关系到两个函数CreateFileA和SetFilePointer。对于CreateFileA他是打开文件的函数,当你下断CreateFileA你会发现程序断下的时候,他的第一个参数就是这个pe文件在磁盘上的完全路径,表示他正在对文件打开,将要对他进行文件读写操作。这个函数我就不多说了。下面详细说说SetFilePointer
DWORD SetFilePointer(
HANDLE hFile, //在用CreateFileA打开后得到的文件句柄
LONG lDistanceToMove, //要移动的距离,这个是低32位
PLONG lpDistanceToMoveHigh, //要移动的距离,这是高32位,要注意这是一个指向数据的指针
DWORD dwMoveMethod //表示指针开始的位置
);
因为用CreateFileA打开一个文件后,系统会给这个文件维护一个指针,一开始是指向文件的第一个字节的。这个函数的作用是人为的移动这个文件指针。理由很简单,overlay数据不是在文件的第一个字节处。而他的返回值就是移动后的文件的新指针的位置。
上面的有两个参数要注意:
1.lpDistanceToMoveHigh这个和上面的lDistanceToMove不一样,他是一个指针。指向一个32位的内存地址里面放着高32位的移动距离,他和lDistanceToMove的低32位的距离合起来表示一共要移动的距离(感觉是多此一举,一个32位就可以寻址4G,难道有一个4G的文件!?,而且高32位还设置成指针,难道是怕直接引用还不够?MS真是考虑得长远!)
2.dwMoveMethod有3个参数
FILE_BEGIN =0 表示指针不管当前的位置在什么地方,从第一个字节开始算。
FILE_CURRENT =1 表示指针继承上面的,最后移动到的位置
FILE_END =2 表示指针不管当前的位置在什么地方,从最后一个字节开始算。
2.为什么dump下来的程序需要定位overlay?
dump的意思就是将内存的数据全部存储到磁盘,也就说对于上面的那个例1的例子来说,当我们从磁盘再把他dump下来的时候并不是还是400的大小了,而是3000的大小了!这一点请务必弄清楚。这时在原来文件中在200位置的数据,在dump下来的文件中就在1000处,依次类推可以得到其他的文件位置,这也就是为什么我们在dump下来后需要将RA=VA RS=VS的原因,幸亏体贴的lordPE自动的完成了这一个步骤!
这时虽然我们把overlay的数据粘贴到脱壳后的程序,但是与原来的文件位置完全变了。导致我们脱壳以后要重新定位文件指针让他读取到正确的数据。
3.为什么有些程序粘帖了overlay的数据就ok了?
这个东西口说无凭让我们来实战一下吧。就用上次pendan2001兄弟给的做例子吧。
http://nj2.onlinedown.net:81/files/zmgb2.0.rar 或 桌面钢笔V2.0 http://www.wltv.net
在脱完壳以后,修复输入表。发现运行不了。呵呵~没有填加附加数据嘛,不然我举这个例子干什么。
看他的区段发现他的最后一个区段是8E00开始,大小是1000。所以我们估计他的附加数据是从8E00+1000=9E00开始的地方。
用winhex打开未脱壳文件,到9E00处吧。呵呵~发现上面有很多0,这就是为什么有些牛人告诉我们找前面是0的原因,其实其本质是因为文件的对齐机制。
复制从这里到后面全部的数据到修复好输入表的那个(为什么是要先修复输入表,等下你就知道了^^)
好了,可以运行了。不明白为什么是吗?
到程序里面看看吧
用od载入,下断bp CreateFileA 和bp SetFilePointer F9以后断下了,看堆栈
0012FC70 00401183 /CALL 到 CreateFileA 来自 桌面钢笔.0040117D
0012FC74 0012FDA0 |FileName = "G:\downloads\桌面钢笔V2.0.exe" //开始打开文件了
0012FC78 80000000 |Access = GENERIC_READ
0012FC7C 00000001 |ShareMode = FILE_SHARE_READ
0012FC80 00000000 |pSecurity = NULL
0012FC84 00000003 |Mode = OPEN_EXISTING
0012FC88 00000080 |Attributes = NORMAL
0012FC8C 00000000 \hTemplateFile = NULL
0012FC90 0041F2B7 桌面钢笔.0041F2B7
再运行又断下
0012FC7C 004011A4 /CALL 到 SetFilePointer 来自 桌面钢笔.004011A2 //移动指针
0012FC80 00000030 |hFile = 00000030
0012FC84 FFFFFFF8 |OffsetLo = FFFFFFF8 (-8.) //负数表示向前移动
0012FC88 00000000 |pOffsetHi = NULL
0012FC8C 00000002 \Origin = FILE_END //表示从文件的最后开始算
又F9一下
0012FC7C 00401220 /CALL 到 SetFilePointer 来自 桌面钢笔.0040121E
0012FC80 00000030 |hFile = 00000030
0012FC84 FFF5F738 |OffsetLo = FFF5F738 (-657608.)
0012FC88 00000000 |pOffsetHi = NULL
0012FC8C 00000002 \Origin = FILE_END //又是从文件的最后算起
好了,如果你再按一次会发现还是用FILE_END这个参数
ok,知道什么了没有?
我来解释一下用FILE_END参数表示指针从文件的最后开始移动,而附加数据无论在原来未脱壳的文件中和还是在脱壳的文件中,因为我们复制的是整个overlay区域,他的overlay部分的数据对于最后的一个字节的距离是不变的。他每次都用FILE_END当然我们这样复制就不需要重定位指针了,也就是说即使我们把原来的文件(从0地址开始)全部复制到脱壳后的文件中也是能够运行的,因为他是靠与最后的地址的距离来定位读取数据。换个说法,如果这个时候你把最后一个数据删掉,或者少复制哪怕是一个字节,就不能运行了。这也是我们为什么要先修复输入表的原因。
总结:也就是说如果你把附加数据复制到脱壳后的程序发现能够运行,呵呵~恭喜你,你碰巧遇到用FILE_END为指针的程序。
4.下面我们来看看如果程序不用FILE_END的情况
还是以下面这个例子来说说吧
http://www.popbase.net/bbs/dispbbs.asp?BoardID=5&ID=1797 即 高等教育自学考试英语词汇全息速记 3.1 下载地址:http://chsst.6to23.com/
脱壳我就不说了。
打开区段发现这个附加数据的位置在4CA00+13724=60124(一个奇怪的位置^^)
复制以后发现不能运行,看来没这么好命,都是用FILE_END的啊!
我们要对比两个程序,所以最好两个都用OD载入,都到停在OEP处。
好了下断bp SetFilePointer
这个是脱壳以后的程序的,运行以后断下
0012FEBC 004091AA /CALL 到 SetFilePointer 来自 1_.004091A5
0012FEC0 00000168 |hFile = 00000168 (window)
0012FEC4 00060124 |OffsetLo = 60124 (393508.) //移动到60124处去
0012FEC8 00000000 |pOffsetHi = NULL
0012FECC 00000000 \Origin = FILE_BEGIN //从头开始移动
再看看未脱壳的,发现是一模一样的。这个60124不就是刚才附加数据开始的地方吗?
看来脱壳后的程序不管这么多,还是访问了原来的位置,导致不能运行。
好了,知道原因了以后我们就去解决他。关键是参数不对嘛~
ATL+F9返回看看他是怎么压栈的。
004091A0 /$ 51 push ecx ; /Origin
004091A1 |. 6A 00 push 0 ; |pOffsetHi = NULL
004091A3 |. 52 push edx ; |OffsetLo //这个EDX是关键
004091A4 |. 50 push eax ; |hFile
004091A5 |. E8 E2E0FFFF call <jmp.&kernel32.SetFilePointer> ; \SetFilePointer
004091AA \. C3 retn //到这里
F8 出来看看是什么地方对EDX赋上60124的。
00413780 . 0FB7C9 movzx ecx,cx
00413783 . 8B40 04 mov eax,dword ptr ds:[eax+4]
00413786 . E8 155AFFFF call 1_.004091A0
0041378B . C3 retn //到这里
还是没什么结果,F8 继续
00493570 . A3 C87A4A00 mov dword ptr ds:[4A7AC8],eax
00493575 . 33C9 xor ecx,ecx
00493577 . 8B15 CC7A4A00 mov edx,dword ptr ds:[4A7ACC] //哈哈~~找到你了
0049357D . A1 C87A4A00 mov eax,dword ptr ds:[4A7AC8]
00493582 . 8B30 mov esi,dword ptr ds:[eax]
00493584 . FF56 0C call dword ptr ds:[esi+C] //从这里call进去到SetFilePointer的
00493587 . E8 54FEFFFF call 1_.004933E0 //到这里
原来是4A7ACC这的地址,对他下硬件写入,看看是什么时候写入了这个60124
好了hw 4a7acc 重来,胜利在一步一步接近我们。
00493496 . A1 D07A4A00 mov eax,dword ptr ds:[4A7AD0] //靠~是4A7AD0传给EAX的
0049349B . A3 CC7A4A00 mov dword ptr ds:[4A7ACC],eax //原来是这个EAX传过去的
004934A0 . EB 07 jmp short 1_.004934A9 //断在这里,望上看
再来下HW 4A7AD0 重来。
0049927C |. C705 D07A4A00 24010>mov dword ptr ds:[4A7AD0],60124 //小样,还找不到你
00499286 |. C605 B27A4A00 01 mov byte ptr ds:[4A7AB2],1 //断在这里
呵呵~~原来是0049927C这一句~~好了爆掉就可以了,把他改成你脱壳后的附加数据的开始地址就可以了。
总结一下:对于定位指针我们一般是依靠SetFilePointer这个函数来一步一步的寻找定位的问题的突破口,这里各人有个人的方法我就不多说了,关键还是看你调试程序的功力如何。在看雪上,FLY大虾的《VB函数速查.eXe 脱壳后附加数据的修复》http://bbs.pediy.com/showthread.php?threadid=8789
问题的实质并不是指针指错了,而是虽然指对了overlay里面的数据,可是读出来的却是关联到未脱壳前的错误数据。可见,虽然是同一个问题但是实质和解决的办法不可能千篇一律,就像解决校验,解决overlay问题也需要具体问题具体分析。
--------------------------------------------------
3.总结
现在我想大家再回答开篇提出的几个问题已经不难了,其实我在文中也有答案了,但是我要强调的一点是对于PE文件的区段映射的概念的理解深入将有助你理解overlay的问题。我在这也不多言了。
--------------------------------------------------
4.以后的思考
1.上面的那个60124是不是很奇怪啊,其实由于文件是对齐读入内存的,这部分的数据也将被影射到内存空间,不行你可以dd 4cb724看看。这就是说如果我们把区段扩大,把overlay也读入内存,虽然没什么用,但是却严重影响了我们对overlay的开始的判断!这种情况应该怎么拌呢?
2.换个思路,如果我们把文件的读写用于检验PE文件,应该怎么样设计一个简单的校验呢?
--------------------------------------------------
5.后话
本来这篇文章打算到放假才放出来毕竟现在太忙了,而且有很多想法也还不成熟,还有待实践,所以觉得这篇文章没有深入下去不得不说是一个遗憾,不过趁着新年就算给大家一个交代吧。随后我将进入紧张的复习阶段。但是,在这里还是要说一声:祝论坛里面的兄弟,新年快乐,万事如意。
谢谢您能看完,如转载请注明作者并保持文章的完整。