FCSC 2023 : Des ptits trous

Task

You are given a bunch of IBM029 perforated cards in the form of a jpeg like this one. And we know that out of the 79 cards and only the first 53 are in the right order.

Solve

To solve this, we first have to decode it into a readable form according to the documentation of those punched cards.

Image to binary format

We have to figure out a way to determine for each (x,y) if it is punched or not. To do so we determine the delta between each row and column and then measure the value at that point of the image :

X_START = 55
Y_START = 56

fname = './cards/'+f'{5:0>10}.jpg'
img = Image.open(fname).convert('L')
pixels = img.load()
for x in np.arange(X_START,X_START+29*31,10.88):
    for y in np.arange(Y_START,Y_START+31*12,31.1):
        pixels[int(x),int(y)] = 50
img.show()

It works ! We notice upon execution that every sampled pixel is aligned with the punching emplacements so we can now move onto the decoding.

Binary format to text

The data is read column by column and in each of them is encoded a character. So we create a dictionary of character to column association and a function that enables to do the conversion :

punchchar = {'001000000000':'0',
            '000100000000':'1',
            '000010000000':'2',
            '000001000000':'3',
            ...
            '000001000010':'#',
            '010000000000':'-',
            '100000000000':'&',
            '001100000000':'/',}

def getchar(input):
    try :
        #print(f'{input}=>{punchchar[input]}')
        return punchchar[input]
    except :
        #print(f'{input}')
        return ' '

Now that we have that we can iterate through all the images and all the columns to convert into text:

data = ""
for i in range(1,80):
    fname = './cards/'+f'{i:0>10}.jpg'
    img = Image.open(fname).convert('L')
    pixels = img.load()
    for x in np.arange(X_START,X_START+29*30,10.88):
        b = ""
        for y in np.arange(Y_START,Y_START+31*12,31.1):
            b += "0" if pixels[x,y] < 128 else "1"
        data += getchar(b)
    data += '\n'
print(data)

The result of the decoding is this :

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

 --------------------------------------------------
901 FORMAT (99A)
  DO I = 0,29,1

 - - - - - -
  DO I = 0,29,1
  CHARACTER :: SSSS(0:255)
  INTEGER :: SSS(0:255)
  END DO

 - - - - - -
END PROGRAM
PROGRAM HELLO
    SSSS(I:I) = CHAR(SSS(I:I))
    SSS(I:I) = S2(SS(I+1)+1:SS(I+1)+1)
  INTEGER :: I
USE HELLO_1
  END DO

 --------------------------------------------------
  WRITE (*,901,ADVANCE=\YES\) \\
  STOP 0
  DO I = 0,29,1
IMPLICIT NONE
  DO I = 0,29,1
    SS(H(I+1)+1:H(I+1)+1) = SSS(I:I)
    WRITE (*,901,ADVANCE=\NO\) SSSS(I:I)
    SSS(I:I) = S1(SS(I+1)+1:SS(I+1)+1)
  END DO
  END DO

Solving the fortran puzzle

The code is complete but as it was said in the beginning it is in part not in the right order. And it appears that it starts being scrambled right after the HELLO_1 module. This part :

901 FORMAT(99A)
DO I=0,29,1
DO I=0,29,1
CHARACTER::SSSS(0:255)
INTEGER::SSS(0:255)
END DO
END PROGRAM
PROGRAM HELLO
SSSS(I:I)=CHAR(SSS(I:I))
SSS(I:I)=S2(SS(I+1)+1:SS(I+1)+1)
INTEGER::I
USE HELLO_1
END DO
WRITE(*,901,ADVANCE=\YES\)\\
STOP 0
DO I=0,29,1
IMPLICIT NONE
DO I=0,29,1
SS(H(I+1)+1:H(I+1)+1)=SSS(I:I)
WRITE(*,901,ADVANCE=\NO\) SSSS(I:I)
SSS(I:I)=S1(SS(I+1)+1:SS(I+1)+1)
END DO
END DO   

There are 4 for loops and 4 assignements. So from this, through logic we can know what comes first in what order. For example, we know fore sure that the assignment of SSSS cannot come before SSS. And we can build the code back piece by piece this way just like a jigsaw puzzle.

I threw the module out the window and made it into one program but you should get this :

PROGRAM HELLO
    IMPLICIT NONE

    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/)
    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

    WRITE(*,901,ADVANCE="YES")

    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
    STOP 0
    901 FORMAT(99A)
END PROGRAM

Try it with an online fortran compiler and finally here we go it works. we get the flag.

FCSC{#!F0RTR4N_1337_FOR3V3R!}STOP 0


...Program finished with exit code 0
Press ENTER to exit console.

And voilà ! It was a very fun challenge it felt like being an archeologue.