代码节空白区添加代码

代码节空白区添加代码

0x01 介绍

这里将使用系统自带的32位notepad程序为例,实现手工向其中添加MessageBox弹窗且不影响其正常功能。

0x02 准备

1、 获取MessageBox地址

方法1:使用OllyDBG加载notepad程序,在command框中输入bp MessageBoxA下断点,然后查看断点即可得到MessageBox地址为:0x77D507EA,如下图所示:

od_bp_MessageBoxA

方法2:使用代码获取,如下所示:
```
#include <stdio.h>
#include <windows.h>

typedef void (*FuncPointer)(LPTSTR);  // 函数指针  

int main()
{   
    HINSTANCE LibHandle;
    FuncPointer GetAddr;
    // 加载成功后返回库模块的句柄 
    LibHandle = LoadLibrary("user32");  
    printf("user32 LibHandle = 0x%X\n", LibHandle);
    // 返回动态链接库(DLL)中的输出库函数地址
    GetAddr=(FuncPointer)GetProcAddress(LibHandle,"MessageBoxA");   
    printf("MessageBoxA = 0x%X\n", GetAddr);
    return 0;
}
```

2、 CALL和JMP的计算

首先,编写一个简单的MessageBox程序,代码如下所示:

#include <stdio.h>
#include <windows.h>
void func()
{
    MessageBox(0, 0, 0, 0);
}
int main()
{
    func();    
    return 0;
}

查看main函数反汇编:

disass_call

得到CALL的硬编码为E8,继续跟进call func:

disass_jmp

得到JMP的硬编码为E9,再查看下func函数反汇编:

disass_func

可以看到MessageBox的四个参数的硬编码均为6A 00。

仔细观察一下:

地址           机器指令             汇编指令                     指令所占字节
0040D7E8   E8 18 38 FF FF       call @ILT+0(func) (00401005)         5
0040D7ED

可以看出E8后面的值并不是真正要调用的函数地址,他们存在以下关系:

E8当前指令的地址 + E8当前指令所占的大小 = E8这条指令的下一行地址
0x0040D7E8      +   0x5              =  0x40D7ED 

真正要跳转的地址 = E8后面的值  + E8当前指令的下一行的地址
0x00401005      = 0xFFFF3818 + 0x0040D7ED

所以可以总结出:

E8后面的值 = 真正要跳转的地址 - E8这条指令的下一行的地址
           = 真正要跳转的地址 - (E8当前指令的地址 + E8当前指令的大小)

3、 构造shellcode

通过之前的准备,可以构造出:

shellcode[] = {0x6A,0x00,0x6A,0x00,0x6A,0x00,0x6A,0x00,  /* MessageBox 4个参数入栈 */
0xE8,0x00,0x00,0x00,0x00, /* 调用MessageBox */
0xE9,0x00,0x00,0x00,0x00  /* 跳到源程序的入口(AddressOfEntryPoint) */
};

使用硬编码表示:

6A 00 6A 00 6A 00 6A 00 E8 00 00 00 00 E9 00 00 00 00

0x03 代码节空白区添加shellcode

1、 分析PE结构

分析结果如下图所示:

notepad_pe_1

notepad_pe_2

从上图中可以得出以下信息:

IMAGE_OPTIONAL_HEADER(可选头)中部分成员信息:
DWORD   AddressOfEntryPoint;  //程序执行入口RVA    (0x000073D9)
DWORD   ImageBase;  //程序的优先装载地址(基址)      (0x01000000)
//程序运行时,PE装载器先创建进程,再将文件载入内存,然后把EIP寄存器的值设置为:
ImageBase + AddressOfEntryPoint;
DWORD   SectionAlignment;  //内存中节的对齐粒度     (0x00001000)
DWORD   FileAlignment;  //文件中节的对齐粒度        (0x00000200)
IMAGE_SECTION_HEADER(节表第一项即代码区部分成员信息):
DWORD   VirtualSize;  //节区在内存中没有对齐前的实际大小  (0x00007748)
DWORD   VirtualAddress;  //节区在内存中起始位置(RVA)     (0x00001000)
DWORD   SizeOfRawData;  //节区在文件中对齐后的大小        (0x00007800)
DWORD   PointerToRawData;  //节区在文件中的偏移          (0x00000400)

2、 判断空闲区域是否能放下shellcode

计算文件中代码区空闲空间:

SizeOfRawData(0x7800) - VirtualSize(0x7748) > 0x12

空闲空间大于shellcode长度,可以放得下。

3、 将构造好的Shellcode写入空闲区

PointerToRawData(0x0400) + SizeOfRawData(0x7800) = 0x7C00

这里在0x7B48与0x7C00之间也就是空闲区中写入shellcode,如下图所示:

shellcode_write_in

4、 计算E8后面的值

在计算相关值由文件映射到内存时,需要考虑内存对齐和文件对齐。

真正要跳转的地址:MessageBox地址:0x77D507EA

文件中,E8下一行地址相对PointerToRawData偏移量:0x7B5D - 0x400 = 0x775D
映射到内存中,E8下一行地址:ImageBase + VirtualAddress + 0x775D = 0x0100875D
E8后面的值:MessageBox - 0x0100875D = 0x76D4808D

5、 计算E9后面的值

要保证MessageBox执行结束后,程序能够正常运行,需要jmp到原来的OEP:

原来的OEP(真正要跳转的地址):ImageBase + AddressOfEntryPoint = 0x0100739D
文件中,E9下一行地址相对PointerToRawData偏移量:0x7B62 - 0x400 = 0x7762
映射到内存中,E9下一行地址:ImageBase + VirtualAddress + 0x7762 = 0x01008762
E9后面的值:0x0100739D - 0x01008762 = 0xFFFFEC3B

修改之后完整的shellcode如下图所示:

shellcode

6、 修改OEP(AddressOfEntryPoint)

文件中shellcode起始地址相对PointerToRawData偏移量:0x7B50 - 0x400 = 0x7750
映射到内存中,相对ImageBase偏移:VirtualAddress + 0x7750 = 0x8750
将原来的OEP修改为映射到内存后的shellcode起始地址:0x8750

修改后的oep如下图所示:

modify_oep

7、 另存文件,运行程序,成功弹出MessageBox,如下图所示:

messagebox

点击“确定”之后发现是可以正常打开记事本的,如下图所示:

notepad