Windows Delphi / System Binary Proxy Execution: Rundll32

Author Jean-Pierre LESUEUR (DarkCoderSc)
Platform Windows
Language Delphi
Technique System Binary Proxy Execution: Rundll32

Description:

The exported function must follow a specific signature to be invoked by rundll32.exe.

You can then call the exported function using the following command:

rundll32.exe <dll>,<exported_function> <args>

rundll32.exe rundll.dll,UnprotectFunction Hello, World

Code

library rundll32;

uses
  System.SysUtils,
  Winapi.Windows;

const LOG_TEMPLATE = 'Unprotect -> %s';

{ Simplify ODS Call }
procedure Debug(const AMessage : PWideChar); stdcall;
begin
  OutputDebugStringW(PWideChar(Format(LOG_TEMPLATE, [WideCharToString(AMessage)])));
end;

{ One method to retrieve process name from PID }
function GetProcessName(const AProcessID : Cardinal) : String;
var QueryFullProcessImageNameW : function(
                                    AProcess: THANDLE;
                                    AFlags: DWORD;
                                    AFileName: PWideChar;
                                    var ASize: DWORD
    ): BOOL; stdcall;

const PROCESS_QUERY_LIMITED_INFORMATION = $00001000;
begin
  result := '';
  ///

  if (TOSVersion.Major < 6) then
    Exit();
  ///

  QueryFullProcessImageNameW := nil;

  var hDLL := LoadLibrary('kernel32.dll');
  if hDLL = 0 then
    Exit();
  try
    @QueryFullProcessImageNameW := GetProcAddress(hDLL, 'QueryFullProcessImageNameW');
    ///

    if Assigned(QueryFullProcessImageNameW) then begin
      var hProc := OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, false, AProcessID);
      if hProc = 0 then exit;
      try
        var ALength : DWORD := (MAX_PATH * 2);

        SetLength(result, ALength);

        if NOT QueryFullProcessImageNameW(hProc, 0, @result[1], ALength) then
          Exit();

        SetLength(result, ALength);
      finally
        CloseHandle(hProc);
      end;
    end;
  finally
    FreeLibrary(hDLL);
  end;
end;

{ Exported Function : Require this function signature be operated by Rundll32 }
procedure UnprotectFunction(hHwnd, hInstance : THandle; lpszCmdLine : PAnsiChar; nCmdShow : Integer); stdcall;
begin
  var AProcessId := GetCurrentProcessId();

  var AMessage := Format('%s' + #13#10#13#10 + 'From: %s (%d / 0x%x)',
    [
      lpszCmdLine,
      ExtractFileName(GetProcessName(AProcessId)),
      AProcessId,
      AProcessId
    ]
  );

  MessageBoxA(0, PAnsiChar(AnsiString(AMessage)), 'Unprotect Test', MB_ICONINFORMATION);

  // Define and parse `lpszCmdLine` to extend functionality of your payload
  // (Ex: rev shell IP, Port, App; Encoded shellcode etc..)
end;

{ DLL Initialization / Finalization }
procedure DllMain(const AReason : DWORD);
begin
  case AReason of
    DLL_PROCESS_ATTACH:
      Debug('DLL_PROCESS_ATTACH');

    // ...
  end;
end;

{ Define Exportations }
exports
  UnprotectFunction index 14 name 'UnprotectFunction';

{ EP }
begin
  DllProc := @DllMain;

  DllMain(DLL_PROCESS_ATTACH);

end.

Created

January 30, 2025

Last Revised

January 30, 2025