Yes that's right ! I've seen your proposal as well and I've a better proposal that solves your proposal issues(Step Over/replicating the codes) and it's a little bit slightly faster !
To begin, the issue arise because there was a mismatch between a call and a ret instruction (a ret instruction that doesn't correspond to a call instruction). In your proposal, you introduced a call to fix the issue but that also introduced Step Over issue !
Here is my proposal : if we jumped without using a call instruction then we simply return without using a ret instruction. How ? we do a lazy stack pop (add esp, 4) to remove the return address from the stack then we jump back to the return address (jmp [esp - 4]).
Program Test;
{$APPTYPE CONSOLE}
{$R *.res}
{$O+,W-}
uses
Diagnostics, Windows;
{$DEFINE PATCH_TRY_FINALLY}
{$DEFINE REPLACE_RET_WITH_JMP}
procedure Test;
var
i: Integer;
begin
i := 0;
try
Inc(i);
asm
nop
nop
end;
finally
Dec(i);
Dec(i);
Dec(i);
{$IFDEF REPLACE_RET_WITH_JMP}
{
payload :
---------
add esp, 4 // remove return address from the stack
jmp [esp - 4] // jmp back (return address)
}
Dec(i);
Dec(i);
{$ENDIF}
end;
if i = 0 then;
end;
procedure PatchTryFinally1(address: Pointer);
const
jmp: array [0 .. 14] of Byte = ($33, $C0, $5A, $59, $59, $64, $89, $10, $E8, $02, $00, $00, $00, $EB, $00);
var
n: NativeUInt;
target: Pointer;
offset: Byte;
begin
target := PPointer(PByte(address) + 11)^;
offset := PByte(target) - (PByte(address) + 10) - 5;
WriteProcessMemory(GetCurrentProcess, address, @jmp, SizeOf(jmp), n);
WriteProcessMemory(GetCurrentProcess, PByte(address) + SizeOf(jmp) - 1, @offset, 1, n);
FlushInstructionCache(GetCurrentProcess, address, SizeOf(jmp));
end;
procedure PatchTryFinally2(address: Pointer);
const
Data: array [0 .. 6] of Byte = ($83, $C4, $04, $FF, $64, $24, $FC);
var
n: NativeUInt;
begin
WriteProcessMemory(GetCurrentProcess, address, @Data, SizeOf(Data), n);
end;
procedure PatchTryFinally(address: Pointer);
begin
{$IFDEF REPLACE_RET_WITH_JMP}
PatchTryFinally2(PByte(@Test) + $32);
{$ELSE}
PatchTryFinally1(PByte(@Test) + 26);
{$ENDIF}
end;
var
i: Integer;
sw: TStopwatch;
begin
{$IFDEF PATCH_TRY_FINALLY}
PatchTryFinally(PByte(@Test));
{$ENDIF}
sw := TStopwatch.StartNew;
Sleep(1);
sw.ElapsedMilliseconds;
sw := TStopwatch.StartNew;
for i := 1 to 100000000 do
Test;
Writeln(sw.ElapsedMilliseconds);
Readln;
end.