Sevael wrote: ↑Tue Sep 29, 2020 7:39 pm
...
I honestly believe you're full of shit
If that were the case, why did you provide a table with a ton shit of editing capability? Why are people here posting stuff that has to do with unlocks, like those numerical ids you're so fond of? If something were to happen to FRF, the topic would either be locked down or removed completely. Since it's still here, knock yourselves out
P.S.#1: Kindly freakin' post the data hashes in HEXADECIMAL format (e.g.: 3139467863 Might -> 0xBB207A57), people..
P.S.#2: And since we're at it.. these are not proper:
Check out the values in the table vs. the ones at the bottom-left. Now I know you've calculated the offsets.. but the actual code accessing the values really shows the way to get to the proper format.
Let's break the code down:
Code: Select all
avengers.exe+1D66DC0 - 48 83 B9 F8030000 00 - cmp qword ptr [rcx+000003F8],00 { 0 }
avengers.exe+1D66DC8 - 74 3E - je avengers.exe+1D66E08
avengers.exe+1D66DCA - 44 8B 02 - mov r8d,[rdx]
avengers.exe+1D66DCD - 33 D2 - xor edx,edx
avengers.exe+1D66DCF - 41 8B C0 - mov eax,r8d
avengers.exe+1D66DD2 - 48 F7 B1 00040000 - div [rcx+00000400]
avengers.exe+1D66DD9 - 48 8B 81 E8030000 - mov rax,[rcx+000003E8]
avengers.exe+1D66DE0 - 48 8B 04 D0 - mov rax,[rax+rdx*8]
avengers.exe+1D66DE4 - 48 85 C0 - test rax,rax
avengers.exe+1D66DE7 - 74 1F - je avengers.exe+1D66E08
avengers.exe+1D66DE9 - 0F1F 80 00000000 - nop dword ptr [rax+00000000]
avengers.exe+1D66DF0 - 44 39 40 08 - cmp [rax+08],r8d
avengers.exe+1D66DF4 - 74 08 - je avengers.exe+1D66DFE
avengers.exe+1D66DF6 - 48 8B 00 - mov rax,[rax]
avengers.exe+1D66DF9 - 48 85 C0 - test rax,rax
avengers.exe+1D66DFC - 75 F2 - jne avengers.exe+1D66DF0
avengers.exe+1D66DFE - 48 85 C0 - test rax,rax
avengers.exe+1D66E01 - 74 05 - je avengers.exe+1D66E08
avengers.exe+1D66E03 - 0FB7 40 0C - movzx eax,word ptr [rax+0C]
avengers.exe+1D66E07 - C3 - ret
avengers.exe+1D66E08 - 33 C0 - xor eax,eax
avengers.exe+1D66E0A - C3 - ret
Your base is:
So from what I could gather, there's a
global pointer that leads to the structure address in use here: "avengers.exe"+0x47B8C30. If you read the quad from this one, you get that base I'm talking about. Who is the base? Well, in my case:
So, for me: [avengers.exe+0x47B8C30] == 0000000176073CF0. Fine so far.
Then if you check the code snippet I posted above, there's this:
Code: Select all
..
avengers.exe+1D66DD9 - 48 8B 81 E8030000 - mov rax,[rcx+000003E8]
avengers.exe+1D66DE0 - 48 8B 04 D0 - mov rax,[rax+rdx*8]
..
Which means a pointer to an array/table is being acquired from 0x3E8 offset, then, based on edx (rdx), a pointer to another structure is retrieved. I am assuming here you've thought "well, the offsets for each item read through here are constant". Hence why I see this in the table:
Now.. if you go like "well, it works for me", then we're done here
Else.. that 0x100 offset you put there I'm afraid isn't a constant.
Back to "who is the base":
Looking at the member-functions table, I saw this function at offset 0x28:
So [avengers.exe+0x47B8C30] == 0000000176073CF0 ==
InventoryComponent. Member-function at [[InventoryComponent]+0x28] is
GetClassName.
Alright, so far so good. Now let's take a look at InventoryComponent+0x3F8, 0x400 and 0x3F8. Because our code looks like this:
Code: Select all
avengers.exe+1D66DC0 - 48 83 B9 F8030000 00 - cmp qword ptr [rcx+000003F8],00 { 0 }
avengers.exe+1D66DC8 - 74 3E - je avengers.exe+1D66E08
avengers.exe+1D66DCA - 44 8B 02 - mov r8d,[rdx]
avengers.exe+1D66DCD - 33 D2 - xor edx,edx
avengers.exe+1D66DCF - 41 8B C0 - mov eax,r8d
avengers.exe+1D66DD2 - 48 F7 B1 00040000 - div [rcx+00000400]
avengers.exe+1D66DD9 - 48 8B 81 E8030000 - mov rax,[rcx+000003E8]
avengers.exe+1D66DE0 - 48 8B 04 D0 - mov rax,[rax+rdx*8]
So:
Therefore, if 0x3F8 is 0, there's no calculus done, as the JE @ avengers.exe+1D66DC8 will exit. If it's not 0, then:
- a value is read into r8d from [rdx]
- edx becomes 0 and the read value is moved into eax
- a division occurs (the div instruction) which, according to the theory behind it (
[Link]), does this:
Code: Select all
mov edx, 0 ; clear dividend
mov eax, 0x8003 ; dividend
mov ecx, 0x100 ; divisor
div ecx ; EAX = 0x80, EDX = 0x3
In our case, "xor edx,edx" does the "clear dividend" part. Then the "dividend" is the value read from [rdx] into r8d, then moved into eax. So "mov eax,0x8003" matches our "mov eax,r8d". Then the "mov ecx,0x100" + "div ecx" is in the form of "div [rcx+400]". So [rcx+400] is our divisor. And we know its value is 0x11. So the operation is "whatever value is in [rdx]->r8d->eax divided by [rcx+400], as in 0x11". The result is the offset you want to combine then in [[InventoryComponent+3E8]+x*8]. Where "x" is the result of the division.
Which brings us to the new question: who the fuck is "x"? Let's see how it's obtained. Just return to game, then set a breakpoint @ avengers.exe+1D66DC0:
Code: Select all
avengers.exe+1D66DC0 - 48 83 B9 F8030000 00 - cmp qword ptr [rcx+000003F8],00 { 0 }
I'm using x64dbg to debug this. Then press TAB to open up the Inventory list. When your CE or x64dbg breaks, exit the function (check [rsp]) and you'll land here:
Notice how I've already set a breakpoint a little bit up because that's where this LOOP starts. The loop that reads all 9 resource types. How do I know there's 9? Well, at the very bottom you have a check to see if loop continues or exits. Here:
So what does it actually happen in the loop and where's the
dividend value read from? It comes from this logic:
Code: Select all
00000001421F9180 | 8BF3 | MOV ESI,EBX | starting index is 0 (ebx=0)
00000001421F9182 | 41:0FB60CB7 | MOVZX ECX,BYTE PTR DS:[R15+RSI*4] | [r15+rsi*4] = 0x5 (offset for idx 0)
00000001421F9187 | E8 D4C088FF | CALL avengers.141A85260 |
Inside the CALL:
Code: Select all
0000000141A85260 | 0FB6C1 | MOVZX EAX,CL |
0000000141A85263 | 48:8B0D E695D202 | MOV RCX,QWORD PTR DS:[1447AE850] |
0000000141A8526A | 48:6BC0 78 | IMUL RAX,RAX,78 |
0000000141A8526E | 48:0341 18 | ADD RAX,QWORD PTR DS:[RCX+18] |
0000000141A85272 | C3 | RET |
So the resulting RAX will be, in my case, 000000015E8301C0.
Then:
Code: Select all
00000001421F918C | 4C:8D05 FD1D7200 | LEA R8,QWORD PTR DS:[14291AF90] | 000000014291AF90:"resource"
00000001421F9193 | 48:8D4C24 68 | LEA RCX,QWORD PTR SS:[RSP+68] |
00000001421F9198 | 8B10 | MOV EDX,DWORD PTR DS:[RAX] | 0xCB78FF8 == resource_hash
Then the CALL will check if the hash in edx is valid (if 0, then it will return 0):
Code: Select all
00000001415D9E70 | 8911 | MOV DWORD PTR DS:[RCX],EDX |
00000001415D9E72 | 85D2 | TEST EDX,EDX | is 0?
00000001415D9E74 | 48:8BC1 | MOV RAX,RCX |
00000001415D9E77 | 0F4415 0688E401 | CMOVE EDX,DWORD PTR DS:[143422684] |
00000001415D9E7E | 8911 | MOV DWORD PTR DS:[RCX],EDX |
00000001415D9E80 | C3 | RET |
Continuing:
Code: Select all
00000001421F91A4 | 8B00 | MOV EAX,DWORD PTR DS:[RAX] | hash
..
00000001421F91AE | E8 2D6FE4FF | CALL avengers.1420400E0 |
The function @ 1420400E0 is a look-up function. It uses the hash value to find a pointer in a table. And that pointer, in my case, is this: 000000028B0C9FA0. If I then check its memory space, I see this:
So.. hey.. 0xCB78FF8 == hash for "Fragments"
Let's continue:
Code: Select all
00000001421F91B7 | 48:8BF8 | MOV RDI,RAX | rdi<-pHash
..
00000001421F91DE | 48:8B4C24 60 | MOV RCX,QWORD PTR SS:[RSP+60] | this becomes [avengers.exe+0x47B8C30] == 0000000176073CF0
00000001421F91E3 | 48:8D5424 34 | LEA RDX,QWORD PTR SS:[RSP+34] | this loads the hash value in rdx (truncated edx is the hash DWORD)
..
00000001421F91FC | E8 BFDBB6FF | CALL avengers.141D66DC0 | our read function, with the CMP and all we talked about
So then inside the CALL:
Code: Select all
0000000141D66DC0 | 48:83B9 F8030000 00 | CMP QWORD PTR DS:[RCX+3F8],0 | 0x11
0000000141D66DC8 | 74 3E | JE avengers.141D66E08 |
0000000141D66DCA | 44:8B02 | MOV R8D,DWORD PTR DS:[RDX] | 0xCB78FF8 (our hash)
0000000141D66DCD | 33D2 | XOR EDX,EDX | 0
0000000141D66DCF | 41:8BC0 | MOV EAX,R8D | 0xCB78FF8
0000000141D66DD2 | 48:F7B1 00040000 | DIV QWORD PTR DS:[RCX+400] | 0x11
.. | --> rdx == 0x8
0000000141D66DD9 | 48:8B81 E8030000 | MOV RAX,QWORD PTR DS:[RCX+3E8] | p
0000000141D66DE0 | 48:8B04D0 | MOV RAX,QWORD PTR DS:[RAX+RDX*8] | [p+8*8]
0000000141D66DE4 | 48:85C0 | TEST RAX,RAX | 0000000175DE92C0 == q
..
0000000141D66DF0 | 44:3940 08 | CMP DWORD PTR DS:[RAX+8],R8D | check if [q+8] == hash
..
0000000141D66DFE | 48:85C0 | TEST RAX,RAX | check if q == valid
..
0000000141D66E03 | 0FB740 0C | MOVZX EAX,WORD PTR DS:[RAX+C] | read amount
So I get 0x2E9 in eax, which is 745 as decimal. And that value matches this, on-screen:
So that's the analysis behind it. The only thing left to determine is where R15 comes from. Which R15?.. This one:
Code: Select all
00000001421F9180 | 8BF3 | MOV ESI,EBX | starting index is 0 (ebx=0)
00000001421F9182 | 41:0FB60CB7 | MOVZX ECX,BYTE PTR DS:[R15+RSI*4] | [r15+rsi*4] = 0x5 (offset for idx 0)
But do you actually need to know anything about it? Or is it sufficient that we've determine the calculus logic? What our read function needs is the base pointer and a hash value. With the breakpoint here:
Code: Select all
00000001421F91B3 | 8B4C24 78 | MOV ECX,DWORD PTR SS:[RSP+78] |
You will see the p->hash|string in RAX. And in my case, these are:
"Fragments" -> 0x0CB78FF8
"Plasma" -> 0x77E2DF2B
"Nanite" -> 0xA101AAAC
"Nanotube" -> 0x5779E7B7
"Catalyst" -> 0x5F13B6D1
"Upgrade Module" -> 0xC6658524
"Polychoron" -> 0xED2554AD
"Uru" -> 0x13C24C60
"DNA Key" -> 0x1B82A710
"Currency Coins" -> 0xF31BB28C
So here's a quick Lua with all of the above:
Code: Select all
local InventoryComponent = readQword( getAddressSafe( process ) + 0x47B8C30 )
local offset = readInteger( InventoryComponent + 0x400 )
print( string.format( "%X", InventoryComponent ) )
print( string.format( "%X", offset ) )
local dwFragmentsHash = 0x0CB78FF8
local Fragments_offset = dwFragmentsHash % offset
print( string.format( "%X", Fragments_offset ) )
local pResource = readQword( InventoryComponent + 0x3E8 )
local Fragments = readQword( pResource + Fragments_offset * 8 )
print( string.format( "%X", Fragments ) )
local readHash = readInteger( Fragments + 0x8 )
print( string.format( "0x%08X", readHash ) )
local readAmount = readInteger( Fragments + 0xC )
print( string.format( "%d", readAmount ) )
BR,
Sun