Blog post moved to my new blog.
Last active
August 29, 2015 14:02
-
-
Save raphaelm/6e89495f5042e7e297f5 to your computer and use it in GitHub Desktop.
GPN14 CTF WriteUp
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
# Author: Nils Martin Klünder | |
# License: Apache License | |
# Do no evil | |
from PIL import Image | |
import traceback | |
import json | |
import shutil | |
import time | |
def solve(debug=False, train=False): | |
debug = debug or train | |
try: | |
varcount = 0 | |
bgs = [] | |
if debug: print('Lade Captcha') | |
img = Image.open('tmp.png') | |
imgpx = img.load() | |
if debug: print('Hintergrund herausfinden') | |
bgcount = int(open('bgcount').read()) | |
for i in range(bgcount): | |
bgi = i | |
bg = Image.open('%02d_filtered.png' % i) | |
bgpx = bg.load() | |
samecount = 0 | |
for y in range(50): | |
for x in range(175): | |
if imgpx[x,y] == bgpx[x,y]: | |
samecount += 1 | |
if samecount > 50*175/2: | |
break | |
if debug: print('Text extrahieren') | |
textimg = Image.new('L', (175,50)) | |
textimgpx = textimg.load() | |
for y in range(50): | |
for x in range(175): | |
textimgpx[x,y] = 255 if imgpx[x,y] == bgpx[x,y] else 0 | |
if debug: print('Buchstaben sammeln') | |
buchstaben = [] | |
newbuchstabe = None | |
for x in range(175): | |
leer = True | |
for y in range(50): | |
if textimgpx[x,y] == 0: | |
leer = False | |
break | |
if leer and newbuchstabe is not None: | |
buchstaben.append((newbuchstabe, x)) | |
newbuchstabe = None | |
elif not leer and newbuchstabe is None: | |
newbuchstabe = x | |
if newbuchstabe is not None: | |
buchstaben.append((newbuchstabe, 151)) | |
if debug: print(buchstaben) | |
if debug: print('Buchstaben ausschneiden') | |
buchstabenalt = buchstaben | |
buchstaben = [] | |
for buchstabe in buchstabenalt: | |
top = None | |
bottom = None | |
for y in range(50): | |
leer = True | |
for x in range(buchstabe[0], buchstabe[1]): | |
if textimgpx[x,y] == 0: | |
leer = False | |
break | |
if leer and top is not None: | |
bottom = y | |
break | |
elif not leer and top is None: | |
top = y | |
if top is None: top = 0 | |
if bottom is None: bottom = 51 | |
if debug: print((buchstabe[0], top, buchstabe[1], bottom)) | |
buchstaben.append(textimg.crop((buchstabe[0], top, buchstabe[1], bottom))) | |
if debug: print('Auf gehts OCR… mit händischer hilfe') | |
try: | |
ocr = json.load(open('ocr%d' % bgi)) | |
except: | |
ocr = {} | |
loesung = [] | |
for buchstabe in buchstaben: | |
#buchstabe.show() | |
px = buchstabe.load() | |
randpixel = 0 | |
flaechepixel = 0 | |
lochpixel = 0 | |
for y in range(buchstabe.size[1]): | |
hadtoner = False | |
for x in range(buchstabe.size[0]): | |
p = (px[x,y] == 0) | |
if p: | |
flaechepixel += 1 | |
hadtoner = True | |
if p: | |
isrand = False | |
for dy in (-1, 0, 1): | |
for dx in (-1, 0, 1): | |
try: | |
if px[x+dx,y+dy] == 255: | |
isrand = True | |
break | |
except: | |
pass | |
if isrand: | |
break | |
if isrand: | |
randpixel += 1 | |
if debug: print('#' if px[x,y] > 0 else ' ', end='') | |
if debug: print('') | |
# Lochpixel | |
# randpixel rot | |
for y in range(buchstabe.size[1]): | |
if px[0,y] == 255: px[0,y] = 200 | |
if px[buchstabe.size[0]-1,y] == 255: px[buchstabe.size[0]-1,y] = 200 | |
for x in range(buchstabe.size[0]): | |
if px[x,0] == 255: px[x,0] = 200 | |
if px[x,buchstabe.size[1]-1] == 255: px[x,buchstabe.size[1]-1] = 200 | |
for i in range(int(max(buchstabe.size)/2-1)): | |
for y in range(buchstabe.size[1]): | |
for x in range(buchstabe.size[0]): | |
for dy in (-1, 0, 1): | |
for dx in (-1, 0, 1): | |
try: | |
if px[x,y] == 255 and px[x+dx,y+dy] == 200: | |
px[x,y] = 200 | |
break | |
except: | |
pass | |
lochpixel = 0 | |
for y in range(buchstabe.size[1]): | |
for x in range(buchstabe.size[0]): | |
if px[x,y] == 255: | |
lochpixel += 1 | |
mine = (flaechepixel, randpixel, lochpixel) | |
if debug: print(mine) | |
minscore = 20000 | |
minval = None | |
for knownn, val in ocr.items(): | |
if val == '': continue | |
known = [int(i) for i in knownn.split('-')] | |
diff = (mine[0]-known[0], mine[1]-known[1], mine[2]-known[2]) | |
score = pow(sum([i*i for i in diff]), 0.5) | |
if score < minscore: | |
minscore = score | |
minval = val | |
if debug: print(minscore) | |
if minscore < 1500: | |
loesung.append(minval) | |
else: | |
if not train: | |
return None | |
if debug: print('') | |
bla = '' | |
if debug: print('known: %s', ''.join(sorted(ocr.values()))) | |
if debug: print('one letter or „empty“') | |
while (len(bla) != 1 or bla not in 'QWERTZUIOPASDFGHJKLYXCVBNM1234567890') and bla != 'empty': | |
bla = input('>') | |
if bla == 'empty': | |
bla = '' | |
ocr['-'.join([str(i) for i in mine])] = bla | |
loesung.append(bla) | |
json.dump(ocr, open('ocr%d' % bgi, 'w')) | |
ll = ''.join(loesung) | |
shutil.copyfile('tmp.png', 'tries/' + ll + '_' + str(time.time()) + '.png') | |
return ll | |
except: | |
traceback.print_exc() | |
return None |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
# Author: Nils Martin Klünder | |
# License: Apache License | |
# Do no evil | |
from PIL import Image | |
import urllib.request | |
url = 'http://ctf.gpn.entropia.de:50234/download.php?q=7bf63ebe6646c009' | |
varcount = 0 | |
bgs = [] | |
print('Schritt 1: So lange Captchas laden (mindestens 100) bis wir jede Variation 50 mal haben') | |
total = 0 | |
while True: | |
urllib.request.urlretrieve('http://ctf.gpn.entropia.de:50234/image.php', 'tmp.png') | |
img = Image.open('tmp.png') | |
for bg in bgs: | |
px = img.load() | |
comp = bg[0] | |
comppx = comp.load() | |
samecount = 0 | |
for y in range(50): | |
for x in range(175): | |
if px[x,y] == comppx[x,y]: | |
samecount += 1 | |
if samecount > 50*175/2: | |
bg.append(img) | |
break | |
else: | |
bgs.append([img]) | |
img.save('%02d.png' % varcount) | |
varcount += 1 | |
open('bgcount', 'w').write(str(varcount)) | |
total += 1 | |
if total%10 == 0: | |
print(total) | |
if total > 100: | |
bla = [len(bg) for bg in bgs] | |
if total%10 == 0: | |
print(tuple(bla)) | |
if min(bla) >= 50: break | |
print('Schritt 2: Jetzt die Hintergründe extrahieren, dann glücklich sein.') | |
i = 0 | |
for bg in bgs: | |
print('Hintergrund %d/%d' % (i, len(bgs))) | |
loaded = [img.load() for img in bg[:50]] | |
print('Loaded') | |
result = Image.new('RGB', (175,50)) | |
loaded_result = result.load() | |
for y in range(50): | |
print(y) | |
for x in range(175): | |
tmp = [px[x,y] for px in loaded] | |
loaded_result[x,y] = max(set(tmp), key=tmp.count) | |
result.save('%02d_filtered.png' % i) | |
i += 1 | |
open('bgcount', 'w').write(str(len(bgs))) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
# Author: Raphael Michel | |
# License: Apache License | |
# Do no evil | |
import requests | |
import re | |
import time | |
import captchasolver | |
import os.path | |
s = requests.Session() | |
# Load list | |
listfile = open('list.txt') | |
ids = listfile.read().split('\n') | |
for i in ids: | |
while True: | |
# Load detail page | |
r = s.get('http://ctf.gpn.entropia.de:50234/download.php?q=' + i) | |
html = r.content.decode('utf-8') | |
mg = re.search('flag.part[0-9]{3}.rar', html) | |
print(mg.group(0)) | |
if os.path.exists(mg.group(0)): | |
# Skip downloaded | |
print("SKIP") | |
break | |
# Load CAPTCHA | |
rc = s.get('http://ctf.gpn.entropia.de:50234/image.php', stream=True) | |
if rc.status_code == 200: | |
with open('tmp.png', 'wb') as f: | |
for chunk in rc.iter_content(1024): | |
f.write(chunk) | |
# Solve CAPTCHA | |
print('Enter CAPTCHA: ', end="") | |
captcha = captchasolver.solve() | |
if captcha is None: | |
continue | |
else: | |
print(captcha) | |
r = s.post('http://ctf.gpn.entropia.de:50234/download.php', data={ | |
'x': 1, | |
'y': 1, | |
'captcha': captcha, | |
'q': i | |
}) | |
if 'html' in r.headers['Content-Type']: | |
html = r.content.decode('utf-8') | |
if 'expired' in html: | |
print("Captcha expired") | |
elif 'Wrong' in html: | |
print("Wrong!") | |
continue | |
elif 'octet-stream' in r.headers['Content-Type']: | |
with open(r.headers['Content-Disposition'].split('filename=')[1], 'wb') as f: | |
for chunk in r.iter_content(1024): | |
f.write(chunk) | |
print('YESS!') | |
break |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment