Let's dance. nc choreography.chal.pwni.ng 1337
Solved after the CTF was ended.
We have block cipher: encrypt1 and encrypt2. key and block size is 4 bytes. Let key = [k0, k1, k2, k3]. We can perform chosen ciphertext attack with QUERIES = 500 queries for each encryption. Our goal is to recover the key.
The block cipher has the structure of generalized Feistel structure, with alternating key setting, having ROUNDS = 2 ** 22 + 2 performed per each encryption. So large round numbers. The only attack I knew to apply in this tremendously large round setting was Slide Attack. The complexity of slide attack is independent of the number of rounds, and it exploits the fact of weak key structures in iterated-round block ciphers.
We can implement the decryption function decrypt1 of encrypt1 using encrypt2. So, we basically have a plaintext/ciphertext oracle with QUERIES = 500 queries. Proof:
def encrypt1(k, plaintext):
a,b,c,d = plaintext
for i in range(ROUNDS):
a ^= sbox[b ^ k[(2*i)&3]]
c ^= sbox[d ^ k[(2*i+1)&3]]
a,b,c,d = b,c,d,a
return bytes([a,b,c,d])
def decrypt1(k, ciphertext):
c,d,a,b = ciphertext
c,d,a,b = encrypt2(k, bytes([a,b,c,d]))
return bytes([a,b,c,d])
def encrypt2(k, plaintext):
a,b,c,d = plaintext
for i in range(ROUNDS)[::-1]:
b,c,d,a = a,b,c,d
c ^= sbox[d ^ k[(2*i)&3]]
a ^= sbox[b ^ k[(2*i+1)&3]]
return bytes([a,b,c,d])
key = os.urandom(4)
m1 = os.urandom(4)
c1 = encrypt1(key, m1)
m1_ = decrypt1(key, c1)
assert m1 == m1_Let me visualize each round of encrypt1. a, b, c and d correspond to each input byte.
Each round can be decomposed to two separate sub rounds r1 and r2, which have exactly the same structure but different keys. Also, we notice that for each sub round, half of the input is preserved(b and d does not change). For each sub round r1 and r2, we use different keys: (k0 and k1) or (k2 and k3). So overall structure is that, running 2 * ROUNDS sub rounds, with alternating key pairs.
We cannot directly apply vanilla slide attack in this setting and need something fancier.
We need to consider two properties:
- Half of input data is preserved(
bandd), having structure of generalized Feistel structure. - Each sub round is alternating, using two different key pairs.
Advanced Slide Attack was suitable for the problem setting. We combine two attacks: Complementation Slide and Twisted Slide Attack. Section 3.3 Figure 4 describes the combination between two attacks.
To use the generalized Feistel structure, we first study how plain Feistel structure helps to reduce the attack complexity. See Section 3.2 fifth paragraph. The paragraph assumes that the setting is plain feistel structure with alternating key settings. Paragraph:
Moreover, there is a chosen-plaintext/ciphertext variant that allows us to reduce the number of texts down to
2 ** 17with the use of structures. We generate a pool of2 ** 16plaintexts of the form(Li, R)and obtain their encryptions. Also, we build a pool of2 ** 16ciphertexts of the form(M0j, N0)and decrypt each of them, where the valueN0 = Ris fixed throughout the attack. This is expected to give one slid pair, and then the analysis proceeds as before.
We now extend the attack for generalized Feistel structures. We have chosen-plaintext/ciphertext variant that allows us to reduce the number of texts down to 2 ** 9 with the use of structure. Generate a pool of 2 ** 8 < 500 plaintext of the form (a', b, c', d) and obtain their encryptions, Also, we build a pool of 2 ** 8 < 500 ciphertexts of the form (a', b, c', d) and decrypt each of them. This is expected to give one slid pair. and by using up to 500 queries, it is likely to obtain several slid pairs.
- Generate
2 * QUERIES = 1000data of the form[a, b, c, d].banddwill be constants, to use the fact that they are preserved after sub rounds. All data must be unique. - Send
QUERIES = 500plaintext obtained from 1, and get ciphertext using encryption oracle. - Send
QUERIES = 500ciphertext obtained from 1, and get plaintext using decryption oracle. - Find candidate slide pairs.
- Let
(A, B),(C, D)be two ciphertext/plaintext pairs. We can detect candidate slid pairs ifA[1] == D[1],A[3] == D[3],B[1] == C[1],B[3] == C[3].
- Let
- Find correct slide pairs by recovering
k0,k1using slid pair(A, D),(B, C). Letdeltabe the difference for Complementation Slide.deltais single byte so bruteforcable. Relations:D[0] = sbox[k0 ^ A[1]] ^ A[0] ^ deltaD[2] = sbox[k1 ^ A[3]] ^ A[2] ^ deltaB[0] = sbox[k0 ^ C[1]] ^ C[0] ^ deltaB[2] = sbox[k1 ^ C[3]] ^ C[2] ^ delta
- Recover
k2andk3usingdelta.k2 ^ k3 == delta, so bruteforcable, having a search space of single byte.k2 ^ k3 == deltabecause we first make an odd sub roundr1always line up. By this effect, the even sub roundr2have the constant differencedeltato swap effect ofk2andk3.
Let n bits be block/key size. Time complexity: O(2 ** (n // 4)), Space complexity: O(2 ** (n // 4)). I get flag(after the CTF was ended :C)
PCTF{square_dancin'_the_night_away~}
Problem src: cipher.py, pow.py, run.sh
exploit driver code: solve.py
