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.
$ 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.
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:
Almost every exploit has the same structure, in particular:
it loads the context from the parent directory
→ the parent directory contains the executable, while the current directory has its .dat
it contains a short description of the patch
it patches the executable
it adds executable privilege and then runs the patched game.
#!/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.