Anti-Attach 与修正镜像大小

在不方便使用调试器启动程序的时候,可以先运行目标程序,再使用调试器附加到目标进程;

Ring3 调试器附加使用 DebugActiveProcess 函数,在附加相关进程时,会先执行 ntdll.dll 下的 ZwContinue 函数,最后停留在 ntdll.dll 的 DgbBreakPoint 处;

事实上,调试器会在此处设置一个 INT3 断点,然后由调试器自己来捕获;

使用工具

  • OllyDbg 1.10原版,简称OD
  • OD 汉化插件均来自互联网;
  • UnPackMe来自互联网,仅供学习使用;
  • OEP 检索工具为 PEiD,来自互联网;
  • 进程管理工具为Estricnina,来自互联网;
  • 反 Anti-Attach 工具为 POKEMON_AntiAttach
  • Dump 工具为 OD 插件 OllyDump,来自互联网;
  • IAT 重建工具为 ImportREC,来自互联网;
  • PE 重建工具为LoadPE,来自互联网;
  • 文中特殊数字均是HEX,为了书写方便采用DEC

操作流程

  1. 运行 CM 后虚拟机变得非常卡,打开任务管理器会发现当前 CM 的 CPU 占用率为 99%;

  2. 打开 OD,在文件菜单选择附加 CM 后,会提示无法附加:

    在文件菜单选择附加

    不过这个 CM 的 Anti-Attach 好像不够完善,在弹出所有异常弹窗后,还是会附加成功;

  3. 同时查看堆栈窗口,可以看到调用来自 DgbBreakPoint:

    DgbBreakPoint

    并且函数的首行指令被替换为 INT3;

  4. F7 单步执行,程序返回到调用的下一行:

    程序返回到调用的下一行

    这里是 DbgUiRemoteBreakin 函数内部,为了使运行中进程能够即时中断到调试器中,操作系统提供了函数 DbgUiRemoteBreakin,其内部通过调用 DbgBreakPoint 产生一个中断异常从而被调试器捕获;

    为了实现及时中断,需要在运行中的进程中创建远程线程,线程回调函数就是 DbgUiRemoteBreakin 函数,最后的 RtlExitUserThread 就是结束远程线程;

  5. 使用 LoadPE 的 PE 编辑器打开程序,收集程序的重要信息:

    LoadPE

    基址是:00400000,镜像大小是:6000,代码段 RVA:10000,数据段 RVA 是:2000,不过 NumberOfRvaAndSize 肯定是不对的,应该是 10(10 进制的 16);

  6. 接着使用 PEiD 自带的插件 GenOEP 获取程序的 OEP:

    GenOEP

    OEP 为 401000,处于 LoadPE 获取的代码段区间,应该是正确的;

  7. 信息收集完成,运行 CM,然后使用 Estricnina 挂起 CM 的所有线程:

    Estricnina

    暂停 CM 的线程只是为了让 CPU 占用率降低,使虚拟机用起来不是那么的卡;

  8. 接着,使用 POKEMON_AntiAttach 绕过程序的反附加:

    POKEMON_AntiAttach

  9. 使用 LoadPE 定位到 CM 的进程,修正进程的映像尺寸:

    修正进程的镜像尺寸

    PE 的 IMAGE_OPTIONAL_HEADER 结构体中有个字段叫 SizeOfImage,作用是指出 PE 文件在载入内存后的总尺寸,还有个字段叫 ImageBase,表示 PE 文件在内存中的首选载入地址;

    在 Dump 文件时,一些关键参数是通过 MODULEENTRY32 结构的快照获取的,因此可以通过在 modBaseSize 和 modBaseAddr 字段中填入错误的值,让 Dump 软件无法正确读取进程中的数据,但由于修改 modBaseAddr 会使系统出现问题,因此只能修改 modBaseSize 的值;

    某些壳在做 Anti-Dump 的时候,会对内存中的 SizeOfImage 大小进行修改,通常是改得很小,这样,Dump 工具读取当前值后获取的就会是一个映像尺寸不对的无用文件;

    所以,所谓的修复镜像大小,就是读取磁盘文件的 SizeOfImage 与内存中的 SizeOfImage 进行大小比对,然后利用文件中的值进行修正;

  10. 接着,使用 OD 附加当前进程,程序中断在 ntdll.DbgBreakPoint 函数中,然后 goto 到 OEP:

    OEP

    可以看到,由于当前程序处于运行状态,所以代码段已经解码了;

    设置 EIP 为 OEP,即让 CPU 即将执行的指令指向 OEP:

    设置 EIP 为 OEP

    然后跟随任意调用,获取 IAT 相关信息:

    获取IAT相关信息

    IAT 起始 RVA 为:2000,Size 为:1C;

  11. 使用 OllyDump 将内存数据 Dump 下来:

    OllyDump

  12. 使用 ImportREC 获取并重建 IAT:

    ImportREC

  13. 直接运行修复后的程序,弹出错误弹窗:

    直接运行修复后的程序

  14. 将修复后的程序拖入 LoadPE,重建 PE:

    重建 PE

  15. 导入 OD 并运行程序,完美运行:

    完美运行