Rating:
The challenge text reads:
"I heard that add-rotate-xor are good operations for a cipher so I tried to make my own..."
And we can download the following python script:
```python
class baby_arx():
def __init__(self, key):
assert len(key) == 64
self.state = list(key)
def b(self):
b1 = self.state[0]
b2 = self.state[1]
b1 = (b1 ^ ((b1 << 1) | (b1 & 1))) & 0xff
b2 = (b2 ^ ((b2 >> 5) | (b2 << 3))) & 0xff
b = (b1 + b2) % 256
self.state = self.state[1:] + [b]
return b
def stream(self, n):
return bytes([self.b() for _ in range(n)])
FLAG = open('./flag.txt', 'rb').read().strip()
cipher = baby_arx(FLAG)
out = cipher.stream(64).hex()
print(out)
# cb57ba706aae5f275d6d8941b7c7706fe261b7c74d3384390b691c3d982941ac4931c6a4394a1a7b7a336bc3662fd0edab3ff8b31b96d112a026f93fff07e61b
```
We assume that the hex dump in the last line comment is the 'encrypted' flag.
We know that all flags usually start with 'DUCTF{'.
Modifying the script to
```python
FLAG = 'DUCTF{}'
```
produces the output
```
cb57ba706a21
```
We clearly see that the first 5 hex tuples correspond to the hex dump in the comment. These first 5 tuples consist of the encrypted string 'DUCT'. We can assume that adding the correct suffix to 'DUCTF{' will provide the correct hex tuple for '{' at the end of our output.
The idea is to add letters to the plaintext and encrypt it until we find the matching hex tuple (e.g. brute-forcing):
```
DUCTF{a
DUCTF{b
DUCTF{c
...
DUCTF{|
DUCTF{}
DUCTF{~
```
To compare the produced hex tuple to the known cipher text we first split up the cipher text into tuples
```python
cipher_flag = 'cb57ba706aae5f275d6d8941b7c7706fe261b7c74d3384390b691c3d982941ac4931c6a4394a1a7b7a336bc3662fd0edab3ff8b31b96d112a026f93fff07e61b'
cipher_flag_byte_pairs = [''.join([cipher_flag[i], cipher_flag[i+1]]) for i in range(0, 128, 2)]
```
Since we know the prefix of our flag we can already provide a partial solution.
```python
flag_solution = 'DUCTF{'
```
Next we need a function to add letters to our partial solution:
```python
import string
def add_letters(flag):
return [flag+c for c in string.printable]
```
We also need a function to encrypt the extended flag and return the last hex tuple we want to compare:
```python
def get_encrypted_tuple(flag_solution):
cipher = baby_arx(bytes(flag_solution, 'utf-8'))
out = cipher.stream(len(flag_solution)).hex()
return out[-4:-2]
```
Another function makes use of these two functions to extend the current flag, encrypt all extended flags and return the next correct char to append to our flag solution:
```python
def get_next_char(flag, cipher_flag_bytes):
cipher_prefix = cipher_flag_bytes[:len(flag)]
for extended_flag in add_letters(flag):
last_correct_tuple = get_encrypted_tuple(extended_flag)
if last_correct_tuple == cipher_prefix[-1]:
return extended_flag[-1]
```
The last function creates a partial solution until the length of the encrypted solution is equal to the length of the cipher text:
```python
def print_solution(partial_solution):
remaining_chars = len(cipher_flag_byte_pairs[len(partial_solution):])
while remaining_chars > 0:
partial_solution += get_next_char(partial_solution, cipher_flag_byte_pairs)
remaining_chars = len(cipher_flag_byte_pairs[len(partial_solution):])
print(partial_solution)
```
Finally we can call this function with our flag_solution as inital input.
The whole script looks like this:
```python
class baby_arx():
def __init__(self, key):
self.state = list(key)
def b(self):
b1 = self.state[0]
b2 = self.state[1]
# b1 = (b1 XOR ((b1 LSHIFT 1) OR (b1 AND 1))) AND 0xff
b1 = (b1 ^ ((b1 << 1) | (b1 & 1))) & 0xff
# b2 = (b2 XOR ((b2 RSHIFT 5) | (b2 LSHIFT 3))) AND 0xff
b2 = (b2 ^ ((b2 >> 5) | (b2 << 3))) & 0xff
# b = (b1 ADD b2) MOD 256
b = (b1 + b2) % 256
self.state = self.state[1:] + [b]
return b
def stream(self, n):
return bytes([self.b() for _ in range(n)])
# FLAG = open('./flag.txt', 'rb').read().strip()
# cipher = baby_arx(bytes(FLAG, 'utf-8'))
# out = cipher.stream(len(FLAG)).hex()
# print(out)
import string
cipher_flag = 'cb57ba706aae5f275d6d8941b7c7706fe261b7c74d3384390b691c3d982941ac4931c6a4394a1a7b7a336bc3662fd0edab3ff8b31b96d112a026f93fff07e61b'
cipher_flag_byte_pairs = [''.join([cipher_flag[i], cipher_flag[i+1]]) for i in range(0, 128, 2)]
flag_solution = 'DUCTF{'
def add_letters(flag):
return [flag+c for c in string.printable]
def get_encrypted_tuple(flag_solution):
cipher = baby_arx(bytes(flag_solution, 'utf-8'))
out = cipher.stream(len(flag_solution)).hex()
return out[-4:-2]
def get_next_char(flag, cipher_flag_bytes):
cipher_prefix = cipher_flag_bytes[:len(flag)]
for extended_flag in add_letters(flag):
last_correct_tuple = get_encrypted_tuple(extended_flag)
if last_correct_tuple == cipher_prefix[-1]:
return extended_flag[-1]
def print_solution(partial_solution):
remaining_chars = len(cipher_flag_byte_pairs[len(partial_solution):])
while remaining_chars > 0:
partial_solution += get_next_char(partial_solution, cipher_flag_byte_pairs)
remaining_chars = len(cipher_flag_byte_pairs[len(partial_solution):])
print(partial_solution)
print_solution(flag_solution)
```
Executing this produces the output:
```
DUCTF{i
DUCTF{i_
DUCTF{i_d
DUCTF{i_d0
DUCTF{i_d0n
...
DUCTF{i_d0nt_th1nk_th4ts_h0w_1t_w0rks_actu4lly_92f45fb961ecf42
DUCTF{i_d0nt_th1nk_th4ts_h0w_1t_w0rks_actu4lly_92f45fb961ecf420
DUCTF{i_d0nt_th1nk_th4ts_h0w_1t_w0rks_actu4lly_92f45fb961ecf420}
```