Hacker News new | past | comments | ask | show | jobs | submit login
Recreating an Old “Dirty Gamedev Trick” (2019) (kylehalladay.com)
82 points by mlejva on March 27, 2021 | hide | past | favorite | 36 comments



Nice writeup.

Reminds me of a famous 'intentional buffer overflow' by AOL in 1999 to determine if the user was using a 'genuine' AIM client, this came after Microsoft had added an AIM client to MSN Messenger.

https://www.geoffchappell.com/notes/security/aim/index.htm


> The first thing that jumped out at me in this story was the part about sending machine code over the network to be executed by the game. It had never occurred to me that this was possible, despite it being obvious in hindsight.

This is a good reminder that us software devs generally live in bubbles, not really thinking about or (sometimes intentionally) ignoring things that are not immediately relevant to our work.

The author appears to mostly work in game development, specifically shading, 3D, and engine development. Generally you think very little about writing secure code in those fields.

You can actually see him going down the "low-level rabbit-hole" if have a look at his posts: http://kylehalladay.com/archive.html


> Generally you think very little about writing secure code in those fields.

That sounds incredibly patronising. Most software in any field is insecure. It is not unique to games development. SQL injection and buffer overruns is still a thing. Even today.


What's the low-level rabbit-hole?


There's a surface rabbit hole for regular rabbits, but if you follow it down, sometimes low-level rabbits have made another rabbit hole at the bottom which leads into their warrens. That's all though there's no third level.


Why not? A rabbit could feel like the hardware still too mysterious and go even deeper, making their own chips from their self sourced silicon.


Yes but getting mining helmets that are compatible with rabbit physiology but still meet OSHA guidelines is a pain. I'd recommend outsourcing to other deeper-delving creatures. Some abstractions are useful.


The only rabbit I know of that dares to go that deep is Åkesson (see Parallelogram [0])

[0] http://www.linusakesson.net/scene/parallelogram/index.php


I would encourage all developers to download a Commodore 64 emulator and spend some time learning 6502 assembly.

There is no memory protection and no software abstractions. You display graphics on the screen by writing directly to video memory.

Programming for the C64 made me have innumerable lightbulb moments and significantly changed my perspective on what a computer really is.


> There is no memory protection and no software abstractions. You display graphics on the screen by writing directly to video memory

You can have similar experience with a little more power by writing code in mode 13h for dos. Want to light a pixel with specific color? Change byte of memory at A000 + y*320+x :).

You have 256 colors, every pixel is a byte, and you have another place in memory where it says what each color number maps to in rgb values. So you can do palette animations trivially.

There is no easier way to program graphic IMHO.

