【文章标题】: 为程序添加右键快捷菜单
【文章作者】: laomms
【软件名称】: 附件下载
【操作平台】: winxp
【作者声明】: 只是感兴趣,没有其他目的。失误之处敬请诸位大侠赐教!

    WINRAR、FLASHGET等的右键快捷菜单很让人羡慕,它是通过Windows Shell扩展编程实现的,所谓的Shell扩展就是添加某种功能到Windows Shell的COM对象,然后通过注册COM控件在注册表中增加或删除相关键值。如果打开FLASHGET会发现目录有个上下文菜单扩展控件Jccatch.dll,通过注册该DLL实现右键菜单关联。本篇文章通过DIY为程序添加右键关联菜单。
我们就拿看雪大哥的pediy.EXE来做实验,要实现下图的功能:
 
 
 

一、编写COM控件。
    首先我们需要写一个上下文菜单扩展控件控件供程序调用,网上有很多的相关代码,我找了个DELPHI的,稍微修改了一下,源码看附件,名称是contextmenu.dll,跟其它的COM控件一样,也有四个输出函数:DllGetClassObject,  DllCanUnloadNow, DllRegisterServer,  DllUnregisterServer。我们想实现按下“添加右键关联”时调用DllRegisterServer注册控件,按下“取消右键关联”时调用DllUnregisterServer取消注册。

二、为程序添加DLL链接。
    接下来我们要做的就是将DLL引入到程序中,可以利用Loadlibrary函数和GetProcessAddress函数相结合,然后获取需要的函数的地址,也可以用LOADPE、IIDKING等工具加载,类似的文章已经很多了。我选择用IIDKing2.01。用IIDKing打开PEDIY.EXE,选择contextmenu.dll,IIDKing自动显示了DLL的所有输出函数,选中后添加,输入表中便已经有了contextmenu.dll。我们用LOADPE载入PEDIY.EXE,看看输入表中contextmenu.dll各函数的THUNKRVA值
  
DllRegisterServer为00005080,DllUnregisterServer为005084,那调用时就可以用基地址+THUNKRVA值的方法(记住勾选始终查看FIRSTTHUNK值)。
CALL DWORD PTR DS:[405080]调用DllRegisterServer
CALL DWORD PTR DS:[405084]调用DllUnregisterServer
 
三、为程序添加菜单项目
    我们先用Resource Hacker为程序添加两个子菜单:
  
    已知“ABOUT MENUDEMO”的菜单ID为40019(9C53H),再设置“添加右键关联”为40020(9C54H),“取消右键关联”40021(9C55H)。
由于原程序的关于的窗口是引用MessageBox消息框的,所以弹出的对话框信息我们就直接引用原来的MessageBoxA。

四、添加响应代码。
     简单讲一下程序是如何处理菜单响应的:当你按下菜单执行相应的命令后,CreateDialogBoxParamA会调用窗口回调函数来处理,即把包含菜单命令的信息作为参数送给窗口回调函数,有时候是CreateWindowsEx或者MessageBoxA,因为一般界面中菜单或按钮有多个,所以代码组合一般都是这样:
cmp eax,xxxx(菜单id) ;比较一下是不是这个菜单命令
jz label1 ;如果是就转到这个菜单的响应函数
jmp label2 ;如果不是就对比是否为下一个菜单
……
Label2:
cmp eax,xxx2(菜单id2) ;比较一下是不是这个菜单

jz xxxxxxxx2 ;如果是就转到这个菜单的响应函数
jmp label3 ;不是继续判断下一个

    查看PEDIY.EXE的输入表,知道关于的菜单响应函数是MessageBoxA,所以我们在OD中对MessageBoxA下断,F9运行程序后,按下ABOUT MENUDEMO,程序就被断下了。我们向上看,很容易看到菜单ID对比的地方:
