Sure, I don't mind. About r9, you can tell from the first screenshot, where you see the function call.fecdan wrote: ↑Fri Nov 04, 2022 3:39 pm
In the beginning, I can only understand about 20%.
After another 10 plus hours work on your answer. Now I understand a lot more, about 70% I guess?
If you don't mind, may I ask some more questions, plz?
The first one is that I don't know how to find r9 is the pointer to a struct on the stack, though it seems not that important if I don't know the reason.
lea r9,[rbp-30] //this takes the address (mind you not the content like mov) of [rbp-30] and moves it into r9
So r9 contains the address of something, i.e. it is a pointer. And it contains the address of memory on the stack, because of rbp.
The next few lines are essentially:
mov [rbp-30],rsi -> mov [r9],rsi
mov [rbp-28],rbx -> mov [r9+8],rbx
movups [rbp-20],xmm0 -> mov [r9+10],xmm0(1) + mov [r9+18],xmm0(2)
movups [rbp-10],xmm0 -> mov [r9+20],xmm0(1) + mov [r9+28],xmm0(2)
This could either be a struct, just a collection of different values. Or it could be more input arguments than 4 and since the architecture only has 4 registers for arguments, the 4th must be a pointer to the remaining arguments, probably what happens here, now that I think about it.
You just need any instruction there, so a breakpoint can be used between before that line and after it. Could be any instruction really, lea rax,[rax] just doesn't do anything other than writing rax back to itself. Could be mov rax,rax too I guess. It's the same as while writing c++ or java or something and you write something like "int test = 4;" somewhere, just so you can put a breakpoint there.fecdan wrote: ↑Fri Nov 04, 2022 3:39 pmseikur0 wrote: ↑Thu Nov 03, 2022 1:03 pmWe inject after the whole register backup and rbp setup is complete to (hopefully) see the same rbp offsets as at the place where to value actually gets changed within the function. rbp typically only gets changed at the function start.
Set our value to 20(41) (can even freeze that) and prepare the game so that we only need to confirm the item usage with one click (to avoid the function being called for other reasons than the value change), in my case "Salmon of Health".
Set the breakpoint in our injected script at the "lea rax,[rax]" line. (Doesn't break = success) Now use the item (Break = success²).
The injection code line lea rax,[rax]. The reason why to use the instruction "lea" is that to make sure the change happen in an address.
Cuz Cheat Engine only can tell the addresses change, If the changes only happens between registers, the breakpoint won't work. Is my understanding right?
The first injection location would be good, not the code.
The first injection is just for testing purposes, it has the really specific condition that it only reaches the lea rax,[rax] line,
if the value gets changed to 19, which isn't that useful. Also it's very unspecific at that point and the line might get executed while browsing the menu or doing anything else. You don't know, if the function will change the value later at that point.
For the place, if the filter was already perfect, we could inject here with one disadvantage: We don't know the currentValue. So we could just overwrite the new value here.
The injection at the very start of the function would be more stable in regards to updates, as you can access rdx there directly and don't have to rely on it being in [rbp+28] randomly.
You could also change the id passed to the function in rdx / [rdx] and [rdx+10], so you could actually give yourself an item you don't have yet, also only possible at that location. (If you injected in the calling function, you could even change it, so you'd call the function more than once, with different ids from 0 to x, to give yourself all items. But that'd be done with lua probably or you'd need two injections one before and one after the function itself to do the loop.)
No the target address is the address for "Salmon of Health". You can do "Browse this memory region" on it and then in the memory viewer set a data breakpoint with "Break on write" per right click.fecdan wrote: ↑Fri Nov 04, 2022 3:39 pmseikur0 wrote: ↑Thu Nov 03, 2022 1:03 pmNow we need to know where rdx went at the place where we inject. You can step through the code or use "break and trace" or you use the breakpoint where we injected before and once it gets hit, you write down rdx, then you put a breakpoint on the write to our target address and take a look at where you can find rdx (simplest method).
I don't understand " put a breakpoint on the write to our target address and take a look at where you can find rdx (simplest method)" part.
I mean, does the target address mean the new rdx that I write down?
After that you unpause the function and it will run from the beginning to that write and break there. And at that point, you look for the rdx you wrote down.
That's exactly it. push rbp, push r15, push r14, push r13, push r12, push rdi <----fecdan wrote: ↑Fri Nov 04, 2022 3:39 pmSorry, I don't mean to ask stupid basic questions, but I don't know where is the top of the function. I don't see "push rdi". I thought it's push rbp, push r15, push r14, the whole registers backup part?seikur0 wrote: ↑Thu Nov 03, 2022 1:03 pmFrom that I can see that rdx is stored in [rbp+28]. It's the 6th value pushed to the stack after the caller address in [rbp+50]. If we look at the top of the function that is the "push rdi". And sure enough in the calling function we see the mov rdx,rdi we've seen before already. It's kind of random/lucky, but hey it works.
Because the function has to backup the registers, we luckily have a backup of rdx.
Rdx itself isn't pushed, because it is the input argument, however before the function is called,
we can see the mov rdx,rdi.
So by backing up rdi, the function essentially backs up rdx, though it's never gonna access that value on the stack for itself, only for restoring rdi at the end.
0x100000000 could be anything really. After looking it up yesterday, I'm not even sure anymore, if the heap is always in the 64bit space of memory, so >0x100000000, or if it's just very very probable due to ASLR (google it). As I wrote, it's the quick and dirty method, but it works. You could always find more precise ways or track down, how the function itself knows, if it's a pointer.fecdan wrote: ↑Fri Nov 04, 2022 3:39 pmseikur0 wrote: ↑Thu Nov 03, 2022 1:03 pmOkay so let's put a breakpoint where we actually want to inject, directly where the value gets written.
It will imediately break, so let's take a look at the [[rbp+28]] data with the dissect data tool again:
But what's this? It's not a pointer?Great our input apparently isn't always a pointer to a struct..Spoiler
On the bright side, if it's a pointer, we can probably determine the item type from it. So we need a way to tell if it's a valid pointer, otherwise the game crashes, when we try to access that invalid memory location.
At that point the clean, but also fragile and time-consuming way, would be to figure out, how the function itself knows if it's a pointer or not, i.e. what exactly does it do here. I did that for a while but didn't find any easy solution, so the not so clean method it is.
We saw that rdx, if it's a pointer, is a pointer to the heap memory. (Can also use the "memory regions" tool to verify.)
I think I read somewhere that heap memory for x64 only starts at 0x100000000 for the addresses.
So the first condition to accept rdx is, that ist must be >= 0x100000000.
I also notice, that r10 is always somewhat close to the [rbp+28] address. Okay so for the upper limit I take r10 plus another 0x100000000 and if [rbp+28] is within these limits I'll assume it is a pointer.
We do another injection to take a look at the actual values.Put the breakpoint at "lea rax,[rax]". Okay seems to be working, all rax values look similar and seem to be pointers now.Spoiler
Take a look at these with the "data dissect" tool again.
So before for the two values we're interested in we saw that the id at struct offset 0 always had 0x12007 as first value. Now we can see that's not the case. So this seems to be another good filter, the assumption is 0x12007 is the id for "item". Then at offset 0x10 there would be an 8 byte item id, that's the item type, we saw before there.
So the remaining filters we need would be "[[rbp+28]] == 0x12007" and we could even do a whitelist/blacklist for item types, if we evaluate the "[[rbp+28]+10]" value, i.e. "[[rbp+28]+10] == #45" means it is the "Salmon of Health".
The rest is simple, in my script I want to allow increases, not decreases, so i just compare the currentValue to the newValue and execute the write at the end or not.
Another last thing for the filter, sometimes the new value is a pointer, likely that's some internal data structure management and we don't want to modify that, so the last filter we need is for the actual new value. If that's larger than the 0x100000000 we've established before, we always allow the write in the end.
Similarly for stats:
Here [rbp+28] won't be a pointer, but contain the stat id directly, filter those for ids below 0x10000. And I think [rbp+30] might be the class, seems to be always 0, add that to the filter.
Add, that the newValue must be lower than 0x100000000 (as before) and that's enough to only find stat changes (to be exact, stat modifier changes), STR, MAG, SKL, MaxHP, LDR. With the stat items, HP increases by 5, the other stats 2, LDR what it says, if you want to find the values, mind you in term of the "RPG VX type" value type.
The 0x100000000 way is so brilliant and comes from no where to me
I seem to be able to get the stat modifier now, but only the modifier from seize or item, not lvl up.
If you know how to find lvl up stat modifier, would you plz teach me?
I will continue digesting this last part.
Thank you so much.
No idea about the lvl up stat modifier, but if you can find one of the values with the CE search, you should be able to build a filter for that too. Might be a totally different location, I don't know. If it's the same location, you can do an injection and compare rdi+10 and the address you have directly and only break, if they match and then look at [rbp+28].
Do it for MAG, STR or SKL as armor and weapon stats might be entirely lvl dependent, but I don't know.
You can find the lvl of someone and use xp codices to trigger the changes until you find the values.
Have a great weekend,
seikur0
How to use this cheat table?
- Install Cheat Engine
- Double-click the .CT file in order to open it.
- Click the PC icon in Cheat Engine in order to select the game process.
- Keep the list.
- Activate the trainer options by checking boxes or setting values from 0 to 1