Cheat Engine Forum Index Cheat Engine
The Official Site of Cheat Engine
 
 FAQFAQ   SearchSearch   MemberlistMemberlist   UsergroupsUsergroups   RegisterRegister 
 ProfileProfile   Log in to check your private messagesLog in to check your private messages   Log inLog in 


lua aob understanding symbols, addresses for injecting

 
Post new topic   Reply to topic    Cheat Engine Forum Index -> Cheat Engine Lua Scripting
View previous topic :: View next topic  
Author Message
ralfonat
Newbie cheater
Reputation: 0

Joined: 03 Nov 2022
Posts: 11

PostPosted: Sun Dec 10, 2023 4:35 am    Post subject: lua aob understanding symbols, addresses for injecting Reply with quote

hey there,

so the basic AOBScan template fails because the address is not unique. I know however that there are only 3 exactly identical results and it is always the first one.

So since I cannot find a good documentation on AA:aobScan I found the Lua:aobScan where I can simply check the number of results and use the first one in the return array.

Now I am having difficulty incorporating this into the regular aob AA template.

Can someone give me a pointer? Basically I am just trying to put the correct found address into the INJECT symbol, but there seems to be some sort of magic going on in the AA:aobScan symbol registering that I cant seem to replicate.

This is what I have so far and it won't work:

Code:

{$lua}
function myLuaFunc(addr)
--  print(string.format("%x", addr))
  local d = nil
  local ptr = addr + 0x10 --offset!
  print(ptr)

  if ptr >= 0x900000 then
    d = readInteger(ptr)
    --print(string.format("%x", ptr), ' = ', d)
    if d >= 1 and d <= 1000 then
      print(string.format("%x", ptr), ' = ', d)
    else
      d = nil -- not a candidate
    end
  else
    --print(string.format("%x", ptr), ' = ?')
  end

  if d ~= nil then
    local addressList = getAddressList()
    for i = 0, addressList.Count-1 do
      local mr = addressList.MemoryRecord[i]
      if mr.CurrentAddress == ptr then
        return
      end
    end

    local mr = addressList.createMemoryRecord()
    mr.setAddress(ptr)
    mr.Description = tostring(d)
  end
end
{$asm}

loadlibrary(luaclient-x86_64.dll)
luacall(openLuaServer('CELUASERVER'))

CELUA_ServerName:
db 'CELUASERVER',0

alloc(functionid,4)
alloc(functionname,32)

functionid:
dd 0

functionname:
db 'myLuaFunc',0

{
//luacall call example:
//Make sure rsp is aligned on a 16-byte boundary when calling this function
mov rcx, addresstostringwithfunction //(The lua function will have access to the variable passed by name "parameter")
mov rdx, integervariableyouwishtopasstolua
sub rsp,20
call CELUA_ExecuteFunction // or CELUA_ExecuteFunctionAsync if you don't need GUI access or want to handle it yourself
add rsp,20

//------
//Alternate call by ref example:

mov ecx,[addresswithluafunctionidstored]
test ecx,ecx
jne short hasrefid

mov rcx,addresswithluafunctionname
call CELUA_GetFunctionReferenceFromName  //Basically calls createRef(functionname) and returns the value
mov [addresswithluafunctionidstored],eax
mov ecx,eax

hasrefid:
mov edx,numberofparameterstopass
mov r8,addresswithparameterlist  //could be the stack.  e.g lea r8,[rsp+8]
mov [r8],param1
mov [r8+8],param2
mov [r8+c],param3
//...
mov r9,0 //0=no async, 1=async.  Use async if you do not wish to update the GUI. Faster
call CELUA_ExecuteFunctionByReference

When done RAX will contain the result of the lua function
And as per 64-bit calling convention, RCX, RDX, R8, R9, R10, R11 may have been altered. So save/restore them beforehand
}

[ENABLE]
{$lua}
if syntaxcheck then return end

aobresult=AOBScan("0F 86 B6 00 00 00 48 6B C9 18 48 03 C1 48 8B F8 48 83 C7 20 48 8B C7 48 63 00 85 C0 0F 8C 46 00 00 00 48 8B 4F 08 48 63 47 10 33 D2 48 89 55 C8 48 89 55 D0 48 89 4D C8 89 45 D0 48 8B 45 C8 48 89 45 D8 48 8B 45 D0 48 89 45 E0 48 8D 46 10")
if (aobresult~=nil) then
  print("Results found: "..aobresult.Count)
  if aobresult.Count>0 then
    print("First result="..aobresult[0])
    writeQword(INJECT, aobresult[0])
  end

  --when done doping "things"
  aobresult.destroy()
  aobresult=nil
