isJuhn / KAMI

Kot And Mouse Injector
MIT License
124 stars 13 forks source link

[INFO] How do you find camera addresses? #74

Closed FilipePS closed 1 year ago

FilipePS commented 1 year ago

I know how to find camera address with Cheat Engine. But the address you use is internal to the game and not to the RPCS3 process. What programs/tools do you use to find these addresses? If you use the Cheat Engine, how do you convert the address found in the Cheat Engine to the game's internal address? Any way to connect the Cheat Engine directly to the game's memory instead of the RPCS3 process?

dio-gh commented 1 year ago

RPCS3's VM base address used to be fixed, if you ask in the RPCS3 Discord's #development channel they should be able to tell you if that changed, and if not, what the value of it is (I forgot, maybe 0x300000000?). You then simply subtract that from the address you see in Cheat Engine and there you go. I think they may have also exported the base address as a variable for Cheat Engine users to pick up. I know PCSX2 does that, but I don't remember if RPCS3 does too.

Note that by the PS3 era most games had their camera objects heap allocated, so you'll need to trace out the whole allocation chain to make KAMI's hooking reliable. Once you have it traced out, you can represent this allocation chain in KAMI like this, and KAMI will automatically dereference its way through to get to the camera object, while also ensuring it doesn't corrupt random stuff.

Of course for a single shot test, no need to worry about this. If you have any further questions, we have a very barebones Discord server set up, if you pass me your username I can send you an invite.

dio-gh commented 1 year ago

Seems like it's at 0x2'0000'0000 these days:

https://github.com/RPCS3/rpcs3/blob/df718bcb0f2b7be809bf48b6059be7322fc17202/rpcs3/Emu/Memory/vm.cpp#L43-L44

Probably have been for the longest time in fact, I'm probably mixing it with another emu.

FilipePS commented 1 year ago

Thanks for the info. But I'm a little confused as to what so you'll need to trace out the whole allocation chain really means.

Looking at the KAMI code DerefChain seems to be a way to read a pointer. But I'm not sure about that.

Could you explain what you mean by trace out the whole allocation chain and what DerefChain really is?

By the way KAMI doesn't work for me, it always crashes on any emulator in any game.

FilipePS commented 1 year ago

On line 32 the allocation address is incremented by 0x100000000 so the base address must be 0x300000000. https://github.com/RPCS3/rpcs3/blob/df718bcb0f2b7be809bf48b6059be7322fc17202/rpcs3/Emu/Memory/vm.cpp#L32

dio-gh commented 1 year ago

By the way KAMI doesn't work for me, it always crashes on any emulator in any game.

That's a bit suspect, I went ahead and gave it a whirl, it works correctly here. Could you share some details, like what OS are you on, what versions of the emulators you're using, what games are you testing with, etc.?

But I'm a little confused as to what so you'll need to trace out the whole allocation chain really means.

For a proof of concept for yourself, there's no need. You can just hardcode the guest addresses in KAMI and give it a shot. The hook may fail if you reboot the game however, since dynamically allocated objects may land elsewhere. So if you'd like to actually use your hook or submit it to here, you'll need to work out a pointer chain that resolves the camera addresses reliably.

Perhaps I'm using the wrong nomenclatures, my understanding of low level systems programming is very poor. Your camera object is likely to be placed at a dynamic location in memory, so its address will be held by another object somewhere. This indirection may happen a few more times, but eventually you'll end up somewhere static - specifically, you'll end up somewhere inside the game executable's area. Per the PS3 ABI that area should be immutable, so if you can find a pointer chain that starts there and takes you to the camera object reliably, KAMI provides you with DerefChains to represent this process.

