1.首先我们看看最原始的程序,我们之后要做的工作就是将他的功能进行拓展。
上面这个是使用Delphi编译的一个简单程序。
2.我们使用VC来编写一个DLL,写入主循环。
// lvusyy.cpp : Defines the entry point for the DLL application.
//
#include "stdafx.h"
#include <COMMDLG.H>
#include <WINDOWS.H>
#pragma comment(lib,"comdlg32") //必须包含lib
//BOOL OpenMenuS(HWND hWnd);
BOOL APIENTRY DllMain( HANDLE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
return TRUE;
}
BOOL _stdcall OpenMenuS(HWND hWnd)
{
char filename[513]={'\0'};
DWORD dw=0;
char buf[65536]={'\0'}; //可以动态申请内存....
OPENFILENAME opfn;
ZeroMemory(&opfn,sizeof(opfn));//使用前清空内存
opfn.lStructSize=sizeof(opfn);//结构体的大小
opfn.Flags=OFN_FILEMUSTEXIST; //标志
opfn.lpstrFilter="Text File(*.txt)\0*.txt\0\0"; //过滤器
opfn.lpstrFile=filename;//返回的文件全路径
opfn.nMaxFile=512;//最大长度
if(!GetOpenFileName(&opfn)) // 获得打开的文件名
{
return 0;
}
HANDLE hfile=CreateFile(filename,GENERIC_READ,FILE_SHARE_READ,0,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,0); // 在内存当中映射文件
if (GetLastError()) // 如果发生错误
{
MessageBox(NULL,"CreateFileERRER","ERRER",MB_ICONERROR);
return 0;
}
if (!ReadFile(hfile,buf,65535,&dw,0)) // 读取文件
{ CloseHandle(hfile);
MessageBox(NULL,"ReadFileErr","ERRER",MB_ICONERROR);
return 0;
}
CloseHandle(hfile);
if(!SetWindowText(hWnd,buf)) // 设置
{
MessageBox(NULL,"SetWindowTextERR","ERRER",MB_ICONERROR);
return 0;
}
return 1;
}
BOOL _stdcall SaveMenuS(HWND hWnd)
{ char buf[65536]={'\0'};
OPENFILENAME opfm;
char path[260]={'\0'};
ZeroMemory(&opfm,sizeof(opfm));
opfm.lStructSize=sizeof(opfm);
opfm.Flags=OFN_FILEMUSTEXIST;
opfm.lpstrDefExt=".Txt"; //这个是如果没输入后缀 程序会自动添加".txt"上去
opfm.lpstrFilter="Text File(*.txt)\0*.txt\0\0";
opfm.lpstrFile=path;
opfm.nMaxFile=259;
DWORD dw=0;
if (!GetSaveFileName(&opfm))
{
return 0;
}
HANDLE hfile=CreateFile(path,GENERIC_WRITE,FILE_SHARE_WRITE,0,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,0);
if (GetLastError())
{
return 0;
}
GetWindowText(hWnd,buf,65535);
if(strlen(buf)<1)
{ CloseHandle(hfile);
MessageBox(0,"获取编辑框内容失败!",0,MB_ICONERROR);
return 0;
}
if(!WriteFile(hfile,buf,65535,&dw,0))
{CloseHandle(hfile);
MessageBox(0,"写入文件失败!",0,MB_ICONERROR);
return 0;
}
CloseHandle(hfile);
return 1;
}
BOOL _stdcall AboutBoxS()
{
MessageBox(0,"获取编辑框内容失败!",0,MB_ICONERROR);
return 1;
}
LIBRARY "lvusyy" // DLL库名称
EXPORTS // 导出函数
OpenMenuS @1
SaveMenuS @2
AboutBoxS @3
之后编译过后产生Lvusyy.dll文件,使用LordPE查看刚才使用Delphi编译的程序,在输入表当中添加DLL函数。
之后我们使用ResScope给原程序添加两个菜单项目。
将图中所示文字NOP掉,得到几段空白地址。
00401204 . /EB 56 JMP SHORT Pediy_Ch.0040125C
00401206 . |EB 1A JMP SHORT Pediy_Ch.00401222
这两句是转向0040125C和401222地址的语句,我们跳转到所示的语句。
这里我详细解释一下这些语句的意思:
0040125C > \817D F8 729C0>CMP DWORD PTR SS:[EBP-8],9C72
00401263 . 90 NOP
00401264 . 74 24 JE SHORT Pediy_Ch.0040128A
00401266 . 817D F8 739C0>CMP DWORD PTR SS:[EBP-8],9C73
0040126D . 90 NOP
0040126E . 74 3F JE SHORT Pediy_Ch.004012AF
00401270 . 817D F8 539C0>CMP DWORD PTR SS:[EBP-8],9C53
00401277 . 90 NOP
00401278 .^ 74 8E JE SHORT Pediy_Ch.00401208
0040127A . 817D F8 459C0>CMP DWORD PTR SS:[EBP-8],9C45
00401281 . 90 NOP
00401282 .^ 74 A0 JE SHORT Pediy_Ch.00401224
00401284 .^ EB C0 JMP SHORT Pediy_Ch.00401246
第一句,是响应菜单编号为9752的,也就是说菜单编号为9C72的按下的时候,会执行
00401264 . /74 24 JE SHORT Pediy_Ch.0040128A
这一句其实就是OpenMenu的函数跳转
我们看看9C72翻译成十进制就是40050,其实就是我们刚才使用ResScope添加的菜单编号。
0040128A > \60 PUSHAD
0040128B . FF35 60304000 PUSH DWORD PTR DS:[403060]
00401291 . FF15 52504000 CALL DWORD PTR DS:[<&lvusyy.OpenMenuS>] ; lvusyy.OpenMenuS
00401297 . 61 POPAD
00401298 >^ EB AC JMP SHORT Pediy_Ch.00401246
这里 PUSH AD,是将参数压入栈,这个参数实质上就是编辑框的句柄。
0040128B . FF35 60304000 PUSH DWORD PTR DS:[403060]
这句,是将DS寄存器,偏移为403060所在的双字压入栈当中。
我们函数在实行的时候,编译器首先会将参数从右至左依次入栈,然后给程序调用。
我们看看403060是在哪里出现的吧:
00401173 > \6A 00 PUSH 0 ; /lParam = NULL
00401175 . 8B4D 14 MOV ECX,DWORD PTR SS:[EBP+14] ; |
00401178 . 8B51 04 MOV EDX,DWORD PTR DS:[ECX+4] ; |
0040117B . 52 PUSH EDX ; |hInst
0040117C . 6A 01 PUSH 1 ; |hMenu = 00000001
0040117E . 8B45 08 MOV EAX,DWORD PTR SS:[EBP+8] ; |
00401181 . 50 PUSH EAX ; |hParent
00401182 . 6A 00 PUSH 0 ; |Height = 0
00401184 . 6A 00 PUSH 0 ; |Width = 0
00401186 . 6A 00 PUSH 0 ; |Y = 0
00401188 . 6A 00 PUSH 0 ; |X = 0
0040118A . 68 C400B050 PUSH 50B000C4 ; |Style = WS_CHILD|WS_VISIBLE|WS_VSCROLL|WS_HSCROLL|WS_BORDER|C4
0040118F . 6A 00 PUSH 0 ; |WindowName = NULL
00401191 . 68 3C304000 PUSH Pediy_Ch.0040303C ; |Class = "edit"
00401196 . 6A 00 PUSH 0 ; |ExtStyle = 0
00401198 . FF15 1C204000 CALL DWORD PTR DS:[<&USER32.CreateWindow>; \CreateWindowExA
0040119E . A3 60304000 MOV DWORD PTR DS:[403060],EAX
这里很明显,使用API - Create WindowEXA创建了一个"edit"的编辑框,
0040119E . A3 60304000 MOV DWORD PTR DS:[403060],EAX
这一句,就是将编辑框的句柄压入EAX。
我们回到刚才菜单响应那里,看看
0040128A > \60 PUSHAD
0040128B . FF35 60304000 PUSH DWORD PTR DS:[403060]
00401291 . FF15 52504000 CALL DWORD PTR DS:[<&lvusyy.OpenMenuS>] ; lvusyy.OpenMenuS
00401297 . 61 POPAD
00401298 >^ EB AC JMP SHORT Pediy_Ch.00401246
最后一句。
00401298 >^ EB AC JMP SHORT Pediy_Ch.00401246
其实就是菜单执行完毕之后,返回主循环当中,使程序继续响应其他事件。
0040128A > \60 PUSHAD
0040128B . FF35 60304000 PUSH DWORD PTR DS:[403060]
00401291 . FF15 52504000 CALL DWORD PTR DS:[<&lvusyy.OpenMenuS>] ; lvusyy.OpenMenuS
00401297 . 61 POPAD
00401298 >^ EB AC JMP SHORT Pediy_Ch.00401246
我们其实可以将这一句:
00401291 . FF15 52504000 CALL DWORD PTR DS:[<&lvusyy.OpenMenuS>] ; lvusyy.OpenMenuS
看做是函数调用,这一句调用的便是我们DLL当中的OpenMenuS函数,只不过参数已经被上面压入栈了。
修改之后的程序就成了: