You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Delphi中检测无窗口、可改名的后台运行目标应用?

问题描述

我有一款初始带常规窗口的应用,其包含一个「隐藏」按钮。点击该按钮后,应用会继续运行并在任务管理器中可见,但不再有任何可视窗口。

我尝试了几种在Delphi中通过编程查找该应用的方法:

  • 使用GetWindowText:此方法无效,因为应用隐藏后没有窗口。
  • 使用TProcessEntry32.szExeFile:此方法不可靠,原因如下:
    • 用户可以将可执行文件保存为任意名称
    • 即使可执行文件名称已知,它也不会出现在TProcessEntry32列表中
  • 使用WMI查询:此方法同样无法通过任务管理器中显示的名称找到该应用。

我的问题是:在以下约束条件下,如何在Delphi中创建一个函数来检测该应用是否正在运行?

  1. 可执行文件名未知。
  2. 应用没有可视窗口。
  3. 它在系统中隐藏但在任务管理器中可见。

编辑补充

我所针对的这款应用是用于恶意操纵用户活动追踪的。如前所述,它初始以窗口形式出现,之后可被隐藏。即使隐藏后,它仍会继续模拟用户活动。如之前提到的,用户可以修改其名称后启动,因此唯一的常量是任务管理器中显示的名称。遗憾的是,我无法在其隐藏时找到它。以下是我当前使用的代码,请告知是否存在错误。

可见时有效(隐藏时无效)的代码:

var WinTitle : array[0..256] of Char;
begin
  if GetWindowText(Wnd, WinTitle, SizeOf(WinTitle)) > 0 then
  begin
    if pos('GDollar', WinTitle) > 0 then
      PostMessage(Wnd, WM_CLOSE, 0, 0);
  end;
  Result := True;
end;

procedure TForm1.btn_1Click(Sender: TObject);
begin
  EnumWindows(@EnumWindowsProc, 0);
end;

尝试过但无效且运行极慢的代码:

procedure TForm1.btn_2Click(Sender: TObject);
var PHandle, FHandle: THandle;
    Process:TProcessEntry32;
    Done, Next: Boolean;
    WinTitle : array[0..256] of Char;
    pn : String;
begin
  FHandle := CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,0);
  Process.dwSize := Sizeof(Process);
  Next := Process32First(FHandle, Process);
  while Next do
  begin
    pn := GetProcessNameByPID(GetHWndByPID(process.th32ProcessID));
    if pos('GDollar', pn) > 0 then
      ShowMessage(pn);

    Next := Process32Next(FHandle,Process);
  end;
  CloseHandle(FHandle);
end;

function TForm1.GetHWndByPID(const hPID: THandle): THandle;
  type
    PEnumInfo = ^TEnumInfo;
    TEnumInfo = record
    ProcessID: DWORD;
    HWND: THandle;
  end;

  function EnumWindowsProc(Wnd: DWORD; var EI: TEnumInfo): Bool; stdcall;
  var PID: DWORD;
  begin
    GetWindowThreadProcessID(Wnd, @PID);
    Result := (PID <> EI.ProcessID) or (not IsWindowVisible(WND)) or (not IsWindowEnabled(WND));
    if not Result then EI.HWND := WND;
  end;

  function FindMainWindow(PID: DWORD): DWORD;
  var EI: TEnumInfo;
  begin
    EI.ProcessID := PID;
    EI.HWND := 0;
    EnumWindows(@EnumWindowsProc, Integer(@EI));
    Result := EI.HWND;
  end;

begin
  if hPID <> 0
    then Result:=FindMainWindow(hPID)
    else Result:=0;
end;

function TForm1.GetProcessNameByPID(const PID: DWORD): string;
var
  objSWbemLocator, objWMIService: OLEVariant;
  colItems: OLEVariant;
  colItem: OLEVariant;
  oEnum: IEnumvariant;
  iValue: LongWord;
begin
  Result := ''; // Initialize result to empty string

  objSWbemLocator := CreateOleObject('WbemScripting.SWbemLocator');
  objWMIService   := objSWbemLocator.ConnectServer('localhost','root\cimv2', '','');
  colItems := objWMIService.ExecQuery(Format('SELECT * FROM Win32_Process WHERE ProcessId=%d', [PID]), 'WQL', 0);
  oEnum := IUnknown(colItems._NewEnum) as IEnumVariant;

  if oEnum.Next(1, colItem, iValue) = 0 then
  begin
    Result := colItem.Name;
    colItem := Unassigned;
  end;
