Recently c0re made a post ( http://forum.gamedeception.net/showthread.php?t=18794 ) that mentioned getting the address of a function in the QVM *after* it's been compiled to native code. I feel I should explain more about what this is and what it can do.
The Quake VM allows a developer to compile a module to a bytecode form and gain two benefits at a minimal cost of speed. These benefits are security and cross compatibility. Any platform that has an implementation of the Quake VM can run a module compiled to QVM code. QVM code is "more secure" because the QVM code can't do anything that the underlying VM can't(unless you find a vulnerability in the VM itself or the syscalls that are implemented in the VM give you access to do so).
There are a few different ways that QVMs are used in Q3 based games but I am going to focus on games that compile the QVM code to native code. That is, it converts the QVM instructions to analogous instructions or instruction sequences of the underlying architecture. In my case, it converts QVM bytecode to PowerPC machine code.
When the game wants to load a module, it calls VM_Create. VM_Create then checks a certain parameter and if the game uses DLLs, it'll load the DLL that was requested. However, if the game uses QVMs it'll call VM_LoadQVM. VM_LoadQVM loads the QVM into memory and does some sanity checks. If everything checks out, it initializes a vm_t struct. This vm_t struct has various information about the VM such as the syscall handler(syscall hooking is as simple as saying vm->systemCalls = &my_syscalls).
Here's the interesting bit. Once the QVM is loaded into memory, depending on how the game uses QVMs(interpreted or compiled, we're talking about compiling here) it'll compile the QVM to native machine code. It does this by going through every instructing in the QVM and creating analogous instruction sequences. If we are able to modify the QVM code *after* it's loaded into memory, but *before* it's compiled to native code, we can essentially change the QVM itself.
The easiest way to do this is to hook VM_LoadQVM, call the original, but before returning do something like this....
instructionPointers is a member of the vm_t struct that is an array of bytes, each representing a QVM opcode. 0x0FF5E7 is the number of the instruction(NOT the offset from the start of the code segment) in the QVM to be modified, and the element at instructionPointers[0xFF5E7] is the opcode of the 0x0FF5E7th instruction in the QVM. I don't know of any standard terminology to be applied here, but the main idea is to note that code addresses in the VM are not represented using byte offsets, they are represented using instruction numbers.Code:vmHeader_t *hook_VM_LoadQVM( vm_t *vm, qboolean alloc ) { vmHeader_t *ret = orig_VM_LoadQVM(vm, alloc); if(!strcmp(vm->name, "cgame") { vm->instructionPointers[0x0FF5E7] = 0x00; //0x00 is the opcode of whatever you want the instruction to be } return ret; }
Now once the QVM is compiled to native machine code, it compiles a completely different sequence of instructions. You've effectively changed the VM code.
This can be used for ton of things. I'm currently using it for no recoil in Urban Terror. c0re used it to find the address of a function after it's been compiled which would allow for native hooking instead of doing everything in the syscall handler. If you did a bit of work you might be able to implement completely QVM based function hooking.
I plan on doing more research and releasing a complete paper on the Quake 3 VM, as well as an example(UrT no recoil) utilizing this method.
It is 6AM here and I may sound like an idiot at the moment. Please excuse me. :-)
Credits:
q3 sdk
c0re, I believe he said he found this or something similar though we did discover it independently
Shouts: c0re, chaplja, kingOrgy, Ecc, #nixcoders, #game-deception, Naomi
Happy hacking,
Macpunk


Reply With Quote