else
  print("No results found")
end

{$asm}
alloc(newmem,$1000,INJECT)

label(code)
label(return)

newmem:
  // Save all general-purpose registers, including XMM registers
  sub rsp, 190 // 15*8 + 16*16 ; Allocate space for 15 QWORDs for general-purpose registers
                              // and 16 XMM registers (256 bytes in total)

  // Save general-purpose registers
  mov [rsp], rax
  mov [rsp+8], rcx
  mov [rsp+10], rdx
  mov [rsp+18], rbx
  mov [rsp+20], rsi
  mov [rsp+28], rdi
  mov [rsp+30], rbp
  mov [rsp+38], r8
  mov [rsp+40], r9
  mov [rsp+48], r10
  mov [rsp+50], r11
  mov [rsp+58], r12
  mov [rsp+60], r13
  mov [rsp+68], r14
  mov [rsp+70], r15

  // Save XMM registers
  movaps [rsp+80], xmm0
  movaps [rsp+90], xmm1
  movaps [rsp+a0], xmm2
  movaps [rsp+b0], xmm3
  movaps [rsp+c0], xmm4
  movaps [rsp+d0], xmm5
  movaps [rsp+e0], xmm6
  movaps [rsp+f0], xmm7
  movaps [rsp+100], xmm8
  movaps [rsp+110], xmm9
  movaps [rsp+120], xmm10
  movaps [rsp+130], xmm11
  movaps [rsp+140], xmm12
  movaps [rsp+150], xmm13
  movaps [rsp+160], xmm14
  movaps [rsp+170], xmm15

  mov ecx,[functionid]
  test ecx,ecx
  jne short hasrefid

  //no reference yet
  mov rcx,functionname
  call CELUA_GetFunctionReferenceFromName  //Basically calls createRef(functionname) and returns the value
  mov [functionid],eax
  mov ecx,eax

  hasrefid:
  //here ecx contains the referenceid
  mov edx, 1  //number of params

  lea r8,[rsp+180]
  // parameter 1
  mov [r8],rdi //in 64-biut the parameters are 64-bit as well

//  mov rax,[rsp+50]
//  mov [r8+10], rax
//  lea rax, [r8+10]
//  mov [r8+08], rdi

  mov r9,0 //0=no async, 1=async. In our case 0 because of showMessage

  call CELUA_ExecuteFunctionByReference

//  mov [rbx+790],eax //set health to the return value of the function

  // Restore general-purpose registers
  mov rax, [rsp]
  mov rcx, [rsp+8]
  mov rdx, [rsp+10]
  mov rbx, [rsp+18]
  mov rsi, [rsp+20]
  mov rdi, [rsp+28]
  mov rbp, [rsp+30]
  mov r8, [rsp+38]
  mov r9, [rsp+40]
  mov r10, [rsp+48]
  mov r11, [rsp+50]
  mov r12, [rsp+58]
  mov r13, [rsp+60]
  mov r14, [rsp+68]
  mov r15, [rsp+70]

  // Restore XMM registers
  movaps xmm0, [rsp+80]
  movaps xmm1, [rsp+90]
  movaps xmm2, [rsp+a0]
  movaps xmm3, [rsp+b0]
  movaps xmm4, [rsp+c0]
  movaps xmm5, [rsp+d0]
  movaps xmm6, [rsp+e0]
  movaps xmm7, [rsp+f0]
  movaps xmm8, [rsp+100]
  movaps xmm9, [rsp+110]
  movaps xmm10, [rsp+120]
  movaps xmm11, [rsp+130]
  movaps xmm12, [rsp+140]
  movaps xmm13, [rsp+150]
  movaps xmm14, [rsp+160]
  movaps xmm15, [rsp+170]

  add rsp, 190 // 8*16 + 16*16

code:
  movsxd  rax,dword ptr [rdi+10]
  xor edx,edx
  jmp return

INJECT:
  jmp newmem
  nop 3
return:

registersymbol(INJECT)

[DISABLE]
{$lua}
if syntaxcheck then return end

