如何在Delphi中检测无窗口、可改名的后台运行目标应用?
问题描述
我有一款初始带常规窗口的应用,其包含一个「隐藏」按钮。点击该按钮后,应用会继续运行并在任务管理器中可见,但不再有任何可视窗口。
我尝试了几种在Delphi中通过编程查找该应用的方法:
- 使用
GetWindowText:此方法无效,因为应用隐藏后没有窗口。 - 使用
TProcessEntry32.szExeFile:此方法不可靠,原因如下:- 用户可以将可执行文件保存为任意名称
- 即使可执行文件名称已知,它也不会出现在
TProcessEntry32列表中
- 使用WMI查询:此方法同样无法通过任务管理器中显示的名称找到该应用。
我的问题是:在以下约束条件下,如何在Delphi中创建一个函数来检测该应用是否正在运行?
- 可执行文件名未知。
- 应用没有可视窗口。
- 它在系统中隐藏但在任务管理器中可见。
编辑补充
我所针对的这款应用是用于恶意操纵用户活动追踪的。如前所述,它初始以窗口形式出现,之后可被隐藏。即使隐藏后,它仍会继续模拟用户活动。如之前提到的,用户可以修改其名称后启动,因此唯一的常量是任务管理器中显示的名称。遗憾的是,我无法在其隐藏时找到它。以下是我当前使用的代码,请告知是否存在错误。
可见时有效(隐藏时无效)的代码:
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;
解决方案
问题分析
你当前的第二段代码存在两个核心问题:
GetHWndByPID逻辑错误:EnumWindowsProc中,当进程ID匹配但窗口不可见时,直接返回True继续枚举,导致跳过了该进程的所有窗口(包括隐藏窗口)。目标程序隐藏后仍可能存在不可见窗口,只是没有可视界面。- WMI查询效率极低:每次遍历进程都创建WMI连接并执行查询,这是代码运行极慢的根本原因。
修正方案
思路
既然任务管理器能看到该进程,说明它的进程信息肯定在系统进程列表中。你需要:
- 遍历所有进程,获取每个进程的窗口标题(即使窗口不可见),而非依赖可执行文件名。
- 避免重复创建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
相关产品推荐
相关产品推荐