By tracing these out I meant that you'll need to use RPCS3's (or PCSX2's) debugger and find each of these intermediate objects using it.

For RPCS3 this consists of placing a memory breakpoint on your camera addresses, then while in PPU Interpreter mode, you'll see the memory address of the VM's instruction pointer register in one of the host registers. I forgot which one has it. If you then go there in CE, you'll be able to see the guest address of the current instruction that is trying to access the given camera value. By resuming and repeatedly copying these addresses out, collect all the different ones you can find, then remove your memory breakpoint and open the RPCS3 debugger. Set a breakpoint in each of those addresses and try working your way backwards in the control flow. It's worth using Ghidra alongside this. Note that your breakpoints in the RPCS3 debugger will be thread specific, so you might need to test around which thread was the game reading the camera value from.

Once that's done the rest is more or less reverse engineering. You'll have to find what the base address of your camera object is, what other objects hold it, then rinse repeat. You'll need to work your way backwards until you find a structure that's within the game executable's area. Once you have that, you should have a starting address and a list of offsets that eventually point you to the camera value. You could test this in CE manually first, then implement it in KAMI. Beware of objects with lifecycles shorter than the camera object itself.

We used to have an experimental automated way of doing this by the way, but it hasn't been rebased in a while or further developed. You might find it useful or insightful though. Note that it was always a bit unreliable and may not produce an optimal pointer chain.

On line 32 the allocation address is incremented by 0x100000000 so the base address must be 0x300000000.

Good catch! Seems like I should have trusted my memories more :D

FilipePS commented 1 year ago

I tried to use KAMI v0.0.4 in RPCS3 latest version in game Call of Duty 3 and Battlefield bad Company. When I start the game KAMI shows information about the game and then closes without showing any error message.

I found the camera address in the Cheat Engine and converted it to RPCS3 by subtracting 0x300000000. And indeed it was the correct address. I put it in the RPCS3 Debugger and selected PPU[0x1000000] main_thread then I tried to put a breakpoint and this message came up Cannot set breakpoint on non-executable memory!, the message is explanatory, but I don't know what to do (maybe use only the Cheat Engine debugger?).

dio-gh commented 1 year ago

When I start the game KAMI shows information about the game and then closes without showing any error message.

Hmm, that is very strange. What happens with the debugger attached, could you provide a stack trace per chance?

Are you sure you have the correct IPC port set? (28012) Is this on Windows by the way? Is the specific game version supported?

I put it in the RPCS3 Debugger

Sorry if it was unclear, you're meant to place the memory breakpoint in CE. I'm aware that elad has since implemented memory breakpoints in RPCS3's debugger infra, but I do not have any experience using it so I can't advise you on how to use it.

Or maybe I misunderstand and you tried placing an instruction breakpoint at the camera address you have? That's not going to work, please read the process I laid out again.

FilipePS commented 1 year ago

Are you sure you have the correct IPC port set? (28012) Is this on Windows by the way? Is the specific game version supported?

RPCS3 latest on Windows 10 latest. I don't know what specific game version is supported, simply because I don't know which version KAMI supports.

Sorry if it was unclear, you're meant to place the memory breakpoint in CE. I'm aware that elad has since implemented memory breakpoints in RPCS3's debugger infra, but I do not have any experience using it so I can't advise you on how to use it.

Or maybe I misunderstand and you tried placing an instruction breakpoint at the camera address you have? That's not going to work, please read the process I laid out again.

Breakpoint in CE will give me an instruction on x86 architecture. I don't know if RPCS3 has any option to get the Power PC (or is it Mips?) instruction from the x86 instruction that the Cheat Engine finds.

Anyway, I believe that only with the Cheat Engine is it possible to find the base/static address as well as all the offsets.

In the RPCS3 code, the memory allocated in the g_base_addr pointer has a size of 0x2'0000'0000. So I assume the region to scan is between 0x3'0000'0000 and 0x5'0000'0000 correct? Are static addresses also in this range? https://github.com/RPCS3/rpcs3/blob/df718bcb0f2b7be809bf48b6059be7322fc17202/rpcs3/Emu/Memory/vm.cpp#L43

dio-gh commented 1 year ago

I don't know what specific game version is supported

For Call of Duty 3, all versions of the european release (BLES00016), and for BF Bad Company all versions of the european and american releases (BLES00261, BLUS30118). Either that or the base versions only, and patched versions weren't tested.

Thinking about it, I'm not sure it should CTD though even if you try hooking an unsupported game, so a stack trace would still be appreciated.

Breakpoint in CE will give me an instruction on x86 architecture.

The host instruction that your CE memory breakpoint will provide to you is irrelevant, what you want to inspect is the host registers when the bp is hit. One of the registers (EBX? ECX?) will hold the (host) memory location of the given PPU interpreter thread's (guest) instruction pointer register. If you then jump there in CE's memory viewer, you'll be able to inspect the guest address of the guest instruction the interpreter was executing when it tried to access the camera.

0x5'0000'0000

You only need to go up to 0x4'0000'0000, the rest is mirrored memory. And yes, the static addresses are supposed to be in that range too, if my memory serves me right.

FilipePS commented 1 year ago

Thinking about it, I'm not sure it should CTD though even if you try hooking an unsupported game, so a stack trace would still be appreciated.

Stack trace of KAMI?

I took the base address KAMI uses and put it in the RPCS3 memory view. That memory region only contained bytes with value 00 so maybe the program is out of date. This in Battlefield bad company.

I made a plugin for Cheat Engine to get raw mouse input. And I made a script to write the address of the camera according to the mouse movement. I used AOB scan to get the camera address, sometimes AOB stops working.

dio-gh commented 1 year ago

Stack trace of KAMI?

Well yes, that's what crashes after all, no?

so maybe the program is out of date

It's more likely that the game has been updated and the hook is not compatible with the updated version. Either that, or the pointer chain isn't correct, but that's less likely as it would have came up during testing.

The scipts sound cool btw, great job!

FilipePS commented 1 year ago

I was able to get the PowerPC instruction that writes to the address. But it's kind of hard to find the pointer. I think if I find a code cave I can make a path to get the address from that instruction.

I compiled KAMI instead of using version 0.0.4. And this time the application didn't crash, but it didn't work either.

image image

isJuhn commented 1 year ago

Try installing 1.20 update for the game

FilipePS commented 1 year ago

With version 1.20 it works thanks.

FilipePS commented 1 year ago

Using ida pro + rpcs3 debugger I finally found a pointer. It's a different pointer than the one KAMI uses. Thank you for your help.

FilipePS commented 1 year ago

I was able to do a pointer scan with the Cheat Engine on RPCS3.

With this Lua script in Cheat Engine I dumped all memory regions between 0x300000000 and 0x400000000, the regions that were inaccessible I filled with NULL. The file C:/final_dump.bin will be generated.

local memory_regions = enumMemoryRegions()

local final_file = io.open("c:/final_dump.bin", "wb")
local file_offset = 0

for i=1,#memory_regions do
    local region = memory_regions[i]
    if
        region.BaseAddress >= 0x300000000
    and region.BaseAddress <  0x400000000
    and region.State       == 4096
    and region.Type        == 262144
    then
        print(string.format("%X", memory_regions[i].BaseAddress), memory_regions[i].State, memory_regions[i].Type)
        local filename = "region_" .. string.format("%X", memory_regions[i].BaseAddress) .. ".bin"
        writeRegionToFile(filename, memory_regions[i].BaseAddress, memory_regions[i].RegionSize)
        local file = io.open(filename, "rb")
        local fill_space_size = memory_regions[i].BaseAddress - 0x300000000 - file_offset
        if fill_space_size > 0 then
            print(fill_space_size)
            final_file:write(string.rep("\0", fill_space_size))
            file_offset = file_offset + fill_space_size
        end
        local content = file:read("a")
        final_file:write(content)
        file:close()
        os.remove(filename)
        file_offset = file_offset + #content
    end
end

final_file:close()

So with this script in C I transformed the Big Endian values into Little Endian from the final_dump.bin file (needs to be in the executable directory). The final_dump_2.bin file will be generated in the same directory as the executable.

#include <Windows.h>
#include <iostream>

#pragma warning(disable: 4996)

using namespace std;

struct FileMemory {
    BYTE* buffer = NULL;
    size_t size = NULL;
}fileMemory;

void copyToMemory() {
    OFSTRUCT openBuff;
    HFILE file = OpenFile("final_dump.bin", &openBuff, OF_READ);
    if (file == HFILE_ERROR) {
        cerr << "Nao foi possivel abrir o arquivo. " << endl;
        CloseHandle((HANDLE)file);
        return;
    }

    cout << "GetFileType: " << GetFileType((HANDLE)file) << endl;

    LARGE_INTEGER file_size;
    if (GetFileSizeEx((HANDLE)file, &file_size) == FALSE) {
        cerr << "Nao foi possivel obter o tamanho do arquivo." << endl;
        CloseHandle((HANDLE)file);
        return;
    }

    cout << "Tamanho do arquivo: " << file_size.LowPart << endl;

    BYTE* buffer = (BYTE*)calloc(file_size.LowPart, 1);
    if (!buffer) {
        cerr << "Nao foi possivel alocar memoria." << endl;
        CloseHandle((HANDLE)file);
        return;
    }

    DWORD copied_size = NULL;
    if (ReadFile((HANDLE)file, buffer, file_size.LowPart, &copied_size, NULL) == FALSE) {
        cerr << "Nao foi possivel copiar o arquivo para a memoria." << endl;
        CloseHandle((HANDLE)file);
        return;
    }

    CloseHandle((HANDLE)file);

    cout << "Bytes do arquivo copiado para a memoria: " << copied_size << endl;

    fileMemory.buffer = buffer;
    fileMemory.size = file_size.LowPart;
}

void bigEndianToLittleEndian() {
    for (DWORD offset = 0; offset < fileMemory.size - 4; offset += 4) {
        BYTE temp = *(fileMemory.buffer + offset + 3);
        *(fileMemory.buffer + offset + 3) = *(fileMemory.buffer + offset);
        *(fileMemory.buffer + offset) = temp;

        temp = *(fileMemory.buffer + offset + 2);
        *(fileMemory.buffer + offset + 2) = *(fileMemory.buffer + offset + 1);
        *(fileMemory.buffer + offset + 1) = temp;
    }

    cout << "bigEndianToLittleEndian finalizado." << endl;
}

void copyToFile() {
    OFSTRUCT openBuff;
    HFILE file = OpenFile("final_dump_2.bin", &openBuff, OF_WRITE | OF_CREATE);
    if (file == HFILE_ERROR) {
        cerr << "Nao foi possivel abrir o arquivo. " << endl;
        CloseHandle((HANDLE)file);
        return;
    }

    cout << "GetFileType: " << GetFileType((HANDLE)file) << endl;

    DWORD copied_size = NULL;
    if (WriteFile((HANDLE)file, fileMemory.buffer, fileMemory.size, &copied_size, NULL) == FALSE) {
        cerr << "Nao foi possivel escrever no arquivo." << endl;
        CloseHandle((HANDLE)file);
        return;
    }

    CloseHandle((HANDLE)file);

    cout << "Bytes escritos no arquivos: " << copied_size << endl;
}

int main() {
    copyToMemory();
    if (!fileMemory.buffer) {
        cerr << "erro." << endl;
        getchar();
        return 1;
    }

    bigEndianToLittleEndian();

    copyToFile();

    cout << "Operacao finaliza." << endl;

    getchar();
    return 0;
}

After dumping a few times (I think 3 is fine), and transforming the files to Little Endian (remember to save the camera address for each dump). Just open each of the final_dump.bin files in the Cheat Engine (CE will show a window with some options, leave 32-Bit and Start address: 0x00000000) and generate a Pointermap for each file. Then you can do a pointer scan. To do so, open the first dump file and do a pointer scan with the following configuration: image Notice that I used Max offset: 700 and Max level: 3. This is to speed up the pointer scan. If you don't get results try increasing the Max level one by one.