004011FA  |.  8955 F8       MOV DWORD PTR SS:[EBP-8],EDX
004011FD  |.  817D F8 539C0>CMP DWORD PTR SS:[EBP-8],9C53            ;  是不是关于对话框的ID
00401204  |. /74 02         JE SHORT 00401208                        ;  更改这里的代码,跳到下面执行
00401206  |>  EB 1A         JMP SHORT 00401222
00401208  |>  6A 40         PUSH 40                                  ; /Style = MB_OK|MB_ICONASTERISK|MB_APPLMODAL
0040120A  |.  68 00304000   PUSH 00403000                            ; |Title = "pediy"
0040120F  |.  68 44304000   PUSH 00403044                            ; |Text = "逆向分析技术
(c) 段钢, 2002"
00401214  |.  8B45 08       MOV EAX,DWORD PTR SS:[EBP+8]             ; |
00401217  |.  50            PUSH EAX                                 ; |hOwner
00401218  |.  FF15 44204000 CALL DWORD PTR DS:[<&USER32.MessageBoxA>>; \MessageBoxA
0040121E  |.  33C0          XOR EAX,EAX
    我们需要对00401204处的代码进行更改,以便它跳到我们添加的其它菜单的ID对比,并执行相应的响应事件。找到空余的地方00401250处开始修改:
00401204  |. /74 02         JE SHORT 00401208                        ;  更改这里的代码,跳到下面执行,改为:
00401204  |.  EB 4A         JMP SHORT 00401250  


00401250  |>^\74 B6         JE SHORT 00401208                        ;  如果是关于对话框的ID,跳回到上面的对话框调用处
00401252  |.  817D F8 549C0>CMP DWORD PTR SS:[EBP-8],9C54            ;  如果不是,则判断不是添加右键关联的ID
00401259  |.  74 15         JE SHORT 00401270                        ;  如果是,就跳到401270处处理相关信息
0040125B  |.  817D F8 559C0>CMP DWORD PTR SS:[EBP-8],9C55            ;  如果不是,判断不是取消右键关联的ID
00401262      74 1C         JE SHORT 00401295                        ;  如果是,就跳到401295处处理相关信息
00401264  |.^ EB A0         JMP SHORT 00401206                       ;  跳回原来的地方
00401266  |   00            DB 00
00401267  |   00            DB 00


00401270      60            PUSHAD                                   ;  保护现场
00401271      FF75 08       PUSH DWORD PTR SS:[EBP+8]                ;  压入添加右键关联的ID
00401274      FF15 80504000 CALL DWORD PTR DS:[405080]               ;  调用contextm.Dll的DLLRegisterServer函数
0040127A      6A 40         PUSH 40                                  ;  对话框风格
0040127C      68 BA124000   PUSH 004012BA                            ;  对话框标题“关于”,写在了4012BA处
00401281      68 C5124000   PUSH 004012C5                            ;  对话框内容:已关联菜单,在4012C5处
00401286      6A 00         PUSH 0                                   ;  对话框句柄
00401288      FF15 44204000 CALL DWORD PTR DS:[402044]               ;  呼叫MessageBoxA函数
0040128E      61            POPAD                                    ;  恢复现场
0040128F  |.^ EB B5         JMP SHORT pediy.00401246                 ;  跳回到正常流程
00401291      00            DB 00
00401292      00            DB 00



0040127E      90            NOP
0040127F      90            NOP
00401295  |> \60            PUSHAD                                   ;  保护现场
00401296  |.  FF75 08       PUSH DWORD PTR SS:[EBP+8]                ;  压入取消右键关联的ID
00401299  |.  FF15 84504000 CALL DWORD PTR DS:[405084]               ;  调用contextm.Dll的DllUnregisterServer函数
0040129F  |.  6A 40         PUSH 40                                  ;  对话框风格
004012A1  |.  68 BA124000   PUSH pediy.004012BA                      ;  对话框标题“关于”,写在了4012BA处
004012A6  |.  68 D1124000   PUSH pediy.004012D1                      ;  对话框内容:已取消关联,在4012D1处
004012AB  |.  6A 00         PUSH 0                                   ;  对话框句柄
004012AD  |.  FF15 44204000 CALL DWORD PTR DS:[402044]               ;  呼叫MessageBoxA函数
004012B3  |.  60            POPAD                                    ;  恢复现场
004012B4    ^ EB 90         JMP SHORT 00401246                       ;  跳回到正常流程
00401291      00            DB 00
00401292      00            DB 00







004012BA      CC            INT3                                     ;  ASCII:关于
004012BB    ^ E1 CA         LOOPDE SHORT 00401287
004012BD      BE 00000000   MOV ESI,0
004012C2      00            DB 00
004012C3      00            DB 00
004012C4      00            DB 00
004012C5      D2D1          RCL CL,CL                                ;  ASCII:已关联菜单
004012C7      B9 D8C1AAB2   MOV ECX,B2AAC1D8
004012CC      CB            RETF                                     ;  远距返回
004012CD      B5 A5         MOV CH,0A5
004012CF      00            DB 00
004012D0      00            DB 00
004012D1      D2D1          RCL CL,CL                                ;  ASCII:已取消关联
004012D3      C8 A1CFFB     ENTER 0CFA1,0FB
004012D7      B9 D8C1AA00   MOV ECX,0AAC1D8
004012DC      00            DB 00

    打好补丁后另存为一个可执行文件,这时打开这个文件,按下“添加右键关联”,程序并在系统中注册了contextmenu.dll,并弹出对话框提示已关联菜单,按下“取消右键关联”则反注册DLL控件。可以在任意文件上右击,菜单中已经出现了“打开PEDIY”选项。当然直接注册DLL控件也能实现这样的功能,或者直接修改注册表键值也能达到效果。

【版权声明】: 本文原创于看雪技术论坛, 转载请注明作者并保持文章的完整, 谢谢!

                                              2006年08月17日 8:56:19

  • 标 题: 答复
  • 作 者:laomms
  • 时 间:2006-08-17 09:10

但是有一点,由于DLL中调用应用程序路径的语句用的是绝对路径,所以只能把PEDIY和DLL放在D盘根目录下。代码是这样的:
 ShellExecute(0,nil,'d:\pediy.exe',PChar(sSaveFile),charSavePath,SW_NORMAL);
我试着将d:\pediy.exe改为相对路径,但始终没有成功。试了以下几种方法:
   PathStr:=ExtractFilePath(Paramstr(0));
   Strname:=pathstr+'diy.exe';
   ShellExecute(0,nil,PChar(Strname),nil,nil,SW_NORMAL); 
   //WinExec(PChar(Strname), lpici.nShow);
--------------
    PathStr:=GetCurrentDir;
    Strname:=pathstr+'diy.exe';
    ShellExecute(0,nil,PChar(Strname),PChar(sSaveFile),charSavePath,SW_NORMAL);
---------------
function GetDllPath(): PChar;   //获取DLL路径
var 
  ModuleName: string;
begin
  SetLength(ModuleName, 260);
  GetModuleFileName(HInstance, PChar(ModuleName), Length(ModuleName));
  result := PChar(ModuleName);
end;

ShellExecute(0,nil,'pediy.exe',PChar(sSaveFile),GetDllPath(),SW_NORMAL);


但是都不成功,希望高手能够解决。

  • 标 题: 答复
  • 作 者:Buddhat
  • 时 间:2006-08-19 17:13

ShellExecute的第三个参数需要传入的是绝对路径,如果你需要省略路径,那么把
pediy.exe这个文件放入windows目录,传入参数时只给文件名就可调用了。

为什么放入windows目录就可以了,其实这里是一个全局环境变量起的作用(打开
一个console窗口,输入path,就可以看到当前的环境变量值)。函数会轮流搜索
这里的每一个值,改变这个环境变量值,加入一个新值D:\,调用ShellExecute传入
"pediy.exe"才会成功。

ExtractFilePath和GetCurrentDir这二个函数可能是Delphi的版本,我在MSDN上找
不到说明,不过我猜这二个函数返回的是进程的当前路径而不是DLL的路径。调用DLL
的进程是Explorer,所以你也要把pediy.exe放入和Explorer.exe相当的文件目录才
行。这二个函数的行为是不是和我说的一样,你输出这二个函数返回值看下吧。

GetModuleFileName我不知道Delphi的版本函数行为是怎么样的,不过在MSDN上的说
明函数返回的是一个包含文件名的完整路径,你把返回值直接拿来做为ShellExecute
的第5个参数,不正确吧?而且ShellExecute的这个参数是设计为进程运行后以这个
参数值为它的当前路径,但是你却认为这是指定加载时以这个路径找pediy.exe这个
文件,显然你理解错了它的意思。

最后,解决方法:
1、注册环境变量值(SetEnvironmentVariable),使用相对路径。
2、用了ExtractFilePath和GetCurrentDir这类函数,那么把pediy.exe文件放到调
   用该DLL的程序目录下吧。
3、自己处理路径保证传入ShellExecute的是绝对路径。

Buddhat