GTA Vice City | Infinite coins | Memory editing
Introduction #
I didn’t get to play GTA VC/SA in my childhood, otherwise, I would’ve wasted a lot of time grinding for coins to finish missions. Recently, I’ve been reading about frida and was very happy pwning applications. I took my chance in these games too!
Script #
You will need to install python
and get frida
, psutil
and frida-tools
packages from pip.
# In a shell
pip install frida frida-tools psutil
# The main.py script
import frida
import os
import argparse
import logging as logger
logger.basicConfig(level=logger.INFO, format='[%(levelname)s] %(message)s')
import psutil
CODE = """ptr("0x94add0").writeInt({coins})
ptr("0x94add4").writeInt({coins})"""
def get_pid_by_name(process):
for proc in psutil.process_iter():
if process in proc.name():
return proc.pid
raise Exception(f"process with name:{process} not found!")
def main():
logger.info("This works if the `gta-vc` executable has ASLR disabled.")
pid = get_pid_by_name("gta-vc")
logger.info(f"Attaching to pid:{pid} ...")
session = frida.attach(pid)
logger.info(f"Injecting code to pid:{pid}")
logger.info(CODE)
script = session.create_script(CODE)
script.load()
logger.info(f"Script loaded!")
script.unload()
if __name__ == '__main__':
parser = argparse.ArgumentParser(usage=f'python {__file__} -coins 123456')
parser.add_argument('-coins', type=int)
args = parser.parse_args()
if args.coins:
if args.coins > 99999999:
logger.error(f"{args.coins} larger than maximum coins 99999999")
else:
CODE = CODE.format(coins=args.coins)
main()
else:
os.system(f'python {__file__} -h')
Approach #
Frida is a “Dynamic instrumentation toolkit for developers, reverse-engineers, and security researchers”. It helps us to inject custom code into processes easily or play with its memory.
/ _ | Frida 16.0.19 - A world-class dynamic instrumentation toolkit
| (_| |
> _ | Commands:
/_/ |_| help -> Displays the help system
. . . . object? -> Display information about 'object'
. . . . exit/quit -> Exit
. . . .
. . . . More info at https://frida.re/docs/home/
. . . .
. . . . Connected to Local System (id=local)
My first guess was that the coins
variable should be an integer stored somewhere in the program memory.
So, I used the following script to search the program memory:
import os
import logging as logger
logger.basicConfig(level=logger.ERROR)
import psutil
import frida
CODE = """
function search(base, size, target) {
const results = Memory.scanSync(base, size, target)
if (results != '') {
for (let i=0; i<results.length; i++) {
console.log(results[i].address)
}
}
}
console.log(`Attach success`)
const exe_base = Process.enumerateModules()[0].base;
console.log(`${exe_base} ==> ASLR Disabled`)
const ranges = Process.enumerateRanges('rw-');
for (let i=1; i<ranges.length; i++) {
console.log(`#${i} base:${ranges[i].base} size:${ranges[i].size} file:${ranges[i].file} prot:${ranges[i].protection}`)
search(ranges[i].base, ranges[i].size, '64 00 00 00')
}
"""
_CODE = """
function search(base, size, target) {
const results = Memory.scanSync(base, size, target)
if (results != '') {
for (let i=0; i<results.length; i++) {
console.log(results[i].address)
}
}
}
// search(ptr("0x6f7000"), 3252224, '4e 4e')
// search(ptr("0x700000"), 3252224, '3A 00')
"""
_CODE = """
ptr("0x94add0").writeInt(1337)
ptr("0x94add4").writeInt(1337)
"""
def get_pid_by_name(process):
for proc in psutil.process_iter():
if process in proc.name():
return proc.pid
pid = get_pid_by_name("gta-vc")
session = frida.attach(pid)
logger.info(f"Injecting to process {pid} with code:")
logger.info(CODE)
script = session.create_script(CODE)
script.load()
script.unload()
Not so functional, but it just coveys the idea.
After using the above script, 0x94add0
, 0x94add4
were the two locations where the coins
variable / integer was found.
By inspecting the address with the memory map, the addresses belong to the .bss
section of the image. This means that, the coins
variable is a stack variable 😂
From my observations, the game tries to maintain a player
structure similar to this:
struct player {
// ...
int coins; // Actual number of coins at that instant
int displayCoins; // The coins which are displayed in the UI
// This second coins variable is maintained to update coins steadily
// ...
};
If the first coins variable was overwritten, the UI would take a lot of time to display the correct number of coins. Hence, we overwrite both.