Lorenzo La Corte - 2023/2024 - Università degli Studi di Genova


This report provides descriptions of potential cheats for the toppler game in both its 32-bit and 64-bit versions.

Warm Up

$ file toppler{32,64}
toppler32: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, BuildID[sha1]=bdf9f3366f6d6ed7418bb013a97b59569f9ecb22, for GNU/Linux 3.2.0, with debug_info, not stripped
toppler64: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=a4e4a54f6182a71dfa1a866a24c263245c8eb1da, for GNU/Linux 3.2.0, stripped

toppler32 has symbols and debug information, while toppler64 is stripped.

I will start with the 32 version, to grasp a general idea of the most interesting functions, and then in the 64 version, I will try to use the knowledge coming from the previous analysis and use the hints provided in the exercise description as the main pivots of the analysis.

Summary

Unfortunately, I didn’t have the time to try out all my ideas or to go much more in-depth on some of them, such as those regarding jumps and snowballs. I have tried to recap everything inside this table:

Cheats

Exploits

Almost every exploit has the same structure, in particular:

#!/usr/bin/env python3
from pwn import *

# Set up pwntools for the correct architecture
e = context.binary = ELF('../toppler32')

'''
08056417 83 2d 9c        SUB        dword ptr [lifes],0x1
         91 06 08 01
Becomes:
08056417 83 2d 9c        SUB        dword ptr [lifes],0x0
         91 06 08 00
'''

output_path = "./infinite_lives"
address = 0x08056417
patch = b'\\x83\\x2d\\x9c\\x91\\x06\\x08\\x00'
e.write(address, patch)
e.save(output_path)

subprocess.run(['chmod', '+x', output_path])
subprocess.run([output_path])

Sometimes, if more exploits are present, it also includes some user input:

...
print("Do you also want more lives? (Y/N)")
decision = str(input()).strip()
if decision == "Y" or decision == "y":
    address = 0x080563cf
    patch = b'\\xc7\\x05\\x9c\\x91\\x06\\x08\\xff\\xff\\x00\\x00'
    e.write(address, patch)
...

I have found myself really comfortable with writing directly the raw bytes and only rarely I have used e.asm() function.