Snakecode
7 minutes to read
We are given a binary file called chall.pyc
, so we know it is a binary compiled using Python:
$ file chall.pyc
chall.pyc: python 2.7 byte-compiled
First of all, we can extract the Python source code using uncompyle6
. Since it is compiled with python2.7
, we can use a Docker container for that purpose:
$ 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
Ok, it is a bit obfuscated with marshal
… Let’s run the script and see what we have:
root@11d1f884672f:/ctf# python chall.py
++++++++++
+ +
+ +
+ $+
+ +
+ +
+ # +
+ +
+ +
++++++++++
We have a Snake game where the snake is the #
and the apples are the $
. If we play a bit, we will notice that the $
changes after some time to T
, then to B
, then to {
… But eventually the snake will be so long that we can’t continue playing:
++++++++++
+ +
+ +
+ T # +
+ # +
+ # +
+ # +
+ # +
+ +
++++++++++
++++++++++
+ ## +
+ # +
+ # +
+ # +
+ # +
+ # +
+ # +
+ ## B+
++++++++++
++++++++++
+### +
+# +
+# { +
+# +
+# +
+# +
+####### +
+ +
++++++++++
So the flag is there, in the apples. Looking again at the main function, we have some variables we can analyze:
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
These variables must be the parameters used by the game to play. To know which variable is which value (position, snake, position of the apple, score…), we can use a file to output all the variables on each movement:
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
Now we can play a bit and then check the log file:
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'>
We can guess that p
are the coordinates of each #
of the snake and pl
is the score we have (that equals the length of the snake). d
seems to be the direction of the snake (up, right, down, left), and s
is the number of movements from start. Then w
, c
and r
are just curses
objects to render the map.
There might be other ways to get the flag. I decided to trick the game to increase the score by one on every step and set the p
variable to a fix coordinate, so that I can see the apple changing from $
to a character of the flag without worrying about the snake. Then, I can just make notes of the characters and done:
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
Here is a GIF that shows the cheated game:
And the flag is HTB{SuP3r_S3CRt_Sn4k3c0d3}
.