{$asm}
INJECT:
  db 48 63 47 10 33 D2

unregistersymbol(INJECT)

dealloc(newmem)

{
// ORIGINAL CODE - INJECTION POINT: 1FC0DC40C3F

1FC0DC40C19: 0F 86 B6 00 00 00  - jbe 1FC0DC40CD5
1FC0DC40C1F: 48 6B C9 18        - imul rcx,rcx,18
1FC0DC40C23: 48 03 C1           - add rax,rcx
1FC0DC40C26: 48 8B F8           - mov rdi,rax
1FC0DC40C29: 48 83 C7 20        - add rdi,20
1FC0DC40C2D: 48 8B C7           - mov rax,rdi
1FC0DC40C30: 48 63 00           - movsxd  rax,dword ptr [rax]
1FC0DC40C33: 85 C0              - test eax,eax
1FC0DC40C35: 0F 8C 46 00 00 00  - jl 1FC0DC40C81
1FC0DC40C3B: 48 8B 4F 08        - mov rcx,[rdi+08]
// ---------- INJECTING HERE ----------
1FC0DC40C3F: 48 63 47 10        - movsxd  rax,dword ptr [rdi+10]
// ---------- DONE INJECTING  ----------
1FC0DC40C43: 33 D2              - xor edx,edx
1FC0DC40C45: 48 89 55 C8        - mov [rbp-38],rdx
1FC0DC40C49: 48 89 55 D0        - mov [rbp-30],rdx
1FC0DC40C4D: 48 89 4D C8        - mov [rbp-38],rcx
1FC0DC40C51: 89 45 D0           - mov [rbp-30],eax
1FC0DC40C54: 48 8B 45 C8        - mov rax,[rbp-38]
1FC0DC40C58: 48 89 45 D8        - mov [rbp-28],rax
1FC0DC40C5C: 48 8B 45 D0        - mov rax,[rbp-30]
1FC0DC40C60: 48 89 45 E0        - mov [rbp-20],rax
1FC0DC40C64: 48 8D 46 10        - lea rax,[rsi+10]
}
Back to top
View user's profile Send private message
ParkourPenguin
I post too much
Reputation: 150

Joined: 06 Jul 2014
Posts: 4651

PostPosted: Sun Dec 10, 2023 12:31 pm    Post subject: Reply with quote

You don't need all of that. You aren't calling a CE Lua function from within the game's process- you're just executing a bit of Lua code when the script is enabled.
Code:
[ENABLE]
{$lua}
if syntaxcheck then return 'define(INJECT,0)' end

-- only scan through executable, non-CoW, non-writable memory (this is usually what you want for a code injection)
local results = assert(AOBScan('01 23 45', '+X-C-W'), 'No results found')

local addr = getAddressSafe(results[0])
results.destroy()

return ('define(INJECT,%X)'):format(addr)
{$asm}
...
This {$lua} block more or less replaces the AA code "aobscan(INJECT,01 23 45)". Use a more unique name than "INJECT". If two or more scripts register the same symbol, bad things happen.
_________________
I don't know where I'm going, but I'll figure it out when I get there.
Back to top
View user's profile Send private message
ralfonat
Newbie cheater
Reputation: 0

Joined: 03 Nov 2022
Posts: 11

PostPosted: Sun Dec 10, 2023 2:07 pm    Post subject: Reply with quote

Hey ParkourPenguin,

thank you so much for your reply. That made it work right away. Help me understand this please, as I would have never come up with it.

What does the "return" in a root lua block actually mean? I mean there is really nothing to return to as we are not in a function?

Is that documented somewhere?

So of course now the program decided to shuffle things up and now it's not the first anymore. How would I go on about injecting *ALL* found memory locations with being able to revert the injects on disable?

Since the return statement will end the call right there?

I tried to emulate the "return" with:

Code:
autoAssemble("{$asm}\n" .. ('define(INJECT,%X)'):format(addr))


but that doesn't seem to work. What kind of magic is this return statement I wonder Wink

THANKS!
Back to top
View user's profile Send private message
ParkourPenguin
I post too much
Reputation: 150

Joined: 06 Jul 2014
Posts: 4651

PostPosted: Sun Dec 10, 2023 4:21 pm    Post subject: Reply with quote

A {$lua} block basically contains a Lua function that gets called when the script is run. The string it returns gets substituted back into the script. If it doesn't return anything, nothing gets substituted.

