Windows API : FindWindow

The FindWindowA / FindWindowW function can be used to search for windows by name or class.

It is also possible to use EnumWindows API in conjunction with GetWindowTextLength and GetWindowText to locate a piece of string that could reveal the presence of a known debugger.

Some Known Debuggers

  • ImmunityDebugger
  • OllyDbg
  • IDA
  • x64dbg / x32dbg
  • WinDbg

Code Snippets

Jean-Pierre LESUEUR

Description

Feel free to edit both fw_debuggers and contains_in_title to extend the search of known debuggers.

import ctypes
import os

from ctypes.wintypes import BOOL, HWND, LPARAM,\
                            LPWSTR, INT, MAX_PATH,\
                            LPDWORD, DWORD, HANDLE,\
                            HMODULE


def found(description, hwnd):
    """
    When a Window handle is found it will output to console several information about spotted process.
    :param description: Description of found object.
    :param hwnd: Handle of found object.
    """
    lpdwProcessId = ctypes.c_ulong()

    output = "-" * 60 + "\r\n"
    output += description + "\r\n"
    output += "-" * 60 + "\r\n"

    output += f"Handle: {hwnd}\r\n"

    _GetWindowThreadProcessId(hwnd, ctypes.byref(lpdwProcessId))

    if (lpdwProcessId is not None) and (lpdwProcessId.value > 0):
        PROCESS_QUERY_INFORMATION = 0x0400
        PROCESS_VM_READ = 0x0010

        procHandle = ctypes.windll.kernel32.OpenProcess(
            PROCESS_QUERY_INFORMATION | PROCESS_VM_READ,
            False,
            lpdwProcessId.value
        )

        if procHandle > 0:
            output += f"Process Id: {lpdwProcessId.value}\r\n"

            lpFilename = ctypes.create_unicode_buffer(MAX_PATH)

            if _GetModuleFileNameEx(procHandle, 0, lpFilename, MAX_PATH) > 0:
                path, process_name = os.path.split(lpFilename.value)

                output += f"Process Name: {process_name}\r\n"
                output += f"Image Path: {path}\r\n"

            ctypes.windll.kernel32.CloseHandle(procHandle)

    output += "-" * 60 + "\r\n\r\n"

    print(output)


def enum_window_proc(hwnd, lparam):
    """
    EnumWindows API CallBack
    :param hwnd: Current Window Handle
    :param lparam: Not used in our case
    :return: Always True in our case
    """
    if hwnd > 0:
        nMaxCount = ctypes.windll.user32.GetWindowTextLengthW(hwnd)+1

        if nMaxCount > 0:
            lpWindowName = ctypes.create_unicode_buffer(nMaxCount)

            if _GetWindowText(hwnd, lpWindowName, nMaxCount) > 0:
                for description, in_title in contains_in_title:
                    if in_title in lpWindowName.value:
                        found(description, hwnd)

    return True


if __name__ == '__main__':
    '''
        Description | Window Class Name (lpClassName) | Window Title (lpWindowName)
    '''
    fw_debuggers = [
        ("OllyDbg", "OLLYDBG", None),
        ("x64dbg (x64)", None, "x64dbg"),
        ("x32dbg (x32)", None, "x32dbg"),
        # ......... #
    ]

    '''
        Description | Text contained in debugger title.
    '''
    contains_in_title = [
        ("Immunity Debugger", "Immunity Debugger"),
        # ......... #
    ]

    # Define GetWindowThreadProcessId API
    _GetWindowThreadProcessId = ctypes.windll.user32.GetWindowThreadProcessId

    _GetWindowThreadProcessId.argtypes = HWND, LPDWORD
    _GetWindowThreadProcessId.restype = DWORD

    # Define GetModuleFileNameEx API
    _GetModuleFileNameEx = ctypes.windll.psapi.GetModuleFileNameExW
    _GetModuleFileNameEx.argtypes = HANDLE, HMODULE, LPWSTR, DWORD
    _GetModuleFileNameEx.restype = DWORD

    '''
        Search for Debuggers using the FindWindowW API with ClassName /+ WindowName
    '''
    for description, lpClassName, lpWindowName in fw_debuggers:
        handle = ctypes.windll.user32.FindWindowW(lpClassName, lpWindowName)

        if handle > 0:
            found(description, handle)

    '''
        Search for Debuggers using EnumWindows API.
        We first list all Windows titles then search for a debugger title pattern.
        This is useful against debuggers or tools without specific title / classname. 
    '''

    # Define EnumWindows API
    lpEnumFunc = ctypes.WINFUNCTYPE(
        BOOL,
        HWND,
        LPARAM
    )

    _EnumWindows = ctypes.windll.user32.EnumWindows

    _EnumWindows.argtypes = [
        lpEnumFunc,
        LPARAM
    ]

    # Define GetWindowTextW API
    _GetWindowText = ctypes.windll.user32.GetWindowTextW

    _GetWindowText.argtypes = HWND, LPWSTR, INT
    _GetWindowText.restype = INT

    # Enumerate Windows through Windows API
    _EnumWindows(lpEnumFunc(enum_window_proc), 0)
