Recent Posts
Archives

PostHeaderIcon [KotlinConf2023] Video Game Hacking with Kotlin/Native: A Low-Level Adventure with Ignat Beresnev

At KotlinConf’23, Ignat Beresnev, a member of the Kotlin team at JetBrains and a core contributor to Dokka, stepped onto the stage with an unexpected topic: using Kotlin/Native for video game hacking. Far from a gimmick, his talk offered a solid foundation in low-level memory manipulation techniques, revealing how Kotlin/Native can be a surprisingly practical tool for creating trainers, bots, and various forms of game hacks. Drawing on examples such as modding Grand Theft Auto, the session aimed to demystify game hacking and present it as an accessible and technically rich domain for Kotlin developers.

Ignat began by recounting his personal journey into game modification, which started years earlier with a self-imposed challenge to write a World of Warcraft bot in Java. While technically successful, the attempt exposed the limitations of Java when it comes to low-level system access. Rediscovering the problem through the lens of Kotlin/Native, he found a much better fit: a language that retained Kotlin’s expressiveness while unlocking native-level capabilities required for deep interaction with operating system APIs.

Understanding Game Internals and Memory Manipulation

The core premise of game hacking, Ignat explained, lies in understanding how games store and manage state. Just like any other program, a game keeps its data—such as player health, in-game currency, or positional coordinates—in memory. If you can discover where a particular value is stored, you can read it, monitor it, or even modify it in real time. He illustrated this with a relatable scenario: if a character in a game has 100 units of currency, then somewhere in memory exists a variable holding that value. The trick is finding it.

To pinpoint such a memory address, one typically uses a memory scanning tool like Cheat Engine. The process involves searching the memory space for a known value—say, 100—then performing an in-game action that changes it, such as spending money. The search is then repeated for the new value (e.g., 80), gradually narrowing down the list of candidate addresses until the correct one is isolated.

Once the memory address is identified, Kotlin/Native can be used to read from or write to that memory location by interfacing directly with native Windows API functions. Ignat detailed this workflow by first obtaining a handle to the target process using functions like OpenProcess, which grants the required permissions to access another process’s memory. He pointed out how abstract and opaque some of the types in C APIs can be—such as HANDLE, which is essentially a pointer behind the scenes—but noted that Kotlin/Native handles these seamlessly through its C interop layer.

To read memory, one would use ReadProcessMemory, specifying the process handle, the target memory address, a buffer to receive the data, and additional arguments to track the number of bytes read. Kotlin/Native’s support for functions like allocArray and cValuesOf simplifies this otherwise complex operation. Writing memory involves a nearly identical process, this time using the WriteProcessMemory function to inject new values into specific memory locations.

Ignat added a humorous aside, noting how tricky it can be to locate something like a string literal in a game’s memory. Nonetheless, he successfully demonstrated a live example, changing an on-screen number in a lightweight game to prove the concept in action.

DLL Injection and Function Invocation

While reading and writing memory opens up powerful capabilities, more advanced techniques allow even deeper integration with the target game. One such technique is DLL injection. This method involves writing a Dynamic Link Library and tricking the game process into loading it. Once loaded, the DLL gains access to the same memory and execution privileges as the game itself, effectively becoming part of its runtime.

Ignat outlined the injection process step by step. First, the target process must be located and a handle acquired. Next, the injector allocates memory in the target’s address space to store the path of the DLL. This path is written into the allocated space, after which the program looks up the address of the LoadLibrary function in kernel32.dll. Since this system library is mapped at the same location across processes, it provides a reliable anchor point. Finally, CreateRemoteThread is used to spawn a new thread inside the game process that calls LoadLibrary, thereby loading the malicious DLL and executing its code.

Once injected, the DLL has full access to call the game’s internal functions—if their memory addresses and calling conventions are known. Ignat demonstrated this by referencing documentation from a modding community for GTA: San Andreas, which includes reverse-engineered addresses and function signatures for in-game routines like CreateCar. By defining a function pointer in Kotlin/Native that matches the signature and calling it through the injected DLL, one can trigger effects like spawning vehicles at will. Of course, this requires careful adherence to calling conventions, such as stdcall, and a deep understanding of binary interfaces.

Beyond Games: Broader Applications

While the talk focused on game modification, Ignat was clear in emphasizing that these techniques extend far beyond entertainment. The same approaches can be used to patch bugs in native libraries, build plugins for applications not originally designed for extensibility, gather usage metrics from legacy software, or automate GUI interactions. What Kotlin/Native brings to the table is the rare combination of modern Kotlin syntax with native-level system access, making it a uniquely powerful tool for developers interested in systems programming, reverse engineering, or unconventional automation.

By the end of the session, Ignat had shown not just how to hack a game, but how to think like a systems programmer using Kotlin/Native. It was a fascinating, fun, and deeply technical presentation that pushed the boundaries of what most Kotlin developers might expect from their tooling—and opened the door to new realms of possibility.

Links:

Leave a Reply