#!/usr/bin/env python
# coding: utf-8
# # SECCON 2019 CTF Quals - Crazy Repetition of Codes (crypto)
#
# The challenge abbreviation is CRC, so we can guess what this challenge is about:
#
# ```py
# import os
# from Crypto.Cipher import AES
#
# def crc32(crc, data):
# crc = 0xFFFFFFFF ^ crc
# for c in data:
# crc = crc ^ ord(c)
# for i in range(8):
# crc = (crc >> 1) ^ (0xEDB88320 * (crc & 1))
# return 0xFFFFFFFF ^ crc
#
# key = b""
#
# crc = 0
# for i in range(int("1" * 10000)):
# crc = crc32(crc, "TSG")
# assert(crc == 0xb09bc54f)
# key += crc.to_bytes(4, byteorder='big')
#
# crc = 0
# for i in range(int("1" * 10000)):
# crc = crc32(crc, "is")
# key += crc.to_bytes(4, byteorder='big')
#
# crc = 0
# for i in range(int("1" * 10000)):
# crc = crc32(crc, "here")
# key += crc.to_bytes(4, byteorder='big')
#
# crc = 0
# for i in range(int("1" * 10000)):
# crc = crc32(crc, "at")
# key += crc.to_bytes(4, byteorder='big')
#
# crc = 0
# for i in range(int("1" * 10000)):
# crc = crc32(crc, "SECCON")
# key += crc.to_bytes(4, byteorder='big')
#
# crc = 0
# for i in range(int("1" * 10000)):
# crc = crc32(crc, "CTF!")
# key += crc.to_bytes(4, byteorder='big')
#
# flag = os.environ['FLAG']
# assert(len(flag) == 32)
#
# aes = AES.new(key, AES.MODE_ECB)
# encoded = aes.encrypt(flag)
# assert(encoded.hex() == '79833173d435b6c5d8aa08f790d6b0dc8c4ef525823d4ebdb0b4a8f2090ac81e')
# ```
# In order to decrypt the flag, we need to compute the standard $CRC32$ function on strings build by repeating a short chunk $11\ldots11$ times, where the number has 10000 **digits**! There is no hope to perform this number of operations (*at least* during the CTF). However, if we recall that $CRC32$ is an *affine* function, we can speed this up by using fast matrix exponentiation.
#
# *In fact, the simplest solution is to recall that the order of $CRC32$ is $2^{32}-1$, and for any fixed chunk the order will divide this number. Therefore, we can simply reduce `int("1" x 10000)` modulo $2^{32}-1$, and run the code directly. After replacing the `crc32` implementation with `zlib.crc32`, it takes just a couple of minutes to compute the answer!*
#
#
#
# ```py
# import os
# from Crypto.Cipher import AES
# from zlib import crc32
#
# key = b""
#
# crc = 0
# for i in range(int("1" * 10000) % (2**32-1)):
# crc = crc32(b"TSG", crc)
# assert(crc == 0xb09bc54f)
# key += crc.to_bytes(4, byteorder='big')
#
# crc = 0
# for i in range(int("1" * 10000) % (2**32-1)):
# crc = crc32(b"is", crc)
# key += crc.to_bytes(4, byteorder='big')
#
# crc = 0
# for i in range(int("1" * 10000) % (2**32-1)):
# crc = crc32(b"here", crc)
# key += crc.to_bytes(4, byteorder='big')
#
# crc = 0
# for i in range(int("1" * 10000) % (2**32-1)):
# crc = crc32(b"at", crc)
# key += crc.to_bytes(4, byteorder='big')
#
# crc = 0
# for i in range(int("1" * 10000) % (2**32-1)):
# crc = crc32(b"SECCON", crc)
# key += crc.to_bytes(4, byteorder='big')
#
# crc = 0
# for i in range(int("1" * 10000) % (2**32-1)):
# crc = crc32(b"CTF!", crc)
# key += crc.to_bytes(4, byteorder='big')
#
# flag = bytes.fromhex("79833173d435b6c5d8aa08f790d6b0dc8c4ef525823d4ebdb0b4a8f2090ac81e")
# aes = AES.new(key, AES.MODE_ECB)
# encoded = aes.decrypt(flag)
# print(repr(encoded))
# ```
#
#
#
#
#
# The idea is to construct the $32\times 32$ matrix $M$ and the 32-bit vector $c$ such that $CRC32_s(x, s) = M\times x \oplus c$ (where $s$ is the short string chunk used to build the string). This can be done in 33 black-box calls to $CRC32$: evaluating it at the zero vector and at all unit vectors.
#
# Finally, observe that the $e$-time repetition of $CRC32$ is expressed as
# $$
# CRC32^e(x, s) = M\times (\ldots M\times(M\times x \oplus c) \oplus c \ldots )\oplus c = M^e\times x \oplus (M^{e-1} \oplus M^{e-2} \oplus \ldots M \oplus I) \times c,
# $$
# where $I$ is the $32\times32$ identity matrix.
#
# In our case $x=0$ so we only need to evaluate the sum $M^{e-1} \oplus M^{e-2} \oplus \ldots M \oplus I$. This can be done using standard geometric series formula (luckily, $M\oplus I$ is invertible):
#
# $$
# (M^{e-1} \oplus M^{e-2} \oplus \ldots M \oplus I) = \frac{M^e\oplus I}{M\oplus I}.
# $$
#
#
# In[1]:
from sage.all import *
from Crypto.Cipher import AES
def crc32(crc, data):
crc = 0xFFFFFFFF ^^ crc
for c in data:
crc = crc ^^ ord(c)
for i in range(8):
crc = (crc >> 1) ^^ (0xEDB88320 * (crc & 1))
return 0xFFFFFFFF ^^ crc
def frombin(v):
return ZZ(tuple(map(int, v))[::-1], base=2)
def tobin(v, n):
return tuple(ZZ(v).digits(2, padto=n))[::-1]
E = int("1" * 10000) % (2**32-1)
ID = identity_matrix(GF(2), 32, 32)
def rep(s):
print("Computing for", repr(s))
# build the vector
c = crc32(0, s)
# build the matrix
m = matrix(GF(2), 32, 32)
for i in range(32):
v = [0] * 32
v[i] = 1
v = frombin(tuple(v))
v = crc32(v, s) ^^ c
v = tobin(v, 32)
m.set_column(i, v)
c = vector(GF(2), tobin(c, 32))
matsum = (m**E + ID) * ~(m + ID)
res = matsum * c
return int(frombin(res)).to_bytes(4, byteorder="big")
key = b""
key += rep("TSG")
key += rep("is")
key += rep("here")
key += rep("at")
key += rep("SECCON")
key += rep("CTF!")
flag = bytes.fromhex("79833173d435b6c5d8aa08f790d6b0dc8c4ef525823d4ebdb0b4a8f2090ac81e")
aes = AES.new(key, AES.MODE_ECB)
encoded = aes.decrypt(flag)
print(encoded)