Jean-Pierre LESUEUR

Description

You can build this snippet as a classic Delphi Console Application and add your own signatures for detecting debuggers and related tools.

program FindWindowAPI;

{$APPTYPE CONSOLE}

uses
  System.SysUtils, WinAPI.Windows, Generics.Collections, psAPI;

{+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  TFindWindowSignature Class
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++}

type
  TFindWindowSignature = class
  private
    FDescription : String;
    FClassName   : String;
    FWindowName  : String;
  public
    {@C}
    constructor Create(ADescription, AClassName, AWindowName : String);

    {@G}
    property Description : String read FDescription;
    property ClassName   : String read FClassName;
    property WindowName  : String read FWindowName;
  end;

{-------------------------------------------------------------------------------
  ___constructor
-------------------------------------------------------------------------------}
constructor TFindWindowSignature.Create(ADescription, AClassName, AWindowName : String);
begin
  FDescription := ADescription;
  FClassName   := AClassName;
  FWindowName  := AWindowName;
end;

{+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  Main
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++}

var LFindWindowSignatures  : TObjectList<TFindWindowSignature>;
    LEnumWindowsSignatures : TDictionary<String, String>;

{-------------------------------------------------------------------------------
  When a Window handle is found it will output to console several information
  about spotted process.
-------------------------------------------------------------------------------}
procedure Found(ADescription : String; AHandle : THandle);
const CRLF = #13#10;

var AStdout_TXT    : String;
    AProcessId     : Cardinal;
    AProcessHandle : THandle;
    ARet           : DWORD;
    pImagePath     : PWideChar;
begin
  try
      AStdout_TXT := AStdout_TXT + StringOfChar('-', 60) + CRLF;
      AStdout_TXT := AStdout_TXT + ADescription + CRLF;
      AStdout_TXT := AStdout_TXT + StringOfChar('-', 60) + CRLF;

      AStdout_TXT := AStdout_TXT + Format('Handle: %d%s', [AHandle, CRLF]);

      GetWindowThreadProcessId(AHandle, @AProcessId);

      if (AProcessId > 0) then begin
        AProcessHandle := OpenProcess(
                                        (PROCESS_QUERY_INFORMATION or PROCESS_VM_READ),
                                        False,
                                        AProcessId
        );

        if (AProcessHandle > 0) then begin
          AStdout_TXT := AStdout_TXT + Format('Process Id: %d%s', [AProcessId, CRLF]);

          pImagePath := nil;
          try
              GetMem(pImagePath, (MAX_PATH * 2));
              ARet := GetModuleFileNameExW(AProcessHandle, 0, pImagePath, (MAX_PATH * 2));
              if (ARet > 0) then begin
                AStdout_TXT := AStdout_TXT + Format('Process Name: %s%s', [ExtractFileName(String(pImagePath)), CRLF]);
                AStdout_TXT := AStdout_TXT + Format('Image Path: %s%s', [ExtractFilePath(String(pImagePath)), CRLF]);
              end;
          finally
            if Assigned(pImagePath) and (ARet > 0) then
              FreeMem(pImagePath, ARet);
          end;
        end;
      end;

      AStdout_TXT := AStdout_TXT + StringOfChar('-', 60) + CRLF + CRLF;

      ///
  finally
    WriteLn(AStdout_TXT);
  end;
end;

{-------------------------------------------------------------------------------
  Find Debuggers by Window Name or Class Name using FindWindow API
-------------------------------------------------------------------------------}
function Locate_FindWindow() : Boolean;
var AFindWindowSignature : TFindWindowSignature;
    i                    : Integer;
    pClassName           : Pointer;
    pWindowName          : Pointer;
    AHandle              : THandle;