You can use any language - my favorite was Turbo Pascal 7 (but the compiler has a bug that breaks on cpus with over 200 MHz clock - you need to download turbo pascal and a patch that fixes this issue for example https://www.trsek.com/download/T7tplfix.zip ).


> There is no easier way to program graphic IMHO.

You might enjoy https://github.com/kragen/bubbleos/blob/master/yeso.

    /* Munching squares, generalized to TrueColor. */

    #include <yeso.h>

    int main()
    {
      ywin w = yw_open("munching squares", yp_p(1024, 1024), "");

      for (int t = 0;; t++) {
        ypic fb = yw_frame(w);
        for (int y = 0; y < fb.size.y; y++) {
          ypix *p = yp_line(fb, y);
          for (int x = 0; x < fb.size.x; x++) p[x] = (t & 1023) - ((x ^ y) & 1023);
        }

        yw_flip(w);
      }
    }
I think that, in terms of accessibility, these 14 lines of C compare favorably to the 15 instructions (and one assembler directive) in the 31-byte mode-X demo Klappquadrat, for example. Yeso also has Lua and Python bindings, which are somewhat incomplete. I haven't written Pascal bindings for it yet, but feel free.

On the other hand, that won't give you the same kind of satori experience as the bare-metal C64 experience, because Yeso is running on top of some arbitrary stack of software you don't understand, and the C64 VIC (or VGA mode 13h, at least if you're using a real VGA) is really just circuits. Using Yeso won't stop you from feeling that what's behind the library calls is magic.


C64 was my first computer (got it as a first communion gift in 1992) and I remember typing in programs from a German manual which nobody understood watching what they will do.

I also remember that I couldn't make graphic programs do anything, I only played with SID and commodore-ascii graphic. Somehow all the programs from the manual with sprites didn't do anything when I typed them in. I had no internet back then and it was a big deal for me. So big I remember it to this day :)

I remember I thought they misprinted the pokev adress so I did a for loop that put the things they wanted me to poke into any adress in a for loop, but that didn't worked either. I still have that C64 and I WILL at some point find out what was the problem :)




Any recommendations for books/tutorials/videos or other starting points? I don't know any assembly but wanted to learn 6502 assembly because of how ubiquitous it was in lots of older systems (2600, NES, Apple II, and of course C64 among others). That SMB 3 speed run [1] really blew my mind and made me want to learn more.

[1] https://news.ycombinator.com/item?id=24456247


I have a pile of IOT devices in the field that I made malfunction en-masse with a buffer overrun. I used the boilerplate, generated bluetooth LE stubs to make a GATT interface and didn't pay much attention to what it generated (in C). In my defence it was the first embedded system I made and the fact that it worked at all amazed me.

At one point during the first year, I changed some code in the android app that set up the device and wrote a larger value into a characteristic than it was expecting (32 bit int instead of 8). The boilerplate code checks the length of the incoming data and copies it in full. I managed to overwrite the structure in memory for the next couple of characteristics which made the devices malfunction.

Kicked myself a few times, managed to salvage the situation, got customers up and running (mostly) but at the time it seemed like a company killing disaster.

However later on, when dealing with a new feature that dropped the battery usage I realised that setting the characteristic for power in my interface sometimes caused problems itself due to a problem with the way I was accessing the radio on the device during operation, which was not supported.

However, remembering that the buffer overrun worked, I was able to craft a set of bytes that didn't trigger the original problem but allowed me to change the radio setting without triggering the radio code (the device sets the radio on its 4 hourly reboot).

In the end the buffer overrun saved me a lot more time than getting 10,000 devices back to have new firmware installed.


If your software ships with no self-update capability and you decide to update it on your users' computers by exploiting a vulnerability, is that even legal?


Ironically, perhaps the EULA permits it.


Since the EULA was the one thing downloaded from their servers, they could have easily make it permit this specific hack.

Though it really seems weird to me that someone thinks of the necessity of maybe updating the EULA later (and making it a download from a server to allow that) but not of the necessity of updating the software itself.


> not of the necessity of updating the software itself.

*Disclaimer: I don't know the specifics of this case. That said...

I don't think game developers are supposed or allowed to update console games directly, without going through the update and approval process by the console manufacturer. And this game was on PS2, which didn't have permanent storage at launch; PS2 games were generally not updateable. The optional HDD that came out later (see [1]). This title most likely did not require it, but even if it did, I doubt Sony had prepared a strategy for official game updates yet.

[1] - https://en.wikipedia.org/wiki/PlayStation_2_Expansion_Bay


Maybe usual "we reserve the right to change these terms without notice" clause in the EULA? Although it's a pretty nutty jurisdiction where that is legal.


I think my pragmatic solution would just to have the first part of the downloaded EULA say something like "New updates available, go to example.com and download the latest installer" or something. Of course most people don't read it. But makes me wonder what was so important to patch.

Edit: It was a PS2 title, not PC. Explains the reasoning I guess. Didn't know they even installed stuff / patches at the time. My consoles weren't connected to the net at that time.


It sounds like it patched the game every time in ran during online play, which makes sense because the PS2 did not ship with a hard drive (one was eventually available as an accessory, but wasn’t supported by many games, especially in America) and the memory card was only 8MB.

Edit: Did a little research after remembering that when I played SOCOM2 (an FPS game) back in the day it would download patches to the memory card. For reference, it seems the patch file took up around 3,000 kB on the memory card! (And that was basically just gameplay tweaks and bug fixes from what I remember, not additional assets).


Good read.

So, dumb question, I had never considered that assembly would have ‘network access’, so are there other ‘higher-level’ systems accessible in assembly?

It just seemed like a different level of abstraction to me.

[edit]

I get it now. I reread the last section and looked over the assembly. ‘Mapping’ to syscalls on the OS.


As has already been explained in other answers, ultimately everything comes down to machine code. 0s and 1s. So the closer to the hardware your language is, the _more_ access you actually have. Higher level languages, compilers and operating systems paired with newer hardware features are what _limits_ the access.

If you have a higher level language, that simply has no features to talk to the network and no facilities to insert lower level code, make direct syscalls etc. somehow, then barring exploits, you really can't access the network card.

If you think about it, some parts of the operating system of a computer are actually written in assembly. How are the syscalls themselves implemented etc?

If you want to learn more about what can be done without all these abstractions, then the "toilet paper computer" is awesome if you ask me. We did this in high school comp sci class (with a simulator but the teacher started us off with an actual roll of toilet paper to explain the concept).

https://en.wikipedia.org/wiki/Busy_beaver


There are no dumb questions! In non-VM languages everything is compiled down to assembly (or, more accurately, machine code) since that's ultimately what your CPU executes. Anything else on top of it is just sugar!

Raw machine code does have peripheral access via interrupts and/or the BIOS (which is kind of a "barebones" OS in firmware).

Usually there are several layers of abstraction on top of this (network card drivers, the TCP/IP stack, Unix sockets, etc.) so we don't have to deal with sending raw commands to the network card, but that's just convenience (and those are themselves machine code too).


In non-VM languages everything is compiled down to assembly (or, more accurately, machine code) This is a misconception: Both AOT and JIT compilers can generate directly machine code OR intermediate representations (bytecode, llvm IR) which ultimately reduce to machine code. AOT and JIT simply differ in when the machine code generation is executed. VM are just a special case of JIT that enable hardware agnosticity without fatELF.

The rest of your comment was instructive.


Normally VMs (although strictly speaking doesn't have to be) are not only an abstraction layer but also enforce some form of isolation and memory safety.


Right however full featured VMs allow to escape such safety when needed (for performance and interop with the outside world)


Some systems can be particularly difficult to access through raw machine code. For example with ASLR the addresses of functions and data can move around at runtime, so without using the linker it can be hard to access. Or there are binary obfuscation techniques that are even more thorough with their randomization.

But in the end it all compiles to assembly so everything is potentially accessible there.


All of the higher level systems map down to something very low level, like a syscall or writing to a specific memory address in a specific pattern. The layers of abstraction between the lowest levels and the highest levels can get pretty dizzying, but understanding even a piece of it reveals how fantastic the whole system is.


TLDR: they used a conventional buffer overflow attack on their EULA to install a network hook that downloads and patches their game.

This is what security researcher/CNO devs do every day, but without the source code.


including the "no NULLs in the buffer" part, which is typical shellcode rewriting.

Sometimes I forget that most developers nowadays didn't start out learning to hack, or even learning to program as teenagers. Sucks for them I guess - security contains some of the coolest, most fun programming techniques.


Funny, they never even consider the user reading the EULA and seeing it garbled by a bunch of code. I bet nobody even noticed.


I presume they padded it out by spaces until the end of the buffer.


Didn't the buffer overflow happen before the document was displayed? If so, the exploit code could skip over the broken EULA prompt or fix the document before showing it.




Consider applying for YC's Summer 2025 batch! Applications are open till May 13

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: