Solution de lrstx pour Des p'tits trous

misc

1 mai 2024

Solution

On est donc en présence de cartes perforées, du type de celle ci-dessous :

Il n’y a pas de grosses difficultés à comprendre comment ces cartes fonctionnent, tout est expliqué dans le lien fourni dans l’énoncé. Pour résumer, chaque carte est composée de 80 colonnes et 10 lignes. Chaque colonne représente un caractère, et les trous codent le caractère en question. On ne parle pas ici d’ASCII, mais d’une version d’EBCDIC, bien décrit dans cette page dédiée à l’IBM 029.

La première étape est évidemment de décoder les cartes. Pour cela, j’ai écrit un décodeur en python (code ci-dessous). Malheureusement, les emplacements des trous ne se trouvaient pas exactement à intervalles réguliers, ce qui m’a conduit à cette horreur consistant à faire une liste des coordonnées possibles. On s’en fiche, on est en CTF :). Quant au mapping entre les trous et les caractères, je l’ai lâchement volé au script python PyPunch.

Le résultat obtenu, avec la numérotation des cartes, est le suivant :

1 ! --------------------------------------------------
2 MODULE HELLO_1         ! BEGIN S1,S2,H,SS DEFINITION
3 ! --------------------------------------------------
4   INTEGER :: S1(256) = (/ 96,172,121,222,15,140,53,104,39,145,51,250,217,  &
5  27,32,127,70,179,21,46,236,189,143,133,77,171,208,223,113,139,158,54,203,227,&
6  160, &
7  95,99,155,85,169,103,130,238,31,226,41,52,90,152,183,8,173,72,131,229,231,243,&
8  184, &
9  199,146,134,6,249,4,117,84,151,201,47,25,180,33,79,230,166,142,10,161,233,7,&
10  112, &
11  255,126,19,138,193,83,125,168,106,24,48,198,177,209,2,56,185,108,16,200,65, &
12  186, &
13  225,12,167,137,105,98,43,62,150,147,38,149,251,49,234,119,212,29,86,129,110,&
14  93, &
15  204,9,34,74,73,176,120,195,67,205,123,196,244,175,241,102,245,162,248,218, &
16  232,219,  &
17  59,28,197,87,170,221,57,92,214,37,247,116,100,26,239,107,216,188,148,22,&
18  60,192,  &
19  13,23,80,91,44,66,42,153,18,40,76,165,220,206,144,115,82,114,20,253,202,&
20  174,215,  &
21  163,211,78,124,228,11,0,1,97,58,35,128,61,14,159,17,132,94,178,252,182,&
22  240,111,  &
23  141,71,187,235,213,154,63,190,3,45,75,191,135,207,101,88,118,181,89,164,&
24  50,36,55,  &
25  246,81,254,242,30,210,194,237,157,136,224,69,109,156,122,64,5,68 /)
26   INTEGER :: S2(256) = (/ 216,182,122,143,69,3,117,72,42,134,53,75,179,  &
27  49,27,189,26,30,254,78,83,139,194,237,60,93,70,105,109,240,178,46,158,210,&
28  193,24,  &
29  172,141,23,156,234,31,220,62,145,127,4,51,19,176,247,255,111,81,55,135,71,&
30  0,177,  &
31  38,211,155,166,90,181,224,202,195,137,221,43,170,201,159,230,44,108,196,&
32  133,132,  &
33  183,144,225,120,129,147,121,164,136,45,157,1,186,115,13,206,68,217,252,&
34  251,233,  &
35  95,59,184,187,241,37,113,98,25,162,235,118,47,79,35,80,50,89,250,192,229,&
36  110,56,  &
37  58,185,40,150,253,205,191,87,231,124,223,198,173,160,142,222,239,226,190,&
38  168,167,  &
39  174,207,21,140,76,28,52,152,100,14,16,48,163,92,33,197,154,238,161,15,84,&
40  116,11,  &
41  12,73,232,128,215,67,204,8,209,20,96,17,200,188,9,214,104,131,91,64,99,18,&
42  61,249,  &
43  203,153,138,36,103,5,39,22,151,227,41,165,219,246,101,74,236,65,218,180,148,&
44  123,  &
45  242,85,57,29,169,63,54,146,245,7,77,106,32,208,126,149,175,94,212,130,114,6,&
46  125,  &
47  213,112,119,66,244,102,82,10,97,248,86,107,243,34,228,171,2,199,88 /)
48   INTEGER :: H(30) = (/ 17,10,1,18,25,28,27,14,22,20,4,8,15,5,26,19,6,12,  &
49  7,21,3,29,13,23,9,24,0,16,11,2 /)
50   INTEGER :: SS(30) = (/ 68,180,51,31,68,20,206,229,56,160,219,251,169,  &
51  184,56,229,206,66,160,186,51,153,83,68,56,157,160,68,56,187 /)
52  END MODULE ! END S1,S2,H,SS DEFINITION (END HELLO_1)
53 ! --------------------------------------------------
54 901 FORMAT (99A)
55   DO I = 0,29,1
56 ! - - - - - -
57   DO I = 0,29,1
58   CHARACTER :: SSSS(0:255)
59   INTEGER :: SSS(0:255)
60   END DO
61 ! - - - - - -
62 END PROGRAM
63 PROGRAM HELLO
64     SSSS(I:I) = CHAR(SSS(I:I))
65     SSS(I:I) = S2(SS(I+1)+1:SS(I+1)+1)
66   INTEGER :: I
67 USE HELLO_1
68   END DO
69 ! --------------------------------------------------
70   WRITE (*,901,ADVANCE='YES') ''
71   STOP 0
72   DO I = 0,29,1
73 IMPLICIT NONE
74   DO I = 0,29,1
75     SS(H(I+1)+1:H(I+1)+1) = SSS(I:I)
76     WRITE (*,901,ADVANCE='NO') SSSS(I:I)
77     SSS(I:I) = S1(SS(I+1)+1:SS(I+1)+1)
78   END DO
79   END DO

