Jump to content

How to search string resources in executable files


Go to solution Solved by ioa747,

Recommended Posts

Posted

I have used _WinAPI_LoadString() before in a couple of projects for loading string resources. However, in those projects, I knew the string identifier.

How can I get the string identifier if I don't know it?

Similar to the Strings program from SysInternals, I would like to search all of the strings within a specified binary executable file to see if the string exists. I would prefer to do this entirely in AutoIt instead of using an external program.

If we can get the string identifier this way, that would be fine. But ideally what I need is to just know whether or not an executable contains a specific string or not.

Does anyone know if this can be done in AutoIt without any external programs?

Thank you so much. :)

 

Posted (edited)

Just make a loop :

#include <WinAPI.au3>

SearchString("shell32.dll", 10000, "système")

Func SearchString($sDLL, $iEnd, $sString)
  Local $hInstance = _WinAPI_LoadLibraryEx($sDLL, $LOAD_LIBRARY_AS_DATAFILE)
  If Not $hInstance Then Return SetError(1)
  Local $sText
  For $i = 1 To $iEnd
    $sText = _WinAPI_LoadString($hInstance, $i)
    If @error Then ContinueLoop
    If StringInStr($sText, $sString) Then ConsoleWriteEx($i & " = " & $sText & @CRLF)
  Next
  _WinAPI_FreeLibrary($hInstance)
EndFunc   ;==>SearchString

Func ConsoleWriteEx($sString)
  ConsoleWrite(BinaryToString(StringToBinary($sString, $SB_UTF8), $SB_ANSI))
EndFunc   ;==>ConsoleWriteEx

You can make the research as complex as you want...

Edited by Nine
  • Solution
Posted (edited)

@Nine beat me to it, but since I did it, I'm posting it.
... a more complicated version

#include <WinAPIRes.au3>
#include <WinAPILocale.au3>
#include <Array.au3>

_Main("AutoPlay")

Func _Main($sSubstring)
    Local Const $sTargetDLL = "shell32.dll" ; The target DLL to scan
    ; Use the user's default locale ID
    Local $iID = 1033 ; _WinAPI_GetUserDefaultLCID() ; 1033 (Standard US English Language ID)
    ; Get the language name for display purposes
    Local $sLANGUAGE = _WinAPI_GetLocaleInfo($iID, $LOCALE_SENGLANGUAGE)

    ConsoleWrite("Target LCID => " & $iID & ', Language => ' & $sLANGUAGE & @CRLF)

    ; Pass the calculated LCID to the main function
    Local $aStrings = _GetAllStringsByLCIDToArray($sTargetDLL, $iID)

    ConsoleWrite("" & @CRLF & @CRLF)

    If Not IsArray($aStrings) Then
        ConsoleWrite("! EXTRACTION FAILED! Error code: " & @error & @CRLF)
        ConsoleWrite("The DLL might not contain " & $sLANGUAGE & " string resources or could not be loaded." & @CRLF)
    Else
        ConsoleWrite("- Successfully extracted " & $aStrings[0][0] & " " & $sLANGUAGE & " strings from " & $sTargetDLL & @CRLF)
        ConsoleWrite("- Results stored in 2D array: [Index][0]=String ID, [Index][1]=String Value" & @CRLF)

        For $i = 1 To $aStrings[0][0]
            If StringInStr($aStrings[$i][1], $sSubstring) > 0 Then
                ConsoleWrite($i & ") -> ID " & $aStrings[$i][0] & "  -> Value: " & $aStrings[$i][1] & @CRLF)
            EndIf
        Next

        ; The full array
        ; _ArrayDisplay($aStrings)

    EndIf
EndFunc   ;==>_Main


