Hackcon 2015: Pythonista
Category: pwn - Points: 25
Description: The program apparently once could print more than stories.
The task comes with two files:
The file message.txt
seems to contain a base64 string, but when decoding it, we receive another base64 string. Let’s check the other file to understand what is going on.
The file hide.pyo
is a compiled python object file. We can decompile it using the uncompyle2
package.
$ uncompyle2 --py -o . hide.pyo
+++ okay decompyling hide.pyo
# decompiled 1 files: 1 okay, 0 failed, 0 verify failed
So we can now check the content of hide.py
:
import os
import random
MESSAGE = os.environ.get('MESSAGE')
x = 18446744073709551616L
r = 29
y = [ not bool(x * r) for x in range(2) ]
algorithm = ['bz2', 'base64', 'uu', 'quopri', 'zlib'][int(y[0])]
final_strength = random.randint(1, x)
def _encode(message, rounds, strength, encoding):
for _ in xrange(strength):
for _ in xrange(rounds):
message = message.encode(encoding)
return message
encoded = _encode(MESSAGE, r, final_strength, algorithm)
print encoded
We add some comment in order to understand what is going on:
import os
import random
# get the MESSAGE from the environment variable MESSAGE
MESSAGE = os.environ.get('MESSAGE')
x = 18446744073709551616L
r = 29
y = [ not bool(x * r) for x in range(2) ] # = [1, 0]
algorithm = ['bz2', 'base64', 'uu', 'quopri', 'zlib'][int(y[0])]
# since y[0] = 1, algorithm = 'base64'
# we can not know which random number was chosen here at runtime
final_strength = random.randint(1, x)
# for a number of times rounds*strength,
# replace message with its encoding (nested encoding)
def _encode(message, rounds, strength, encoding):
for _ in xrange(strength):
for _ in xrange(rounds):
message = message.encode(encoding)
return message
# encode MESSAGE with 29 * final_strength passes of base64
encoded = _encode(MESSAGE, r, final_strength, algorithm)
print encoded
So we are dealing with multiple passes of base64. The easiest way is to read the file message.txt
and iterate a base64 decoding, until an exception is raised (meaning that the string is no longer in base64, so it is the original string).
We created this python script in order to do that:
def decode(message):
while True:
try:
message = message.decode('base64')
except:
return message
with open('message.txt') as m:
print decode(m.read())
The result is the following string: https://xkcd.com/936/
, which is:
So we tried the super-famous correcthorsebatterystaple
as the flag but it didn’t work. The other one was Tr0ub4dor&3
and it worked, so this is our flag.