木马编程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

posted @ 2008-08-13 10:30  可乐罐  阅读(1065)  评论(0编辑  收藏  举报