AA scripts, and by extension {$lua} blocks, get executed twice when the AA script is run. The first time is to check the syntax of the AA script- this doesn't really do anything significant other than complain if you did something wrong. If that's successful, the script runs a second time where it actually allocates memory, writes instructions/values, registers symbols, etc.
{$lua} blocks need to account for this. CE sets a local variable "syntaxcheck" before the first line in {$lua} blocks for this purpose.
In the case of the script I posted, it just defines the symbol of the injection point to be wherever for the syntax check stage. It's not important what exactly the symbol is- the only important matter is that it's defined. When it's not doing a syntax check, it actually needs to do the aobscan.

ralfonat wrote:
How would I go on about injecting *ALL* found memory locations with being able to revert the injects on disable?
That sounds like a bad idea as you could screw with stuff you didn't intend. I'd try to inject at a caller if that's feasible. Details here:
https://forum.cheatengine.org/viewtopic.php?p=5787422#5787422

Regardless, doing that is more difficult than you think. There's no guarantee your code (i.e. newmem) will be within 2 GiB of all the injection points (see RIP-relative addressing), so you can't just jump to your code from all the injection points. Otherwise, CE might decide to assemble a 14-byte jmp instead of a 5-byte jmp and screw you over.

There's also the more obvious issue of `jmp return` being ambiguous.

If you always know it's going to be 3 results, you could just inject at 3 different spots. Something like this:
Code:
[ENABLE]
{$lua}
if syntaxcheck then return [[
define(INJECT0,0)
define(INJECT1,0)
define(INJECT2,0)
]] end

-- only scan through executable, non-CoW, non-writable memory (this is usually what you want for a code injection)
local results = assert(AOBScan('01 23 45', '+X-C-W'), 'No results found')
assert(results.Count == 3, results.Count .. ' results found. Expected 3')

local addr0 = getAddressSafe(results[0])
local addr1 = getAddressSafe(results[1])
local addr2 = getAddressSafe(results[2])
results.destroy()

return ([[
define(INJECT0,%X)
define(INJECT1,%X)
define(INJECT2,%X)
]]):format(addr0, addr1, addr2)
{$asm}

alloc(newmem0,2048, INJECT0)
alloc(newmem1,128, INJECT1)
alloc(newmem2,128, INJECT2)

label(myInjection)
label(return0)
label(return1)
label(return2)

newmem0:
  call myInjection
  jmp return0

myInjection:
  // NB: the `call` instruction temporarily modifies rsp
  movsxd  rax,dword ptr [rdi+10]
  xor edx,edx
  ret

newmem1:
  call myInjection
  jmp return1

newmem2:
  call myInjection
  jmp return2


INJECT0:
  jmp newmem0
  nop 3
return0:

INJECT1:
  jmp newmem1
  nop 3
return1:

INJECT2:
  jmp newmem2
  nop 3
return2:


registersymbol(INJECT0)
registersymbol(INJECT1)
registersymbol(INJECT2)

[DISABLE]

INJECT0:
  //db <originalbytes>
INJECT1:
  //db <originalbytes>
INJECT2:
  //db <originalbytes>

unregistersymbol(INJECT0)
unregistersymbol(INJECT1)
unregistersymbol(INJECT2)

dealloc(newmem0)
dealloc(newmem1)
dealloc(newmem2)
Injecting at an arbitrary number of spots is more complicated and would need to be done mostly in Lua.
_________________
I don't know where I'm going, but I'll figure it out when I get there.
Back to top
View user's profile Send private message
ralfonat
Newbie cheater
Reputation: 0

Joined: 03 Nov 2022
Posts: 11

PostPosted: Mon Dec 11, 2023 5:26 am    Post subject: Reply with quote

thank you *so* much for the detailed explanation, that really helped me cross the bridge here Wink

There is just one unanswered thing for me:

ending the ${lua} block, are these 2 "identical" ?

Code:
return ('define(INJECT,%X)'):format(addr)


Code:
autoAssemble("{$asm}\n" .. ('define(INJECT,%X)'):format(addr))
Back to top
View user's profile Send private message
ParkourPenguin
I post too much
Reputation: 150

Joined: 06 Jul 2014
Posts: 4651

PostPosted: Mon Dec 11, 2023 1:21 pm    Post subject: Reply with quote

