该文章首次发表于看雪论坛
链 接: http://bbs.pediy.com/showthread.php?t=102414
任务管理器是在windows系统用得最频繁的一个软件之一吧.你是否会觉得任务管理器的功能过于简单,不能满足你的需要呢?那好,就让我们一起来动手打造一个更强大的任务管理器.
我就觉得任务管理器的网络项的功能不够,如果能显示实时的上传,下载速度就好了.这样我们就可以随时关注自己的网络状态.虽然显示实时速度的软件很多,但是每次查看的时候都要找出来打开,不够方便,加在任务管理器就方便调出来查看了,并且它本身已经有了曲线显示了.好,开始吧!
最终效果图:
思路:
怎么样才能在任务管理器中插入两列速度呢?我认为有下面两种方法:
1.PEDIY技术,对原来的任务管理器进行修改扩充,增加一定的代码以增加原程序的功能.
2.通过DLL注入的方法,把已编写好的DLL注入到原程序中已实现想要的功能.
对于第一种方法,修改了原来的程序,不方便切换为没修改时的状态.第二种就可以很方便的进行加载和卸载DLL,并且对原程序不需要做任何修改,不会破坏到原来的程序.下面就讨论第二种方法.
步骤:
一,远程注入DLL;
二,编写DLL实现相关功能;
一,远程注入DLL
在微软的Windows中,每个进程都能获得自身的私有地址空间.当使用指针引用内存时,指针值将引用自身进程地址空间中的一个内存地址.进程不能创建一个引用属于其他进程的内存指针.
进程不能创建一个引用属于其他进程的内存指针,否则就会出现内存的错误,该错误不会影响到其他进程所使用的内存.那如何才能将自己编写的DLL放到任务管理器的进程空间中呢?通过远程注入!远程注入技术需要满足一个条件就是要求目标进程中的一个线程调用LoadLibrary函数来加载所需要的DLL.由于除了自己进程的线程中之外,我们不能简单的控制其他进程中的线程,因此该解决方法要求我们在目标进程中创建一个新线程,这样我们就可以控制它执行任何代码.Windows提供了一个
CreateRomoteThread的函数可以简单地在另一个进程中创建线程.
返回值:
函数成功,返回线程句柄;函数失败返回false。
现在已经知道了如何在另一个进程中创建线程了,但是如何让该线程来加载DLL呢?答案很简单:让线程去调用LoadLibrary函数:
pszLibFile:指定要载入的动态链接库的名称
返回值:
成功则返回库模块的句柄,零表示失败;
归结起来就应该是如下显示的一行代码:
MyLib.dll就是下面要编写的DLL.
可是还它不能达到预期的目的,里面还存在两个问题.
1.如果目标程序并没有使用到LoadLibrary函数,生成的exe的导入表就没有该函数,如果直接这样使用一些不可以预测的东西,可能会导致访问为例等错误.为强制使用LoadLibrary函数,必须通过调用GetProcAddress得到该函数的确切内存位置.
LoadLibrary函数在Kernel32.dll中,函数应该变成如下:
2.由于字符串"E://MyLib.dll"是在调用者的进程空间里,当LoadLibrary时并不能使用其他进程空间的内存地址,将会出现上述的访问违例.所以还要将DLL的路径名字字符串放进远程的地址空间中.当调用CreateRemoteThread时,需要将所放置字符串的地址(相对远程进程)传递给它.同样Windows提供了一个函数,即VirtualAllocEx,他允许一个进程分配另一个进程的地址空间中的内存:
另外一个函数允许释放该内存:
分配好了内存,现在需要从进程的地址空间复制到远程进程的地址空间中去.
到此已经解决了需要了解工作,下面来总结一下操作步骤:
1)使用VirtualAllocEx函数分配远程的地址空间中的内存.
2)使用WriteProcessMemory函数将DLL的路径名复制到步骤1)所分配的内存中.
3)使用GetProcAddress函数得到LoadLibrary函数的实际地址(在Kernel32.dll中).
4)使用CreateRemoteThread函数在远程进程中创建一个线程,调用正确的LoadLibrary函数,将步骤1)所分配的内存地址传递给他.
这时,DLL已经注入到了远程进程的地址空间中,DLL的DllMain函数接收到一个DLL_PROCESS_ATTACH通知,并且可以执行所需要执行的代码了.
具体代码如下:
二,编写DLL实现相关功能
好了,DLL就注入到了目标进程了,下面就开始编写DLL来实现想要的功能了.
我想要在任务管理器的这一个控件后面加上两列,该如何做呢?
要想在这个控件中添加两列就必须要知道这个控件的句柄,通过EnumChildWindows函数可以枚举父窗口的所有子窗口.这个控件也在其中.
通过这个函数可以看出,也必须要知道父窗体的句柄,很简单,通过FindWindow就可以得到任务管理器的句柄了.
FindWindow第一个参数是类名,第二个参数是窗体的标题.第一个参数32770是什么,从何而来的?通过vs自带的一个工具SPY++可以查看到:
看到了吧,32770就是任务管理器的类名,也是一般对话框的类名.
前面说到,既然是枚举所有控件,我怎么知道那个控件的句柄才是我要找的控件句柄呢?也是通过SPY++可以找到该控件的ID:
控件ID: 00000A28 ;
EnumChildWindows第二个参数是回调的函数,所以写一个_EnumChildProc函数来比较该控件是否是想要的控件,最后一个参数是一个自定义参数,所以我们需要定义一个结构来保存获取到句柄和需要查找的控件ID,传递给EnumChildProc;
控件的句柄找到了,下一步就是在该控件中插入两个列头具体代码如下:
列已经建好了,接下来要做的就是计算出当前的速度了.
GetIfTable()可以从操作系统维护的MIB库中读出本机各个接口的当前信息,如接口数目、类型、速率、物理地址、接收/发送字节数、错语字节数等.
通过MIB_IFROW可以看出,里面已经包含了我们需要的信息 dwInOctets,dwOutOctets就分别是下载和上传的自己数了,他是累积量,我们可以通过计算可以获得每秒的字节数
当前下载速率=(当次输入的字节数-上次输入的字节数)/1秒.
当前上传速率=(当次输出的字节数-上次输出的字节数)/1秒.
具体代码如下:
下面是创建一个线程,每秒读取一次,然后更新到界面上
到此为止,整个程序已经完成了90%了,还有些就是关于一些如何判断DLL已经加载了,如何卸载等请看源代码,这里就不一一介绍了.这个程序还可以做得跟好,比如在每个进程那个列表加上一个实时速度,这样就可以看到每个进程网络情况等,有兴趣的朋友可以自己添加完善.
如果本文有什么不对之处,希望各位指出!
本程序使用的编译器是vs.net2008 sp1