Zeecka zeecka

Moar Turtles

TJCTF 2018 - Forensic (80 pts)

TJCTF 2018: Moar Turtles

Détails du challenge

EventChallengeCategoryPointsSolves
TJCTF 2018Moar TurtlesForensique8012 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 :

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

flag.png

tjctf{1_h4Te_cUr1y_bR4c3s}

Zeecka