On voit que les 53 premières cartes devaient être dans l’ordre sous peine de rendre l’épreuve impossible à résoudre. Seule la fin du programme est à réordonner. Fun fact : la chute et le mélange des cartes perforées était un incident classique de l’époque, donc on peut considérer ce challenge comme réaliste (si on met de côté le fait que les 53 premières soient restées dans l’ordre 😉). Ah, et dernier point, le code est bien évidemment un langage de l’époque, à savoir, du FORTRAN.

L’ordre des dernières cartes est relativement facile pour démarrer. Un code en FORTRAN suit une structure bien précise : définition du nom du programme, dépendances, définitions des variables, etc.. Quelques exemples trouvés sur le net permettent de reconstituer cette structure :

PROGRAM HELLO
USE HELLO_1
IMPLICIT NONE
! - - - - - -
  CHARACTER :: SSSS(0:255)
  INTEGER :: SSS(0:255)
  INTEGER :: I
! - - - - - -
  [ **Du code ici** ]
  STOP 0
901 FORMAT (99A)
END PROGRAM
! --------------------------------------------------

Ensuite, on remarque des commandes WRITE, qui sont probablement à la fin. L’une d’entre elles utilise un index de tableau et la variable SSSS. On va donc supposer que c’est l’affichage du flag, et que cela doit être inclus dans une boucle :

PROGRAM HELLO
USE HELLO_1
IMPLICIT NONE
! - - - - - -
  CHARACTER :: SSSS(0:255)
  INTEGER :: SSS(0:255)
  INTEGER :: I
! - - - - - -
  [ **Encore du code à insérer ici** ]
  DO I = 0,29,1
    SSSS(I:I) = CHAR(SSS(I:I))
    WRITE (*,901,ADVANCE='NO') SSSS(I:I)
  END DO
  WRITE (*,901,ADVANCE='YES') ''
  STOP 0
901 FORMAT (99A)
END PROGRAM
! --------------------------------------------------

Il nous reste trois boucles et trois affectations d’éléments de tableau. On va considérer que chaque affectation a droit à sa boucle. Reste à savoir dans quel ordre. On pourrait essayer de comprendre, mais c’est plus rapide de tester les 6 possibilités différentes. Par exemple à l’aide d’un interpréteur en ligne comme celui-ci.

Finalement, la version qui fonctionne est celle-ci (code-source intégral ci-dessous):

