 |
Cheat Engine The Official Site of Cheat Engine
|
View previous topic :: View next topic |
Author |
Message |
ralfonat Newbie cheater
Reputation: 0
Joined: 03 Nov 2022 Posts: 11
|
Posted: Sun Dec 10, 2023 4:35 am Post subject: lua aob understanding symbols, addresses for injecting |
|
|
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 |
|
 |
ParkourPenguin I post too much
Reputation: 150
Joined: 06 Jul 2014 Posts: 4651
|
Posted: Sun Dec 10, 2023 12:31 pm Post subject: |
|
|
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 |
|
 |
ralfonat Newbie cheater
Reputation: 0
Joined: 03 Nov 2022 Posts: 11
|
Posted: Sun Dec 10, 2023 2:07 pm Post subject: |
|
|
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
THANKS!
|
|
Back to top |
|
 |
ParkourPenguin I post too much
Reputation: 150
Joined: 06 Jul 2014 Posts: 4651
|
Posted: Sun Dec 10, 2023 4:21 pm Post subject: |
|
|
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 |
|
 |
ralfonat Newbie cheater
Reputation: 0
Joined: 03 Nov 2022 Posts: 11
|
Posted: Mon Dec 11, 2023 5:26 am Post subject: |
|
|
thank you *so* much for the detailed explanation, that really helped me cross the bridge here
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 |
|
 |
ParkourPenguin I post too much
Reputation: 150
Joined: 06 Jul 2014 Posts: 4651
|
Posted: Mon Dec 11, 2023 1:21 pm Post subject: |
|
|
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 |
|
 |
ralfonat Newbie cheater
Reputation: 0
Joined: 03 Nov 2022 Posts: 11
|
Posted: Tue Dec 12, 2023 5:17 am Post subject: |
|
|
OK, I'm still not 100% sure I understand the purpose of autoAssemble then.
Anyways, back to the question that drags on me:
the 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 |
|
 |
ParkourPenguin I post too much
Reputation: 150
Joined: 06 Jul 2014 Posts: 4651
|
Posted: Tue Dec 12, 2023 12:36 pm Post subject: |
|
|
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 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:
- Create a stringlist
- Add "local syntaxcheck,memrec=..." as the first line of code (memrec is just the memory record associated with the script, if one exists)
- Move all the lines between {$lua} and {$asm} into the stringlist
- Load the lines of code from the stringlist as a chunk via luaL_loadstring (this generates a Lua function)
- Push the syntaxcheck / memrec parameters and call the function w/ lua_pcall
- 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 |
|
 |
ralfonat Newbie cheater
Reputation: 0
Joined: 03 Nov 2022 Posts: 11
|
Posted: Wed Dec 13, 2023 5:47 am Post subject: |
|
|
now everything is clear. thanks again SO much for helping out. much appreciated!
|
|
Back to top |
|
 |
|
|
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
|
|