end;

解决方案

问题分析

你当前的第二段代码存在两个核心问题:

  1. GetHWndByPID逻辑错误:EnumWindowsProc中,当进程ID匹配但窗口不可见时,直接返回True继续枚举,导致跳过了该进程的所有窗口(包括隐藏窗口)。目标程序隐藏后仍可能存在不可见窗口,只是没有可视界面。
  2. WMI查询效率极低:每次遍历进程都创建WMI连接并执行查询,这是代码运行极慢的根本原因。

修正方案

思路

既然任务管理器能看到该进程,说明它的进程信息肯定在系统进程列表中。你需要:

  1. 遍历所有进程,获取每个进程的窗口标题(即使窗口不可见),而非依赖可执行文件名。
  2. 避免重复创建WMI连接,改用更高效的进程遍历方式。

修正后的代码

unit ProcessDetector;

interface

uses
  Windows, SysUtils, Classes, TLHelp32;

function IsTargetProcessRunning(const TargetWindowTitle: string): Boolean;

implementation

type
  PEnumInfo = ^TEnumInfo;
  TEnumInfo = record
    ProcessID: DWORD;
    Found: Boolean;
    TargetTitle: string;
  end;

function EnumWindowsProc(Wnd: HWND; var EI: TEnumInfo): BOOL; stdcall;
var
  PID: DWORD;
  WinTitle: array[0..255] of Char;
begin
  GetWindowThreadProcessID(Wnd, @PID);
  if PID = EI.ProcessID then
  begin
    // 不管窗口是否可见,都尝试获取标题
    if GetWindowText(Wnd, WinTitle, SizeOf(WinTitle)) > 0 then
    begin
      if Pos(EI.TargetTitle, WinTitle) > 0 then
      begin
        EI.Found := True;
        Result := False; // 找到后停止枚举
        Exit;
      end;
    end;
  end;
  Result := True; // 继续枚举其他窗口
end;

function CheckProcessForTargetTitle(PID: DWORD; const TargetTitle: string): Boolean;
var
  EI: TEnumInfo;
begin
  EI.ProcessID := PID;
  EI.Found := False;
  EI.TargetTitle := TargetTitle;
  EnumWindows(@EnumWindowsProc, Integer(@EI));
  Result := EI.Found;
end;

function IsTargetProcessRunning(const TargetWindowTitle: string): Boolean;
var
  Snapshot: THandle;
  ProcessEntry: TProcessEntry32;
begin
  Result := False;
  Snapshot := CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
  if Snapshot = INVALID_HANDLE_VALUE then Exit;
  try
    ProcessEntry.dwSize := SizeOf(TProcessEntry32);
    if not Process32First(Snapshot, ProcessEntry) then Exit;
    repeat
      // 检查当前进程是否包含目标标题的窗口(不管可见与否)
      if CheckProcessForTargetTitle(ProcessEntry.th32ProcessID, TargetWindowTitle) then
      begin
        Result := True;
        Break;
      end;
    until not Process32Next(Snapshot, ProcessEntry);
  finally
    CloseHandle(Snapshot);
  end;
end;

end.

使用示例

procedure TForm1.btnCheckClick(Sender: TObject);
begin
  if IsTargetProcessRunning('GDollar') then
    ShowMessage('目标进程正在运行')
  else
    ShowMessage('未找到目标进程');
end;

额外说明

如果目标程序隐藏后销毁了所有窗口,你需要寻找其他唯一标识:

  • 检查进程的命令行参数:通过WMI的Win32_Process.CommandLine字段,恶意软件可能携带固定参数。
  • 检查进程的数字签名:如果恶意软件未签名,可筛选无签名的进程;如果有固定签名,可验证签名信息。
  • 检查进程的内存特征:通过读取进程内存,查找特定字符串或字节序列(需逆向分析目标程序)。

内容的提问来源于stack exchange,提问作者MSSI

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.10 08:10:58