begin
  result := False;
  ///

  for i := 0 to LFindWindowSignatures.Count -1 do begin
    AFindWindowSignature := LFindWindowSignatures.Items[i];
    if NOT Assigned(AFindWindowSignature) then
      continue;
    ///

    pClassName  := nil;
    pWindowName := nil;

    if NOT AFindWindowSignature.ClassName.isEmpty then
      pClassName := PWideChar(AFindWindowSignature.ClassName);

    if NOT AFindWindowSignature.WIndowName.isEmpty then
      pWindowName := PWideChar(AFindWindowSignature.WindowName);

    AHandle := FindWindowW(pClassName, pWindowName);
    if (AHandle > 0) then begin
      Found(AFindWindowSignature.Description, AHandle);

      ///
      result := True;
    end;
  end;
end;

{-------------------------------------------------------------------------------
  Find Debuggers by Window Name (via Window Name Pattern) using EnumWindows API
-------------------------------------------------------------------------------}
function EnumWindowProc(AHandle : THandle; AParam : LPARAM) : BOOL; stdcall;
var AMaxCount   : Integer;
    AWindowName : String;
    AOldLen     : Cardinal;
    APattern    : String;
    AKey        : String;
begin
  result := True;
  ///

  if (AHandle = 0) then
    Exit();
  ///

  AMaxCount := GetWindowTextLength(AHandle) + 1;
  if (AMaxCount = 0) then
    Exit();

  SetLength(AWindowName, AMaxCount); // Other technique instead of using GetMem / FreeMem a new Pointer.
  try
      if (GetWindowTextW(AHandle, PWideChar(AWindowName), AMaxCount) = 0) then
        Exit();
      ///

      AOldLen := Length(AWindowName);

      for AKey {Description} in LEnumWindowsSignatures.keys do begin
        if NOT LEnumWindowsSignatures.TryGetValue(AKey, APattern) then
          continue;

        AWindowName := StringReplace(AWindowName, APattern, '', []);

        if (Length(AWindowName) <> AOldLen) then begin
          Found(AKey, AHandle);

          break;
        end;
      end;
  finally
    SetLength(AWindowName, 0);
  end;
end;

function Locate_EnumWindows() : Boolean;
begin
  EnumWindows(@EnumWindowProc, 0);
end;

{-------------------------------------------------------------------------------
  Append FindWindow Technique Signature
-------------------------------------------------------------------------------}
procedure AppendFindWindowSignature(ADescription, AClassName, AWindowName : String);
var AFindWindowSignature : TFindWindowSignature;
begin
  if NOT Assigned(LFindWindowSignatures) then
    Exit();
  ///

  AFindWindowSignature := TFindWindowSignature.Create(ADescription, AClassName, AWindowName);

  LFindWindowSignatures.Add(AFindWindowSignature);
end;

{-------------------------------------------------------------------------------
  ___entry
-------------------------------------------------------------------------------}
begin
  try
    LFindWindowSignatures := TObjectList<TFindWindowSignature>.Create();
    LEnumWindowsSignatures := TDictionary<String, String>.Create();
    try
      {
        Configure debuggers signatures here for FindWindow API technique.
      }
      AppendFindWindowSignature('OllyDbg', 'OLLYDBG', '');
      AppendFindWindowSignature('x64dbg (x64)', '', 'x64dbg');
      AppendFindWindowSignature('x32dbg (x32)', '', 'x32dbg');

      // ...
      // AppendFindWindowSignature('...', '...', '...');
      // ...

      {
        Configure debuggeers signatures here for EnumWindows API technique.
      }
      LEnumWindowsSignatures.Add('Immunity Debugger', 'Immunity Debugger');

      // ...
      // AEnumWindowsSignatures.Add('...', '...');
      // ...

      {
        Fire !!!
      }
      Locate_FindWindow();
      Locate_EnumWindows();

      readln;
    finally
      if Assigned(LFindWindowSignatures) then
        FreeAndNil(LFindWindowSignatures);

      if Assigned(LEnumWindowsSignatures) then
        FreeAndNil(LEnumWindowsSignatures);
    end;
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;

end.

Additional Resources

Subscribe to our Newsletter and don't miss important updates