Zeecka zeecka

Take an L

CSAW'18 CTF Qualification - Prog (200 pts)

CSAW’18 CTF Qualification: Take an L

Détails du challenge

EventChallengeCategoryPointsSolves
CSAW’18 CTF QualificationTake an LMisc (Prog)200192

Téléchargement : description.pdf - md5: 79b489dcacb5b0002ed34a270bdfe184

Description

Fill the grid with L’s but avoid the marked spot for the W

nc misc.chal.csaw.io 9000

The origin is at (0,0) on the top left

TL;DR

Il s’agissait d’un challenge de programmation qui ressemblait à un puzzle de polyominos. Pour résoudre le challenge, j’ai trouvé un algorithme récursif pour le problème donné : https://www.geeksforgeeks.org/divide-and-conquer-set-6-tiling-problem/

Méthodologie

Formuler comme un problème algorithmique

La première chose à faire était de lire la description et de réfléchir au problème mathématique/de programmation que nous avions. En lançant le challenge, nous obtenons ce qui suit :

nc misc.chal.csaw.io 9000
each L shaped block should be sent in its 
own line in a comma separated list
eg: (a,b),(a,c),(d,c)

grid dimensions 64x64
marked block: (18, 3)

J’ai fait quelques recherches sur les « puzzles » comme Tetris et j’ai trouvé la page wikipedia « polyomino ». Nos polyominos étaient des « trominos », j’ai décidé de chercher un solveur de polyominos/trominos en ligne.

Geek for Geek

Comme d’habitude avec les problèmes algorithmiques, j’ai consulté le site « GeeksForGeeks ». Avec une petite recherche sur google, j’ai trouvé exactement ce dont j’avais besoin : un solveur de L-tromino pour une grille donnée avec une case « noire » (remplie).

Explication de l’algorithme récursif

L’algorithme que j’ai décidé d’utiliser était récursif. L’algorithme exécute le processus suivant : on place un « L » (L-tromino) au milieu de la grille, puis on divise la grille en sous-grilles et on effectue le même processus pour chaque sous-grille. La fonction récursive s’arrête lorsque la grille a la même taille que notre « L » (2*2). Pour remplir la grille, la case manquante de notre « L » doit être orientée dans la direction de la case occupée de la grille.

Voici une représentation des 2 premières étapes pour une grille 8*8 :

Étape 1 :

step1.png

Étape 2 :

step2.png

Code pour une grille 64*64

Comme 64 est un multiple de 4, nous pouvons utiliser notre algorithme. J’ai décidé de l’implémenter en python, voici mon code (pas le meilleur que j’ai écrit…) :

# -*- coding:utf-8 -*-
from pwn import *
 
HOST = "misc.chal.csaw.io"
PORT = 9000

r = remote(HOST, PORT)

def send(c):
    """ Send new 'L' to server """
    print(c)
    r.sendline(c)


rec =  r.recvuntil("marked block: ")
print(rec)
black = eval(r.recvuntil("\n").strip())
print("Black cell: "+str(black))

n = 64 # Array Size
a = [[' ' for x in range(n)] for y in range(n)] # Init empty array
a[black[1]][black[0]] = "@" # Black case (filled one) (given when started)


def pgrille():
    """ Save current Grid to logs """
    out = ""
    out += "-"*n
    out += "\n"
    for l in a:
        out += str(''.join([x for x in l]))+"\n"
    out += "-"*n
    with open("logs","a+") as fi:
        fi.write(out+"\n\n\n")


def getBlack(a,x_start,y_start,x_end,y_end):
    """ Get position of "black" square of array / sub array """
    for j in range(y_start,y_end+1):
        for i in range(x_start,x_end+1):
            if a[j][i] == "o" or a[j][i] == "@":
                return i,j
    return None,None # Should not happen


def Tile(a,x_start,y_start,x_end,y_end):
    """ Recursive function, put an L in the middle and act recursivly.
    'L' are represented with 3*'o'. """
    xcenter_left = x_start+((x_end-x_start)/2)
    xcenter_right = xcenter_left+1
    ycenter_top = y_start+((y_end-y_start)/2)
    ycenter_bottom = ycenter_top+1
    
    xBlack,yBlack = getBlack(a,x_start,y_start,x_end,y_end)
    
    # Define "L" direction/orientation and send new triangle
    if xBlack <= xcenter_left: # Black cell on the left
        if yBlack <= ycenter_top: # Black cell on the top
            a[ycenter_top][xcenter_right] = "o"
            a[ycenter_bottom][xcenter_right] = "o"
            a[ycenter_bottom][xcenter_left] = "o"
            send("("+str(xcenter_right)+","+str(ycenter_top)+"),("+str(xcenter_right)+","+str(ycenter_bottom)+"),("+str(xcenter_left)+","+str(ycenter_bottom)+")")
        else: # Black cell on the bottom
            a[ycenter_top][xcenter_left] = "o"
            a[ycenter_top][xcenter_right] = "o"
            a[ycenter_bottom][xcenter_right] = "o"
            send("("+str(xcenter_left)+","+str(ycenter_top)+"),("+str(xcenter_right)+","+str(ycenter_top)+"),("+str(xcenter_right)+","+str(ycenter_bottom)+")")
    else: # Black cell on the right
        if yBlack <= ycenter_top: # Black cell on the top
            a[ycenter_top][xcenter_left] = "o"
            a[ycenter_bottom][xcenter_left] = "o"
            a[ycenter_bottom][xcenter_right] = "o"
            send("("+str(xcenter_left)+","+str(ycenter_top)+"),("+str(xcenter_left)+","+str(ycenter_bottom)+"),("+str(xcenter_right)+","+str(ycenter_bottom)+")")
        else: # Black cell is on the bottom
            a[ycenter_bottom][xcenter_left] = "o"
            a[ycenter_top][xcenter_left] = "o"
            a[ycenter_top][xcenter_right] = "o"
            send("("+str(xcenter_left)+","+str(ycenter_bottom)+"),("+str(xcenter_left)+","+str(ycenter_top)+"),("+str(xcenter_right)+","+str(ycenter_top)+")")
    
    pgrille() # Print in logs
    
    if abs(x_end-x_start) > 1: # If grid is big enough, go recursiv
        Tile(a,x_start,y_start,xcenter_left,ycenter_top)
        Tile(a,xcenter_right,y_start,x_end,ycenter_top)
        Tile(a,x_start,ycenter_bottom,xcenter_left,y_end)
        Tile(a,xcenter_right,ycenter_bottom,x_end,y_end)
    
Tile(a,0,0,len(a[0])-1,len(a)-1) # Start on full array
    
r.interactive() # Get shell

Flag

flag.png

flag{m@n_that_was_sup3r_hard_i_sh0uld_have_just_taken_the_L}

Zeecka