Snakecode
7 minutos de lectura
Se nos proporciona un binario llamado chall.pyc
, por lo que sabemos que es un binario compilado mediante Python:
$ file chall.pyc
chall.pyc: python 2.7 byte-compiled
En primer lugar, podemos extraer el código fuente en Python usando uncompyle6
. Como está compilado con python2.7
, podemos emplear un contenedor de Docker para ello:
$ docker run -v "$PWD":/ctf -it python:2.7 bash
root@11d1f884672f:~# cd /ctf
root@11d1f884672f:/ctf# pip install uncompyle6
DEPRECATION: Python 2.7 reached the end of its life on January 1st, 2020. Please upgrade your Python as Python 2.7 is no longer maintained. A future version of pip will drop support for Python 2.7. More details about Python 2 support in pip, can be found at https://pip.pypa.io/en/latest/development/release-process/#python-2-support
Collecting uncompyle6
Downloading uncompyle6-3.8.0-py2-none-any.whl (317 kB)
|████████████████████████████████| 317 kB 3.6 MB/s
Collecting spark-parser<1.9.0,>=1.8.9
Downloading spark_parser-1.8.9-py2-none-any.whl (17 kB)
Collecting xdis<6.1.0,>=6.0.2
Downloading xdis-6.0.3-py2-none-any.whl (137 kB)
|████████████████████████████████| 137 kB 9.8 MB/s
Collecting click
Downloading click-7.1.2-py2.py3-none-any.whl (82 kB)
|████████████████████████████████| 82 kB 1.5 MB/s
Installing collected packages: click, spark-parser, xdis, uncompyle6
Successfully installed click-7.1.2 spark-parser-1.8.9 uncompyle6-3.8.0 xdis-6.0.3
WARNING: You are using pip version 20.0.2; however, version 20.3.4 is available.
You should consider upgrading via the '/usr/local/bin/python -m pip install --upgrade pip' command.
root@11d1f884672f:/ctf# uncompyle6 chall.pyc > chall.py
root@11d1f884672f:/ctf# cat chall.py
# uncompyle6 version 3.8.0
# Python bytecode 2.7 (62211)
# Decompiled from: Python 2.7.18 (default, Apr 21 2020, 09:53:40)
# [GCC 8.3.0]
# Warning: this version of Python has problems handling the Python 3 byte type in constants properly.
# Embedded file name: ./snake_obf.py
# Compiled at: 2022-01-17 22:16:46
import marshal, types, time
ll = types.FunctionType(marshal.loads(('YwEAAAABAAAABQAAAEMAAABzNAAAAHQAAGoBAHQCAGoDAHQEAGQBAIMBAGoFAHwAAGoGAGQCAIMB\nAIMBAIMBAHQHAIMAAIMCAFMoAwAAAE50BAAAAHpsaWJ0BgAAAGJhc2U2NCgIAAAAdAUAAAB0eXBl\nc3QMAAAARnVuY3Rpb25UeXBldAcAAABtYXJzaGFsdAUAAABsb2Fkc3QKAAAAX19pbXBvcnRfX3QK\nAAAAZGVjb21wcmVzc3QGAAAAZGVjb2RldAcAAABnbG9iYWxzKAEAAAB0AQAAAHMoAAAAACgAAAAA\ncwcAAAA8c3RkaW4+dAoAAABsb2FkTGFtYmRhAQAAAHQAAAAA\n').decode('base64')), globals())
i0 = ll('eJxLZoACJiB2BuJiLiBRwsCQwsjQzMgQrAES9ythA5JFiXkp+bkajCB5kKL4+Mzcgvyikvh4DZAB\nCKKYHUjYFJekZObZlXCA2DmJuUkpiXaMEKMZGAC+nBJh\n')
i1 = ll('eJxLZoACJiB2BuJiLiBRwsCQwsjQzMgQrAES9ythA5LJpUXFqcUajCB5kKL4+Mzcgvyikvh4DZAB\nCKKYHUjYFJekZObZlXCA2DmJuUkpiXaMEKMZGADEORJ1\n')
f0 = ll('eJxLZmRgYABhJiB2BuJiXiBRw8CQxcCQwsjQzMgQrAGS8ssEEgwaIJUl7CAiMzc1v7QEIsAMJMoz\n8zTASkBEMUiJTXFJSmaeXQkHiJ2TmJuUkmgHVg5SAQBjWRD5\n')
f1 = ll('eJxLZmRgYIBhZyAu5gISNQwMWQwMzQwMwRogcT8wWcIKJNJTS5IzIFxmIFGemacBpBjARDE7kLAp\nLknJzLMr4QCxcxJzk1IS7cDKQSoAvuUPJw==\n')
f2 = ll('eJx1kL1uwkAQhOfOBsxPQZUmL+DOEnWUBghEQQbFIESVglUkY5ECX+lHoMz7Jrt7HCgSOWlGO/rm\n1tbtIwBBY1b9zdYYkEFlcRqiAQoWxaginDJhjcUBijNQy+O24jxgfzsHdTxOFB8DtoqPoK7HPcXn\ngCPFZ1BfcUGsdMA/lpc/fEqeUBq21Mp0L0rv/3grX/f5aELlbryVYzbXZnub7j42K5dcxslym7vu\nJby/zubrK1pMX9apPLOTraReqe9T3SlWd9ieakfl17OTb36OpFE/CDQDE5vHv7K/FKBNmA==\n')
f3 = ll('eJxVj00KAjEMhV+b8Q9040IZT9C9WxHEvRvBC1iFUhhk2sUIIwgexLWn1KQzI9qSl/DlhaZHDSDj\nII4tR3ix1IBVyK1GXitImt/0l1JDSSih1rAZfIZyI4x9BRIkeKA8SLeF1Dl9clIHG+c9OakdZ35O\nT/o+yiciZI2Hgvpt702Pt925Nx/HFZwSGbIYqaL87FS5aKSIgi5JbZR/F1WTrkZmk4QByypE64p1\nap6X4g8LaaoZ3zFGfzFVE/UBTuovhA==\n')
f4 = ll('eJw1zDsKgEAMBNCJilb2drZ7AEuxsbfxBOIHFFkWNqWdF3eyYJEXkgxZcwB/jazYkkdwUeAVCAcV\nW3F4MjTt7ISZyWVUS7KEsPtN7cW9e2ddLeKTIXk7gkSsSB91O/2g9uToLBELO0otH2W6Ez8=\n')
f5 = ll('eJxdjr0OwjAMhM9J+as6M7HTF0AsiKV7F54ACJUKVaiSjOnEi2MbISQGf4rtu3OuMwBSBVfDFQdG\nBhzwMAgNMsER1s58+wJ3Hlm4Ai/z33YGE+A1IrNljnBBtiLYT1ZSf2sr6lMt19u+ZPYQkGDJqA0j\nycfap7+lBT/C2bveJ/UkEQ7KqByTGMbPKNQSpojiPMTEzqNKup2aKlnShramopJW5g2ipyUM\n')
f6 = ll('eJxdjTEOglAQRB98iMbEKxhLbkBjaLSwsrHWBEUJCRKULTT5VFzc3W9nMS+zk93ZqwNS1UK1VQ17\nRQ0CVcQUsTvljO4vWjEmSIRP8A4PXn3MlHKOea4DlxyzWMsOjXUHK/bpVXb1TWy855kF2gN9SPo2\nDD9+At8Zdm4YZorNIFXTFTI335aPS1UWtie28QV3xx4p\n')
f7 = ll('eJxtjz8LwjAQxV/S1mrRxcnZKat/qyAuOrv0E4ilIJRS2hsUCg7OfmcvubZTIe/97nKPcHkEADpd\nWPWPjYCGj0Kj0fjIfHwVqiWIbzxbJ6SHEleQ1yf8ocQHFLSJqgKN+nTYVUUEGndNCiRG8UY3M7F7\nabb7TrAS7AVrQSw4CDaCreBo7CfJPvdy/nZeummZuyY3bHBWh2ynmtJncXaRLLaJem6HaqGiVlMV\n6Zn+Azn/L1k=\n')
f8 = ll('eJwljr0KAkEMhCf3o2hrIb7BlWIhFiKC1jYWViKHe+qKnHob0GKt7sVNcsV8ZDeTSc45gJ5oINqI\nwkkQgTvQAvRdgwmO0BK2xxl+uTUTxBwugUtxT8EZIiHKZ4o21dZE7FLRe4yD+nMLixlchvG+0KU7\nPxR6EVjhSVDoKazt86MqG6uasr5WrI3SucCNbJPEp685keIy576aqktThVs3r0kf48s8r4c9Ogaj\nL3SnIej8MrDz9aqLXJhPzwMNaURT4R/aUC0X\n')
a1 = ll('eJxLZmRgYIBhZyAuZgESKYwMwRpMQIZfCUhcWwNIMGiAmGB+DoQPIorZgYRNcUlKZp5dCQeInZOY\nm5SSaAdWDFIBALI0C1U=\n')
a2 = ll('eJxLZmRgYIBhZyAuZgESKYwMwRpMQIZfCUhcWQNIMGiAmGB+DoQPIorZgYRNcUlKZp5dCQeInZOY\nm5SSaAdWDFIBALBMC00=\n')
a3 = ll('eJw10EtLw0AUBeAzTWLqo74bML8gSyFdiotm40rEZF+kRyVtCGKmqzar/nHvHBDmfty5c+fBrB2A\niUVuUVkMG4MOnIARGIMJeAKm4BQ8Bc9UsfwcvABn/5VL8Aq81tINeAveKb/Hd47R4WDDTp5j7hEm\nR4fsoS4yu+7Vh1e8yEYu5V7WciffZCl/5UpW8l162cuF3Mq1fJSUY5uYhTZFRvfZF+EvfOCnU89X\ngdATGFLjafBs+2e1fJShY4jDomvcH1q4K9U=\n')
a4 = ll('eJxLZmRgYIBhZyAuZgESKYwMwRpMQIZfCUhcRQNIMGiAmGB+DoQPIorZgYRNcUlKZp5dCQeInZOY\nm5SSaAdWDFIBALCJC04=\n')
a5 = ll('eJxNzTELwjAQBeCXS4r6TzKJP6DUgruLq0s1S7BKIRkqJP/dd3Hp8D4ex3H3NAA6xjEXJo2kAHeH\nalAF1aI6FINg8BIsZxTZdM5lM2/95i2PXCNBPBCvzeubLOR4yvp2bX6bS3P+LyppR/qUQ/wMea99\nnt6PMA26l/SKxQ/XGxky\n')
a6 = ll('eJwlzLsKwkAQheF/L0afw2qr4AOENOnT2NpEgyDGENgtFHbfPTNrcT6G4cw8DHCQeMkgiWchw81T\nDMVSHMWTDdnytGTHu+Ea9G4MAkHPkxXaS9L1t/qrbtXlX1TiUehiml9rn046L9PnPk+99qJ+cewN\nxxM9\n')
a7 = ll('eJwlzLEKwjAQxvF/rhF9jk6Zig8gXdy7uLq0FqFYRUiGFpJ39y4O34/j+O4eDjhovOaqia2S4e4p\njiKUhuLJjiw8hex5Cbdgd0NQCHaeROnOydZbda9+q+u/aMSjcolpXj59Otm8ju9pHnvrRfvS8AMM\nqhM6\n')
a8 = ll('eJxLZmRgYABhJiB2BuJiPiBRw8CQwsgglsLEkM3EEKzBDBTyy2QFkplAzKABJkCaSkBEjgZcsJgd\nSNgUl6Rk5tmVcIDYOYm5SSmJdmDFIBUAVDAM/Q==\n')
a9 = ll('eJxLZmRgYIBhZyAuZgESKYwMwRpMQIZfCUhcQQNIMGiAmGB+DoQPIorZgYRNcUlKZp5dCQeInZOY\nm5SSaAdWDFIBAK+VC0o=\n')
m0 = ll('eJw1jTELwjAUhC9Jq/0VzhldBAfr4u7i6mYpFFSKCXRJp/7x3rsi5L5Avnsvrx0AS8PcmNQSGSg8\nDsWjBJQKS42nxwzMQSog09b/gsrs9AGP6LjhHr3tMfSn7TpH+yebfYtJHGXH7eknTpGAkPbEJeVu\n+F5V/Bw1Wpl0B7cCYGsZOw==\n')
m1 = ll('eJw1zUEKAjEMBdCfdMQreIRuRwU3Mhv3bjzCDAOCitCAm7rqxU1+cZGX0v408wbAvy5e5eQYUAUm\nqAnNHdASvsJLhSVUBpryoPG6Km5ZfPaah/hBnXXf29jbsbdDjl0W2Tdd6IN+6JwdkLJ1zsWW+2vi\n/HOMRIklkJ38AF2QGOk=\n')
m2 = ll('eJxNjj8LAjEMxV96fz+Fk0NHdT5c3F1cD5c7BEHlsAWXdrov7kuKICS/0LyXpFMP4JcnZrgSEUgM\nQXJIDVKLtcHokAWZKvsVUm0eGjr1rC3GCplBW/03Xpy2hM5bj4sXnjh7p4cUz30pO6+fiKouxtn6\ny8MehcH4MU7GtydgCB0xhDjfX8ey8mAzrYqyka18AW5IIKw=\n')
def snake(w):
r = i0()
c = i1()
f0(w)
d = (0, 1)
p = [(5, 5)]
pl = 1
s = 0
l = None
while 1:
p, d, pl, l, s, w, c, r = m2(p, d, pl, l, s, w, c, r)
time.sleep(0.4)
return
i1().wrapper(snake)
# okay decompiling chall.pyc
Vale, está un poco ofuscado con marshal
… Vamos a ejecutar el script a ver qué tenemos:
root@11d1f884672f:/ctf# python chall.py
++++++++++
+ +
+ +
+ $+
+ +
+ +
+ # +
+ +
+ +
++++++++++
Se trata de un juego del Snake, donde la serpiente es el #
y las manzanas son los $
. Si jugamos un rato, vemos que el $
cambia después de algún tiempo a T
, luego a B
, luego a {
… Pero eventualmente la serpiente será tan larga que no podremos seguir jugando:
++++++++++
+ +
+ +
+ T # +
+ # +
+ # +
+ # +
+ # +
+ +
++++++++++
++++++++++
+ ## +
+ # +
+ # +
+ # +
+ # +
+ # +
+ # +
+ ## B+
++++++++++
++++++++++
+### +
+# +
+# { +
+# +
+# +
+# +
+####### +
+ +
++++++++++
Pero la flag está ahí, en las manzanas. Mirando de nuevo a la función principal, vemos que hay algunas variables que podemos analizar:
def snake(w):
r = i0()
c = i1()
f0(w)
d = (0, 1)
p = [(5, 5)]
pl = 1
s = 0
l = None
while 1:
p, d, pl, l, s, w, c, r = m2(p, d, pl, l, s, w, c, r)
time.sleep(0.4)
return
Estas variables tienen que ser parámetros usados por el juego para poder jugar. Para saber qué variable es qué valor (posición, serpiente, posición de la manzana, puntuación…), podemos utilizar un archivo para guardar los valores de las variables en cada movimiento:
def snake(w):
with open('log.txt', 'a') as fd:
r = i0()
c = i1()
f0(w)
d = (0, 1)
p = [(5, 5)]
pl = 1
s = 0
l = None
while 1:
fd.write('p = %s; d = %s, pl = %s; l = %s; s = %s; w = %s; c = %s; r = %s\n' % (p, d, pl, l, s, w, c, r))
p, d, pl, l, s, w, c, r = m2(p, d, pl, l, s, w, c, r)
time.sleep(0.4)
return
Ahora podemos jugar un poco y mirar el archivo de log:
root@11d1f884672f:/ctf# tail log.txt
p = [(5, 4), (4, 4), (3, 4), (2, 4), (1, 4)]; d = (-1, 0), pl = 5; l = None; s = 37; w = <_curses.curses window object at 0xffff8d53f5d0>; c = <module 'curses' from '/usr/local/lib/python2.7/curses/__init__.pyc'>; r = <module 'random' from '/usr/local/lib/python2.7/random.pyc'>
p = [(4, 4), (3, 4), (2, 4), (1, 4), (8, 4)]; d = (-1, 0), pl = 5; l = None; s = 38; w = <_curses.curses window object at 0xffff8d53f5d0>; c = <module 'curses' from '/usr/local/lib/python2.7/curses/__init__.pyc'>; r = <module 'random' from '/usr/local/lib/python2.7/random.pyc'>
p = [(3, 4), (2, 4), (1, 4), (8, 4), (7, 4)]; d = (-1, 0), pl = 5; l = None; s = 39; w = <_curses.curses window object at 0xffff8d53f5d0>; c = <module 'curses' from '/usr/local/lib/python2.7/curses/__init__.pyc'>; r = <module 'random' from '/usr/local/lib/python2.7/random.pyc'>
p = [(2, 4), (1, 4), (8, 4), (7, 4), (6, 4)]; d = (-1, 0), pl = 5; l = None; s = 40; w = <_curses.curses window object at 0xffff8d53f5d0>; c = <module 'curses' from '/usr/local/lib/python2.7/curses/__init__.pyc'>; r = <module 'random' from '/usr/local/lib/python2.7/random.pyc'>
p = [(1, 4), (8, 4), (7, 4), (6, 4), (5, 4)]; d = (-1, 0), pl = 5; l = (6, 5); s = 41; w = <_curses.curses window object at 0xffff8d53f5d0>; c = <module 'curses' from '/usr/local/lib/python2.7/curses/__init__.pyc'>; r = <module 'random' from '/usr/local/lib/python2.7/random.pyc'>
p = [(8, 4), (7, 4), (6, 4), (5, 4), (5, 5)]; d = (0, 1), pl = 5; l = (6, 5); s = 42; w = <_curses.curses window object at 0xffff8d53f5d0>; c = <module 'curses' from '/usr/local/lib/python2.7/curses/__init__.pyc'>; r = <module 'random' from '/usr/local/lib/python2.7/random.pyc'>
p = [(7, 4), (6, 4), (5, 4), (5, 5), (6, 5)]; d = (1, 0), pl = 6; l = None; s = 43; w = <_curses.curses window object at 0xffff8d53f5d0>; c = <module 'curses' from '/usr/local/lib/python2.7/curses/__init__.pyc'>; r = <module 'random' from '/usr/local/lib/python2.7/random.pyc'>
p = [(7, 4), (6, 4), (5, 4), (5, 5), (6, 5), (7, 5)]; d = (1, 0), pl = 6; l = None; s = 44; w = <_curses.curses window object at 0xffff8d53f5d0>; c = <module 'curses' from '/usr/local/lib/python2.7/curses/__init__.pyc'>; r = <module 'random' from '/usr/local/lib/python2.7/random.pyc'>
p = [(6, 4), (5, 4), (5, 5), (6, 5), (7, 5), (8, 5)]; d = (1, 0), pl = 6; l = None; s = 45; w = <_curses.curses window object at 0xffff8d53f5d0>; c = <module 'curses' from '/usr/local/lib/python2.7/curses/__init__.pyc'>; r = <module 'random' from '/usr/local/lib/python2.7/random.pyc'>
p = [(5, 4), (5, 5), (6, 5), (7, 5), (8, 5), (1, 5)]; d = (1, 0), pl = 6; l = None; s = 46; w = <_curses.curses window object at 0xffff8d53f5d0>; c = <module 'curses' from '/usr/local/lib/python2.7/curses/__init__.pyc'>; r = <module 'random' from '/usr/local/lib/python2.7/random.pyc'>
Podemos deducir que p
son las coordenadas de cada #
de la serpiente y pl
es la puntuación que tenemos (que equivale a la longitud de la serpiente). d
parece la dirección de la serpiente (arriba, derecha, abajo, izquierda), y s
es el número de movimientos desde el inicio. Luego w
, c
y r
son objetos curses
para renderizar el mapa.
Es posible que haya más formas de obtener la flag. Yo decidí trucar el juego para que aumente la puntuación en uno en cada movimiento y fijar la variable p
a una coordenada fija, de manera que pueda ver la manzana cambiando de $
a un carácter de la flag sin preocuparme de la serpiente. Luego, solo hay que tomar nota de los caracteres y listo:
def snake(w):
r = i0()
c = i1()
f0(w)
d = (0, 1)
p = [(5, 5)]
pl = 1
s = 0
l = None
while 1:
p, d, pl, l, s, w, c, r = m2(p, d, pl, l, s, w, c, r)
pl += 1
p = [(5, 5)]
time.sleep(0.4)
return
Este es un GIF que muestra el juego trucado:
Y la flag es HTB{SuP3r_S3CRt_Sn4k3c0d3}
.