【文章标题】: 【原创】给任务管理器添加拷贝命令的功能
【文章作者】: nickwu
【作者邮箱】: wuayi@qq.com
【软件名称】: taskmgr.exe
【下载地址】: 附件
【使用工具】: eXeScope,OD,LoadPE,ZeroAdd
【操作平台】: Windows XP SP2


要检验自己学到的知识到底有多少,或者自己还有哪些知识没掌握,最好的办法就是多实践,从实践中获得的成就感会让你更拼命的为之奋斗,这也是为什么我喜欢PEDIY的原因

在这之前先得感谢那些令人敬仰的前辈们,没有他们的文章,我碰到的困难会更多,所以很多时候,当我们欢呼雀跃于最终的胜利时,有必要回过头来想一下,
我们之所以成功,是因为站在巨人的肩膀上,而且那巨人还往往不止一个。。
参考文献:
stalker大侠写的《给任务管理器增加显示程序完整路径功能》
链接:http://bbs.pediy.com/showthread.php?t=82582
slore大侠写的《第一次PEDIY---让Windows 任务管理器更强,更人性!》
链接:http://bbs.pediy.com/showthread.php?t=95905


下面开始动工,

1.用LoadPE给taskmgr.exe添加输入函数:USER32.dll中的几个API(OpenClipboard、EmptyClipboard、SetClipboardData和CloseClipboard),等一下补丁代码要用到的,直接上图

                                          AddImport.jpg

2.用eXeScope添加相关菜单项,资源-->菜单-->111,插入一项菜单“50128,拷贝命令(&K)”,上图

                                          AddMenu.jpg

3.用ZeroAdd软件给它添加一个200KB的区段,用来写补丁代码

                                  ZeroAdd.jpg

用LoadPE可以看到最后一个区段的区段名为myCode  开始地址:1060000(虚拟地址)     3CA00(物理偏移)

                                  myCode.jpg

自己总结的一个经验,我认为修改程序的最佳步骤应该为:LoadPE(添加输入函数)-->eXeScope(添加资源项)-->ZeroAdd(添加区段),不按这个次序来可能会出错

接下来启用强大的OD在新增区段中添加消息响应代码,
一步一步来,我先在新增的区段中加入以下代码:

