木马编程DIY (Delphi版) - 第2篇 单实例运行
注:《木马编程DIY》由冷风(chinafe)创作完成。本文在其基础上将原文中的C++源代码修改为Delphi源代码,并加以相关说明。感谢冷风(chinafe)的无私奉献。
在02-03年之间常玩木马的朋友可能还记得,当时有款叫广外男生的木马比较流行。用过的朋友友可能也发现了,广外男生有个不大不小的BUG就是:如果服务端被运行多次的话,那么客户端就会有多个服务端上线,当然并不是说肉鸡变多了,而是同一个服务端被多次加载。还有,我写了一个CMD窗口的程序,如果运行一个是没问题的,但运行两个,嘿嘿,那家伙可是相当壮观,两个程序像得了疯牛病一样疯狂输出垃圾数据,停都停不了。
现在我们就来解决这个问题,如何让程序只能运行一个实例,这里根据程序是否有窗口,来分别介绍。
一、无窗口程序的实现
实现思路
这类程序的典型代表就是木马的服务端了,我们想要达到目的,会有这样的想法:程序运行时先检查有没有另一个实例在运行,没有的话就运行自己,有的话就退出自己。
编程实现
通常有两种方法可以实现,我们分别来介绍。
1. 使用互斥对像
使用API函数CreateMutex来创建命名互斥对象来实现程序互斥是一个比较通用的方法。
program Project1; {$APPTYPE CONSOLE} uses Windows; var MutexHand: THandle; s: string; begin // 创建互斥对象,并命名为"LengFeng" MutexHand := CreateMutex(nil, False, 'LengFeng'); // 判断该互斥对象是否存在 if (MutexHand <> 0) and (GetLastError = 0) then begin try ReadLn(s); finally CloseHandle(MutexHand); // 释放对象 end; end else begin // 该互斥对象已经存在 Write('程序已经运行。'); end; end.
以上的CreateMutext函数创建一个称为"LengFeng"的命名的互斥对象。当程序的第二个实例运例时,调用CreateMutex函数将显示名为"LengFeng"的互斥对像以存在,也就是说明程序以运行。
2. 用编译器创建新节
能不能为程序中加入一个全局变量?让这个全局变量可被程序的多个实例所共享,每当程序实例运行时就对该全局变量进行修改,通过对该全局变量的访问,就可以知道有多少个实例在运行了。当然为了系统的安全和稳定性,默认情况下是不允许这样做的,为了阻止这种事情的发生系统使用copy-on-write(写入时拷贝)机制,不过我们就使用创建新节的方法来绕过系统的copy-on-write机制其方法如下:
#pragma data_seg("Shared") int volatile g_lAppInstance =0; #pragma data_seg()
我们来看一下上面的内容:
第一句 #pragma data_seg("Shared") 创建一个称为Shared的新节。
第二句 int volatile g_lAppInstance =0 将 g_lAppInstance 放入Shared节中。注意此时只有将g_lAppInstance初始化,编译器才会将其放入Shared节中,否则,将放入Shared以外的节。
第三句指示编译器Shared节结束。
仅仅告诉编译器将某些变量放入它们自己的节中,是不足以实现对这些变量的共享的。还必须告诉链接程序某个节中的变量是需要加以共享的。若要进行这项操作,可以使用链接程序的命令行上的/SECTION开关,在冒号的后面,是想要改变其属性的节的名字。在我们的例子中,想要改变Shared节的属性因此应该创建下面的链接程序开关:
#pragma comment(linker,"/section:Shared,RWS")
这一句,我们使编译链接器知道我们的Shared节具有读,写,共享的属性。这是我们实现互斥运行的关键,这样我们就可以在应用程序之间的多个实例之间共享g_lAppInstance 变量了。
下面我们看一下在SDK程序中它的完整实现代码如下:
#include <windows.h> #pragma data_seg("Shared") // 创建新节 int volatile g_lAppInstance =0; // 必须有初示化 #pragma data_seg() // 结束 #pragma comment(linker,"/section:Shared,RWS") int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { if (++g_lAppInstance > 1) { MessageBox(NULL, "程序已经运行!", NULL, NULL); ExitProcess(0); } while(1) {} }
注:此处提到的方法是“共享数据段”。这种方法在Delphi中无法直接实现,这也是Delphi与VC++相比的一个不足之处,Delphi还是不如VC++更靠近低层。如果在Delphi中想使用“共享数据段”方法,需要使用第三方的工具直接修改.exe文件,或者通过链接ASM的.OBJ文件来实现,但是这里,我个人不推荐大家这样做。另外,在Delphi中使用内存映射文件也可以模拟上述效果,但这里我们重点讨论的是单实例问题,而内存映射文件主要用于进程通讯,就不细说它了。
回头再看这两种方法,在实现上使用互斥对像比较简单,但功能却不如创建新节的方法强大,不过在无窗口程序中使用互斥对像还是比较实用的。
二、基于窗口程序的实现
基于窗口程序的实现,除了上面提到的为“互斥对像”方法和“共享数据段”之外,又多了一个“枚举窗口”的方法。但是我个人认为“枚举窗口”的方法可靠性不高,而且冷风(chinafe)同学此处也没有对“枚举窗口”的方法进行详细描述。
基于窗口程序的实现,除了要求阻止多实例运行,还有一个要求就是激活前一个实例的窗口,使其窗口提前引起用户注意。
关于这个实现,大家可以看一下ysai同学在《Delphi 5 开发人员指南》中第13章中一篇"防止同时出现多个应用程序实例"的代码基础上的修改版。
http://www.qqgb.com/Program/Delphi/DelphiSL/Program_151574.html