added default.nix and various ctf examples

This commit is contained in:
Emile Clark-Boman 2025-06-27 03:08:31 +10:00
parent b9c5a5bf3e
commit 89973b803c
6 changed files with 197 additions and 1 deletions

View file

@ -0,0 +1,92 @@
'''
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')

View file

@ -0,0 +1,55 @@
'''
Solution to https://cryptohack.org/courses/symmetric/flipping_cookie/
'''
import requests
from datetime import datetime, timedelta
URL = 'https://aes.cryptohack.org/flipping_cookie'
# NOTE: assumes A and B are equal length
def xor_bytes(A: bytes, B: bytes) -> bytes:
return b''.join([(a ^ b).to_bytes() for (a, b) in zip(A, B)])
def xor_str(A: str, B: str) -> str:
return ''.join([chr(ord(a) ^ ord(b)) for (a, b) in zip(A, B)])
def gen_expiry() -> str:
return (datetime.today() + timedelta(days=1)).strftime("%s")
def get_cookie() -> tuple[bytes, bytes]:
resp = requests.get(f'{URL}/get_cookie/')
cookie = resp.json()['cookie']
iv = bytes.fromhex(cookie[:32])
ciphertext = bytes.fromhex(cookie[32:])
return iv, ciphertext
def main() -> None:
# cookie flipping preprocessing step
admin_len = len('admin=')
expiry_len = len(';expiry=') + len(gen_expiry())
admin_mask = '\x00' * admin_len
expiry_mask = '\x00' * expiry_len
# we aim to replace "admin=False;" with "admin=True;;"
# NOTE: double semicolon ("True;;") is intentional
# NOTE: and the server won't ever realise it happened!
deletion = admin_mask + 'False' + expiry_mask
insertion = admin_mask + 'True;' + expiry_mask
# determine the value that replaces deletion with insertion
cookie_flip = xor_str(deletion, insertion)
# get our new cookie and apply the cookie flip!
iv, ciphertext = get_cookie()
flipped_iv = xor_bytes(cookie_flip.encode(), iv)
print('Flipped Cookie:')
print('IV:', flipped_iv.hex())
print('Body:', ciphertext.hex())
if __name__ == '__main__':
main()

39
ctf-solutions/symmetry.py Normal file
View file

@ -0,0 +1,39 @@
'''
Solution to https://cryptohack.org/courses/symmetric/symmetry/
'''
import os
import requests
from imp.math.util import xor_bytes, xor_str
URL = 'https://aes.cryptohack.org/symmetry'
def get_encrypted_flag() -> tuple[bytes, bytes]:
resp = requests.get(f'{URL}/encrypt_flag/')
ciphertext = resp.json()['ciphertext']
iv = bytes.fromhex(ciphertext[:32])
flag = bytes.fromhex(ciphertext[32:])
return iv, flag
def encrypt(plaintext: bytes, iv: bytes) -> bytes:
plaintext, iv = plaintext.hex(), iv.hex()
resp = requests.get(f'{URL}/encrypt/{plaintext}/{iv}')
return bytes.fromhex(resp.json()['ciphertext'])
def main() -> None:
iv, flag = get_encrypted_flag()
# generate a random plaintext of equal length to the flag
random_plaintext = os.urandom(len(flag))
random_ciphertext = encrypt(random_plaintext, iv)
# XOR random plaintext with corresponding ciphertext to
# get the set generated by IV under the keyed AES permutation
aes_orbit = xor_bytes(random_plaintext, random_ciphertext)
# XOR this orbit with the flag to get the hexed flag plaintext
flag_plaintext = xor_bytes(flag, aes_orbit)
print('Flag:', flag_plaintext.decode())
print('IV:', iv.hex())
if __name__ == '__main__':
main()

10
default.nix Normal file
View file

@ -0,0 +1,10 @@
let
sources = import ./nix/sources.nix;
pkgs = import sources.nixpkgs {};
# Let all API attributes like "poetry2nix.mkPoetryApplication"
# use the packages and versions (python3, poetry etc.) from our pinned nixpkgs above
# under the hood:
poetry2nix = import sources.poetry2nix {inherit pkgs;};
myPythonApp = poetry2nix.mkPoetryApplication {projectDir = ./.;};
in
myPythonApp

View file

@ -34,7 +34,7 @@ def encrypt(b: bytes) -> bytes:
return bytes.fromhex(resp['ciphertext'])
def main() -> None:
paddingoracle.crack(encrypt, pad, CHARSET, 64, batch_size=2, debug=True)
paddingoracle.crack(encrypt, pad, CHARSET, 16, batch_size=20, debug=True)