题目:
一、将_text,_rdata,_data合并成一个EXE文件,重建一个PE头
二、在第一步的基础上加入一个菜单
三、加入点击菜单调用MessageBox
*************************************************************
因为最终结果是一个用WIN32API(非MFC)编写窗口程序,所以建议用汇编一个差不多的程序,做为比较。
一、合成PE文件:
1、从名字上看_text,_rdata,_data分别应该是代码段,只读数据段(输入表之类)和可读写数据段。所以用WINHEX按顺序加上PE头后,复制粘贴就可以了。
WINHEX也用一个合成文件的功能。这时记做1.exe。
2、修改PE头数据。用STUD_PE打开1.exe,先修改“区段数目”,后区段的偏移和大小。这里题目没有规定文件大小,所以PE头大小最好为0X1000。分析如下:
DOS和PE头-0X0000-->┌┈┈┈┈┐
│ │
│0X1000 │
.text---0X1000---->├────┤
│ │
│0X6000 │
.rdata--0X7000---->├────┤
│ │
│0X1000 │
.data---0X8000---->├────┤
│ │
│0X3000 │
.rsrc---0XB000---->├────┤<---未加资源段时这里是文件结尾
│ │
│0X0200 │
文件尾--0XB200---->└────┘
根据以上图表得如下:
No | 名称 | 虚拟大小 | 虚拟偏移量| 原始大小 | 原始偏移量| 特性 |
01 | .text | 00006000 | 00001000 | 00006000 | 00001000 | E0000020 |
02 | .rdata | 00001000 | 00007000 | 00001000 | 00007000 | C0000040 |
03 | .data | 00003000 | 00008000 | 00003000 | 00008000 | C0000040 |
04 | .rsrc | 00000200 | 0000B000 | 00000200 | 0000B000 | 40000040 |
注:.rsrc段为加入菜单时才加入的,合成PE文件可以不看
根据文件的大小修改“镜像大小”。保存后,重新用STUD_PE载入,看它的提示,一般会提示“资源表”和“输入表”错误,“资源表”错误会让PE加载器无法
识别PE文件,所以先把“数据目录:IMAGE_DIR_ENTRY_RESOURCE”改成0。“输入表”错误只会至程序运行错误。
现在分析.rdata段,找到输入表的正确地址和大小。输入表是什么东西呢?
输入表的地址是:
IMAGE_OPTIONAL_HEADER.DataDirectory[1].VirtualAddress
输入表的大小是:
IMAGE_OPTIONAL_HEADER.DataDirectory[1].Size
VirtualAddress里存的是一个指向IMAGE_IMPORT_DESCRIPTOR数组的指针,该数组以一个全是0的IMAGE_IMPORT_DESCRIPTOR结构结束。
IMAGE_IMPORT_DESCRIPTOR结构的大小为0x14,所以输入DLL的个数等于输入表大小除以0x14后得的商再减1。
typedef struct _IMAGE_IMPORT_DESCRIPTOR {
union {
DWORD Characteristics; // 0 for terminating null import descriptor
DWORD OriginalFirstThunk; // RVA to original unbound IAT (PIMAGE_THUNK_DATA)
};
DWORD TimeDateStamp; // 0 if not bound,
// -1 if bound, and real date\time stamp
// in IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT (new BIND)
// O.W. date/time stamp of DLL bound to (Old BIND)
DWORD ForwarderChain; // -1 if no forwarders
DWORD Name;
DWORD FirstThunk; // RVA to IAT (if bound this IAT has actual addresses)
} IMAGE_IMPORT_DESCRIPTOR;
IMAGE_IMPORT_DESCRIPTOR结构主要看最后两个变量,DWORD Name为指向DLL名字的指针,DWORD FirstThunk指向一个数组,数组里存有该DLL函数名的指针。
分析正常EXE文件的输入表后发现,输入表一般是在调用函数名字的前面,用WINHEX打开1.exe,看地址0x77A0的地方,这里全是一些函数名,如:lstrcpyA和系统
,所以输入表应该在这0x77A0的前面(注意与正常文件对比),还有就是注意看DLL的名字,如:user32.dll的位置是0x788A,那么我们从0x7000的地方开始找0x8A
,找到后看看0x8A前面是不是0x78,这样就容易多了。在这里应该是0x7618,大小为0x50。
现在就差程序的“入口点”了,用PEID打开1.exe应该显示为VC6写的,这样我们就找一个VC6写的程序来看看它的入口,这是一个正常VC6程序的入口:
math.<Mod>/$ 55 push ebp
00409657 |. 8BEC mov ebp, esp
00409659 |. 6A FF push -1
0040965B |. 68 F8474200 push 004247F8
00409660 |. 68 20CD4000 push 0040CD20 ; SE 处理程序安装
00409665 |. 64:A1 0000000>mov eax, dword ptr fs:[0]
0040966B |. 50 push eax
0040966C |. 64:8925 00000>mov dword ptr fs:[0], esp
00409673 |. 83EC 58 sub esp, 58
00409676 |. 53 push ebx
00409677 |. 56 push esi
00409678 |. 57 push edi
00409679 |. 8965 E8 mov dword ptr ss:[ebp-18], esp
0040967C |. FF15 14224200 call dword ptr ds:[<&KERNEL32.GetVers>; kernel32.GetVersion
math.<Mod>/$ 55 push ebp
00409657 |. 8BEC mov ebp, esp
这句是每个函数都有的,不看它,看这里
00409659 |. 6A FF push -1
0040965B |. 68 F8474200 push 004247F8
00409660 |. 68 20CD4000 push 0040CD20 ; SE 处理程序安装
三个PUSH在函数里可不多呀,所在用WINHEX打开1.EXE,从0X1000开始找0X6A FF 68,会停在地址0X152A,看看前0X1527的地方,就是0X55 8B EC,所以这里应
该就是入口。修改完后一个PE格式的可执行文件就合成成功了。
二、加入菜单
刚才合成的程序是没有资源段的,所以要给它加入一个资源,里面只要有一个菜单就行了。资源文件这样写
#include <resource.h>
#define pediy.com 0x30
#define IDM_ABOUT 0x20
pediy.com MENU
BEGIN
popup "HELP"
BEGIN
menuitem "ABOUT",IDM_ABOUT
END
END
但程序是怎样把菜单加载到窗口里的呢?这里有几种方法:
1、用LoadMenu加载菜单并取得句柄后,设置CreateWindow.hMenu等于该句柄,但看看输入表,没有LoadMenu函数,所以用这种方法的话又要改输入表,太麻烦。
2、在写资源文件(.RC)的时候菜单名设置一个编号,在RegisterClass之前设置WNDCLASS.lpszMenuName等于这个编号或菜单名。
这里可先看看代码,看看它是怎样设置WNDCLASS.lpszMenuName的,用OD打开1.EXE,下断点在40125A,运行程序,停下后看ESP(我这是12FEAC),它是一个指
向WNDCLASS的指针,在内存窗口找到这个地址:
0012FEAC 03 00 00 00 D5 12 40 00 00 00 00 00 00 00 00 00 .ዕ@....
0012FEBC 00 00 40 00 27 00 01 00 03 00 01 00 10 00 90 01 .@'Ɛ
0012FECC F0 FE 12 00 A0 80 40 00 ﻰ肠@
WNDCLASS结构有10个成员,第9个就是菜单,所以12FEAC+(4*(9-1))=12FECC=》0012FECC F0 FE 12 00。看看12FEF0是什么,原来是一个字串"pediy.com"
,所以我用第2种方法,写资源时把菜单名写成"pediy.com"就不用改代码了。把资源写好后,生成EXE,用WINHEX剥离.RSRC段,粘贴到1.EXE后,再用STUD_PE改
一改段数据和“镜像大小”就可以了。
三、加入提示窗口
先看看正常的菜单是怎样响应单击事件的。单击菜单后,会由WM_COMMAND(0X111)消息处理,它的wParam参数就是被单击控件的ID,如果这个ID下有处理函数
就处理,没有就返回,我们要做的就是增加这个处理函数。
用OD打开1.EXE,找到消息循环:
0040120B |. 57 push edi ; /RsrcName => IDI_APPLICATION
0040120C |. 56 push esi ; |hInst => NULL
0040120D |. 891D ACAB4000 mov dword ptr ds:[40ABAC], ebx ; |
00401213 |. C745 B0 03000>mov dword ptr ss:[ebp-50], 3 ; |
0040121A |. C745 B4 D5124>mov dword ptr ss:[ebp-4C], 004012D5 ; |<========================这里面就是设置消息循环的地方
00401221 |. 8975 B8 mov dword ptr ss:[ebp-48], esi ; |
00401224 |. 8975 BC mov dword ptr ss:[ebp-44], esi ; |
00401227 |. 895D C0 mov dword ptr ss:[ebp-40], ebx ; |
0040122A |. FF15 04714000 call dword ptr ds:[<&USER32.LoadIconA>; \LoadIconA
00401230 |. 57 push edi ; /RsrcName => _ARROW
00401231 |. 56 push esi ; |hInst => NULL
00401232 |. 8945 C4 mov dword ptr ss:[ebp-3C], eax ; |
00401235 |. FF15 08714000 call dword ptr ds:[<&USER32.LoadCurso>; \LoadCursorA
0040123B |. 56 push esi ; /ObjType => WHITE_BRUSH
0040123C |. 8945 C8 mov dword ptr ss:[ebp-38], eax ; |
0040123F |. FF15 18704000 call dword ptr ds:[<&GDI32.GetStockOb>; \GetStockObject
00401245 |. 8945 CC mov dword ptr ss:[ebp-34], eax
00401248 |. 8D45 F4 lea eax, dword ptr ss:[ebp-C]
0040124B |. 8945 D0 mov dword ptr ss:[ebp-30], eax
0040124E |. 8D45 B0 lea eax, dword ptr ss:[ebp-50]
00401251 |. BF A0804000 mov edi, 004080A0 ; ASCII "pediy.com"
00401256 |. 50 push eax ; /pWndClass
00401257 |. 897D D4 mov dword ptr ss:[ebp-2C], edi ; |
0040125A |. FF15 0C714000 call dword ptr ds:[<&USER32.RegisterC>; \RegisterClassA
来到消息循环,发现没有WM_COMMAND:
004012D5 /. 55 push ebp
004012D6 |. 8BEC mov ebp, esp
004012D8 |. 83EC 40 sub esp, 40
004012DB |. 8B45 0C mov eax, dword ptr ss:[ebp+C]
004012DE |. 48 dec eax ; Switch (cases 2..F)
004012DF |. 48 dec eax
004012E0 |. 74 68 je short 0040134A
004012E2 |. 83E8 03 sub eax, 3
004012E5 |. 74 4D je short 00401334
004012E7 |. 83E8 0A sub eax, 0A
004012EA |. 74 14 je short 00401300
004012EC |. FF75 14 push dword ptr ss:[ebp+14] ; /lParam; Default case of switch 004012DE
004012EF |. FF75 10 push dword ptr ss:[ebp+10] ; |wParam
004012F2 |. FF75 0C push dword ptr ss:[ebp+C] ; |Message
004012F5 |. FF75 08 push dword ptr ss:[ebp+8] ; |hWnd
004012F8 |. FF15 F4704000 call dword ptr ds:[<&USER32.DefWindow>; \DefWindowProcA
004012FE |. EB 54 jmp short 00401354
注意看有三个JE,JE前面是SUB和DEC命令,不是平时的CMP命令,有猫腻。CMP是不保存结果的,但SUB和DEC是保存结果的,所以可以写成这样:
CMP EAX,2
je short 0040134A
CMP EAX,2+3
je short 00401334
CMP EAX,2+3+A
je short 00401300
所以就要在这些比较之前用"跳出跳回法”写入WM_COMMAND消息处理。问题又来了,输入表里面没有MessageBox怎么办。我是用风险比较大的方法,就是绝对地
址,因为大多数程序在加载USER32.DLL等函数的时候,它的分配的空间90%都是一样的,所以直接使用USER32.DLL的空间加上MessageBox函数的偏移就可以了。
至于字串,可以在数据段加入,注意换行符为0X0A。最终如下:
004012D5 . 55 push ebp
004012D6 . 8BEC mov ebp, esp
004012D8 . 83EC 40 sub esp, 40
004012DB . E9 A0580000 jmp 00406B80 《=====跳出
00406B80 > \8B45 0C mov eax, dword ptr ss:[ebp+C]
00406B83 . 3D 11010000 cmp eax, 111 ; Switch (cases 2..111)
00406B88 . 74 0D je short 00406B97
00406B8A . 48 dec eax
00406B8B . 48 dec eax
00406B8C .^ 0F84 B8A7FFFF je 0040134A
00406B92 .^ E9 4BA7FFFF jmp 004012E2 《=====跳回
00406B97 > 6A 40 push 40 ; Case 111 of switch 00406B83
00406B99 . 68 8C804000 push 0040808C ; ASCII "pediy"
00406B9E . 68 50804000 push 00408050
00406BA3 . 6A 00 push 0
00406BA5 . E8 06000000 call 00406BB0
00406BAA .^ E9 A3A7FFFF jmp 00401352
00406BAF 00 db 00
00406BB0 $- FF25 B86B4000 jmp dword ptr ds:[406BB8] ; user32.7696EA11
00406BB6 00 db 00
00406BB7 00 db 00
00406BB8 . 11EA9676 dd user32.7696EA11