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.