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}'