Using 'FileSeek' and 'Inline ASM' in Memory?

Just starting out? Need help? Post your questions and find answers here.
PoorMan
User
User
Posts: 15
Joined: Sat Oct 14, 2023 2:54 pm

Using 'FileSeek' and 'Inline ASM' in Memory?

Post by PoorMan »

; Hello PB Team
; I'm using FileSeek to move the Pointer to a new location in the 'Current Process Memory'
; Then at that location I'm trying to change EAX value, but this didn't work!
; Please advise?

Code: Select all

Procedure.i DoASM() 
  
hModule    = GetModuleHandle_(0)   ; Get the Current Process Handle
lpBaseAddress = hModule + $111F    ; Address of the New Location

FileSeek(0, lpBaseAddress)         ; Move the Pointer To a new location in the 'Current Process Memory'

EnableASM
MOV EAX, 99
DisableASM

MessageRequester("EAX new value: ", Str(EAX))

ProcedureReturn 
EndProcedure
// Code Tags added (Kiffi)
infratec
Always Here
Always Here
Posts: 7582
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

Re: Using 'FileSeek' and 'Inline ASM' in Memory?

Post by infratec »

EAX is a register.
You can not access it with Str(EAX).
In this case EAX is a PB variable and since it is never used before the value is 0

Use Enablexplicit to avoid such faults.
PoorMan
User
User
Posts: 15
Joined: Sat Oct 14, 2023 2:54 pm

Re: Using 'FileSeek' and 'Inline ASM' in Memory?

Post by PoorMan »

; Hi, thank you for your response.
; Done as you said, but didn't work (Eax register is not changed))

Code: Select all

EnableExplicit

Procedure.i DoASM()
  
hModule = GetModuleHandle_(0)
  lpBaseAddress = hModule + $111F 
  
  FileSeek(0, lpBaseAddress)
  
  EnableASM
  MOV EAX, 01
  DisableASM
  
EndProcedure

DisableExplicit
// Code Tags added (Kiffi)
Olli
Addict
Addict
Posts: 1200
Joined: Wed May 27, 2020 12:26 pm

Re: Using 'FileSeek' and 'Inline ASM' in Memory?

Post by Olli »

poorman wrote:DisableExplicit
:lol:

Code: Select all

Procedure.i DoASM()
  
 Protected hModule = GetModuleHandle_(0)
 Protected lpBaseAddress = hModule + $111F
 Protected EAX
  
  FileSeek(0, lpBaseAddress)
  
  ! mov eax, [p.v_lpbaseaddress]
  ! mov [p.v_eax], eax
  MessageRequester("EAX new value", Str(EAX) )
  
EndProcedure

Olli
Addict
Addict
Posts: 1200
Joined: Wed May 27, 2020 12:26 pm

Re: Using 'FileSeek' and 'Inline ASM' in Memory?

Post by Olli »

You can nest your source codes between the markups :

[code]
; mySourceCode
[/code]
PoorMan
User
User
Posts: 15
Joined: Sat Oct 14, 2023 2:54 pm

Re: Using 'FileSeek' and 'Inline ASM' in Memory?

Post by PoorMan »

Hi Olli
Thank you for your reply & Advice.
Please note:
1- How can I set the Register EAX to 9 in your code? I didn't get it!
2- I tried to use it in the following DLL Code, but it gave an error:

Code: Select all

ProcedureDLL DoASM()

 Protected hModule = GetModuleHandle_(0)
 Protected lpBaseAddress = hModule + $111F
 Protected EAX
  
  FileSeek(0, lpBaseAddress)
  
  ! mov eax, [p.v_lpbaseaddress]
  ! mov [p.v_eax], eax
  
  MessageRequester("EAX new value", Str(EAX))
  
EndProcedure
infratec
Always Here
Always Here
Posts: 7582
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

Re: Using 'FileSeek' and 'Inline ASM' in Memory?

Post by infratec »

Code: Select all

EnableExplicit

Procedure DoASM()
  
  Protected EAX.l
  
  
  Debug "EAX old value: " + Str(EAX)
  
  ! mov eax, 9
  ! mov dword [p.v_EAX], eax  ; p.v_EAX -> pointer to variable EAX
  
  Debug "EAX new value: " + Str(EAX)
  
EndProcedure

DoASM()
User avatar
yuki
Enthusiast
Enthusiast
Posts: 101
Joined: Sat Mar 31, 2018 9:09 pm

Re: Using 'FileSeek' and 'Inline ASM' in Memory?