; #FUNCTION# ====================================================================================================================
; Name...........: _GetAllStringsByLCIDToArray
; Description....: Scans ALL String ID blocks in a DLL and returns all available strings for the specified LCID.
; Syntax.........: _GetAllStringsByLCIDToArray($sDLLName, $LCID)
; Parameters:
;   $sDLLName: The path or name of the DLL (e.g., "shell32.dll").
;   $LCID:     The Language Code ID (e.g., 1033 for English, 1032 for Greek, etc.).
; Returns values .: Success: A 2D array [N][2] where [N][0] is the String ID and [N][1] is the string value.
;                  Failure: Returns False and sets @error (1: Could not load DLL, 2: No string resources).
; ===============================================================================================================================
Func _GetAllStringsByLCIDToArray($sDLLName, $LCID)
    Local $sLANGUAGE = _WinAPI_GetLocaleInfo($LCID, $LOCALE_SENGLANGUAGE) ; Use English name for logging
    Local Const $iCols = 2 ; We will store [String ID, String Value]

    ; Load the library as a data file
    Local $hInstance = _WinAPI_LoadLibraryEx($sDLLName, $LOAD_LIBRARY_AS_DATAFILE)
    If Not $hInstance Then
        Return SetError(1, 0, False) ; Could not load DLL
    EndIf

    ; Initialize the array to store the results
    Local $aStringData[1][$iCols]
    $aStringData[0][0] = 0      ; Counter for the number of found strings
    $aStringData[0][1] = $iCols ; Store column count

    ; Enumerate all string resource blocks (Type $RT_STRING)
    Local $aResourceNames = _WinAPI_EnumResourceNames($hInstance, $RT_STRING)
    If Not IsArray($aResourceNames) Then
        _WinAPI_FreeLibrary($hInstance)
        Return SetError(2, 0, False) ; No string resources found
    EndIf

    ConsoleWrite("Scanning " & $sDLLName & " for " & $sLANGUAGE & " Strings (LCID " & $LCID & ") in " & $aResourceNames[0] & " resource blocks..." & @CRLF)

    ; Iterate through each resource name (block)
    For $i = 1 To $aResourceNames[0]
        Local $vResourceName = $aResourceNames[$i]
        Local $iBlockStartID = ($vResourceName - 1) * 16

        ; Iterate through all 16 potential strings in this block
        For $k = 0 To 15
            Local $iCurrentStringID = $iBlockStartID + $k
            ; Use the passed LCID for loading the string
            Local $sString = _WinAPI_LoadStringEx($hInstance, $iCurrentStringID, $LCID)

            ; Process and store the string if loaded successfully
            If StringLen($sString) > 0 Then

                ; === ENHANCED CLEANING ===
                ; Replace CR/LF with space for basic formatting
                $sString = StringReplace($sString, @CR, " ")
                $sString = StringReplace($sString, @LF, "")

                ; Remove non-printable control characters (e.g., SOH, EOT, ACK, NUL etc.)
                ; Regex: [\x00-\x08\x0B\x0C\x0E-\x1F] covers ASCII control chars (0-31)
                ; excluding Tab (0x09), Line Feed (0x0A), Carriage Return (0x0D).
                $sString = StringRegExpReplace($sString, "[\x00-\x08\x0B\x0C\x0E-\x1F]", "")

                ; Remove leading/trailing spaces left from replacement
                $sString = StringStripWS($sString, 3)

                ; === END CLEANING ===

                ; Check if the string is empty after cleaning
                If StringLen($sString) > 0 Then
                    $aStringData[0][0] += 1
                    _ArrayAdd($aStringData, "")

                    $aStringData[$aStringData[0][0]][0] = $iCurrentStringID ; String ID
                    $aStringData[$aStringData[0][0]][1] = $sString ; Cleaned String Value

                    If Mod($aStringData[0][0], 500) = 0 Then
                        ConsoleWrite(".") ; Progress indicator
                    EndIf
                EndIf
            EndIf
        Next
    Next

    ; Cleanup and return result
    _WinAPI_FreeLibrary($hInstance)

    If $aStringData[0][0] > 0 Then
        Return $aStringData
    Else
        Return SetError(0, 0, False)
    EndIf
EndFunc   ;==>_GetAllStringsByLCIDToArray

 

Edited by ioa747

I know that I know nothing

Posted
Global $sDLL = 'uxtheme.dll' ; 9,000+ resource strings
Global $sDLL2 = 'C:\Windows\resources\themes\Aero\Aero.msstyles' ; 26,000+ resource strings
Global $sString1 = 'DarkMode_Explorer' ; win10/11
Global $sString2 = 'DarkMode_DarkTheme' ; win11 25H2


Thank you both.

For some reason, neither script seems to find the strings. I’ve confirmed with System Informer that the resource strings are there.

Both of those binaries have both strings. So I’m not sure what I’m doing wrong. I posted the stuff that I am searching above.

 

 

Posted

Thank you both for your time. My understanding of this was clearly wrong and I should have put more details about what I was specifically looking for in the first post. I ended up determine Windows build and revision number to know for certain whether it contained the updated theme-related files.

However, given my original question, both of your answers would work great as a Solution to anyone searching the forum in the future regarding finding strings in binaries. Both scripts work great for the purpose of my original question. I actually like the simplicity of the first script but I also like the complexity of the second script as well.

I'm literally going to flip a coin on this one to decide the solution because both solve the question. :)

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
×
×
  • Create New...