Fleeting
3 minutos de lectura
Se nos proporciona este código en Python para cifrar la flag, y también el texto cifrado como un comentario:
print((f:=lambda s:s and[s[0]^s[-1],*f(s[::-1][1:])])([*open('flag','rb')][0]))
#[20, 37, 47, 47, 56, 52, 38, 46, 51, 56, 23, 58, 57, 50, 86, 95, 95, 103, 0]
Análisis de código fuente
Si analizamos el código, veremos que se define una función lambda nombrada f y la llama con el contenido de la flag en bytes:
$ echo -n 'ictf{test_flag}' > flag
$ python3 -q
>>> [*open('flag','rb')][0]
b'ictf{test_flag}'
Entonces, f es una función que devuelve s and [...]. En Python, esta expresión es equivalente a retornar el último objeto, ya que s es un valor truthy, verdadero (s es la flag en bytes):
>>> s = b'ictf{test_flag}'
>>> s and [1, 2, 3]
[1, 2, 3]
>>> s and []
[]
La lista [...] está formada por s[0] ^ s[-1] (que es el XOR entre el primer byte y el último byte de s). Luegp, tenemos *f(s[::-1][1:]), que es el resultado de f al usar como argumento s al revés y desde el segundo carácter:
>>> s
b'ictf{test_flag}'
>>> s[::-1][1:]
b'galf_tset{ftci'
De hecho, el operador * en esta situación se usa para poner los elementos de la lista que devuelve f en [s[0] ^ s[-1], ...].
Por tanto, f es una función que coge un objeto bytes llamado s y devuelve [s[0] ^ s[-1], ...], donde ... es el resultado de f(s[::-1][1:]). En resumen, es una función recursiva hasta que s esté vacío.
Ingeniería inversa
Esta es la lista de salida:
[20, 37, 47, 47, 56, 52, 38, 46, 51, 56, 23, 58, 57, 50, 86, 95, 95, 103, 0]
Sabemos que 20 es el resultado de s[0] ^ s[-1]. Como las flags de ImaginaryCTF son del tipo ictf{...}, podemos encontrar s[0] debido a las propiedades de la operación XOR:
>>> chr(ord('}') ^ 20)
'i'
Como era de esperar, el primer carácter es i.
Luego, 37 es de nuevo el XOR entre el primer byte y el último bytes, pero de s[::-1][1:], por lo que tenemos que s[-1] = b'i' y podemos encontrar s[0]:
>>> chr(ord('i') ^ 37)
'L'
Luego 47 es el XOR entre L y s[0] (que debería ser c):
>>> chr(ord('L') ^ 47)
'c'
Otro 47, que ahora es el XOR entre s[0] y c:
>>> chr(ord('c') ^ 47)
'L'
Y otro más, que debería ser t:
>>> chr(ord('L') ^ 56)
't'
Flag
Al final, podemos programar un algoritmo para recuperar la flag usando el procedimiento anterior:
$ python3 -q
>>> arr = [20, 37, 47, 47, 56, 52, 38, 46, 51, 56, 23, 58, 57, 50, 86, 95, 95, 103, 0]
>>> last_char = '}'
>>> flag = [0] * len(arr)
>>> for i, a in enumerate(arr):
... flag[i // 2 if i % 2 else -(i + 1) // 2] = last_char
... last_char = chr(a ^ ord(last_char))
...
>>> ''.join(flag)
'ictf{TW33TlenCH@LL}'