首页
论坛
专栏
课程

[原创]手工向可执行程序添加MessageBox弹窗(一)

2017-4-21 01:35 7863

[原创]手工向可执行程序添加MessageBox弹窗(一)

2017-4-21 01:35
7863

最近在学习PE结构,网上找到滴水第三期中关于PE的讲解,视频中是用impsg做的讲解,用那个实现起来比较简单,因为impsg文件对齐和内存对齐粒度一样,所以不需要考虑文件对齐和内存对齐,为了加深自己对PE文件的理解,事后我用notepad做实验时候总是出错,主要原因是notepad文件对齐和内存粒度不同导致FOA与RAW转换出错,最后将过程记录下来。文中难免有差错,请指正。

在此之前需要对Windows PE结构有个基本的了解。

1.准备工作

1.1获取MessageBox地址

方法①:

#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;
}

方法②: 用OllyDBG加载notepad,左下角command框中输入bp MessageBoxA下断点。查看断点即可得MessageBox地址为:0x77D507EA

1.2CALL和JMP的计算

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

main函数反汇编:

11:       func();
0040D478 E8 8D 3B FF FF       call        @ILT+5(func) (0040100a)
12:       return 0;
0040D47D 33 C0                xor         eax,eax

得到CALL的硬编码是E8
call func继续跟进去:

0040100A E9 01 00 00 00       jmp         func (00401010)
0040100F CC                   int         3

得到JMP的硬编码是E9
func函数反汇编:

6:        MessageBox(0, 0, 0, 0);
00401028 8B F4                mov         esi,esp
0040102A 6A 00                push        0
0040102C 6A 00                push        0
0040102E 6A 00                push        0
00401030 6A 00                push        0
00401032 FF 15 AC A2 42 00    call        dword ptr [__imp__MessageBoxA@16 (0042a2ac)]

观察到:

  地址           机器指令              汇编指令                 指令所占字节数
0040D478    E8 8D 3B FF FF   call @ILT+5(func)(0040100a)       5
0040D47D

E8后边的值并不是真正我们要调用的函数地址, 他们有以下关系:

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

真正要跳转的地址      E8后边的值       E8当前指令的下一行地址
  0x0040100A    =    0xFFFF3B8D     +    0x0040D47D

总结出:

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

1.3构造shellcode

通过之前的学习,可以构造出:

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

2.代码区添加shellcode

2.1分析PE结构

                                                                                 图1

图2具体信息.png

                                                                                图2  

从图2中可以得到以下信息:

IMAGE_OPTIONAL_HEADER(可选头)中部分成员信息:
DWORD   AddressOfEntryPoint;  // 程序执行入口RVA      (0x0000739D)
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.2判断空闲区域是否能放下shellcode

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

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

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

2.3将构造好的ShellCode写入空闲区:

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

在0x7B48与0x7C00之间写入shellcode:

图3.png

                                                 图3

2.4计算E8后边的值

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

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

文件中,E8下一行地址相对PointerToRawData偏移量:0x7B5D - 0x400 = 0x775D  

映射到内存中,E8下一行地址:ImageBase + VirtualAddress + 0x775D = 0x0100875D 

E8后边的值:MessageBox - 0x0100875D = 0x76D4808D

2.5计算E9后边的值

要保证MessageBox关闭后,程序能够正常运行,需要jmp到原来的OEP

原来OEP(真正要跳转的地址):ImageBase + AddressOfEntryPoint = 0x0100739D

文件中,E9下一行地址相对PointerToRawData偏移量:0x7B62 - 0x400 = 7762  

映射到内存中,E9下一行地址:ImageBase + VirtualAddress + 0x7762 = 0x01008762 

E9后边的值:`0x0100739D - 0x01008762 = 0xFFFFEC3B`

2.6修改OEP(AddressOfEntryPoint)

文件中shellcode起始地址相对PointerToRawData偏移量:0x7B50 - 0x400 = 0x7750  

映射到内存中,相对ImageBase偏移:VirtualAddress + 0x7750 = 0x8750  

将原来的OEP修改为,映射到内存后的shellcode起始地址(0x8750)。

另存文件,双击,成功弹出MessageBox,如图4:

图4.png



[招聘]欢迎市场人员加入看雪学院团队!

打赏 + 1.00
打赏次数 1 金额 + 1.00
收起 
赞赏  CCkicker   +1.00 2017/05/08
最新回复 (10)
kanxue 8 2017-4-21 15:41
2
0
感谢分享,这文章适合新手学习
空白即是正义 2017-4-21 16:13
3
0
构造PE和插shellcode是一个复杂的过程呢!
chinasmu 2017-4-21 20:11
4
0
感谢分享,这文章适合我等新手学习
黑手鱼 3 2017-4-23 19:21
5
0
挺棒的
hkxiaoyu 2017-4-23 19:57
6
0
这也能加精...
我的熊猫 2018-3-24 16:57
7
0
楼主有没研究过怎么直接调用messageboxa呢?最近在研究这个,怎么能直接得到messageboxa的地址,然后call一下,目前只能用call  [0042a2ac]去实现,但我用od调出了[0042a2ac]的值,然后直接call这个值,程序在od调试时能成功,但正常运行时出错。。,我在win10下的
严启真 2018-5-2 10:33
8
0
学习了,谢谢分享
冰冥星寻 2018-5-2 18:23
9
0
学习了,谢谢分享。。。
wbqsohucom 2018-10-10 17:08
10
0
谢谢分享
mb_nzrqtgjl 2019-4-17 15:20
11
0
程序在内存中真正运行的地址怎么求啊,是FOA转RVA然后加上imagebase么,还有SectionAlignment和FileAlignment不相同的话和这个有关系么?
最后于 2019-4-17 16:40 被mb_nzrqtgjl编辑 ,原因:
游客
登录 | 注册 方可回帖
返回