Table of Contents

Reference


实验效果


先弹出 error 窗口(恶意代码得以执行)

点击 OK 后弹出 hello world 窗口(执行原程序)

插入思路


两个思路

实验中给出了两种思路:

  1. 在 PE 文件中新增一个section,在该 section 中写入恶意代码然后最后在 JMP 到原本的 EntryPoint
  2. 在 PE 文件的节空隙中插入恶意代码

这里我使用了第二种方法,这种方法不用新增一个 section,涉及到的需要修改的东西也更少,所以更简单

在 section 空隙中插入机器代码

这里选择插入到 .text 节中,因为该节具有执行权限,该节内容如下

由此可见,有好多0x00,这些都可以被我们利用,写入有意义的恶意代码

程序流分析

修改后的程序流:

插入恶意代码


机器码的获得

在 vs2010 中获得我们将要插入的恶意代码的机器码

#include<stdio.h>

int main()
{
    printf("hello!!!!!!!!!!!!!!!!!!");

    _asm{
        mov eax,0x7796ed60 ;通过动调得到MessageBoxA的VA
        push 0h
        push 0h
        push 0h
        push 0h
        call eax ;调用该MessageBox函数
        call delta  ;动态定位方法
delta:
        pop ebp   ;获得当前位置实际VA,解决imagebase变化的问题
        sub ebp,0x1098; 减去当前位置的RVA,0x484-0x400+0x1000+0x14
        add ebp,0x1000 ; 加上OLD EP的RVA
        jmp ebp ;得到OLD EP的实际地址VA,跳转到Old EP
    }

    return 0;
}

对应机器代码

第一段机器码,功能:执行恶意代码,这里是弹出一个 Error 的弹窗

B8 60 ED 96 77 6A 00 6A 00 6A 00 6A 00 FF D0

第二段机器码,功能:重定位后返回到原本的EntryPoint

E8 00 00 00 00 5D 81 ED 84 10 00 00 81 C5 00 10 00 00 FF E5

关键地址

在上面的汇编代码中一共有 3 处涉及到内存地址,这三处都很重要

0x7796ed60

这个地址为 MessageBoxA 在内存中的地址,即如果能跳转到这里便是调用了该函数,获取方法:

使用 WinDBG 动态调试,首先发现 user32.dll 的加载基址是0x778f0000

重新加载程序 user32.dll 的加载基址仍是0x778f0000

在系统重启前,uesr32.dll 的加载基址应该是不变的,而且不同程序加载 user32.dll 其加载基址也应该是0x778f0000

然后在 user32.dll 中找到 MessageBoxA 的 VA

此即地址0x7796ed60的来历

0x1098

这个也许是本次实验最复杂的部分,其实也很简单

通过重定位call pop我们得到了pop ebp汇编指令的 VA(并不是恶意代码开头的 VA !!),然后将该 VA 减去其在 .text 的偏移((0x484+0x14)-0x400)再减去 .text 节在内存中的加载基址(0x1000)就是整个程序的加载基址 imagebase

这里还要注意的是 .text 的空隙这么多,我们从哪里开始插入呢,我们随便挑一空闲位置,从 PE 文件的0x484 开始插入恶意代码,这时计算pop ebp指令到恶意代码第一条指令mov eax, 0x7796ed600x14个字节,所以在计算时加上了这 0x14 个字节

修改的地方一,填入恶意代码:

修改的地方二,修改 EntryPoint 为插入恶意代码的 RVA(0x1000+(0x484-0x400)=0x1084):

0x1000

sub ebp,0x1098指令执行完成之后,ebp里面保存的便是 imagebase ,imagebase 再加上原本的 EntryPoint:0x1000,就是原程序的入口点的 VA,这时执行JMP就能正常执行原程序了

动调验证


初始断点:bp 0x401084,查看该处汇编代码:u 0x401084(代码段一)

果然是我们插入的代码

查看代码段二u 0x401093

执行到0x4010a5查看 ebp 的值

果然是0x401000,之后便执行正常代码