代码:
01060000    81F9 D0C30000   CMP ECX,0C3D0                                                   ;判断选择的是否是“拷贝命令”,50128的十六进制为C3D0
01060006    0F85 C6000000   JNZ taskmgr_.010600D2                                           ;如果不是就跳过下面相应的处理指令
0106000C    60              PUSHAD                                                          ;保存堆栈(可能保存环境更准确些,但我已经习惯了这么说~)
0106000D    8B40 08         MOV EAX,DWORD PTR DS:[EAX+8]                                    ;取出进程PID标识
01060010    83F8 04         CMP EAX,4                                                       ;判断是否为System.exe进程
01060013    0F84 B8000000   JE taskmgr_.010600D1                                            ;是的话就跳过处理,因为它是老大。。
01060019    50              PUSH EAX                                                        ;传递的第一个参数:dwProcessId(进程PID)
0106001A    6A 00           PUSH 0                                                          ;第二个参数:bInheritHandle
0106001C    68 10040000     PUSH 410                                                        ;第三个参数:dwDesiredAccess 
01060021    FF15 14110001   CALL DWORD PTR DS:[<&kernel32.OpenProces>; kernel32.OpenProcess ;API调用OpenProcess,原句为CALL DWORD PTR DS:[1001114]
01060027    36:A3 60010601  MOV DWORD PTR SS:[1060160],EAX                                  ;API返回的结果一般都在EAX里,这里返回的是一个具体进程的句柄,将其存入虚拟地址[1060160]处,这句按我的本意应该是"MOV DWORD PTR DS:[1060160],EAX",只是因为后来复制代码时一时疏忽忘改了,后来发现运行正常,将错就错吧~(Win7里面也畅通无阻==|)
0106002D    85C0            TEST EAX,EAX                                                    ;测试EAX,即判断OpenProcess是否执行成功
0106002F    0F84 9C000000   JE taskmgr_.010600D1                                            ;如果失败(0表示失败),就跳过下面的处理,来到恢复堆栈的地方
01060035    33C0            XOR EAX,EAX                                                     ;EAX清零
01060037    A3 68010601     MOV DWORD PTR DS:[1060168],EAX                                  ;真正的目的:为DS:[1060168]初始化
0106003C    68 04010000     PUSH 104                                                        ;第一个参数,至于这些参数是干嘛的,我现在也还没搞懂,不过这里的一些代码可以在原程序中照搬
01060041    8D85 F4FBFFFF   LEA EAX,DWORD PTR SS:[EBP-40C]                                  ;
01060047    50              PUSH EAX                                                        ;第二个参数,按照slore大侠的提示,这个应该是保存命令行的地方
01060048    FF35 60010601   PUSH DWORD PTR DS:[1060160]                                     ;第三个参数,这个是进程PID,下面也会用到的
0106004E    E8 F525FBFF     CALL taskmgr_.01012648                                          ;这里call的1012648是程序的一个子函数,作用是获取带参数的命令行
01060053    85C0            TEST EAX,EAX                                                    ;经典判断语句,需要知道的是EAX里面保存值的意义:命令行字符串长度
01060055    74 30           JE SHORT taskmgr_.01060087                                      ;获取命令行失败时就跳过下面的处理,来到关闭句柄的地方
01060057    8D3C00          LEA EDI,DWORD PTR DS:[EAX+EAX]                                  ;因为是Unicode的缘故,这里将EAX里的长度值扩大一倍
0106005A    8D47 02         LEA EAX,DWORD PTR DS:[EDI+2]                                    ;还要加上一个结尾符的长度
0106005D    50              PUSH EAX                                                        ;将算出来的EAX值推进去,意思是告诉系统需要分配多大内存
0106005E    6A 40           PUSH 40                                                         ;第二个参数uFlags,allocation attributes(内存分配属性)
01060060    FF15 38110001   CALL DWORD PTR DS:[<&kernel32.LocalAlloc>; kernel32.LocalAlloc  ;API调用LocalAlloc,用于分配内存
01060066    85C0            TEST EAX,EAX                                                    ;
01060068    74 1D           JE SHORT taskmgr_.01060087                                      ;内存分配失败就跳过下面的处理,来到关闭句柄的地方
0106006A    A3 68010601     MOV DWORD PTR DS:[1060168],EAX                                  ;虚拟地址[1060168]处保存的是分配到的内存"句柄"
0106006F    8BCF            MOV ECX,EDI                                                     ;要复制的长度值放入ECX
01060071    8BF8            MOV EDI,EAX                                                     ;字符串复制目的地址放入EDI
01060073    8BC1            MOV EAX,ECX                                                     ;复制字节数保存在EAX
01060075    C1E9 02         SHR ECX,2                                                       ;字节数/4=按DWORD复制的次数
01060078    8DB5 F4FBFFFF   LEA ESI,DWORD PTR SS:[EBP-40C]                                  ;字符串复制源地址存入ESI
0106007E    F3:A5           REP MOVS DWORD PTR ES:[EDI],DWORD PTR DS>                       ;执行字符串双字复制
01060080    8BC8            MOV ECX,EAX                      
01060082    83E1 03         AND ECX,3                                                       ;计算剩余字节数,即按字节复制次数
01060085    F3:A5           REP MOVS DWORD PTR ES:[EDI],DWORD PTR DS>                       ;执行字符串字节复制
01060087    FF35 60010601   PUSH DWORD PTR DS:[1060160]                                     ; /hObject
0106008D    FF15 5C110001   CALL DWORD PTR DS:[<&kernel32.CloseHandl>                       ; kernel32.CloseHandle
01060093    6A 00           PUSH 0 
01060095    FF15 67F00501   CALL DWORD PTR DS:[105F067]                                     ; USER32.OpenClipboard   
0106009B    09C0            OR EAX,EAX 
0106009D    74 32           JE SHORT taskmgr_.010600D1                                      ;OpenClipboard失败,就跳过下面的处理,来到恢复堆栈的地方
0106009F    FF15 8BF00501   CALL DWORD PTR DS:[105F08B]                                     ; USER32.EmptyClipboard ,拷贝之前清空一下剪贴板
010600A5    FF35 68010601   PUSH DWORD PTR DS:[1060168]                                     ; 参数1,hMem,数据句柄,前面获得的内存句柄,里面有带参数的命令行字符串
010600AB    6A 0D           PUSH 0D                                                         ; 参数2,uFormat,剪贴板数据格式
010600AD    FF15 44F00501   CALL DWORD PTR DS:[105F044]                                     ; USER32.SetClipboardData;API调用SetClipboardData,设置剪贴板的意思,带上面两个参数
010600B3    FF15 AFF00501   CALL DWORD PTR DS:[105F0AF]                                     ; USER32.CloseClipboard  ;API调用CloseClipboard,顾名思义,关闭剪贴板的意思
010600B9    6A 00           PUSH 0                                                          ; 参数1,uType 消息框类型:只显示一个确定按钮
010600BB    68 90010601     PUSH taskmgr_.01060190                                          ; 参数2,lpCaption,标题 UNICODE "OK",物理偏移:3CB90处开始的二进制:4F 00 4B 00,可以用其他十六进制编辑器修改,我是直接用OD添加的
010600C0    68 98010601     PUSH taskmgr_.01060198                                          ; 参数3,lpText,提示内容 UNICODE "复制完毕!",物理偏移:3CB98处开始的二进制:0D 59 36 52 8C 5B D5 6B 21 00   
010600C5    FF35 645D0101   PUSH DWORD PTR DS:[1015D64]                                     ; 参数4,hWnd,父窗口句柄,根据slore的提示,加上这句后可以保证提示窗口出现在任务管理器上方
010600CB    FF15 94120001   CALL DWORD PTR DS:[<&user32.MessageBoxW>>; USER32.MessageBoxW   ;MessageBoxW,作用:弹出消息框,提示"OK,复制完毕!"
010600D1    61              POPAD                                                           ;恢复堆栈
010600D2    81F9 CFC30000   CMP ECX,0C3CF                                                   ;保留的原指令                     
010600D8  - E9 74BDFAFF     JMP taskmgr_.0100BE51                                           ;跳回原代码
      

代码添加完后,全选这些代码,右键“复制到可执行文件”,保存为taskmgr1.exe
再用OD载入刚才生成的taskmgr1.exe,代码窗口来到100BE4B(文件偏移:B24B)处,将CMP ECX,0C3CF这句先NOP掉,再汇编为JMP 1060000,
选取红色的刚才修改过的代码部分,右键“复制到可执行文件”,另存为taskmgr2.exe
至此完成修改,测试通过!
摸来身旁的一杯可乐,小酌一口,超解暑的说~~(不过听说这玩意杀精,少喝为妙~~)

后记:
以上有关剪贴板操作的代码,部分参考了popeylj大侠写的一篇文章《汇编学习之剪贴版操作》
链接:http://bbs.pediy.com/showthread.php?p=544904

前不久看过“我是土匪”大侠写的《DIY 让我们的任务管理器再智能一点》
链 接: http://bbs.pediy.com/showthread.php?t=106870
觉得功能不错,于是便有了整合的念头
虽然没有经过他同意,不过我知道他是位好同志,一位打着“土匪”
口号“行侠仗义”的好同志,应该不会计较我复制了代码~呵呵
秉着资源共享的精神,我将完成后的作品上传到这里~~(毛爷爷说:要做一个纯粹的人,一个对社会有用的人......)
迄今为止,这个任务管理器是我所见过的功能最强的“官方”任务管理器~~

最后感谢看雪,感谢看雪大哥“发明”(开发)了看雪论坛,这是一个很好的交流知识和技术的平台,汇聚了众多高手,当然这里面不包括我,忽忽~~
祝大家学习生活愉快!