UFOCTF 2017: decompiling Python in King Arthur's quest (PPC600)

Greetings habrayuzer! Recently, the annual UFO CTF 2017 Information Security Olympiad has ended. In this article, there will be a single task wright from the PPC section called “King Arthur” , for which you could get the maximum number of points - 600.
Let's start
In the description of the assignment was the following:
We got some weird email attachment from a knight fan. Help me figure out what the author had in mind.
And the file was available for download:
sword.py
#!/usr/bin/python
import serpent
"""
___
{ _ }
|/|
{___}
|_|
|/|
. |/| .
(\________|w|________/)
( ___________________ )
v | | | v
| | |
| | |
| | |PO0.HN7
| | | * TP0~~~<
z| | |ON4.YH1
OP6| | |
ON2| |
|PO3|
| |PY0
| | PY0
| | |PY0
z| | |PY0
PY0 | |z
PY0| | |
PY0| |
|PY0|
| |PH0
| | PY0
| | |PY0
z| | |PY0
PY0 | |z
PY0| | |
PY0| |
|PY0|
| |HN3
| | PY1
| | |PY0
z| | |PY0
PY0 | |z
PN3| | |
PT0| |
|PY0|
| |PN3
| | PY0
| | |PY0
z| | |TH3
PY0 | |z
PY0| | |
NY2| |
|PY0|
| |PY0
| | PN3
| | |PH0
z| | |PY0
PN4 | |z
PY0| | |
PY0| |
|NY2|
| |PT0
| | PY0
| | |YP3
z| | |PT0
PY0 | |z
PO4| | |
PY0| |
|PY0|
| |PT0
| | PN3
| | |PY0
z| | |PY0
HN2 | |z
YO1| | |
PO0| |
|PY0|
| |PY0
| | PY0
| | |TN2
z| | |YN3
YY7 | |z
YY7| | |
YY7| |
|YY7|
| |PO3
| | PY0
| | |PY0
z| | |PY0
PY0 | |z
PH0| | |
PY0| |
|PY0|
| |PY0
| | HO0
| | |PY0
z| | |PY0
PY0 | |z
PO0| | |
PY0| |
|PY0|
| |PY0
| | HN3
| | |YY4
z| | |PY0
PY0 | |z
PY0| | |
OP3| |
|PY0|
| |PY0
| | PN3
| | |PT0
z| | |PY0
PO4 | |z
PT0| | |
PY0| |
|NO3|
| |PT0
| | PY0
| | |NH3
z| | |PT0
PY0 | |z
PN3| | |
PY0| |
|PY0|
| |PN3
| | PH0
| | |PY0
z| | |PN3
PO0 | |z
PY0| | |
YP4| |
|PO0|
| |PY0
| | NP0
| | |PN3
z| | |PN0
PY0 | |z
TY3| | |
PO0| |
|PY0|
| |HO3
| | TO1
| | |PY0
z| | |PN3
YP0 | |z
PY0| | |
YH2| |
|YO2|
| |TN3
| | TN3
| | |PY0
z| | |NH3
PT0 | |z
PY0| | |
PN3| |
|PH0|
| |PY0
| | PN3
| | |PY0
z| | |PY0
PN3 | |z
PO0| | |
PY0| |
|YP4|
| |PO0
| | PY0
| | |NP0
z| | |PN3
YT0 | |z
PY0| | |
TY3| |
|PO0|
| |PY0
| | HO3
| | |HO3
z| | |PY0
PN3 | |z
YP0| | |
PY0| |
|YH2|
| |YO2
| | TN3
| | |TO2
z| | |PY0
NH3 | |z
PT0| | |
PY0| |
|PN3|
| |YH0
| | PY0
| | |TY0
z| | |PN3
YO0 | |z
PY0| | |
TY0| |
|PN3|
| |PO0
| | PY0
| | |TY0
z| | |YP4
PO0 | |z
PY0| | |
NP0| |
|PN3|
| |YN0
| | PY0
| | |TY3
z| | |PO0
PY0 | |z
HO3| | |
OT4| |
|PY0|
| |PN3
| | YP0
| | |PY0
z| | |YH2
YO2 | |z
TN3| | |
YN1| |
|PY0|
| |NH3
| | PT0
| | |PY0
z| | |PN3
TP0 | |z
PY0| | |
TY0| |
|PN3|
| |PH0
| | PY0
| | |TY0
z| | |PN3
PO0 | |z
PY0| | |
TY0| |
|YP4|
| |PO0
| | PY0
| | |NP0
z| | |PN3
TY0 | |z
PY0| | |
TY3| |
|PO0|
| |PY0
| | HO3
| | |TN2
z| | |PY0
PN3 | |z
YP0| | |
PY0| |
|YH2|
| |YO2
| | TN3
| | |YP0
z| | |PY0
PN3 | |z
TH0| | |
PY0| |
|YH2|
| |YO2
| | PN3
| | |PY0
z| | |PY0
HN2 | |z
YO1| | |
TO0| |
|PY0|
| |PY0
| | PY0
| | |TN2
z| | |HN3
TO0 | |z
PY0| | |
PY0| |
|PY0|
| |YP2
| | TN3
| | |OP3
z| | |YP3
HO3 | |z
PY1| | |
TH3| |
|YN3|
| |TN3
| | YP3
| | |NY1
z| | |PY1
TP0 | |z
YN3| | |
TN0| |
|PY0|
| |PY0
| | PY0
| | |YN3
z| | |PH0
PY0 | |z
PY0| | |
PY0| |
|OP3|
| |YH0
| | PY0
| | |PY0
z| | |PY0
ON2 | |z
TO2| | |
YH3| |
|NP1|
| |OT3
| | OT1
| | |OT1
z| | |HN3
YP0 | |z
PY0| | |
PY0| |
|PY0|
| |YT2
| | PT3
| | |YN3
z| | |TH3
PT1 | |z
OP3| | |
YO0| |
|PY0|
| |PY0
| | PY0
| | |NP3
z| | |PH2
YT3 | |z
PH2| | |
YH3| |
|NP1|
| |OT3
| | PT3
| | |YN3
z| | |HP0
PY0 | |z
PY0| | |
PY0| |
|YN3|
| |PP0
| | PY0
| | |PY0
z| | |PY0
OP3 | |z
YO0| | |
PY0| |
|PY0|
| |PY0
| | NP2
| | |HT1
z| | |HY2
ON2 | |z
HT3| | |
YH3| |
|YH3|
| |PH2
| | YN3
| | |PT0
z| | |PY0
PY0 | |z
PY0| | |
OP3| |
|YH0|
| |PY0
| | PY0
| | |PY0
z| | |HN1
OH2 | |z
OH1| | |
OP1| |
|TY3|
| |YO3
| | OH3
| | |HN3
z| | |YO0
PY0 | |z
PY0| | |
PY0| |
|HN2|
| |OY3
| | PO3
| | |PO3
z| | |YP3
HN3 | |z
HN3| | |
PT1| |
|YO1|
| |PH0
| | PY0
| | |PY0
z| | |PY0
OP3 | |z
YN0| | |
PY0| |
|PY0|
| |PY0
| | HO3
| | |PT3
z| | |OH3
YY2 | |z
YN3| | |
TN3| |
|HY3|
| |OY3
| | OP3
| | |OP3
z| | |PN0
PY0 | |z
PY0| | |
PY0| |
|TH3|
| |YN3
| | TN3
| | |YP3
z| | |YO1
PH0 | |z
PY0| | |
PY0| |
|PY0|
| |HO2
| | PN0
| | |PY0
z| | |PY0
PY0 | |z
HO2| | |
YP0| |
|PY0|
| |PY0
| | PY0
| | |YO1
z| | |PY0
PY0 | |z
PY0| | |
PY0| |
|YO1|
| |PY0
| | PY0
| | |PY0
z| | |PY0
HN3 | |z
HP2| | |
PY0| |
|PY0|
| |PY0
| | PN2
| | |NY1
z| | |NH2
PN2 | |z
HP3| | |
OH3| |
|TN3|
| |TH3
| | HP3
| | |PT3
z| | |PN3
HN3 | |z
NH2| | |
OY2| |
|YT2|
| |HP2
| | PO2
| | |OP2
z| | |YT2
NH2 | |z
OP2| | |
PT2| |
|HN2|
| |TY2
| | HN2
| | |NH2
z| | |HN3
YP3 | |z
HO3| | |
HY3| |
|YP3|
| |TN3
| | OP3
| | |NH2
z| | |HN3
HP3 | |z
OY3| | |
HO3| |
|PO3|
| |YP3
| | HN3
| | |NH2
z| | |HN2
YP3 | |z
HO3| | |
HY3| |
|YP3|
| |TN3
| | OP3
| | |TO1
z| | |TO3
PT3 | |z
HN3| | |
OP3| |
|YP3|
| |HO3
| | NH2
| | |HN2
z| | |YP3
HO3 | |z
HY3| | |
YP3| |
|TN3|
| |OP3
| | TO1
| | |TO3
z| | |PT3
HN3 | |z
OP3| | |
YP3| |
|HO3|
| |NH2
| | OP3
| | |YP3
z| | |HN3
OP3 | |z
TN1| | |
HY3| |
|NP3|
| |HN3
| | PN0
| | |PY0
z| | |PY0
PY0 | |z
OP3| | |
PT3| |
|HN3|
| |TY3
| | PN0
| | |PY0
z| | |PY0
PY0 | |z
HN3| | |
HY0| |
|PY0|
| |PY0
| | PY0
| | |PY0
z| | |PT0
TH0 | |z
PT0| | |
NP0| |
|PT0|
| |YO0
| | PT0
| | |NP0
z| | |PT0
YO0 | |z
PO0| | |
NH0| |
|PT0|
| |TH1
| | PH0
| | |YO1
z| | |PH0
PY0 | |z
PY0| | |
PY0| |
|OP3|
| |YH0
| | PY0
| | |PY0
z| | |PY0
HN3 | |z
YP3| | |
HO3| |
|HY3|
| |YP3
| | TN3
| | |OP3
z| | |OP3
PN0 | |z
PY0| | |
PY0| |
|PY0|
| |OP3
| | PT3
| | |HN3
z| | |TY3
YO1 | |z
PH0| | |
PY0| |
|PY0|
| |PY0
| | HO2
| | |YT0
z| | |PY0
PY0 | |z
PY0| | |
HO2| |
|YH0|
| |PY0
| | PY0
| | |PY0
z| | |YO1
PY0 | |z
PY0| | |
PY0| |
|PY0|
| |YO1
| | PY0
| | |PY0
z| | |PY0
PY0 | |z
HN3| | |
HP2| |
|PY0|
| |PY0
| | PY0
| | |PN2
z| | |NY1
NH2 | |z
PN2| | |
HP3| |
|OH3|
| |TN3
| | TH3
| | |HP3
z| | |PT3
PN3 | |z
HN3| | |
NH2| |
|OY2|
| |YT2
| | HP2
| | |PO2
z| | |OP2
YT2 | |z
NH2| | |
OP2| |
|PT2|
| |HN2
| | TY2
| | |HN2
z| | |NH2
HN3 | |z
YP3| | |
HO3| |
|HY3|
| |YP3
| | TN3
| | |OP3
z| | |NH2
HN3 | |z
HP3| | |
OY3| |
|HO3|
| |PO3
| | YP3
| | |HN3
z| | |NH2
HN2 | |z
YP3| | |
HO3| |
|HY3|
| |YP3
| | TN3
| | |OP3
z| | |TO1
TO3 | |z
PT3| | |
HN3| |
|OP3|
| |YP3
| | HO3
| | |NH2
z| | |HN2
YP3 | |z
HO3| | |
HY3| |
|YP3|
| |TN3
| | OP3
| | |TO1
z| | |TO3
PT3 | |z
HN3| | |
OP3| |
|YP3|
| |HO3
| | NH2
| | |OP3
z| | |YP3
HN3 | |z
OP3| | |
TN1| |
|HY3|
| |NP3
| | HN3
| | |YO0
z| | |PY0
PY0 | |z
PY0| | |
NH1| |
|TO3|
| |HP3
| | PN3
| | |OY3
z| | |TH3
YP3 | |z
PP1| | |
PT0| |
|PY0|
| |PY0
| | PY0
| | |HN3
z| | |PN0
PY0 | |z
PY0| | |
PY0| |
|TH0|
| |PO0
| | YN0
| | |TO0
z| | |zz
z | |
z | |
| | |
| | |
\|/
v
"""
The import serpent line immediately attracts attention . At the request of "python serpent sword" in Google, you can stumble upon a git repository , which judging by the description is just what we need.
Having studied the code serpent.py we find there a description of the principle of "turning" the code into a sword:
Given the hex digit 65:
65% 32 = 1
floor (65/32) = 2
so the alphabet symbol for 65 is the symbol at index 1: 'PT'.
65 is also the 3rd occurrence of a 32-modulus of 1 (with 1
being the first occurrence and 33 being the second of course.)
So the code for this hex symbol is PT2
But the most interesting is located below:
elif scriptType is _SERPENT:
pyc = _serpent_sword_alphabet_to_hex(_lex_hex(sys.argv[0]))
pycout = ".".join(sys.argv[0].split(".")[0:-1])+".pyc"
with open(pycout, "wb") as f:
for val in pyc:
f.write(chr(val))
This code reads the converted file and translates it back to .pyc . Rename the file sword.py to sword.ss.py and try to reverse it:
redihi@kali:KingArthur$ python sword.ss.py
RuntimeError: Bad magic number in .pyc file
By slightly changing the code so that the sword.ss.pyc file is not deleted, we find that the received file has a damaged header.
After a long search, we find an approximate description of the header of the .pyc file. But all attempts to change it to true did not succeed.
There, in the article, there is an example of compiling not a whole file, but a separate section of code. Probably in the same way, bypassing the header, it will restore the rest of the code. We are throwing a small script for these purposes:
unserpent.py
#!/usr/bin/python
import re
import sys
import marshal
alphabet = [
"PY", "PT", "PH", "PO", "PN", "YP", "YT", "YH", "YO", "YN", "TP", "TY", "TH", "TO", "TN",
"HP", "HY", "HT", "HO", "HN", "OP", "OY", "OT", "OH", "ON", "NP", "NY", "NT", "NH", "NO",
"PP", "YY"
]
def _serpent_sword_alphabet_to_hex(sentence):
"Convert the serpent alphabet string back to python bytecode"
return [alphabet.index(symbol[0:-1]) + int(symbol[-1]) * 32 for symbol in sentence]
def _lex_hex(infile):
"Extract the serpent string from the ss file"
with open(infile, 'r') as source:
regex = re.compile(r'[PYTHON][PYTHON][0-9]')
tokens = []
for line in source:
pos = 0
while(pos < len(line)):
match = regex.match(line, pos)
if match:
tokens.append(match.group(0))
pos += 1
return tokens
def convert_to_pyc(fname):
pyc = _serpent_sword_alphabet_to_hex(_lex_hex(fname))
pycout = ".".join(fname.split(".")[0:-1]) + ".pyc"
with open(pycout, "wb") as f:
for val in pyc:
f.write(chr(val))
f.close()
return pycout
fname = sys.argv[1]
pycout = convert_to_pyc(fname)
f = open(pycout, "rb").read()
for x in range(len(f)):
try:
code = marshal.loads(f[x:])
print('Offset found: %d' % x)
print('\targcount: %s' % code.co_argcount)
print('\tcode: %s' % code.co_code.encode('hex'))
print('\tconsts count: %d' % len(code.co_consts))
for item in code.co_consts:
print('\t\t%s: %r' % (type(item), item))
print('\tfilename: %s' % code.co_filename)
print('\tfirstlineno: %s' % code.co_firstlineno)
print('\tflags: %s' % code.co_flags)
print('\tname: %s' % code.co_name)
print('\tnlocals: %s' % code.co_nlocals)
print('\tstacksize: %s' % code.co_stacksize)
print('\tvarnames count: %d' % len(code.co_varnames))
for item in code.co_varnames:
print('\t\t%r' % item)
break
except ValueError:
continue
After starting, we get the output:
redihi@kali:KingArthur$ ./unserpent.py sword.ss.py
Offset found: 7
argcount: 0
code: 6401006400006c00005a00006402008400005a01006501008300000164000053
consts count: 3
: None
: -1
:
filename: D:\Downloads\UFOCTF\TASKS\serpent\sources\Serpent-master\Serpent-master\test.py
firstlineno: 1
flags: 0
name:
nlocals: 0
stacksize: 2
varnames count: 2
'serpent'
'task'
Как можно заметить, тут есть ещё 1 объект code, он то нам и нужен. Добавим ещё пару строк, для его декомпиляции:
import uncompyle6
...
uncompyle6.main.uncompyle(2.7, code.co_consts[2], sys.stdout)
Снова запускаем и получаем готовый скрипт:
# uncompyle6 version 2.9.10
# Python bytecode 2.7
# Decompiled from: Python 2.7.6 (default, Oct 26 2016, 20:30:19)
# [GCC 4.8.4]
# Embedded file name: D:\Downloads\UFOCTF\TASKS\serpent\sources\Serpent-master\Serpent-master\test.py
line = raw_input('Enter line: \n')
if line[:14:2] != 'XMg9v66':
print 'Fail!'
elif line[14::2] != 'yBfBg9va':
print 'Fail!'
elif line[-15:-30:-2] != 'Y1PXqggB':
print 'Fail!'
elif line[-1:-14:-2] != '3W74khw':
print 'Fail!'
else:
print 'Success!'
Осталось дело за малым, по полученному коду восстанавливаем флаг:
ufoctf{XBMggg9qvX6P61yYBwfhBkg497vWa3}
Задание пройдено! +600 к рейтингу!