No. The Lua function `autoAssemble` runs a new auto assembler script. It's like when you click the "Execute" button at the bottom of a new AA script window. Calling `autoAssemble` from within a {$lua} block has nothing to do with the AA script that the {$lua} block is in- it's an entirely new AA script that's being executed.
Code:
{$lua}
if syntaxcheck then return end
print'script 0'
autoAssemble[=[
{$lua}
print'script 1'
autoAssemble[[
{$lua}
print'script 2'
]]
]=]

The notion of calling `autoAssemble` inside a {$lua} block is odd. I think it's fine- I haven't seen or experienced anything that would indicate the auto assembler has a problem with reentrancy. I'd still avoid it if possible. Making code injections at an arbitrary number of spots is one of the few times I'd consider it a reasonable option.

_________________
I don't know where I'm going, but I'll figure it out when I get there.
Back to top
View user's profile Send private message
ralfonat
Newbie cheater
Reputation: 0

Joined: 03 Nov 2022
Posts: 11

PostPosted: Tue Dec 12, 2023 5:17 am    Post subject: Reply with quote

OK, I'm still not 100% sure I understand the purpose of autoAssemble then.

Anyways, back to the question that drags on me:

the
Code:
return 'define...' 
statement then does 2 things:

1- Act as if the statement was written as is outside the ${lua} block.
2- return / halt execution ?

that means if you were to "return" multiple things to the AA like
Code:
${lua}
return ('define(INJECT,%X)'):format(addr)
return ('define(INJECT2,%X)'):format(addr+10)


the second return would never be reached? That's why I was thinking there must be a statement that just "produces" the output without 'return'ing

Or am I way off here?

Thanks!
Back to top
View user's profile Send private message
ParkourPenguin
I post too much
Reputation: 150

Joined: 06 Jul 2014
Posts: 4651

PostPosted: Tue Dec 12, 2023 12:36 pm    Post subject: Reply with quote

ralfonat wrote:
OK, I'm still not 100% sure I understand the purpose of autoAssemble then.
Its purpose is to run AA code from Lua.
It's not that common, but there are uses for it. Maybe a timer could periodically assemble something.

ralfonat wrote:
the
Code:
return 'define...' 
statement then does 2 things:

1- Act as if the statement was written as is outside the ${lua} block.
2- return / halt execution ?
I've read CE's source. This is what a {$lua} block does:
  1. Create a stringlist
  2. Add "local syntaxcheck,memrec=..." as the first line of code (memrec is just the memory record associated with the script, if one exists)
  3. Move all the lines between {$lua} and {$asm} into the stringlist
  4. Load the lines of code from the stringlist as a chunk via luaL_loadstring (this generates a Lua function)
  5. Push the syntaxcheck / memrec parameters and call the function w/ lua_pcall
  6. If the return value is a string, make a stringlist from it and insert each line into the AA script
See `Cheat Engine/autoassembler.pas` for more information.
See the Lua 5.3 reference for information on the Lua functions.
https://www.lua.org/manual/5.3/contents.html
ralfonat wrote:
that means if you were to "return" multiple things to the AA like
Code:
${lua}
return ('define(INJECT,%X)'):format(addr)
return ('define(INJECT2,%X)'):format(addr+10)


the second return would never be reached? That's why I was thinking there must be a statement that just "produces" the output without 'return'ing
A function can only execute one return statement. If you want to insert multiple lines from a single {$lua} block, then return multiple lines. See my second post for an example.
{$lua} blocks aren't some weird generator that yields values. It's just a function call.

_________________
I don't know where I'm going, but I'll figure it out when I get there.
Back to top
View user's profile Send private message
ralfonat
Newbie cheater
Reputation: 0

Joined: 03 Nov 2022
Posts: 11

PostPosted: Wed Dec 13, 2023 5:47 am    Post subject: Reply with quote

now everything is clear. thanks again SO much for helping out. much appreciated!
Back to top
View user's profile Send private message
Display posts from previous:   
Post new topic   Reply to topic    Cheat Engine Forum Index -> Cheat Engine Lua Scripting All times are GMT - 6 Hours
Page 1 of 1

 
Jump to:  
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum
You cannot attach files in this forum
You can download files in this forum


Powered by phpBB © 2001, 2005 phpBB Group

CE Wiki   IRC (#CEF)   Twitter
Third party websites