Post by yuki »

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:
  1. Allocate some memory (*memDynCode) in the process to patch. Either mark it RWX and leave it or make it executable after writing.
  2. 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.
  3. 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.
  4. 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.
PoorMan
User
User
Posts: 15
Joined: Sat Oct 14, 2023 2:54 pm

Re: Using 'FileSeek' and 'Inline ASM' in Memory?

Post by PoorMan »

Gents, infratec & yuki,
Thank you for your swift responses..

infratec: ASM is working, but you removed the FileSeek function (because it doesn't work with Memory) without an alternative way to position the pointer over required location (lpBaseAddress) before changing EAX Register.

yuki: You are right, I'm trying to hook a game.
Please help amend the following code with one of the 2 functions you mentioned (CopyMemory or WriteProcessMemory_) as I don't see any relation between these functions & the CPU Registers!

Code: Select all

ProcedureDLL DoASM()
 Protected hModule = GetModuleHandle_(0)
 Protected lpBaseAddress = hModule + $111F
 
  Protected EAX.l
  ! mov eax, 9
  ! mov dword [p.v_EAX], eax  ; p.v_EAX -> pointer to variable EAX
EndProcedure
infratec
Always Here
Always Here
Posts: 7582
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

Re: Using 'FileSeek' and 'Inline ASM' in Memory?

Post by infratec »

If you move an absolute value to the register eax, you can place pointers before as many as you want:
the content of the eax register will always the value you move in it. :wink:

And you always told us you want to move the value 9 inside of the register eax.

If I ignore your wish for the value 9, I would think you want something like this:

Code: Select all

! mov eax, [p.v_lpBaseAddress]
! mov dword [p.v_EAX], eax
User avatar
yuki
Enthusiast
Enthusiast
Posts: 101
Joined: Sat Mar 31, 2018 9:09 pm

Re: Using 'FileSeek' and 'Inline ASM' in Memory?

Post by yuki »

infratec wrote: Sun Oct 15, 2023 8:31 pm If you move an absolute value to the register eax, you can place pointers before as many as you want:
the content of the eax register will always the value you move in it. :wink:

And you always told us you want to move the value 9 inside of the register eax.

If I ignore your wish for the value 9, I would think you want something like this:

Code: Select all

! mov eax, [p.v_lpBaseAddress]
! mov dword [p.v_EAX], eax
Immediate-value moves represent of course a clear case where the destination necessarily takes on a set value.

Though, this isn't super useful in completing @OP's task of changing the value of EAX as read from whatever arbitrary code is at $111f (unless it just happens to be a "mov r32, imm32").

PoorMan wrote: Sun Oct 15, 2023 6:25 pm yuki: You are right, I'm trying to hook a game.
Please help amend the following code with one of the 2 functions you mentioned (CopyMemory or WriteProcessMemory_) as I don't see any relation between these functions & the CPU Registers!
As mentioned in my previous post: we can't have EAX magically take on a value of 9 at the offset you've specified. We must to modify the code there in some way to produce this result.

I've no idea what is at $111f in the game you're targetting, so I have no idea how it should be modified. The only way I could provide a universal solution without knowing the content of this memory region would be to include a disassembler as part of that solution.

It's not entirely clear what you're trying to accomplish by setting EAX to 9, but it's possible Ockham is screaming beyond the grave right now.

Your other post suggests you're searching for a memory block — "E8 21 C2 00 00 85 C0 74 1B" — which is equivalent to this X86 assembly:

Code: Select all

; e8 21 c2 00 00
! call  $ + $c226
; 85 c0
! test  eax, eax
; 74 1b
! jz    $ + $1d
So, this code calls some function nearby (CALL), performs a bitwise logical AND of EAX against itself (TEST), and then jumps somewhere if EAX was apparently zero (JZ).

That could be anything. Maybe it's checking if the user is super-admin, then jumping away if not (EAX = 1 when super-admin). Or maybe it's checking if the player is dead and jumping away if still alive (EAX = 1 when dead). We've no idea without more info.

Because we don't know the target game, we've no idea what can safely be modified here. Some possible options:
  • If the CALL doesn't need to take place, you can replace it with your MOV directly.
  • If you want to flip behaviour around: change JZ to JNZ.
  • If you want to pretend the CALL never returns zero, with respect to the subsequent jump: change JZ to JC or similar.
    • e.g., if the CALL has EAX = 1 when super-admin, then doing this will have you appear as super-admin.
  • If you want to pretend the CALL always returns zero, with respect to the subsequent jump: change JZ to JMP.
    • e.g., if the CALL has EAX = 1 when the character is dead, then doing this will leave you always alive.
  • (and many other possibilities)
For example, if you can, and want to replace the CALL with a MOV:

Code: Select all

; Hey, listen!
; ═══════════════════════════════════════════════════════
; You will need FindMemory(…) and TryPatchMemory(…).
; Get them here:
; https://www.purebasic.fr/english/viewtopic.php?p=609265#p609265


; Example patch.
; ═══════════════════════════════════════════════════════
; Replacing "call near $c226" with "mov eax, 9".

TryPatchMemory(*memSearchBase, 512,
               ?pattern_something,
               ?pattern_something_end - ?pattern_something,
               ?patch_mov_eax_9,
               ?patch_mov_eax_9_end - ?patch_mov_eax_9)


; X86 code/patches.
; ═══════════════════════════════════════════════════════
DataSection
  ; X86 code to search for.
  pattern_something:
  Data.b $e8, $21, $c2, $00, $00, $85, $c0, $74, $1b
  pattern_something_end:
  
  ; X86 code for:
  ;   mov eax, 9
  ; In this example, we'll use it to replace:
  ;   call  $ + $c226
  patch_mov_eax_9:
  Data.b $b8
  Data.l 9
  patch_mov_eax_9_end:
EndDataSection

Or, if you instead want to replace JZ with JC:

Code: Select all

; Example patch.
; ═══════════════════════════════════════════════════════
; Replacing "jz near $24" with "jc near $24".

TryPatchMemory(*memSearchBase, 512,
               ?pattern_something,
               ?pattern_something_end - ?pattern_something,
               ?patch_jc_no_arg,
               ?patch_jc_no_arg_end - ?patch_jc_no_arg,
               7)


; X86 code/patches.
; ═══════════════════════════════════════════════════════
DataSection
  ; X86 code to search for.
  pattern_something:
  Data.b $e8, $21, $c2, $00, $00, $85, $c0, $74, $1b
  pattern_something_end:
  
  ; X86 code for:
  ;   jc  <!NO_ARG!>
  ; In this example, we'll use it to replace:
  ;   jz    $ + $1d
  patch_jc_no_arg:
  Data.b $72
  patch_jc_no_arg_end:
EndDataSection

⚠️ I must stress this again: you'll really want to familiarise yourself more with PureBasic (reference manual) and reverse-engineering (begin.re) before continuing. Patching code in arbitrary processes is something that should be done with a deeper understanding of things.

So far I've described the tools/processes/resources you need to do what you want. Because this isn't a reverse-engineering/game-hacking forum, I'm reluctant to write further on the subject here, as it can be an extremely contentious topic…
Olli
Addict
Addict
Posts: 1200
Joined: Wed May 27, 2020 12:26 pm

Re: Using 'FileSeek' and 'Inline ASM' in Memory?

Post by Olli »

yuki wrote: ⚠️ I must stress this again: you'll really want to familiarise yourself more with PureBasic (reference manual) [...]
Very good advice, thank you. I dupplicated it on the other subject.
Olli
Addict
Addict
Posts: 1200
Joined: Wed May 27, 2020 12:26 pm

Re: Using 'FileSeek' and 'Inline ASM' in Memory?

Post by Olli »

infratec wrote: Sat Oct 14, 2023 11:23 pm

Code: Select all

EnableExplicit

Procedure DoASM()
  
  Protected EAX.l
  
  
  Debug "EAX old value: " + Str(EAX)
  
  ! mov eax, 9
  ! mov dword [p.v_EAX], eax  ; p.v_EAX -> pointer to variable EAX
  
  Debug "EAX new value: " + Str(EAX)
  
EndProcedure

DoASM()
:lol: If I had the time, I would put a 3 * 3 product !
dcr3
Enthusiast
Enthusiast
Posts: 181
Joined: Fri Aug 04, 2017 11:03 pm

Re: Using 'FileSeek' and 'Inline ASM' in Memory?

Post by dcr3 »

Best lock both post :mrgreen: .
Olli
Addict
Addict
Posts: 1200
Joined: Wed May 27, 2020 12:26 pm

Re: Using 'FileSeek' and 'Inline ASM' in Memory?

Post by Olli »

Dear poorman,

CPU registers are done to be quick. When you see a pureBasic program seems to be too slow, it is interesting to try to go faster, by handling the registers.

But

1) the C backend compiler does lots of optimizations, enough to forget how does the CPU work.

2) what you try to do, does not require the CPU registers handling.

As infratec explained, use EnableExplicit : this helps a lot to progress without end.
Post Reply