nullcon HackIM 2019 Writeup
Feb 3, 2019 12:35 · 850 words · 4 minute read
Captcha Forest
Problem
A baby captcha just for you.
nc misc.ctf.nullcon.net 6001
Solution
For this challenge, we need to solve 200 captchas that are using the Bill Cipher mappings.
Here is an example captcha:
The first step is to find out which symbol corresponds to which letter in the alphabet. I did a quick google search and found this:
Now with a one-to-one mapping, we can move on to solve the captchas. After some observation, I discover that one, the captcha always consists of four letters; two, the symbols are always the same.
Now with the two observations, I plan to crop each 120x27 captcha into four 30x27 blocks and match each block to the letter it corresponds to.
This method works out great. Here is my final code:
from pwn import *
from PIL import Image
context.log_level = 'error'
keys = {
'c4a35da158e008809f852ecc813dd139': 'A',
'a9bcd51c91b97289146158cd60c14e5d': 'B',
'2e17618143d78f20269f3216166a9a98': 'C',
'0fd8166e85f94c0e46ffa4ba0f29e72f': 'D',
'cc658ac67ce22881fe6f6970c32f761b': 'E',
'921e54169612d85f2d27ce4ebc959cdf': 'F',
'8d0ed3c3e8dcd62632049b3454d6cf34': 'G',
'fd486e2b20c06354bdbfc485dcf569d6': 'H',
'e7555a5a32a9fccc6cf7c0ec72cdddbd': 'I',
'2fc99e3aed0d0cd4fc03108fb2f9745f': 'J',
'e5d056c179c5fb8883eb204746b9bc29': 'K',
'6f6090fe8fe36a83d920c5be3d4a1491': 'L',
'7bc2f0bd22445eb4546c06d5903ba185': 'M',
'6a578dc416c036f725d4b8ce70f5b84f': 'N',
'6c141423aced83a987ef965b126d20c6': 'O',
'd0b3155daff4c52677ebf055d2e8cec0': 'P',
'54a962e6fa4383fef7da22e79aadf7f0': 'Q',
'0a8970d139a6a393889aa616cdcd8b4c': 'R',
'2162480dcac1c231f11c93ff33bcb47d': 'S',
'ac0a5813691cdf6aab84fb74a2e11c61': 'T',
'93e0fd5f54239b8022044ee54f6eb7e6': 'U',
'64d4a2ebcb7b4f87513ce1c67ad52127': 'V',
'08f5debc5b9297b38d91d48078674417': 'W',
'199c795a79eabaf760996065ba4ee884': 'X',
'e58d3228b4949421f411517b62610cfc': 'Y',
'44051309af10c3ff106dbe48c67f1e16': 'Z',
}
# https://stackoverflow.com/questions/5953373/how-to-split-image-into-multiple-pieces-in-python
def crop(path, inputFile, height, width, k):
im = Image.open(inputFile)
imgwidth, imgheight = im.size
for i in range(0, imgheight, height):
for j in range(0, imgwidth, width):
box = (j, i, j+width, i+height)
a = im.crop(box)
try:
a.save(os.path.join(path,"IMG-%s.png" % k))
except:
pass
k +=1
def getHash(count):
p = process(['md5', '-q', 'IMG-%s.png' % count])
h = p.recvall().strip()
p.close()
return h
sh = remote('misc.ctf.nullcon.net', 6001)
for count in range(200):
sh.recvuntil(' ---')
sh.sendline('')
image = sh.recvuntil(' ---').strip().split('\n')[0].decode('hex')
with open('./temp.png', 'wb') as f:
f.write(image)
crop('./', './temp.png', 27, 30, 0)
answer = ''
for i in range(4):
h = getHash(i)
if h not in keys:
print 'IMG-%s.png not found' % i
else:
answer += keys[h]
print '{}: {}'.format(count, answer)
sh.sendline(answer)
sh.interactive()
flag: hackim19{Since_you_are_not_a_robot_I_will_give_you_the_flag}
Captcha Forest Harder
Problem
nc miscc.ctf.nullcon.net 6002
Solution
This is similar to the last challenge, but with two differences. One, all symbols are rotated and scaled each time so their md5 hashes would no longer match. Two, this time, we only need to get 120 of the 200 captchas to get the flag.
I experimented with feature matching and image hashes, but none of my solutions are able to fully automate the process. I solved this challenge in the end through a semi-automated process.
I wrote a script that will guess the letter for each symbol and then I will manually verify it before sending it to the server. Using this method, I am able to get the flag.
Here is the semi-automated script:
from pwn import *
from PIL import Image
from PIL import ImageOps
import imagehash
import uuid
context.log_level = 'debug'
keys = {}
def getHash(img):
return imagehash.average_hash(img, 256)
for i in range(0x41, 0x41+26):
img = Image.open('./images/%s.png' % chr(i))
img = ImageOps.expand(img, border = 10, fill = (255, 255, 255))
for deg in range(-30, 30):
keys[getHash(img.rotate(deg).crop((10, 10, 40, 37)))] = chr(i)
# https://stackoverflow.com/questions/5953373/how-to-split-image-into-multiple-pieces-in-python
def crop(path, inputFile, height, width, k):
im = Image.open(inputFile)
imgwidth, imgheight = im.size
for i in range(0, imgheight, height):
for j in range(0, imgwidth, width):
box = (j, i, j+width, i+height)
a = im.crop(box)
try:
a.save(os.path.join(path,"IMG-%s.png" % k))
except:
pass
k +=1
sh = remote('miscc.ctf.nullcon.net', 6002)
sh.recvuntil(' ---')
sh.sendline('')
for count in range(200):
data = sh.recvuntil(' ---').strip().split('\n')
image = data[0].decode('hex')
with open('./temp.png', 'wb') as f:
f.write(image)
crop('./', './temp.png', 27, 30, 0)
answer = ''
for i in range(4):
h = getHash(Image.open('IMG-%s.png' % i))
minV = -1
minC = ''
for kh, hc in keys.iteritems():
v = abs(h - kh)
# print v
if v < minV or minV < 0:
minV = v
minC = hc
os.system('imgcat ./IMG-%s.png' % i)
value = raw_input('is it {}? '.format(minC)).strip()
if len(value) < 1:
answer += minC
else:
answer += value
os.system('mkdir -p ./images/{} && cp ./IMG-{}.png ./images/{}/{}'.format(answer[-1], i, answer[-1], uuid.uuid4()))
sh.sendline(answer)
sh.recvline()
result = sh.recvline()
sh.recvline()
if 'Correct' in result:
sh.sendline('')
print '{}: {} - {}'.format(count, answer, result)
sh.interactive()
Here’s how it looks in action:
flag: hackim19{I_guess_I_will_stop_making_captchas_now}
mlAuth
Problem
Solution
This is similar to the dog or frog challenge in picoctf 2018 where we need to find the input for a neural network in order to get a certain output.
I took the code I wrote for “dog or frog” and tweaked it a bit. Here is my final solution:
import keras.backend as K
import numpy as np
from keras.models import load_model
model = load_model('./dist/keras_model')
model.summary()
profile = '0x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x80x750xfe0xdc0x590x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00xd0x5f0xd40xfd0xfd0xfd0x9d0x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x100x5f0xd10xfd0xfd0xfd0xf50x7d0x120x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x280x600xce0xfd0xfe0xfd0xfd0xc60x400x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x2c0xb60xf00xfd0xfd0xfd0xfe0xfd0xc60x180x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00xf0x3c0x3c0xa80xfd0xfd0xfe0xc80x170x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x460xf70xfd0xfd0xf50x150x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x4b0xcf0xfd0xfd0xcf0x5c0x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x4f0xdb0xfd0xfd0xfd0x8a0x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x690xfa0xfd0xfd0xfd0x220x10x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x5f0xfe0xfe0xfe0xfe0x5e0x00x00x00x00x00x30xd0xd0xd0x80x00x00x00x00x00x00x00x00x00x00x00x00x6b0xfd0xfd0xfd0xcc0xf0x00x00x00x00x150xa60xfd0xfd0xfd0xd40x190x00x00x00x00x00x00x00x00x00x00x210xd90xfd0xfd0x840x400x00x00x120x2b0x9d0xab0xfd0xfd0xfd0xfd0xfd0xa00x20x00x00x00x00x00x00x00x00x30xa60xfd0xfd0xf20x310x110x310x9e0xd20xfe0xfd0xfd0xfd0xfd0xfd0xfd0xfd0xfd0xb0x00x00x00x00x00x00x00x00xa0xe30xfd0xfd0xcf0xf0xac0xfd0xfd0xfd0xfe0xf70xc90xfd0xd20xd20xfd0xfd0xaf0x40x00x00x00x00x00x00x00x00xa0xe40xfd0xfd0xe00x570xf20xfd0xfd0xb80x3c0x360x90x3c0x230xb60xfd0xfd0x340x00x00x00x00x00x00x00x00x00xd0xfd0xfd0xfd0xfd0xe70xfd0xfd0xfd0x5d0x560x560x560x6d0xd90xfd0xfd0x860x50x00x00x00x00x00x00x00x00x00x20x730xfd0xfd0xfd0xfd0xfd0xfd0xfd0xfd0xfe0xfd0xfd0xfd0xfd0xfd0x860x50x00x00x00x00x00x00x00x00x00x00x00x30xa60xfd0xfd0xfd0xfd0xfd0xfd0xfd0xfe0xfd0xfd0xfd0xaf0x340x50x00x00x00x00x00x00x00x00x00x00x00x00x00x70x230x840xe10xfd0xfd0xfd0xc30x840x840x840x6e0x40x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x0'
np.set_printoptions(suppress=True)
prof_h = profile.split('0x')
ip = [int(_, 16) for _ in prof_h[1:]]
ip = np.array(ip, dtype='float32')/255
ip = ip.reshape([1,28,28,1])
model_input_layer = model.layers[0].input
model_output_layer = model.layers[-1].output
cost_function = model_output_layer[0][1]
gradient_function = K.gradients(cost_function, model_input_layer)[0]
grab_cost_and_gradients_from_model = K.function([model_input_layer, K.learning_phase()], [cost_function, gradient_function])
learning_rate = 0.01
cost = 0.0
while cost < 0.99:
cost, gradients = grab_cost_and_gradients_from_model([ip, 0])
ip += np.sign(gradients) * learning_rate
ip = np.clip(ip, 0, 1.0)
print("value: {:.8}%".format(cost * 100))
predicted = model.predict(ip)[0][1]
print predicted
ip = ip*255
ip = ip.reshape(784)
ip = ip.astype(int)
ip = ''.join([hex(x)[:-1] for x in ip])
print ip
For more details, you can read my writeup for dog or frog.
flag: hackim19{wh0_kn3w_ml_w0uld_61v3_y0u_1337_fl465}