imp/ctf-solutions/bean_counter.py

92 lines
2.6 KiB
Python

'''
Solution for https://cryptohack.org/courses/symmetric/bean_counter/
'''
import os
import requests
from math import ceil
from Crypto.Cipher import AES
from imp.math.util import xor_bytes
class StepUpCounter(object):
def __init__(self, step_up=False):
self.value = os.urandom(16).hex()
self.step = 1
self.stup = step_up
def increment(self):
if self.stup:
self.newIV = hex(int(self.value, 16) + self.step)
else:
self.newIV = hex(int(self.value, 16) - self.stup)
self.value = self.newIV[2:len(self.newIV)]
return bytes.fromhex(self.value.zfill(32))
def __repr__(self):
self.increment()
return self.value
'''
NOTE: Since step_up is used ONLY as false, we can simplify:
class StepUpCounter(object):
def __init__(self):
self.value = os.urandom(16).hex()
# SAME AS DOING NOTHING
def increment(self):
return self.value()
def __repr__(self):
return self.value
'''
URL = 'https://aes.cryptohack.org/bean_counter'
def get_encrypted() -> bytes:
resp = requests.get(f'{URL}/encrypt/')
encrypted = bytes.fromhex(resp.json()['encrypted'])
return encrypted
PNG_HEADER = [
'89', '50', '4e', '47',
'0d', '0a', '1a', '0a',
'00', '00', '00', '0d',
'49', '48', '44', '52'
]
def main() -> None:
# NOTE: the counter is constant!
ctr = StepUpCounter()
# init = ctr.increment()
# init_val = ctr.value
# for i in range(2560000):
# if ctr.increment() != init:
# print('Counter Changed')
# print(i)
# break
# elif ctr.value != init_val:
# print('valued changed')
# print(i)
# break
# PNGs *should* have a constant header, we will use the first 16 bytes
# to determine what the counter value must be set to
encrypted = get_encrypted()
png_header = bytes.fromhex(''.join(PNG_HEADER))
ctr_value = xor_bytes(encrypted[:16], png_header)
# apply the counter value to the entire encrypted image (in blocks of 16 bytes)
BLOCK_SIZE = 16
with open('bean_flag.png', 'wb') as f:
for i in range(ceil(len(encrypted) / BLOCK_SIZE)):
block = encrypted[i*BLOCK_SIZE : (i+1)*BLOCK_SIZE]
# NOTE: the block we get is not guaranteed to be block size (ie if at end)
plaintext_block = xor_bytes(block, ctr_value[:len(block)])
f.write(plaintext_block)
if __name__ == '__main__':
try:
main()
except (KeyboardInterrupt, EOFError):
print('\n[!] Interrupt')