! - - - - - -
  DO I = 0,29,1
    SSS(I:I) = S1(SS(I+1)+1:SS(I+1)+1)
  END DO
  DO I = 0,29,1
    SS(H(I+1)+1:H(I+1)+1) = SSS(I:I)
  END DO
  DO I = 0,29,1
    SSS(I:I) = S2(SS(I+1)+1:SS(I+1)+1)
  END DO
  DO I = 0,29,1
    SSSS(I:I) = CHAR(SSS(I:I))
    WRITE (*,901,ADVANCE='NO') SSSS(I:I)
  END DO
  WRITE (*,901,ADVANCE='YES') ''
  STOP 0

Et lorsqu’on l’exécute, on a droit à :

FCSC{#!F0RTR4N_1337_FOR3V3R!}
STOP 0

Script décodeur

#!/usr/bin/env python3

from PIL import Image
import sys

# Stolen https://github.com/TheToddLuci0/PyPunch/blob/master/PyPunch/mappings.py
MAPPINGS = {
    # Digits
    '&': (0,),
    '-': (1,),
    '0': (2,),
    '1': (3,),
    '2': (4,),
    '3': (5,),
    '4': (6,),
    '5': (7,),
    '6': (8,),
    '7': (9,),
    '8': (10,),
    '9': (11,),

    # Alpha
    'A': (0, 3),
    'B': (0, 4),
    'C': (0, 5),
    'D': (0, 6),
    'E': (0, 7),
    'F': (0, 8),
    'G': (0, 9),
    'H': (0, 10),
    'I': (0, 11),

    'J': (1, 3),
    'K': (1, 4),
    'L': (1, 5),
    'M': (1, 6),
    'N': (1, 7),
    'O': (1, 8),
    'P': (1, 9),
    'Q': (1, 10),
    'R': (1, 11),

    '/': (2, 3),
    'S': (2, 4),
    'T': (2, 5),
    'U': (2, 6),
    'V': (2, 7),
    'W': (2, 8),
    'X': (2, 9),
    'Y': (2, 10),
    'Z': (2, 11),

    # Special
    '¢': (0, 4, 10),
    '.': (0, 5, 10),
    '<': (0, 6, 10),
    '(': (0, 7, 10),
    '+': (0, 8, 10),
    '|': (0, 9, 10),

    '!': (1, 4, 10),
    '$': (1, 5, 10),
    '*': (1, 6, 10),
    ')': (1, 7, 10),
    ';': (1, 8, 10),
    '¬': (1, 9, 10),

    ',': (2, 5, 10),
    '%': (2, 6, 10),
    '_': (2, 7, 10),
    '>': (2, 8, 10),
    '?': (2, 9, 10),

    ':': (4, 10),
    '#': (5, 10),
    '@': (6, 10),
    '\'': (7, 10),
    '=': (8, 10),
    '"': (9, 10),
}

def indices_to_key(l):
    return '-'.join([ str(x) for x in l ])

rev_mapping = { indices_to_key(v) : k for k, v in MAPPINGS.items()}

def decode_car(l):
    key = indices_to_key(l)
    if key in rev_mapping:
        return rev_mapping[key]
    else:
        return ' '

posh = [53, 64, 75, 86, 96, 107, 118, 129, 140, 151, 162, 172, 183, 194, 205, 216, 227, 238, 248, 259, 270, 281, 292, 303, 314, 325, 335, 346, 357, 368, 379, 390, 401, 411, 422, 433, 444, 455, 466, 477, 488, 498, 509, 520, 531, 542, 553, 564, 574, 585, 596, 607, 618, 629, 640, 650, 661, 672, 683, 694, 705, 716, 727, 737, 748, 759, 770, 781, 792, 803, 813, 824, 835, 846, 857, 868, 879, 889, 900, 911]
posv = (49, 80, 111, 142, 174, 205, 236, 267, 303, 332, 368, 395)

def decode_image(img):
    px = img.load()
    card = list()
    for i in posh:
        byte = []
        for jv, jp in enumerate(posv):
            color = px[i, jp][0]
            if color > 127:
                byte.append(jv)
        card.append(byte)
    return card

for i in range(1, 80):
    img = Image.open(f'{i:010d}.jpg')
    card = decode_image(img)
    txt = ''.join([ decode_car(x) for x in card ])
    print(i, txt)

Programme Fortran reconsitué

! --------------------------------------------------
MODULE HELLO_1         ! BEGIN S1,S2,H,SS DEFINITION
! --------------------------------------------------
  INTEGER :: S1(256) = (/ 96,172,121,222,15,140,53,104,39,145,51,250,217,  &
 27,32,127,70,179,21,46,236,189,143,133,77,171,208,223,113,139,158,54,203,227,&
 160, &
 95,99,155,85,169,103,130,238,31,226,41,52,90,152,183,8,173,72,131,229,231,243,&
 184, &
 199,146,134,6,249,4,117,84,151,201,47,25,180,33,79,230,166,142,10,161,233,7,&
 112, &
 255,126,19,138,193,83,125,168,106,24,48,198,177,209,2,56,185,108,16,200,65, &
 186, &
 225,12,167,137,105,98,43,62,150,147,38,149,251,49,234,119,212,29,86,129,110,&
 93, &
 204,9,34,74,73,176,120,195,67,205,123,196,244,175,241,102,245,162,248,218, &
 232,219,  &
 59,28,197,87,170,221,57,92,214,37,247,116,100,26,239,107,216,188,148,22,&
 60,192,  &
 13,23,80,91,44,66,42,153,18,40,76,165,220,206,144,115,82,114,20,253,202,&
 174,215,  &
 163,211,78,124,228,11,0,1,97,58,35,128,61,14,159,17,132,94,178,252,182,&
 240,111,  &
 141,71,187,235,213,154,63,190,3,45,75,191,135,207,101,88,118,181,89,164,&
 50,36,55,  &
 246,81,254,242,30,210,194,237,157,136,224,69,109,156,122,64,5,68 /)
  INTEGER :: S2(256) = (/ 216,182,122,143,69,3,117,72,42,134,53,75,179,  &
 49,27,189,26,30,254,78,83,139,194,237,60,93,70,105,109,240,178,46,158,210,&
 193,24,  &
 172,141,23,156,234,31,220,62,145,127,4,51,19,176,247,255,111,81,55,135,71,&
 0,177,  &
 38,211,155,166,90,181,224,202,195,137,221,43,170,201,159,230,44,108,196,&
 133,132,  &
 183,144,225,120,129,147,121,164,136,45,157,1,186,115,13,206,68,217,252,&
 251,233,  &
 95,59,184,187,241,37,113,98,25,162,235,118,47,79,35,80,50,89,250,192,229,&
 110,56,  &
 58,185,40,150,253,205,191,87,231,124,223,198,173,160,142,222,239,226,190,&
 168,167,  &
 174,207,21,140,76,28,52,152,100,14,16,48,163,92,33,197,154,238,161,15,84,&
 116,11,  &
 12,73,232,128,215,67,204,8,209,20,96,17,200,188,9,214,104,131,91,64,99,18,&
 61,249,  &
 203,153,138,36,103,5,39,22,151,227,41,165,219,246,101,74,236,65,218,180,148,&
 123,  &
 242,85,57,29,169,63,54,146,245,7,77,106,32,208,126,149,175,94,212,130,114,6,&
 125,  &
 213,112,119,66,244,102,82,10,97,248,86,107,243,34,228,171,2,199,88 /)
  INTEGER :: H(30) = (/ 17,10,1,18,25,28,27,14,22,20,4,8,15,5,26,19,6,12,  &
 7,21,3,29,13,23,9,24,0,16,11,2 /)
  INTEGER :: SS(30) = (/ 68,180,51,31,68,20,206,229,56,160,219,251,169,  &
 184,56,229,206,66,160,186,51,153,83,68,56,157,160,68,56,187 /)
 END MODULE ! END S1,S2,H,SS DEFINITION (END HELLO_1)
! ---------------------------------------------------
PROGRAM HELLO
USE HELLO_1
IMPLICIT NONE
! - - - - - -
  CHARACTER :: SSSS(0:255)
  INTEGER :: SSS(0:255)
  INTEGER :: I
! - - - - - -
  DO I = 0,29,1
    SSS(I:I) = S1(SS(I+1)+1:SS(I+1)+1)
  END DO
  DO I = 0,29,1
    SS(H(I+1)+1:H(I+1)+1) = SSS(I:I)
  END DO
  DO I = 0,29,1
    SSS(I:I) = S2(SS(I+1)+1:SS(I+1)+1)
  END DO
  DO I = 0,29,1
    SSSS(I:I) = CHAR(SSS(I:I))
    WRITE (*,901,ADVANCE='NO') SSSS(I:I)
  END DO
  WRITE (*,901,ADVANCE='YES') ''
  STOP 0
901 FORMAT (99A)
END PROGRAM
! --------------------------------------------------