OD加载程序,发现入口处有PUSHAD指令,尝试一下ESP定律
断在了这里,我们可以看到这里将给SEH链添加一个新的节点(**注意看fs:[]**),接着通过JMP指令跳转到下面引发一个异常,我们跟到JMP指令处。点击view-SEH chain查看,可以看到刚刚安装的异常处理程序入口地址为46590B。
不出意外的话到达该异常处理程序,随后就会到达OEP,这里OEP为4271B0(其实和之前的exe一样,只是加壳程序不一样)
修复IAT
和之前一样,我们看一下数据窗口以及find reference,看到这些IAT项目都和之前提到的IAT重定向一样,下图中00590000处的大小在壳执行之前长度为3000字节,但是壳执行以后,该区段变成了47000字节,即可壳将该区段增大了,并且增大的部分供自己使用。
右键Follow,看看被重定向到了哪里
我们先试一下Imprec自带的trace能否修复,show invalid—右键Trace Level1,可以看到修复了一部分
关键跳
先让程序停在OEP处,然后随便定位一个重定向过的IAT项。我们给460ECC这一项设置硬件写入断点,接着如果我们重启OD的话,硬件断点依然存在。重启运行起来,断在了这里
EDX的值460ECC,即指向了之前我们设置了硬件断点的那个重定向的IAT项,这里其被写入的是正确的值,随后会被修改为重定向过的值,我们继续往下跟,看看会发生什么。
到了这里,460ECC处的值有了变化,这里460ECC中被写入重定向过的值,ESI此时保存的就是重定向过的值。
也就是说正常的IAT项只会被写入一次,而需要被重定向的项将被写入两次。正常的IAT项在46577F的跳转处会直接跳转到4657B1,这里我们可以看到46577F这一处JMP指令,其将跳过红色箭头标注的设置为重定向值的指令,所以这里是关键跳,之前还有几处条件跳转,这里我们来尝试NOP掉写入重定向值的指令,看看效果如何。
但是我们首先需要给465799这条指令设置硬件执行断点,这里我们并不向想其被写入重定向的值,所以给其上一行设置硬件执行断点。然后重新加载程序运行起来,断了下来,我们将写入重定向值的指令NOP掉。
删除硬件断点后,运行起来发现行不通,再次回到关键跳处,可以看到46577D处的条件跳转将直接跳到下面,这样46577F处的关键跳将得不到执行,接着将被写入重定向的值,我们尝试将46577D这处条件跳转NOP掉,让其直接执行46577F处的JMP指令,我们来看看效果。
发现依旧不行,那么我们来看看IAT吧,我们可以看到IAT项都被修复了,都是正确的。这里我们有两种选择: 一,再开一个OD,加载该CrackMe的另一个实例,直接跟到OEP处,不修改任何东西,然后将当前我们这个实例的正确的IAT复制出来,覆盖掉新开的这个实例的IAT,注意是二进制复制,这个方法比较简单。二,直接用IMP REC定位到CrackMe的进程,此时IAT全部被写入正确的值了,但是还未到达OEP处,我们直接填上OEP,RVA,SIZE等数据,没有到达OEP处没有关系。以此来进行修复。
SEH链
每次我们定义了一个新的SEH异常处理回调函数,EXCEPTION_REGISTRATION结构的prev字段都被要求填写上一个EXCEPTION_REGISTRATION结构的地址,随着应用程序对模块的调用一层层深入下去的时候,那么最后回调函数会形成一个SEH链.
从数据结构的角度来讲,SEH链就是一个只允许在链表头部进行增加和删除节点操作的单向链表,且链表头部永远保存在fs:[0]
处的TEB结构中。
详细请看SEH笔记。
段寄存器
cs是代码段寄存器
1 | 存放当前正在运行的程序代码所在段的段基址,表示当前使用的指令代码可以从该段寄存器指定的存储器段中取得,相应的偏移量则由IP提供。 |
ds是数据段寄存器
1 | 当前程序使用的数据所存放段的最低地址,即存放数据段的段基址. |
ss是堆栈段寄存器
1 | 当前堆栈的底部地址,即存放堆栈段的段基址。 |
es是扩展段寄存器
1 | 当前程序使用附加数据段的段基址,该段是串操作指令中目的串所在的段 |
fs是标志段寄存器
1 | fs是80386起增加的两个辅助段寄存器之一,在这之前只有一个辅助段寄存器ES |
gs是全局段寄存器
1 | gs是80386起增加的两个辅助段寄存器之一,在这之前只有一个辅助段寄存器ES |
**ps:在x86平台的用户模式下,Windows将FS段选择器指向当前线程的TEB数据,即TEB总是由**fs:[0]**指向的,在x64平台上,这个指向关系变成了**gs:[0]**。**