前置知识壳的类型12压缩壳:压缩的目的是减少程序体积,如 ASPack、UPX、PECompact 等;加密壳:加密是为了防止程序被反编译(反汇编)、跟踪和调试,如 ASProtect、Armadillo、 EXECryptor、Themida、VMProtect。

加壳代码执行123451、保存入口参数加壳程序在初始化时会保存个寄存器的值,待外壳执行完毕后,再恢复各寄存器的内容,最后跳到原程序执行。通常用 pushad/popad、pushfd/popfd 指令对来保存与恢复现场环境。(pushad 用来保存所有通用寄存器的状态,而 pushfd 仅用来保存标志寄存器的状态,一般在 32 位中使用)2、获取壳本身需要使用的API地址在一般情况下,外壳的输入表中只有 GetProAddress、GetModuleHandle和 LoadLibrary 这3个API函数。甚至只有 Kernel32.dll 及 GetProcAddress。如果需要使用其他 API 函数,可以通过函数LoadLibraryA(W) 或 LoadLibraryExA(W) 将 DLL 文件映射调用进程的地址空间,函数返回的HINSTANCE 值用于表示文件映像所映射的虚拟内存地址。

壳代码的基本流程1234567(1)保存寄存器环境 pushad pushfd(2)获取 API 函数的地址,加载一些必要的 API(3)解密代码和数据(4)填充 IAT(5)修复重定位(6)恢复寄存器环境 popad popfd(7)可能会进行大跳转到 OEP

断点类型123456789101112131415161718191、INT3断点(软件断点): 利用 F2 设置的断点就是 INT3 断点,打INT3断点的时候,断点处就会被CCh(INT3指令的机器码)替换。以OD为例当我们在某一汇编指令处设下INT3断点后,调试器会把所设断点地址处的第一个字节改为0xCC(即INT3指令),并把原字节保存。之所以我们看起来OD的此地址处字节没有发生任何变化是因为OD为了维持汇编代码的可读性并没有将改变后的指令进行重新反汇编。- 优点:数量没有限制,操作简单。- 缺点:因为改变机器码所以易被检测,只能在代码段中使用,而且因为其是基于中断的所以当中断描述符表被破坏时其将无效2、硬件断点 使用4个调试寄存器 DR0,DR1,DR2,DR3,设置地址,使用一个寄存器DR7设定状态,最多设置4个硬件断点,通过寄存器来记录需要中断的位置,可以分为:访问中断、写入中断、执行中断。- 优点:速度比较快,在INT3断点容易被发现的地方使用硬件断点— 缺点:数量有限3、内存断点 内存断点实际上是改变了一个内存区域或一个内存页的权限。内存断点的实现方式是将你欲下断地址所在的内存页增加一个名为PAGE_NOACCESS的属性,这个属性会把当前内存页设为禁止任何形式的访问,如果进行访问会触发一个内存访问异常。补充: 1.内存断点很消耗资源,因为PAGE_NOACCESS属性一设置就是一整个内存页无法访问,那么当程序访问该内存页中非断点地址的内容同样会触发异常。 2.虽然内存断点的效率经常很不理想,但是因为仅仅是修改了一个内存属性,所以内存断点可以下数量非常多、单断点范围非常大。这是它的优势。 3.只在写入时断下的内存断点通常是将内存属性设为PAGE_EXECUTE_READ,也就是不可写来实现的。对这种属性的内存进行写操作将会触发异常。

正片开始

单步跟踪法

ESP 定律

内存访问断点

1234567891、单步跟踪法 主要用到 F8 与 F42、ESP定律 ESP定律简单来说就是堆栈平衡,有进栈就要有出栈3、内存访问断点 壳做的事就是将加密或压缩的代码复原也就是对 .text 段的写入,如果我们对.text 段下了内存断点,那么中断的时候 .text 代码可能还没有完全写入,就需要不停地按 F9,另一种方法是再 .rsrc 段下断点,当发生中断时说明 .text 代码已经恢复完成了,这时候再在 .text 上下断点,当可执行完成跳转到OEP时就会停下来。