fecdan wrote: ↑Thu Nov 03, 2022 4:41 am
Really want to know the basic logic so bad.
So you've found the line
x64-msvcrt-ruby240.rb_f_block_given_p+4D2 - 48 89 34 C1 - mov [rcx+rax*8],rsi
which is writing to a whole lot of addresses. You can't even use "Find out what addresses this instruction accesses" without the game crashing.
In that case your goal is to somehow filter for the relevant writes.
If you inject directly at that line you have access to the registers as well as the stack and usually and in this case as well that is enough to filter it.
So we're in a function, a function can have different behavior depending on its inputs (and maybe global variables, which isn't relevant here).
To understand the input, take a look at the
[Link]. Okay so rcx, rdx ,r8 and r9 are the input arguments and we can focus on these. Our function changes many values to a new value so it would look like this "changeValue(valueId, newValue, ...)".
In theory we could inject at the start of the function and change the input arguments to reach the desired effects, however in this case the function apparently does many things and if we inject at the line where the value is changed that's a good filter too, we filter out all function calls that don't write to the value.
Let's take a look at the calling function, we can find that from the stack trace in the "Find out what writes to this address" window:
I've marked the inputs in yellow. So rcx gets assigned, might be some class pointer, if our target function is a class method.
rdx gets assigned too. r8 will have one of two global values depending on some condition before (r12d). r9 is the pointer to a struct on the stack, which has 6*8 bytes created from the values of rsi, rbx and xmm0 (where xmm0 got some global 16 byte value before).
So to analyze the input arguments, ideally we want a breakpoint at the very start of the function, that only triggers, when the value we're interested in gets changed. Let's take a look at the stack for some ideas:
It is the stack trace from the moment where i used an item and thus changed its amount from 3 to 2, which would be 7 to 5 as decoded value. And sure enough on the stack itself the value of 5 can be found multiple times and even the 7 at [rbp+A8].
Okay cool so we use that as condition, in fact we set the condition as "value changed to 19(39)" that shouldn't occur very often.
And we add another condition, the function caller and the function caller of the function caller need to be the same. Those can be found in [rbp+58] and [rbp+E8]. The logic behind that is "if the function is being called from the same functions" hopefully it does the same thing, which we're interested in.
Now for the value, we really want the lowest value in the stack that has it (as the others might only get written later and might not be set at the start of the function. That is [rbp+D0].
Okay all set, no we do a simple code injection:
We 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²).
Okay cool, now let's take a look at rcx, rdx, r8 and r9 as we planned.
For that we use the dissect data tool.
Okay first things first, where is the "new amount" value? It's actually in the pointer to a struct within r9 we saw earlier.
So [r9] == "new amount".
Se second thing we wanted was the id, for that we need a second value, I use "Medal of Valor". Set that to 20 and then used it with our breakpoint active. Bingo.
So rcx looks very much the same as before, in fact it is the same address, so could be a class.
Now rdx is interesting:
The 0x12007 part is the same as before, but [rdx+10] now has a 47 instead of the 45 we saw before. So we could assume that those are item ids. In fact the 0x12007 looks very interesting too, could be an id as well, maybe for the item type.
r8 is the same as before (same address and all) and it doesn't seem as useful. (Not suprising, we saw before it gets assigned one of two globals.)
r9 is the same as well (not suprising, we changed the value to 19(39) in both cases.
Okay cool, let's use that information we gained wisely.
So: "changeValue(valueId, newValue, ...)"
valueId is within the rdx data while newValue is [r9].
Actually since we directly inject at the location where newValue gets written, newValue is not as relevant for us, so we focus on rdx.
Now 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).
From 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.
Okay 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..
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.
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.