If I had to guess, it seems like you're trying to patch the memory of a process by DLL injection. (game hacks, proprietary software mods, …)
If that's the case, there are a couple problems with your approach:
- FileSeek(…) should not be used to select a memory location to be modified. That command is meant for adjusting read/write position of files you've programmatically opened. You probably want CopyMemory(…) or raw pointers. For external processes, WriteProcessMemory_(…) can be used (on Windows).
- EAX is a CPU register. The values of CPU registers are seldom necessarily fixed for a given instruction address (barring, e.g., RIP). You can't directly set the value of EAX as read from an arbitrary target code address, but you can overwrite instructions reading/writing its value.
In the more trivial case…
In cases where the code-to-be-patched is structured such that you can trample over instructions, you can get away with just a memory-copy, though you will have to deal with OS-specific memory protection.
For example, on Windows x64/x32, if you want to replace "
mov eax,
9000" (or any 5-byte instruction sequence) with "
mov eax,
0" it's not too bad:
Code: Select all
Procedure Add9000(a)
! mov eax, 9000
! add eax, [p.v_a]
ProcedureReturn
EndProcedure
;; Finds a needle in a haystack.
;;
;; Returns the address where the needle resides, if found. Otherwise returns zero.
Procedure FindMemory(*memoryHaystack, memoryHaystackLength.i, *memoryNeedle, memoryNeedleLength.i)
; Bail if zero-length region on either side.
If memoryHaystackLength <= 0 Or memoryNeedleLength <= 0
ProcedureReturn 0
EndIf
; Traverse through region seeking our needle in the haystack.
Protected *end = *memoryHaystack + memoryHaystackLength - memoryNeedleLength
While *memoryHaystack <= *end
If CompareMemory(*memoryHaystack, *memoryNeedle, memoryNeedleLength)
ProcedureReturn *memoryHaystack
EndIf
*memoryHaystack + 1
Wend
; Needle does not exist in haystack.
ProcedureReturn 0
EndProcedure
;; Attempts to find a needle in haystack, then overwrite memory at the offset of said
;; needle.
;;
;; Returns the address where the patch was written to, if done at all. A value of zero
;; is returned if either the needle could not be found, or if the haystack doesn't have
;; sufficient space to contain the patch where needle is found (where the patch extends
;; past the needle).
Procedure TryPatchMemory(*memoryHaystack, memoryHaystackLength.i, *memoryNeedle, memoryNeedleLength.i, *memoryPatch, memoryPatchLength.i, memoryPatchOffset.i = 0)
Protected *addrToPatch = FindMemory(*memoryHaystack, memoryHaystackLength, *memoryNeedle, memoryNeedleLength)
; Bail if needle is not found in haystack.
If Not *addrToPatch
ProcedureReturn 0
EndIf
; Apply optional offset.
*addrToPatch + memoryPatchOffset
; Bail if we'd write anywhere outside the haystack.
If *addrToPatch < *memoryHaystack Or (memoryHaystackLength - (*addrToPatch - *memoryHaystack) < memoryPatchLength)
ProcedureReturn 0
EndIf
Protected prevMemoryProtection
; Ensure memory is writable.
If Not VirtualProtect_(*addrToPatch, memoryPatchLength, #PAGE_EXECUTE_READWRITE, @prevMemoryProtection)
ProcedureReturn 0
EndIf
; Perform our patch.
CopyMemory(*memoryPatch, *addrToPatch, memoryPatchLength)
; Restore previous memory protection state, because we're nice :)
VirtualProtect_(*addrToPatch, memoryPatchLength, prevMemoryProtection, @prevMemoryProtection)
ProcedureReturn *addrToPatch
EndProcedure
#SHOULD_PATCH = #False ; <-- Change #False to #True here in order to dynamically patch code.
If #SHOULD_PATCH
; Try and patch the Add9000(...) function.
; We'll search for "mov eax, 9000" and replace it with "mov eax, 0".
; This will of course change the result of calling Add9000().
TryPatchMemory(@Add9000(), 64,
?ops_mov_eax_9000, 5,
?ops_mov_eax_0, 5)
EndIf
MessageRequester("Result:", "9000 + 1 = " + Add9000(1), #PB_MessageRequester_Info)
DataSection
; Below is X86 code for "mov eax, 9000"
; - $B8 is a move IMM32 to destination EAX.
; - $2823000 is just little-endian 9000. (i.e. literal $2328)
ops_mov_eax_9000:
Data.b $B8, $28, $23, $00, $00
; Below is X86 code for "mov eax, 0"
; - $B8 is a move IMM32 to destination EAX.
; - $00000000 is merely 0.
ops_mov_eax_0:
Data.b $B8, $00, $00, $00, $00
EndDataSection
(change the value of #SHOULD_PATCH from #False to #True to see the code dynamically patched, and a sum of 1 resulted instead of 9001)
Some other more trivial cases:
- If you want EAX zeroed, and you have at least 2 bytes you can clobber: use "xor eax, eax" (31 C0) and NOPs where necessary.
- If you want EAX to be 1, and you have at least 3 bytes you can clobber: use "xor eax, eax" (31 C0), "inc eax" (40) and NOPs where necessary.
- …
In the not-so-trivial case…
Now, onto the non-trivial case... things become
tricky when you need to change
EAX, but you don't have something you might simply overwrite, e.g.:
Code: Select all
! call SOME_FUNCTION ; <-- How do we change the EAX resulting from this singular call?
! inc eax
Assume we need the
CALL preserved, since the app would break without it (maybe the function has important side-effects).
There's a problem: "
inc eax" takes up only 1 byte (40) and we can't overwrite it with "
mov eax,
1" as that takes 5 bytes (B8 01 00 00 00) and would clobber other instructions!
So how do we actually make it as if this specific call to SOME_FUNCTION always returns 0? (and is thus incremented to 1)
TLDR: it's not pretty, and you'll need dynamic code generation.
The general approach is:
- Allocate some memory (*memDynCode) in the process to patch. Either mark it RWX and leave it or make it executable after writing.
- Copy as many whole instructions from the destination to be patched into your *memDynCode, until you've copied at least "jmp [p_memDynCode]" worth of bytes (varies by arch and location of *memDynCode in relation to injection target). Make sure you copy full instructions; truncating will fail hard.
- Replace the instructions you've just copied over to *memDynCode with a JMP into *memDynCode, following by as many NOPs as needed to fill the length of instructions copied.
- In the memory space of *memDynCode, after the instructions copied from your patch-destination, insert your own code, followed by a JMP back.
To illustrate what this looks like in memory, assume you have something you want to patch, like this:
Code: Select all
! call SOME_FUNCTION ; <-- How do we change the EAX resulting from this singular call?
! inc eax
You'd overwrite the patch-target with a
JMP to your dynamic code (here replacing just
CALL), making it look like:
Code: Select all
! jmp MEM_DYN_CODE_HERE ; <-- We replaced the CALL, but our dynamic code will invoke it all the same.
! inc eax ; You could replace the INC instead, but you'd have to take at least 4 bytes of instructions
; following it, and copy them over to your dynamic code. CALL is just simpler in this case.
And your
*memDynCode would consist of something like:
Code: Select all
! call SOME_FUNCTION ; Copied instruction from destination, since we need behaviour preserved.
! mov eax, 0 ; Change EAX to 0. This could be whatever.
! jmp MEM_DESTINATION_AFTER_DETOUR ; Continue at "inc eax" in patch destination.
This would have the SOME_FUNCTION still invoked, but we'd continue after it while pretending it always returns 0.
The code you can replace like this is arbitrary. As long as you've room for a
JMP somewhere, you can copy original instructions over and splice your own changes in. It goes without saying, but this sort of thing necessitates significant caution.
Peyman's shown a practical example of this sort of technique with their HookEngine:
viewtopic.php?t=64746
To conclude…
I'd recommend getting more familiar with PB and reverse-engineering in general before continuing. Otherwise, you might find things fairly frustrating and/or error-prone. For PB, the
official reference manual and examples are quite great. For RE,
begin.re is a good introduction.