TJCTF 2018: Moar Turtles
Détails du challenge
| Event | Challenge | Category | Points | Solves |
|---|---|---|---|---|
| TJCTF 2018 | Moar Turtles | Forensique | 80 | 12 solves |
Téléchargement :
turtle.png - md5: ab0217b81c5bcd235e4e9000c1931354
flag.pcapng - md5: 2cc73f72d11d6f71617452fe4e969cee
Description
Auteur : Alaska47
Hé ! Regarde cette superbe image que j’ai dessinée avec ma manette XBOX 360…
Ah oui, tu voudras peut-être aussi jeter un œil à ça.
Indice : Je suis un joueur pro de fortnite sur console, alors je vais te confier un petit secret. La deadzone de ma manette est de 0.2.
TL;DR
Nous avions un PCAP contenant des paquets USB provenant d’une manette XBOX 360. Il fallait chercher une description des leftoverdata, puis redessiner à partir des données fournies.
Le point le plus difficile était de penser à la vélocité et de trouver de la documentation sur le mapping de la manette XBOX 360.
Méthodologie
Extraction des données
La première chose à faire avec le fichier pcapng était d’extraire les données USB et le temps associé à chaque paquet. J’ai utilisé la commande suivante pour extraire les données :
tshark -r flag.pcapng -T fields -e frame.time_relative -e usb.capdata > flag.raw
head -n 3 flag.raw
0.000000000 00:14:00:00:00:00:47:07:33:10:85:fe:63:07:00:00:00:00:00:00
0.015999000 00:14:00:00:00:00:fe:05:c7:11:85:fe:fc:06:00:00:00:00:00:00
0.028011000 00:14:00:00:00:00:b5:04:92:12:85:fe:63:07:00:00:00:00:00:00
Fichier : flag.raw - md5: a6a74f0d3d924e62c02d89b1795ab8eb
Identification des octets
Pour identifier les différents octets, j’ai d’abord décidé de récupérer le nombre de valeurs possibles pour chaque octet :
f = open("flag.raw").read().split("\n") # Array of time - data
f.pop() # remove empty line
times = []
bytesl = [[] for x in range(20)] # List of 20 lists
for l in f: # for each packet
p1,p2 = l.split("\t")
times.append(float(p1)) # add time of pack in an array
sub = p2.split(":")
for i in range(20): # add each bytes in a specific array
bytesl[i].append(sub[i])
for i,l in enumerate(bytesl):
print(str(i)+" : "+str(len(set(l)))) # Print nuber of values for each bytes position
SORTIE :
0 : 1
1 : 1
2 : 1
3 : 2
4 : 1
5 : 11
6 : 256
7 : 254
8 : 208
9 : 252
10 : 3
11 : 1
12 : 3
13 : 2
14 : 1
15 : 1
16 : 1
17 : 1
18 : 1
19 : 1
On peut voir ici que les octets aux index 6 à 9 pourraient coder les mouvements du joystick.
Pour confirmer notre supposition, nous avons cherché de la documentation :
- http://tattiebogle.net/index.php/ProjectRoot/Xbox360Controller/UsbInfo
- http://euc.jp/periphs/xbox-controller.ja.html
On peut voir que x est codé avec un signed int sur les octets aux index 6 et 7. Y est codé avec les index 8 et 9.
Scripting
Puisque nous avions une image, et que le challenge s’appelle Moar Turtles, nous avons décidé de redessiner une image à partir des paquets fournis.
Analyse des paquets :
- les données x et y ne sont pas des positions mais des mouvements. Il est important de définir un point et de changer la position de ce point progressivement
- ne dessiner que lorsque A est pressé
- prendre en compte la vélocité (paramètre de temps)
Au premier essai, nous n’avions pas remarqué les paramètres de vélocité. Nous avons obtenu beaucoup d’images inutilisables.
Voici le script final - md5: ba19714088c919a8b1fcca4af9a165a5
# -*- coding:utf-8 -*-
from Tkinter import *
import struct
# tshark -r flag.pcapng -T fields -e frame.time_relative -e usb.capdata > flag.raw
f = open("flag.raw").read().split("\n") # Array of time - data
f.pop() # remove empty line
times = []
bytesl = [[] for x in range(20)]
for l in f: # for each packet
p1,p2 = l.split("\t")
times.append(float(p1)) # add time of pack in an array
sub = p2.split(":")
for i in range(20): # add each bytes in a specific array
bytesl[i].append(sub[i])
fen = Tk() # Initializing Tkinter Canvas 1200*800
screenWidth = 1200
screeHeight = 800
screen=Canvas(fen, bg="#d0d0d0", height=screeHeight, width=screenWidth)
screen.pack(expand=YES, fill=BOTH)
cursorX = screenWidth/2 # Set cursor at the middle of the screen
cursorY = screeHeight/2 # Set cursor at the middle of the screen
for i in range(len(bytesl[0])-1): # For each packet
diff = times[i+1]-times[i] # Get time difference between current and next packet
v1 = bytesl[7][i] # Get bytes of x
v2 = bytesl[6][i] # Get bytes of x
v3 = bytesl[9][i] # Get bytes of y
v4 = bytesl[8][i] # Get bytes of y
# Conversion
# signed 16-bit, little-endian, north/east positive
# x and y in [-32768;32768] ==> [-((256**2)/2);(256**2)/2]
# then divide by 32768 to resize interval in [-1;1]
x = float(struct.unpack('<h',chr(int(v2,16))+chr(int(v1,16)))[0])/32768.0
y = float(struct.unpack('<h',chr(int(v4,16))+chr(int(v3,16)))[0])/32768.0
deadzone = 0.2
velocityWeight = 300
for t in range(int(velocityWeight*diff)): # Make multiple movement due to velocity
if abs(x) >= deadzone: # Check if we're not in deadzone
cursorX += x
if abs(y) >= deadzone: # Check if we're not in deadzone
cursorY -= y # - due to inversion of y (first flag, the image had mirror effect)
if bytesl[3][i] != "00": # If "A" is pressed (see documentation)
screen.create_rectangle(cursorX-1,cursorY-1,cursorX+1,cursorY+1,fill="black",outline="black") # Write Current Pixel
fen.mainloop()
Flag

tjctf{1_h4Te_cUr1y_bR4c3s}