Teaser Punch Card + Day 5
This commit is contained in:
parent
ab0b694600
commit
d63b8c7736
@ -1,10 +1,4 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
in1 = "10:a2:a3:2d:60:2e:43:0f:9d:a1:12:5b:fa:1c:36:4e:6c:e2:de:e0:57:3d:8a:69:5a:24:fc:e8:f6:f3:f5:ce"
|
||||
in1_bytes = [int(x, 16) for x in in1.split(":")]
|
||||
print(in1_bytes)
|
||||
print(len(in1_bytes))
|
||||
|
||||
in2 = in1.replace(":", "")
|
||||
d = int(in2, 16)
|
||||
print(d)
|
||||
# https://securitytrails.com/list/apex_domain/hackvent.org
|
||||
# Subdomain: osintiscoolisntit.hackvent.org
|
||||
|
341
Teaser/_ZOoxjUSe1OVB7OPoVrsX.pdf.extracted/39A25/punch_card_decoder.py
Executable file
341
Teaser/_ZOoxjUSe1OVB7OPoVrsX.pdf.extracted/39A25/punch_card_decoder.py
Executable file
@ -0,0 +1,341 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# punchcard.py
|
||||
#
|
||||
# Copyright (C) 2011: Michael Hamilton
|
||||
# The code is GPL 3.0(GNU General Public License) ( http://www.gnu.org/copyleft/gpl.html )
|
||||
#
|
||||
# import Image
|
||||
from PIL import Image
|
||||
import sys
|
||||
from optparse import OptionParser
|
||||
|
||||
CARD_COLUMNS = 80
|
||||
CARD_ROWS = 12
|
||||
|
||||
# found measurements at http://www.quadibloc.com/comp/cardint.htm
|
||||
CARD_WIDTH = 7.0 + 3.0/8.0 # Inches
|
||||
CARD_HEIGHT = 3.25 # Inches
|
||||
CARD_COL_WIDTH = 0.087 # Inches
|
||||
CARD_HOLE_WIDTH = 0.055 # Inches IBM, 0.056 Control Data
|
||||
CARD_ROW_HEIGHT = 0.25 # Inches
|
||||
CARD_HOLE_HEIGHT = 0.125 # Inches
|
||||
CARD_TOPBOT_MARGIN = 3.0/16.0 # Inches at top and bottom
|
||||
CARD_SIDE_MARGIN = 0.2235 # Inches on each side
|
||||
|
||||
|
||||
CARD_SIDE_MARGIN_RATIO = CARD_SIDE_MARGIN/CARD_WIDTH # as proportion of card width (margin/width)
|
||||
CARD_TOP_MARGIN_RATIO = CARD_TOPBOT_MARGIN/CARD_HEIGHT # as proportion of card height (margin/height)
|
||||
CARD_ROW_HEIGHT_RATIO = CARD_ROW_HEIGHT/CARD_HEIGHT # as proportion of card height - works
|
||||
CARD_COL_WIDTH_RATIO = CARD_COL_WIDTH/CARD_WIDTH # as proportion of card height - works
|
||||
CARD_HOLE_HEIGHT_RATIO = CARD_HOLE_HEIGHT/CARD_HEIGHT # as proportion of card height - works
|
||||
CARD_HOLE_WIDTH_RATIO = CARD_HOLE_WIDTH/CARD_WIDTH # as a proportion of card width
|
||||
|
||||
BRIGHTNESS_THRESHOLD = 200 # pixel brightness value (i.e. (R+G+B)/3)
|
||||
|
||||
IBM_MODEL_029_KEYPUNCH = """
|
||||
/&-0123456789ABCDEFGHIJKLMNOPQR/STUVWXYZ:#@'="`.<(+|!$*);^~,%_>? |
|
||||
12 / O OOOOOOOOO OOOOOO |
|
||||
11| O OOOOOOOOO OOOOOO |
|
||||
0| O OOOOOOOOO OOOOOO |
|
||||
1| O O O O |
|
||||
2| O O O O O O O O |
|
||||
3| O O O O O O O O |
|
||||
4| O O O O O O O O |
|
||||
5| O O O O O O O O |
|
||||
6| O O O O O O O O |
|
||||
7| O O O O O O O O |
|
||||
8| O O O O OOOOOOOOOOOOOOOOOOOOOOOO |
|
||||
9| O O O O |
|
||||
|__________________________________________________________________|"""
|
||||
|
||||
translate = None
|
||||
if translate == None:
|
||||
translate = {}
|
||||
# Turn the ASCII art sideways and build a hash look up for
|
||||
# column values, for example:
|
||||
# (O, , ,O, , , , , , , , ):A
|
||||
# (O, , , ,O, , , , , , , ):B
|
||||
# (O, , , , ,O, , , , , , ):C
|
||||
rows = IBM_MODEL_029_KEYPUNCH[1:].split('\n');
|
||||
rotated = [[ r[i] for r in rows[0:13]] for i in range(5, len(rows[0]) - 1)]
|
||||
for v in rotated:
|
||||
translate[tuple(v[1:])] = v[0]
|
||||
#print translate
|
||||
|
||||
# generate a range of floats
|
||||
def drange(start, stop, step=1.0):
|
||||
r = start
|
||||
while (step >= 0.0 and r < stop) or (step < 0.0 and r > stop):
|
||||
yield r
|
||||
r += step
|
||||
|
||||
# Represents a punchcard image plus scanned data
|
||||
class PunchCard(object):
|
||||
|
||||
def __init__(self, image, bright=-1, debug=False, xstart=0, xstop=0, ystart=0, ystop=0, xadjust=0):
|
||||
pass
|
||||
self.text = ''
|
||||
self.decoded = []
|
||||
self.surface = []
|
||||
self.debug = debug
|
||||
self.threshold = 0
|
||||
self.ymin = ystart
|
||||
self.ymax = ystop
|
||||
self.xmin = xstart
|
||||
self.xmax = xstop
|
||||
self.xadjust = xadjust
|
||||
self.image = image
|
||||
self.pix = image.load()
|
||||
self._crop()
|
||||
self._scan(bright)
|
||||
|
||||
# Brightness is the average of RGB values
|
||||
def _brightness(self, pixel):
|
||||
#print max(pixel)
|
||||
return ( pixel[0] + pixel[1] + pixel[2] ) / 3
|
||||
|
||||
# For highlighting on the debug dump
|
||||
def _flip(self, pixel):
|
||||
return max(pixel)
|
||||
|
||||
# The search is started from the "crop" edges.
|
||||
# Either use crop boundary of the image size or the valyes supplied
|
||||
# by the command line args
|
||||
def _crop(self):
|
||||
self.xsize, self.ysize = image.size
|
||||
if self.xmax == 0:
|
||||
self.xmax = self.xsize
|
||||
if self.ymax == 0:
|
||||
self.ymax = self.ysize
|
||||
self.midx = self.xmin + (self.xmax - self.xmin) / 2 + self.xadjust
|
||||
self.midy = self.ymin + (self.ymax - self.ymin) / 2
|
||||
|
||||
# heuristic for finding a reasonable cutoff brightness
|
||||
def _find_threshold_brightness(self):
|
||||
left = self._brightness(self.pix[self.xmin, self.midy])
|
||||
right = self._brightness(self.pix[self.xmax - 1, self.midy])
|
||||
return min(left, right, BRIGHTNESS_THRESHOLD) - 10
|
||||
vals = []
|
||||
last = 0
|
||||
for x in xrange(self.xmin,self.xmax):
|
||||
val = self._brightness(self.pix[x, self.midy])
|
||||
if val > last:
|
||||
left = val
|
||||
else:
|
||||
break
|
||||
last = val
|
||||
for x in xrange(self.xmax,self.xmin, -1):
|
||||
val = self._brightness(self.pix[x, self.midy])
|
||||
if val > last:
|
||||
right = val
|
||||
else:
|
||||
break
|
||||
right = val
|
||||
print left, right
|
||||
return min(left, right,200)
|
||||
|
||||
for x in xrange(self.xmin,self.xmax):
|
||||
val = self._brightness(self.pix[x, self.midy])
|
||||
vals.append(val)
|
||||
vals.sort()
|
||||
last_val = vals[0]
|
||||
biggest_diff = 0
|
||||
threshold = 0
|
||||
for val in vals:
|
||||
diff = val - last_val
|
||||
#print val, diff
|
||||
if val > 127 and val < 200 and diff >= 5:
|
||||
biggest_diff = diff
|
||||
threshold = val
|
||||
last_val = val
|
||||
if self.debug:
|
||||
print "Threshold diff=", biggest_diff, "brightness=", val
|
||||
return threshold - 10
|
||||
|
||||
# Find the left and right edges of the data area at probe_y and from that
|
||||
# figure out the column and hole vertical dimensions at probe_y.
|
||||
def _find_data_horiz_dimensions(self, probe_y):
|
||||
left_border, right_border = self.xmin, self.xmax - 1
|
||||
for x in xrange(self.xmin, self.midx):
|
||||
if self._brightness(self.pix[x, probe_y]) < self.threshold:
|
||||
left_border = x
|
||||
break
|
||||
for x in xrange(self.xmax-1, self.midx, -1):
|
||||
if self._brightness(self.pix[x, probe_y]) < self.threshold:
|
||||
right_border = x
|
||||
break
|
||||
width = right_border - left_border
|
||||
card_side_margin_width = int(width * CARD_SIDE_MARGIN_RATIO)
|
||||
data_left_x = left_border + card_side_margin_width
|
||||
#data_right_x = right_border - card_side_margin_width
|
||||
data_right_x = data_left_x + int((CARD_COLUMNS * width) * CARD_COL_WIDTH/CARD_WIDTH)
|
||||
col_width = width * CARD_COL_WIDTH_RATIO
|
||||
hole_width = width * CARD_HOLE_WIDTH_RATIO
|
||||
#print col_width
|
||||
if self.debug:
|
||||
# mark left and right edges on the copy
|
||||
for y in xrange(probe_y - self.ysize/100, probe_y + self.ysize/100):
|
||||
self.debug_pix[left_border if left_border > 0 else 0,y] = 255
|
||||
self.debug_pix[right_border if right_border < self.xmax else self.xmax - 1,y] = 255
|
||||
for x in xrange(1, (self.xmax - self.xmin) / 200):
|
||||
self.debug_pix[left_border + x, probe_y] = 255
|
||||
self.debug_pix[right_border - x, probe_y] = 255
|
||||
|
||||
return data_left_x, data_right_x, col_width, hole_width
|
||||
|
||||
# find the top and bottom of the data area and from that the
|
||||
# column and hole horizontal dimensions
|
||||
def _find_data_vert_dimensions(self):
|
||||
top_border, bottom_border = self.ymin, self.ymax
|
||||
for y in xrange(self.ymin, self.midy):
|
||||
#print pix[midx, y][0]
|
||||
if self._brightness(self.pix[self.midx, y]) < self.threshold:
|
||||
top_border = y
|
||||
break
|
||||
for y in xrange(self.ymax - 1, self.midy, -1):
|
||||
if self._brightness(self.pix[self.midx, y]) < self.threshold:
|
||||
bottom_border = y
|
||||
break
|
||||
card_height = bottom_border - top_border
|
||||
card_top_margin = int(card_height * CARD_TOP_MARGIN_RATIO)
|
||||
data_begins = top_border + card_top_margin
|
||||
hole_height = int(card_height * CARD_HOLE_HEIGHT_RATIO)
|
||||
data_top_y = data_begins + hole_height / 2
|
||||
col_height = int(card_height * CARD_ROW_HEIGHT_RATIO)
|
||||
if self.debug:
|
||||
# mark up the copy with the edges
|
||||
for x in xrange(self.xmin, self.xmax-1):
|
||||
self.debug_pix[x,top_border] = 255
|
||||
self.debug_pix[x,bottom_border] = 255
|
||||
if self.debug:
|
||||
# mark search parameters
|
||||
for x in xrange(self.midx - self.xsize/20, self.midx + self.xsize/20):
|
||||
self.debug_pix[x,self.ymin] = 255
|
||||
self.debug_pix[x,self.ymax - 1] = 255
|
||||
for y in xrange(0, self.ymin):
|
||||
self.debug_pix[self.midx,y] = 255
|
||||
for y in xrange(self.ymax - 1, self.ysize-1):
|
||||
self.debug_pix[self.midx,y] = 255
|
||||
return data_top_y, data_top_y + col_height * 11, col_height, hole_height
|
||||
|
||||
def _scan(self, bright=-1):
|
||||
if self.debug:
|
||||
# if debugging make a copy we can draw on
|
||||
self.debug_image = self.image.copy()
|
||||
self.debug_pix = self.debug_image.load()
|
||||
|
||||
self.threshold = bright if bright > 0 else self._find_threshold_brightness()
|
||||
#x_min, x_max, col_width = self._find_data_horiz_dimensions(image, pix, self.threshold, self.ystart, self.ystop)
|
||||
y_data_pos, y_data_end, col_height, hole_height = self._find_data_vert_dimensions()
|
||||
data = {}
|
||||
|
||||
# Chads are narrow so find then heuristically by accumulating pixel brightness
|
||||
# along the row. Should be forgiving if the image is slightly wonky.
|
||||
y = y_data_pos #- col_height/8
|
||||
for row_num in xrange(CARD_ROWS):
|
||||
probe_y = y + col_height if row_num == 0 else ( y - col_height if row_num == CARD_ROWS -1 else y ) # Line 0 has a corner missing
|
||||
x_data_left, x_data_right, col_width, hole_width = self._find_data_horiz_dimensions(probe_y)
|
||||
left_edge = -1 # of a punch-hole
|
||||
for x in xrange(x_data_left, x_data_right):
|
||||
# Chads are tall so we can be sure if we probe around the middle of their height
|
||||
val = self._brightness(self.pix[x, y])
|
||||
if val >= self.threshold:
|
||||
if left_edge == -1:
|
||||
left_edge = x
|
||||
if self.debug:
|
||||
self.debug_pix[x,y] = self._flip(self.pix[x,y])
|
||||
else:
|
||||
if left_edge > -1:
|
||||
hole_length = x - left_edge
|
||||
if hole_length >= hole_width * 0.75:
|
||||
col_num = int((left_edge + hole_length / 2.0 - x_data_left) / col_width + 0.25)
|
||||
data[(col_num, row_num)] = hole_length
|
||||
left_edge = -1
|
||||
if (self.debug):
|
||||
# Plot where holes might be on this row
|
||||
expected_top_edge = y - hole_height / 2
|
||||
expected_bottom_edge = y + hole_height / 2
|
||||
blue = 255 * 256 * 256
|
||||
for expected_left_edge in drange(x_data_left, x_data_right - 1, col_width):
|
||||
for y_plot in drange(expected_top_edge, expected_bottom_edge, 2):
|
||||
self.debug_pix[expected_left_edge,y_plot] = blue
|
||||
#self.debug_pix[x + hole_width/2,yline] = 255 * 256 * 256
|
||||
self.debug_pix[expected_left_edge + hole_width,y_plot] = blue
|
||||
for x_plot in drange(expected_left_edge, expected_left_edge + hole_width):
|
||||
self.debug_pix[x_plot, expected_top_edge] = blue
|
||||
self.debug_pix[x_plot, expected_bottom_edge] = blue
|
||||
y += col_height
|
||||
|
||||
if self.debug:
|
||||
self.debug_image.show()
|
||||
# prevent run-a-way debug shows causing my desktop to run out of memory
|
||||
raw_input("Press Enter to continue...")
|
||||
self.decoded = []
|
||||
# Could fold this loop into the previous one - but would it be faster?
|
||||
for col in xrange(0, CARD_COLUMNS):
|
||||
col_pattern = []
|
||||
col_surface = []
|
||||
for row in xrange(CARD_ROWS):
|
||||
key = (col, row)
|
||||
# avergage for 1/3 of a column is greater than the threshold
|
||||
col_pattern.append('O' if key in data else ' ')
|
||||
col_surface.append(data[key] if key in data else 0)
|
||||
tval = tuple(col_pattern)
|
||||
global translate
|
||||
self.text += translate[tval] if tval in translate else '@'
|
||||
self.decoded.append(tval)
|
||||
self.surface.append(col_surface)
|
||||
|
||||
|
||||
return self
|
||||
|
||||
# ASCII art image of card
|
||||
def dump(self, id, raw_data=False):
|
||||
print ' Card Dump of Image file:', id, 'Format', 'Raw' if raw_data else 'Dump', 'threshold=', self.threshold
|
||||
print ' ' + '123456789-' * (CARD_COLUMNS/10)
|
||||
print ' ' + '_' * CARD_COLUMNS + ' '
|
||||
print '/' + self.text + '_' * (CARD_COLUMNS - len(self.text)) + '|'
|
||||
for rnum in xrange(len(self.decoded[0])):
|
||||
sys.stdout.write('|')
|
||||
if raw_data:
|
||||
for val in self.surface:
|
||||
sys.stdout.write(("(%d)" % val[rnum]) if val[rnum] != 0 else '.' )
|
||||
else:
|
||||
for col in self.decoded:
|
||||
sys.stdout.write(col[rnum] if col[rnum] == 'O' else '.')
|
||||
print '|'
|
||||
print '`' + '-' * CARD_COLUMNS + "'"
|
||||
print ' ' + '123456789-' * (CARD_COLUMNS/10)
|
||||
print ''
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
usage = """usage: %prog [options] image [image...]
|
||||
decode punch card image into ASCII."""
|
||||
parser = OptionParser(usage)
|
||||
parser.add_option('-b', '--bright-threshold', type='int', dest='bright', default=-1, help='Brightness (R+G+B)/3, e.g. 127.')
|
||||
parser.add_option('-s', '--side-margin-ratio', type='float', dest='side_margin_ratio', default=CARD_SIDE_MARGIN_RATIO, help='Manually set side margin ratio (sideMargin/cardWidth).')
|
||||
parser.add_option('-d', '--dump', action='store_true', dest='dump', help='Output an ASCII-art version of the card.')
|
||||
parser.add_option('-i', '--display-image', action='store_true', dest='display', help='Display an anotated version of the image.')
|
||||
parser.add_option('-r', '--dump-raw', action='store_true', dest='dumpraw', help='Output ASCII-art with raw row/column accumulator values.')
|
||||
parser.add_option('-x', '--x-start', type='int', dest='xstart', default=0, help='Start looking for a card edge at y position (pixels)')
|
||||
parser.add_option('-X', '--x-stop', type='int', dest='xstop', default=0, help='Stop looking for a card edge at y position')
|
||||
parser.add_option('-y', '--y-start', type='int', dest='ystart', default=0, help='Start looking for a card edge at y position')
|
||||
parser.add_option('-Y', '--y-stop', type='int', dest='ystop', default=0, help='Stop looking for a card edge at y position')
|
||||
parser.add_option('-a', '--adjust-x', type='int', dest='xadjust', default=0, help='Adjust middle edge detect location (pixels)')
|
||||
(options, args) = parser.parse_args()
|
||||
|
||||
for arg in args:
|
||||
image = Image.open(arg)
|
||||
card = PunchCard(image, bright=options.bright, debug=options.display, xstart=options.xstart, xstop=options.xstop, ystart=options.ystart, ystop=options.ystop, xadjust=options.xadjust)
|
||||
print card.text
|
||||
if (options.dump):
|
||||
card.dump(arg)
|
||||
if (options.dumpraw):
|
||||
card.dump(arg, raw_data=True)
|
||||
|
||||
|
||||
# main(["old_school.jpg", "-y", "5", "-b", "124"])
|
||||
# O2Z-H4PAL52Z4-3F19-HV18-0LD$-SCH0-0L1S-4W3S-0M3!-31-@&Q@33@ @@@2LC_B&@@T&@L&W@&@
|
Loading…
Reference in New Issue
Block a user