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
! --------------------------------------------------