EKOParty 2016 CTF - 250 points - Misc
Description - These QR codes look weird
Files: Zip Files
Solved by madeye
My teammates immediately pointed this challenge out to me since they knew I loved punch cards. A few months prior, I had written a challenge for our internal try-out CTF in which they were given a base64 encoced ascii art image of an IBM-29 punch card with a message encoded for them. It was a favorite for the students. It took me about 20 minutes to repurpose my code to solve this challenge.
When extracted, the zip file contains 14 PNG files that are images of IBM-29 punch cards. The images are consistently the same size and with equally spaced punches. Iterating over each column then each row, you can read the punch if the pixel in the top left corner is white. The top row, left most column starts at pixel column 15 and pixel row 20. Each punch location is then spaced 20 pixel vertically and 7 pixels horizontally.
Using PIL, I was able to read each image to find the punches on each row. The following code returns a list of columns of punches, where each column is a list of the rows that are punched.
def convert(imgfile): image = Image.open(imgfile) im = image.load() xstart = 15 ystart = 20 xoff = 7 yoff = 20 allpunches =  for x in range(xstart,image.size,xoff): punches =  for y in range(ystart,image.size,yoff): if im[x,y] == (255,255,255,255): punches.append((y-ystart)/yoff) allpunches.append(holes(punches)) return allpunches
The list of punches now needs to be converted to a string of characters. Understanding the layout of the punches as shown here, we can script this up as follows. note: not all puncutations are converted, just the ones observed on this set of cards.
def readpunch(p): if len(p) > 2: if p == [2,5,10]: return ',' if p == [0,5,10]: return '.' if p == [2,9,10]: return '?' if p == [0,7,10]: return '(' if p == [1,7,10]: return ')' if len(p) == 0: return ' ' if len(p) == 1: return alphabet[p-2] elif p == 0: return alphabet[7+p] elif p == 1: return alphabet[16+p] elif p == 2: return alphabet[24+p]
Now we just need to iterate over all 14 cards provided in the zip file to get the text for each card.
for f in files: print ''.join([readpunch(p) for p in convert(f)])
The cards in alphabetical order by file name produces the following text. Reading through each line to determine the follow of the story is needed. I did not see any logical way to automate this. I have indicated the final line number after each row of text
OF TIME PUNCHING THOSE NARDS, CAN YOU IMAGINE WHAT COULD  THE BUG, BUT THOSE WER3 THE OLD DAYS. CAN YOU FIND THE FLAG  USING THIS OLD TECHNOLOGY? GOOD LUCK, YOU WILL NEED IT)  IT WAS THE SIXTIES, HE WAS TRYKNG TO FIGURE OUT HOW TO  MANUALS TRY1NG TO LEARN HOW TO PROGRAM AND SPEND A LOT  HAPPEN IF YOU FAKE A SMALL MISTAKE IN ON OF THOSE PUNCHED  USE THOSE PONCHED CARDS, HE LIKES TO PROGRAM IN FORTRAN  ERROR DUE TO A SMALL AND ALMOST INSIGNIFICANT MIST4KE BUT  AND COBOL, B(T EVEN AFTER ALL THOSE YEARS HE DOESNT KNOW  CARDS? AFTER THOSE HOURS WAITING ROR A RESULT, THEN IT SAYS  IN THOSE DAYS YOUR ONLY OPTION W4S READ LARGE BOOKS AND  HOW TO PROPERLY MRITE SECURE CODE IN THOSE LANGUAGES  THAT WILL TAKE MORE TIME TO MEBUG AND FIGURE OUT WHERE WAS  ONCE UPON A TEME, THERE WAS A YOUNG HACKER CALLED MJ 
Following along with the story, it talks about mistakes. There are also some mistakes in the story. After double checking that the solver was correct, I noticed the first three typos were E, K, O which matches the flag pattern. In order, the flag properly describes the system that used the punchcards. note: some writeups show the flag using the parenthesis, but since there was no symbol for curly braces I submited the flag with the curlies and got the points
Entire solve script can be found here