【转载】调试器设计(1)

Eddy 发布于2010-2-27 19:57:29 分类: 加密解密 已浏览loading 网友评论0条 我要评论

文章很好,学习之^_

标 题: 调试器设计(1) 
作 者: Tweek
时 间: 2010-2-25

工具:WINHEX  OD   debuggee(简单的弹出hello窗口的win32程序。)
平台:win7

         由于我还没有实现一个完整调试器。对没有测试和实现的部分,都以我自己的设计和理论分析介绍

       最开始了解调试器是看了《应用程序调试技术》这本书。当时觉得,实现调试器很简单,函数也不是很多。但是,后来做起来才发现一个很致命的问题。虽然,调试API不是很多。但是,如果对进程和PE的结构不够熟悉的话,完全无法解决出现的异常和运行错误。

       所以,在第一篇 介绍调试器实现的帖子里面。

      将大致介绍PE文件加载到内存中的形式。以及Ollydbg在分析和处理某些断点的过程中的问题来反映调试器实现的一些细节。


PE加载到内存:

1.  当你双击一个exe文件,系统将创建一个新的进程,对于xp系统,一般是2G用户态内存,2G内核态内存。当然,你自己可以通过修改编译器设置,修改这个大小。首先,PE_LOADER检测imagebase的地址是否可用。如果可用,按照exe给的Imagebase,为exe保留内存空间。如果不可用,则重新保留一片“没有用”的内存。事实上,windows并没有像我们想象的那样,在进程中为exe开辟一片空白的内存空间。Windows只是保留了一片空间。PE_LOADER利用MapViewOfFile将exe文件映射到内存中。但是和我们平时用MapViewOfFile不一样。PE_LOADER是以IMAGE的方式映射到内存。我相信(这个是推测)MS在内核态为保留地址和exe映射后的地址 维护了一张表,以便在需要的时候进行相互的转换。这样,只需要维护一张表,却节约了大批的内存。例如:我们运行debuggee,然后用winhex查看内存

名称:  查看内存.jpg查看次数: 433文件大小:  107.1 KB

PE_LOADER为debuggee保留了一片内存,从13c0000开始(基地址被重定位)。但是,这个13C0000的地址指向的并不是内存,而是映射而来的文件。当真正运行到相应代码的时候,才被读入内存。但是,在我们ring3的编程看来,他们都在内存里面了,这片内存已经被使用。

然后,PE_LOADER开始通过DOS头到Option头 再到数据目录表。然后找到exe将要加载的dll:

名称:  加载dll.jpg查看次数: 432文件大小:  67.1 KB

同样,dll的加载也只是保留地址,然后将dll文件映射到相应的地址,系统没有真的分配内存。PE_LOADER完全映射整个dll,并不是只映射exe会用到的函数。首先加载了Safemon,所有没有重定位,之后的所有dll都被重定位。

看似用户态内存被占用了很大。其实,就只是内核态在为映射和保留地址花费了小量的内存。

如果,我们再次运行一个debugee,PE_LOADER将不会重新创建映射,它会用刚才创建的映射文件。然后在新的进程里面,为exe的执行保留地址,建立对应的映射关系。两个进程用到的exe在物理上是同一个东西。
所以,在没有关闭前面的debuggee时,再次运行debuggee。虽然debuggee会重定位。但是

名称:  重定位但是.jpg查看次数: 429文件大小:  39.7 KB

他们重定位的地址是一样的。

如果关闭debuggee后,在运行,重定位的地址就只有很小的可能性是相同地址(也存在可能)
下图是我关闭debuggee后再次运行,重定位的地址

名称:  重定位地址.jpg查看次数: 429文件大小:  35.4 KB

但是,虽然他们物理上是同一个,逻辑上却是两个。Windows利用copy-on-write,避免他们之间的读写冲突。(这个是操作系统的范围,不是我们讨论的范围了。细节请参考操作系统方面的书籍)
但是,理论上,个人觉得还是有可能出现冲突的,比如:OD加载某个程序,你再次直接打开那个程序,然后做一些操作,关闭。OD有可能会有一些异常返回。


关于一些调试器的细节:

2.  在我们看来,PE_LOADER已经把exe和静态连接的dll都加载到内存里面了。然后PE_LOADER到了运行期库,开始初始化一些内存。然后,PE_LOADER利用重定位信息和可选头的入口地址到了我们自己写的入口(例如C 的main函数)。执行权就这样到了我们手里。但是,我们还不知道,我们的程序是以调试态在运行。到程序执行中出现异常。当然,权限又一次到了PE_LOADER手上,它就会按照一开始设计的方式,向调试程序发送相应的调试异常。

如果我们对debuggee 的MessageBoxW下断点。可以有两种方式,在程序代码里面调用MessageBoxW的位置设置断点。也可以在user32.dll的MessageBoxW函数上设置断点(因为通过上面的过程,我们已经知道user32.dll是被加载到我们的进程用户态空间。所以,对他的操作就和对自己的程序的操作需要的权限是一样的。)

OD支持这两种方式。
所以,我们用OD加载debuggee。

名称:  加载debuggee.jpg查看次数: 427文件大小:  49.6 KB

看到OD,成功获得程序的实际加载地址。

先用bp MessageBoxW

名称:  bpMessageBox.jpg查看次数: 430文件大小:  80.1 KB

在user32.dll上面 设置了int3断点。

查看内存,75E4EABF已经被改成CC

名称:  改成CC.jpg查看次数: 433文件大小:  83.4 KB

在查找模块名称  找到MessageBoxW然后下断

名称:  然后下断.jpg查看次数: 426文件大小:  77.1 KB

OD利用自己强大分析能力,找到了debuggee里面调用MessageBoxW的位置
断点下在debuggee上

名称:  debuggee上.jpg查看次数: 423文件大小:  69.4 KB

查看内存

名称:  后一个查看内存.jpg查看次数: 424文件大小:  71.2 KB

也已经被CC替代。

看起来很好了,没有什么问题,其实不然。
直接bp MessageBoxW运行的很好。成功中断,去掉断点,运行正常。

但是,在debuggee上的断点呢?

我们调试开始。程序在点击ok后

名称:  点击OK.jpg查看次数: 414文件大小:  10.5 KB
名称:  点击OK1.jpg查看次数: 413文件大小:  84.3 KB

成功中断。但是,好像有点问题了,如果我们去掉这个断点。继续运行,并没有弹出MessageBox框。程序异常终止。

我们再次来到OD断下的位置,滚动鼠标,你会发现。断点处的指令变了

名称:  指令变了.jpg查看次数: 414文件大小:  94.3 KB

再看刚才的地方  BF EAE475EA 如果按照内存数据的翻译过来 75E4EABF...

名称:  刚才的地方.jpg查看次数: 409文件大小:  8.9 KB

再看看MessageBoxW在dll上的地址 75E4EABF。

相信你已经知道是为什么了吧!

013A2440指向的并不是一条指令的开始。而是上一条指令结束。它们正好和下一条指令拼凑出了MessageBoxW的地址。

OD在分析debuggee调用MessageBoxW的时候。直接找到MessageBoxW在dll里面的地址75E4EABF,然后比对内存里面相同的位置。然后在此下断点。如果ctrl+G跳到对于地址,OD会直接用这个地址做为指令的开始。进行反汇编。刚好就那么巧,我写的debuggee就出现了个偶合。所以,断点下在了上一条指令的结束处。实现过断点之类的朋友都知道,程序断下后,EIP已经到了断点后面的地址。在恢复断点的时候,需要将EIP-1。如果断点刚好在指令的开始 ,那么,-1后正好。如果想刚才那样,在指令的结束,-1后,EIP并不会指向指令的开始。于是,程序反复异常,无法继续。

上面,大概介绍了一些关于PE加载到内存和调试器的一些实现细节。
希望可以让大家简单了解调试器和实现需要先知道的东西。

                                                             待续……

已经有(0)位网友发表了评论,你也评一评吧!
原创文章如转载,请注明:转载自Eddy Blog
原文地址:http://www.rrgod.com/decryption/380.html     欢迎订阅Eddy Blog

关于 调试器设计  的相关文章

记住我的信息,下次不用再输入 欢迎给Eddy Blog留言