Detecting Window with FindWindow API

Created the Monday 18 March 2019. Updated 3 years, 6 months ago.

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

Technique Identifiers

U0406 U0123

Technique Tags

WinAPI FindWindow


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.

Detection Rules

rule Detect_FindWindowA_iat {
	meta:
		Author = "http://twitter.com/j0sm1"
		Description = "it's checked if FindWindowA() is imported"
		Date = "20/04/2015"
		Reference = "http://www.codeproject.com/Articles/30815/An-Anti-Reverse-Engineering-Guide#OllyFindWindow"
	strings:
		$ollydbg = "OLLYDBG"
		$windbg = "WinDbgFrameClass"
	condition:
		pe.imports("user32.dll","FindWindowA") and ($ollydbg or $windbg)
}

Additional Resources

External Links

Subscribe to our Newsletter


The information entered into this form is mandatory. It will be subjected to computer processing. It is processed by computer in order to support our users and readers. The recipients of the data will be : contact@unprotect.it.

According to the Data Protection Act of January 6th, 1978, you have at any time, a right of access to and rectification of all of your personal data. If you wish to exercise this right and gain access to your personal data, please write to Thomas Roccia at contact@unprotect.it.

You may also oppose, for legitimate reasons, the processing of your personal data.