Patch a running process

The first feature we have to do is to allow on-the-fly change of the assembler code of a process.

In fact, we already use this in order to provide class-level variables, as stated by another article of this Blog.

We've got the PatchCodePtrUInt function at hand to change the address of each a RtlUnWind call.

One patch to rule them all

We'll first define the missing global variable, available since Delphi 6, for the Delphi 5 compiler:

{$ifdef DELPHI5OROLDER}
// Delphi 5 doesn't define the needed RTLUnwindProc variable :(
// so we will patch the System.pas RTL in-place
var
  RTLUnwindProc: Pointer;

The RtlUnwind API call we have to hook is defined as such in System.pas:

procedure RtlUnwind; external kernel name 'RtlUnwind';
 0040115C FF255CC14100     jmp dword ptr [$0041c15c]

The $0041c15c is a pointer to the address of RtlUnWind in kernel32.dll, as retrieved during linking of this library to the main executable process.

The patch will consist in changing this asm call into this one:

 0040115C FF25????????     jmp dword ptr [RTLUnwindProc]

Where ???????? is a pointer to the global RTLUnwindProc variable.

The problem is that we don't have any access to this RtlUnwind declaration, since it was declared only in the implementation part of the System.pas unit. So its address has been lost during the linking process. Requiescat In Pace.

So we will have to retrieve it from the code which in fact calls this external API, i.e. from this assembler content:

procedure       _HandleAnyException;
asm
    (...)
004038B6 52               push edx  // Save exception object
004038B7 51               push ecx  // Save exception address
004038B8 8B542428         mov edx,[esp+$28]
004038BC 83480402         or dword ptr [eax+$04],$02
004038C0 56               push esi  // Save handler entry
004038C1 6A00             push $00
004038C3 50               push eax
004038C4 68CF384000       push $004038cf  // @@returnAddress
004038C9 52               push edx
004038CA E88DD8FFFF       call RtlUnwind

So we will retrieve the RtlUnwind address from this very last line.

The E8 byte is in fact the opcode for the asm call instruction. Then the called function is stored as an integer offset, starting from the current pointing value.

The E8 8D D8 FF FF byte sequence is executed as "call the function available at the current execution address, plus integer($ffffd88d)".
As you may have guessed, $004038CA+$ffffd88d+5 points to the RtlUnwind definition.

So here is the main function of this patching:

procedure Patch(P: PAnsiChar);
var i: Integer;
    addr: PAnsiChar;
begin
  for i := 0 to 31 do
    if (PCardinal(P)^=$6850006a) and  // push 0; push eax; push @@returnAddress
       (PWord(P+8)^=$E852) then begin // push edx; call RtlUnwind
      inc(P,10); // go to call RtlUnwind address
      if PInteger(P)^<0 then begin
        addr := P+4+PInteger(P)^;
        if PWord(addr)^=$25FF then begin // jmp dword ptr []
          PatchCodePtrUInt(Pointer(addr+2),cardinal(@RTLUnwindProc));
          exit;
        end;
      end;
    end else
    inc(P);
end;

We will cal this Patch subroutine from the following code:

procedure PatchCallRtlUnWind;
asm
  mov eax,offset System.@HandleAnyException+200
  call Patch
end;

You can note that we need to retrieve the _HandleAnyException address from asm code. In fact, the compiler does not let access from plain pascal code to the functions of System.pas having a name beginning with an underscore.

Then the following lines:

  for i := 0 to 31 do
    if (PCardinal(P)^=$6850006a) and  // push 0; push eax; push @@returnAddress
       (PWord(P+8)^=$E852) then begin // push edx; call RtlUnwind

will look for the expected opcode asm pattern in _HandleAnyException routine.

Then we will compute the position of the jmp dword ptr [] call, via this line:

        addr := P+4+PInteger(P)^;

After checking that this is indeed a jmp dword ptr [] instruction (expected opcodes are FF 25), we will simply patch the absolute address with our RTLUnwindProc procedure variable.

With this code, each call to RtlUnwind in System.pas will indeed call the function set by RTLUnwindProc.

In our case, it will launch the following procedure:

procedure SynRtlUnwind(TargetFrame, TargetIp: pointer;
  ExceptionRecord: PExceptionRecord; ReturnValue: Pointer); stdcall;
asm
  pushad
  cmp  byte ptr SynLogExceptionEnabled,0
  jz   @oldproc
  mov  eax,TargetFrame
  mov  edx,ExceptionRecord
  call LogExcept
@oldproc:
  popad
  pop ebp // hidden push ebp at asm level
{$ifdef DELPHI5OROLDER}
  jmp RtlUnwind
{$else}
  jmp oldUnWindProc
{$endif}
end;

This code will therefore:
- Save the current register context via pushad / popad opcodes pair;
- Check if TSynLog should intercept exceptions (i.e. if the global SynLogExceptionEnabled boolean is set);
- In case of interception, call our logging function LogExcept with the appropriate parameters;
- Call the default Windows RtlUnwind API, as expected by the Operating System.

Comments are welcome on our forum, just as usual.