Errata
Download
1 / 62

errata - PowerPoint PPT Presentation


  • 121 Views
  • Uploaded on

errata. EuSecWest website references IOActive No longer IOActive employee Independent contractor with IOActive Research is the work of myself and does not relate to IOActive No one at IOActive doing similar research. What?!. Interpreters serve as abstraction layer

loader
I am the owner, or an agent authorized to act on behalf of the owner, of the copyrighted work described.
capcha
Download Presentation

PowerPoint Slideshow about 'errata' - albert


An Image/Link below is provided (as is) to download presentation

Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author.While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server.


- - - - - - - - - - - - - - - - - - - - - - - - - - E N D - - - - - - - - - - - - - - - - - - - - - - - - - -
Presentation Transcript
Errata
errata

  • EuSecWest website references IOActive

  • No longer IOActive employee

    • Independent contractor with IOActive

  • Research is the work of myself and does not relate to IOActive

    • No one at IOActive doing similar research


Errata
What?!

  • Interpreters serve as abstraction layer

  • Conceptually similar to VMs used in managed languages (i.e. Java or .Net)

  • Attacks against interpreted languages typically focus around ‘traditional web-app attacks’

    • Poison NULL byte complications

    • SQL Injection

    • Et cetera

  • Traditionally thought of as being immune to problems that plague other languages- i.e. buffer overflows

    /* PSz 12 Nov 03

    *

    * Be proud that perl(1) may proclaim:

    * Setuid Perl scripts are safer than C programs …

    * Do not abandon (deprecate) suidperl. Do not advocate C wrappers.

    *


Reason for rhyme
Reason for Rhyme

  • Usage of web and managed applications only going to increase

  • Gap between how these are attacked

  • Protections against application based attack are C-centric

    • Stack cookies

      • Higher layers of abstraction may have their own call stacks

    • Heap cookies protect against heap memory corruption

      • Many languages implement their own allocator that lack cookies

    • The unlink() macro sanity checks the forward/backward pointers

      • Many languages implement their own allocator that lack sanity checks

      • Linked lists often implemented on top of block of memory

    • NX protects against execution

      • Byte code is read/interpreted, not executed

    • ASLR protects against return-to-libc/et cetera

      • Still valid


But the future of insecurity
But, the future of insecurity?

  • Hacking community is largely content with the world as it is

  • World is changing

    • Most OSs ship with some hardening anymore

    • GCC ships with SSP

    • Visual Studio 2005 is pretty effective

    • Interpreted & Managed language use on the rise

    • We don’t get to choose what the applications we break are written in

    • Adapt or die

  • Maybe not the future

    • But, I’m at least thinking about it


Goals prior art
Goals & Prior Art

  • Goals:

    • Memory Corruption bug in interpreter

    • Attack interpreted language metadata

    • Return into interpreted language bytecode

  • Stephan Esser

    • Hardened PHP, Month of PHP bugs, et cetera

  • Mark Dowd

    • Leveraging the ActionScript Virtual Machine


Damn the torpedoes
Damn the torpedoes

  • In Mid-April 2008 Google rolled out ‘AppEngine’

  • AppEngine ‘enables you to build web applications on the same scalable systems that power Google applications’

  • AKA Here’s a python interpreter, you can’t break us.

  • A flagship example is the shell application

    • Literally a web-based interface to the interpreter

  • Interpreter runs in a restricted environment

    • All file-based I/O is (supposed to be) disabled and a Google specific datastore API is provided

    • Subprocesses, threads, et cetera disabled

    • No sockets

    • Many modules disabled or modified

  • Perfect target.


Abba zabba you my only friend
Abba Zabba, you my only friend

  • Having direct access to the interpreter allows *a lot* of flexibility

  • Stopping address space leaks becomes incredibly problematic (sys._getframe() ?)

  • Attacker can manipulate interpreter state to match necessary conditions

  • Situation is the same that shared hosting providers have faced for years

  • Except now its Google, they’re pushing this for enterprise use, and the attack surface has been acknowledged


But interpreted languages don t have buffer overflows
But interpreted languages don’t have buffer overflows…

  • More common than expected

    • CVE-2008-1679: Multiple integer overflows in imageop module

    • CVE-2008-1887: Signedness issues in PyString_FromStringAndSize()

    • CVE-2008-1721: Signedness issues cause buffer overflow in zlib module

    • CVE-2008-XXXX: Integer overflow leads to buffer overflow in Unicode processing

    • CVE-2008-XXXX: Integer overflow leads to buffer overflow in Buffer objects

    • Et cetera

  • Interpreter code still relatively ‘virgin’

  • Many in Python due to extensive use of signed integers


On 0 day
On 0-day

  • Over next few slides several bugs are discussed

    • Some are reported and patched

    • Some are reported and unpatched

    • Some are undisclosed and unpatched

  • Not all bugs are equal

    • Most occur in unusual circumstances

    • Most require direct interpreter access

    • Others are typically unexploitable (i.e. memcpy() of 4G)

  • Most undisclosed were found in a very short period of time

    • Point is, they exist & they’re not hard to find


Ethics of 0 day
Ethics of 0-day

  • Arguments would be easier to take serious if contracts didn’t have clauses like this:


When good apis go bad
When good APIs go bad

  • Patched in CVS, broken in Python versions up to 2.5.2

  • Also in PyBytes_FromStringAndSize() & PyUnicode_FromStringAndSize()

    52 PyObject *

    53 PyString_FromStringAndSize(const char *str, Py_ssize_t size)

    54 {

    55 register PyStringObject *op;

    56 assert(size >= 0);

    57 if (size == 0 && (op = nullstring) != NULL) {

    […]

    63 }

    64 if (size == 1 && str != NULL &&

    65 (op = characters[*str & UCHAR_MAX]) != NULL)

    66 {

    […]

    72 }

    73

    74 /* Inline PyObject_NewVar */

    75 op = (PyStringObject *)PyObject_MALLOC(sizeof(PyStringObject) + size);


Where the wild things roam
Where the wild things roam..

  • Currently reported but unpatched

  • Like previous example causes faults in numerous places– including core data types

    85 #define PyMem_New(type, n) \

    86 (assert((n) <= PY_SIZE_MAX / sizeof(type)) , \

    87 ((type *) PyMem_Malloc((n) * sizeof(type))))

    88 #define PyMem_NEW(type, n) \

    89 (assert((n) <= PY_SIZE_MAX / sizeof(type)) , \

    90 ((type *) PyMem_MALLOC((n) * sizeof(type))))

    91

    92 #define PyMem_Resize(p, type, n) \

    93 (assert((n) <= PY_SIZE_MAX / sizeof(type)) , \

    94 ((p) = (type *) PyMem_Realloc((p), (n) * sizeof(type))))

    95 #define PyMem_RESIZE(p, type, n) \

    96 (assert((n) <= PY_SIZE_MAX / sizeof(type)) , \

    97 ((p) = (type *) PyMem_REALLOC((p), (n) * sizeof(type))))


0xbadc0ded
0xbadc0ded

  • Reported, but currently unpatched

    static int

    unicode_resize(register PyUnicodeObject *unicode, Py_ssize_t length)

    {[...]oldstr = unicode->str;PyMem_RESIZE(unicode->str, Py_UNICODE, length + 1);

    [...]unicode->str[length] = 0;

    static

    PyUnicodeObject *_PyUnicode_New(Py_ssize_t length)

    {

    [...] /* Unicode freelist & memory allocation */ if (unicode_freelist) { […] if ((unicode->length < length) && unicode_resize(unicode, length) < 0) { […]

    else {unicode->str = PyMem_NEW(Py_UNICODE, length + 1);


0xbadc0ded1
0xbadc0ded

  • Reported and patched in CVS, versions up to 2.5.2 are vulnerable

    768 static PyObject *

    769 PyZlib_unflush(compobject *self, PyObject *args)

    770 {

    771 int err, length = DEFAULTALLOC;

    772 PyObject * retval = NULL;

    773 unsigned long start_total_out;

    774

    775

    776 if (!PyArg_ParseTuple(args, "|i:flush", &length))

    777 return NULL;

    778 if (!(retval = PyString_FromStringAndSize(NULL, length)))

    779 return NULL;

    […]

    783 start_total_out = self->zst.total_out;

    784 self->zst.avail_out = length;

    785 self->zst.next_out = (Byte *)PyString_AS_STRING(retval);

    786

    787 Py_BEGIN_ALLOW_THREADS

    788 err = inflate(&(self->zst), Z_FINISH);


0xbadc0ded2
0xbadc0ded

  • Currently undisclosed & unpatched

    static PyObject *

    array_fromunicode(arrayobject *self, PyObject *args)

    { Py_UNICODE *ustr; Py_ssize_t n; if (!PyArg_ParseTuple(args, “u#:fromunicode", &ustr, &n)) return NULL; [...] if (n > 0) { Py_UNICODE *item = (Py_UNICODE *) self->ob_item; if (self->ob_size > PY_SSIZE_T_MAX - n) { return PyErr_NoMemory(); } PyMem_RESIZE(item, Py_UNICODE, self->ob_size + n); if (item == NULL) { PyErr_NoMemory(); return NULL; } self->ob_item = (char *) item; self->ob_size += n; self->allocated = self->ob_size;memcpy(item + self->ob_size - n, ustr, n * sizeof(Py_UNICODE));


0xbadc0ded3
0xbadc0ded

  • Currently undisclosed & unpatched

    static PyObject *

    encoder_encode_stateful(MultibyteStatefulEncoderContext *ctx,

    PyObject *unistr, int final)

    { PyObject *ucvt, *r = NULL; Py_UNICODE *inbuf, *inbuf_end, *inbuf_tmp = NULL; Py_ssize_t datalen, origpending; [...] datalen = PyUnicode_GET_SIZE(unistr); origpending = ctx->pendingsize;if (origpending > 0) { if (datalen > PY_SSIZE_T_MAX - ctx->pendingsize) { […]

    }inbuf_tmp = PyMem_New(Py_UNICODE, datalen + ctx->pendingsize); […]

    memcpy(inbuf_tmp, ctx->pending, Py_UNICODE_SIZE *ctx->pendingsize);


Ya dig
ya dig?

  • Currently undisclosed & unpatched

    static PyObject *

    posix_execv(PyObject *self, PyObject *args)

    { […] if (!PyArg_ParseTuple(args, "etO:execv", Py_FileSystemDefaultEncoding, &path, &argv)) return NULL; if (PyList_Check(argv)) { argc = PyList_Size(argv); […] else if (PyTuple_Check(argv)) { argc = PyTuple_Size(argv); […]argvlist = PyMem_NEW(char *, argc+1); [...] for (i = 0; i < argc; i++) { if (!PyArg_Parse((*getitem)(argv, i), "et", Py_FileSystemDefaultEncoding, &argvlist[i])) { [...]


Goals review
Goals review

  • Goal 0: memory corruption bugs

    • Bugs are just as prevalent as other traditional applications

    • Some of them are pretty silly

    • Lots are still easy to spot and require very little in the way of deep thinking

    • Some are exploitable, some require specific circumstances, others are just bugs

  • Goal 1: attack interpreter level metadata..


Python call stack
Python Call stack

  • Simple test program:

    #!/usr/bin/python

    import time

    while 1:

    time.sleep(500)

    gdb> r

    […]

    Program received signal SIGINT, Interrupt.

    [Switching to Thread 0x2b9d5bdd4d00 (LWP 28588)]

    0x00002b9d5bb4f043 in select () from /lib/libc.so.6

    gdb> bt

    #0 0x00002b9d5bb4f043 in select () from /lib/libc.so.6

    #1 0x00002b9d5bdd869f in time_sleep […]

    #2 0x0000000000486097 in PyEval_EvalFrameEx […]

    #3 0x0000000000488002 in PyEval_EvalCodeEx […]

    #4 0x00000000004882a2 in PyEval_EvalCode […]

    #5 0x00000000004a969e in PyRun_FileExFlags […]

    #6 0x00000000004a9930 in PyRun_SimpleFileExFlags […]

    #7 0x0000000000414630 in Py_Main […]

    #8 0x00002b9d5bab2b74 in __libc_start_main […]

    #9 0x0000000000413b89 in _start ()



Python objects
Python Objects

  • Most (interesting) object types start with a reference to PyObject_VAR_HEAD

  • i.e.:

    typedef struct _xyz {

    PyObject_VAR_HEAD

    […]

  • PyObject_VAR_HEAD macro expands to:

    • Contain the objects reference count

    • Contain pointers to next/previous in-use object (doubly linked list)

    • Contains a pointer to the objects type

      • This point is way more important at first may seem


Pycodeobject
PyCodeObject

/* Bytecode object */

typedef struct {

PyObject_HEAD

int co_argcount; /* #arguments, except *args */

int co_nlocals; /* #local variables */

int co_stacksize; /* #entries needed for evaluation stack */

int co_flags; /* CO_..., see below */

PyObject *co_code; /* instruction opcodes */

PyObject *co_consts; /* list (constants used) */

PyObject *co_names; /* list of strings (names used) */

PyObject *co_varnames; /* tuple of strings (local variable names) */

PyObject *co_freevars; /* tuple of strings (free variable names) */

PyObject *co_cellvars; /* tuple of strings (cell variable names) */

PyObject *co_filename; /* string (where it was loaded from) */

PyObject *co_name; /* string (name, for reference) */

int co_firstlineno; /* first source line number */

PyObject *co_lnotab; /* string (encoding addr<->lineno mapping) */

void *co_zombieframe; /* for optimization only (see frameobject.c) */

} PyCodeObject;


Pyeval evalcodeex
PyEval_EvalCodeEx()

  • PyEval_EvalCode() is a simple wrapper to PyEval_EvalCodeEx()

    • Uses default arguments for last seven parameters – passes NULL or 0

  • Takes a PyCodeObject as a parameter

  • Creates a PyFrameObject

  • Sets up local/global/et cetera variables

  • Serves essentially to setup environment


Pyframeobject
PyFrameObject

typedef struct _frame {

PyObject_VAR_HEAD

struct _frame *f_back; /* previous frame, or NULL */

PyCodeObject *f_code; /* code segment */

PyObject *f_builtins; /* builtin symbol table (PyDictObject) */

PyObject *f_globals; /* global symbol table (PyDictObject) */

PyObject *f_locals; /* local symbol table (any mapping) */

PyObject **f_valuestack; /* points after the last local */

/* Next free slot in f_valuestack. Frame creation sets to f_valuestack.

Frame evaluation usually NULLs it, but a frame that yields sets I

to the current stack top. */

PyObject **f_stacktop

PyObject *f_trace; /* Trace function */

[…]

PyThreadState *f_tstate;

int f_lasti; /* Last instruction if called */

[…]

int f_iblock; /* index in f_blockstack */

PyTryBlock f_blockstack[CO_MAXBLOCKS]; /* for try and loop blocks */

PyObject *f_localsplus[1]; /* locals+stack, dynamically sized */

} PyFrameObject;


Pyframeobject destruction
PyFrameObject destruction

  • As frames go out of scope, frame_dealloc() is called to destroy them

  • During destruction, only locals, exception and debugging information is cleared

  • Frame can end up in the PyCodeObject’s zombie frame, the free list, or just destroyed

    void

    frame_dealloc(PyFrameObject *f)

    {

    [...]for (p = f->f_localsplus; p < valuestack; p++) Py_CLEAR(*p);[...]Py_CLEAR(f->f_locals);Py_CLEAR(f->f_trace);Py_CLEAR(f->f_exc_type);Py_CLEAR(f->f_exc_value);Py_CLEAR(f->f_exc_traceback);co = f->f_code;if (co->co_zombieframe == NULL) co->co_zombieframe = f;else if (numfree < MAXFREELIST) { ++numfree; f->f_back = free_list; free_list = f;}


Zombies attack 1
Zombies attack!!1

PyFrameObject *PyFrame_New(PyThreadState *tstate, PyCodeObject *code, PyObject *globals, PyObject *locals)

{

[...] if (code->co_zombieframe != NULL) { f = code->co_zombieframe; code->co_zombieframe = NULL; assert(f->code == code);

} else { [...]}

f->f_stacktop = f->f_valuestack;

f->f_builtins = builtins;Py_XINCREF(back);f->f_back = back;Py_INCREF(code);Py_INCREF(globals);f->f_globals = globals;[...]return f;

}


Unleashing your zombie army
Unleashing your zombie army..

  • Attacking zombie frame not always necessary, or doing so may not make sense

    • Many heap overflows occur in direct control of byte stream

    • Many others either also allow direct control of the argument stack or both

    • Plenty of instances where you don’t hit either

  • Zombie frame is useful for pointer sized writes anywhere in memory

    • On smaller overflows, fairly typical to corrupt members of object

    • Many objects destructors use linked lists with unprotected unlinking functionality


Pyeval evalframeex
PyEval_EvalFrameEx()

  • Implements state-machine for processing bytecode

    #define INSTR_OFFSET() ((int))(next_instr – first_instr))

    #define NEXTOP() (*next_instr++)

    #define NEXTARG() (next_instr += 2, (next_instr[-1]<<8) + next_instr[-2])

    PyObject *

    PyEval_EvalFrameEx(PyFrameObject *f, int throwflag)

    {[...]first_instr = (unsigned char*) PyString_AS_STRING(co->co_code);[...]next_instr = first_instr + f->f_lasti + 1;stack_pointer = f->f_stacktop;[...]for (;;) { [...] f->f_lasti = INSTR_OFFSET(); [...]opcode = NEXTOP(); oparg = 0; /* allows oparg to be stored in a register because it doesn't have to be remembered across a full loop */ if (HAS_ARG(opcode))oparg = NEXTARG(); […]

    switch (opcode) {


Important variables
Important variables

  • first_instr:

    • Taken directly from f->f_code->co_code

    • Determines first instruction in PyCodeObject/bytecode to be executed

    • Local (stack based) variable in PyEval_EvalFrameEx()

    • Points to heap data

  • next_instr:

    • Derived from first_instr– starts out pointing to same location

    • Incremented by one to three bytes per opcode

    • Dictates next instruction in bytecode to be interpreted

    • Local (stack based) variable in PyEval_EvalFrameEx()

    • Points to heap data

  • stack_pointer:

    • Derived from f->f_stacktop

    • Determines next argument to given opcode

    • Makes up data stack

    • Local (stack based) variable in PyEval_EvalFrameEx()

    • Points to heap data


Only handguns and tequila allow bigger mistakes faster
Only handguns and tequila allow bigger mistakes faster

#define JUMPTO(x) (next_instr = first_instr + (x))while (why != WHY_NOT && f->f_iblock > 0) { PyTryBlock *b = PyFrame_BlockPop(f); assert(why != WHY_YIELD); if (b->b_type == SETUP_LOOP && why == WHY_CONTINUE) { PyFrame_BlockSetup(f, b->b_type, b->b_handler,b->b_level); why = WHY_NOT;JUMPTO(PyInt_AS_LONG(retval)); Py_DECREF(retval); break; } [...] if (b->b_type == SETUP_LOOP && why == WHY_BREAK) { why = WHY_NOT;JUMPTO(b->b_handler); break; } if (b->b_type == SETUP_FINALLY || (b->b_type == SETUP_EXCEPT && why == WHY_EXCEPTION)) { [...] why = WHY_NOT;JUMPTO(b->b_handler); break; }} /* unwind stack */


Other interpreter targets
Other interpreter targets..

  • Python’s debugging functionality allows for tracing of the application

    • Whether the currently executing byte-code is being traced is determined by a member in the stack frame

    • Tracing allows for calls before opcode execution, function entry, exception handling, et cetera

  • Many Objects have functionality that can be abused with small amounts of memory corruption

  • Be creative, they haven’t been beat on like the various libc’s, so they haven’t hardened their implementations


Goal review
Goal Review

  • Goal 1: Attack interpreter level metadata

    • In most cases overwriting a PyCodeObject or the stack_pointer is trivial

    • In others attacking the zombie frame allows for an interesting and humorous exercise

    • Python’s exception handling can also be abused

    • Objects are unhardened

  • This allows us to bypass many of the hardening functionality in existence, i.e. stack/heap cookies, unlink() hardening, et cetera

  • Goal 2: Return into byte-code…


Opcodes
opcodes

  • Python opcodes are a single char

    • As of 2.5.2 there are 103 opcodes

  • Opcodes take optional 16-bit modifier

    • Can be thought of as like a sub-opcode

  • Arguments/parameters are pointed to by stack_pointer

  • Thus, parameters need to be placed on the stack first, then the opcode in question called

  • i.e.:

    >>> def test():

    … print “PsychoAlphaDiscoBetaBioAquaDoLoop”

    >>> __import__(‘dis’).dis(test)

    2 0 LOAD_CONST 1 ('PsychoAlphaDiscoBetaBioAquaDoLoop') 3 PRINT_ITEM

    4 PRINT_NEWLINE

    5 LOAD_CONST 0 (None)

    8 RETURN_VALUE


Our house
Our House..

  • Easiest method is to abuse the support for run-time functions (lambda’s)

  • Opcode is MAKE_FUNCTION

    case MAKE_FUNCTION:

    v = POP(); /* code object */ x = PyFunction_New(v, f->f_globals); Py_DECREF(v); /* XXX Maybe this should be a separate opcode? */ if (x != NULL && oparg > 0) { v = PyTuple_New(oparg); if (v == NULL) { Py_DECREF(x); x = NULL; break; } while (--oparg >= 0) { w = POP(); PyTuple_SET_ITEM(v, oparg, w); } err = PyFunction_SetDefaults(x, v); Py_DECREF(v);

    } PUSH(x); break;


In the middle of our street
In the middle of our street..

  • Python natively generates code that has a STORE_FAST/LOAD_FAST

    • Don’t think they’re necessary

    • Pressed for time, so didn’t investigate whether they were necessary or not

      #define GETLOCAL(i) (fastlocals[i]) #define SETLOCAL(i, value) do { PyObject *tmp = GETLOCAL(i); \ GETLOCAL(i) = value; \ Py_XDECREF(tmp); } while (0)

      case LOAD_FAST:

      x = GETLOCAL(oparg); if (x != NULL) { Py_INCREF(x); PUSH(x); goto fast_next_opcode; } […]

      break; case STORE_FAST: v = POP(); SETLOCAL(oparg, v); goto fast_next_opcode;


Let there be light
Let there be light.

  • Now that we’ve built the function and setup the argument stack, its just time to call it

  • Accomplished via CALL_FUNCTION opcode

    case CALL_FUNCTION: { PyObject **sp; PCALL(PCALL_ALL); sp = stack_pointer;#ifdef WITH_TSC x = call_function(&sp, oparg, &intr0, &intr1);#else x = call_function(&sp, oparg);#endif stack_pointer = sp; PUSH(x); if (x != NULL) continue; break; }


Calling the call function function
Calling the call_function() function

static PyObject *

call_function(PyObject ***pp_stack, int oparg#ifdef WITH_TSC , uint64* pintr0, uint64* pintr1#endif )

{

int na = oparg & 0xff;int nk = (oparg>>8) & 0xff;int n = na + 2 * nk;PyObject **pfunc = (*pp_stack) - n - 1;PyObject *func = *pfunc;PyObject *x, *w; if (PyCFunction_Check(func) && nk == 0) { [...] if (flags & METH_NOARGS && na == 0) { C_TRACE(x, (*meth)(self,NULL)); } else if (flags & METH_O && na == 1) { PyObject *arg = EXT_POP(*pp_stack);C_TRACE(x, (*meth)(self,arg)); Py_DECREF(arg); } [...]


Calling the call function function1
calling the call_function() function

static PyObject *

call_function(PyObject ***pp_stack, int oparg

#ifdef WITH_TSC

, uint64* pintr0, uint64* pintr1

#endif

)

{

[…]

if (PyCFunction_Check(func) && nk == 0) { [...] } else { if (PyMethod_Check(func) && PyMethod_GET_SELF(func) != NULL) { PyObject *self = PyMethod_GET_SELF(func); [...]

} else [...] if (PyFunction_Check(func)) x = fast_function(func, pp_stack, n, na, nk);

[…]


A final note about call function
A final note about call_function()

  • Often after overflow the code returned into is call_function()

  • call_function() cleans up the argument stack after calling the function:

    while ((*pp_stack) > pfunc) { w = EXT_POP(*pp_stack); Py_DECREF(w); PCALL(PCALL_POP);

    }

  • Py_DECREF() will almost certainly cause a destructor to get called

  • If data stack_pointer pointed to was corrupted, this will be the first place its felt

  • Unless you’re ready for a ret-into-libc type attack, make sure that w points to valid memory that has a value greater than 1


Pycodeobject s don t grow on trees you know
PyCodeObject’s don’t grow on trees you know!

  • Where to get a PyCodeObject?

  • Two options, dependant on context:

    • AppEngine, et al:

      x = unicode(compile(‘print “zdravstvoyte mir!”, ‘<string>’, ‘exec’))

      • x will contain string along the lines of:

        <code object <module> at 0x4e058f1c37daf18, file “<string>”, line 1>

      • Now stack_pointer just needs to point at 0x4e058f1c37daf18

    • Less controlled environments:

      • Use compile() to obtain code object

      • References in header need to be updated-- PyCodeObject->co_code

      • Requires address space leak


Errata
But..

  • Returning into bytecode in AppEngine doesn’t make much sense?

    • Already have control of the interpreter

    • Return into same restricted environment

    • Not exactly true-- but ret-into-libc or similar eventually becomes necessary

  • Ret-into-libc requires address space info

  • Non-AppEngine attacks require address space info


Tell me about your mother
Tell me about your mother..

  • One reason for return into byte-code on AppEngine: PRINT_EXPR opcode

    case PRINT_EXPR:

    v = POP();w = PySys_GetObject("displayhook");

    if (w == NULL) { PyErr_SetString(PyExc_RuntimeError, "lost sys.displayhook"); err = -1; x = NULL;}if (err == 0) { x = PyTuple_Pack(1, v); if (x == NULL) err = -1;}if (err == 0) { w = PyEval_CallObject(w, x); Py_XDECREF(w); if (w == NULL) err = -1;}Py_DECREF(v);Py_XDECREF(x);break;


All we had to do was ask
All we had to do was ask..

  • Typical results of memory leak:

    ‘\x00\x00\x00\x00\x00\x00\x00\x0f\x00\x00\x80\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\xa0\x91\x81\x00\x00\x00\x00\x00\x00\x90\x91\x81\x00\x00\x00\xb0\x91\x81\x12\x1e\x01\x00\x00\x02\[…]’

  • Leaking heap addresses in this example: 0x8191XXXX

  • If stack_pointer is controlled, can point anywhere in the address space

  • Leak is really only bounded by how much byte-code you can get into the stream

  • Objects used to verify typing information are statically allocated and use fixed offsets– thus once you know the low-order bytes, you can spot them easily

  • Problematic because it prints to standard out, which can be redirected but post-overflow is a lot of trouble

  • Good strategy is to take advantage of fact that we’re dealing with a web-app that can crash an infinite number of times


Loose apis sink ships
Loose APIs sink ships.

  • Python is one of those overly helpful languages

  • Pretty much all objects can be printed out

    • When you print an object, the address of the object is leak

    • Object can also be converted to a string where the same string that gets printed ends up in string, i.e. unicode(compile(…)) gives you the address of a PyCodeObject

  • Leak PyFrameObject addresses:

    • sys._getframe() – returns frame object

    • sys._currentframes() – returns a dictionary with each threads current frame

  • builtin function id()

    • Each object has a unique id

    • This is accomplished by using the address of the object for the id

    • i.e. id(None) yields address of None object (think about that in context of obtaining type object addresses)

  • Builtin functions dir() and getattr()

    • dir() allows you to enumerate attributes of an object

    • getattr() allows you to obtain its value

    • Useful when function pointers cannot be avoided


Goals in review
Goals in review

  • Goal 2: return into interpreter byte-code

    • Easier to accomplish than initially thought

    • Process of executing byte-code is more problematic due to type-checks

    • Successful exploitation absolutely requires address space leaks

    • Python provides us with nice opcodes to allow leaking

    • Easiest method is to use MAKE_FUNCTION/CALL_FUNCTION combination as bootstrap mechanism

    • Restricted interpreters require return-into-C code to break out of

  • Returning into byte-code provides these advantages:

    • Non-executable memory is not necessary- byte-code is interpreted not executed

    • Because its not executed we can use it to dump address space info


Errata
2+2

  • Overall process:

    • Obtain address space information via memory corruption and executing PRINT_EXPR opcode

    • Obtain addresses that were valid at one time and fix up data with addresses

    • Craft a PyCodeObject either in the address space or in the shellcode, update PyCodeObject header information if injecting into address space

    • Execute following opcodes: MAKE_FUNCTION/STORE_FAST/LOAD_FAST/CALL_FUNCTION

    • Return into PyCodeObject

    • From PyCodeObject return into executable memory


Other available opcodes
Other available opcodes

  • LOAD_CONST – loads constant onto argument stack using oparg index into consts member of PyCodeObject

  • POP_TOP – removes member from top of argument stack, decreases reference

  • ROT_TWO/ROT_THREE/ROT_FOUR – rotates position of argument stack, moving 2, 3 or 4 arguments around

  • DUP_TOPX – duplicates either 2 or 3 pointers on argument stack

  • STORE_SLICE+X – some code paths do not have type checks and instead create a new object, allows object creation

  • PRINT_ITEM_TO – allows redirection of output, pops output data from variable stack, falls through to PRINT_ITEM which may redirect to stdout

  • LOAD_LOCALS – places f->f_locals onto argument stack, great opcode to prefix PRINT_X opcodes

  • YIELD_VALUE – takes return value from argument stack, sets f->f_stacktop to point to stack_ponter

  • POP_BLOCK – Obtains PyTryBlock from argument stack, decrements references for each record

  • STORE_GLOBAL / LOAD_GLOBAL – same as with other STORE_X/LOAD_X opcodes, except operates on globals


More opcodes
More opcodes

  • JUMP_ABSOLUTE – like JUMP_FORWARD except is not relative to first_instr

  • FOR_ITER – makes function pointer call from pointer retrieved from argument stack, good once address space layout is known

  • EXTENDED_ARGS – advances to next opcode, obtains another 16-bit oparg and combines it with existing 16-bit oparg, combined with JUMP_ABSOLUTE allows byte-code to exist anywhere (on 32-bit machines)

  • LOAD_CLOSURE – places pointer on argument stack from different section of heap memory

  • BUILD_X – several opcodes, allows for creation of Tuples, Lists, Maps and Slices

  • JUMP_FORWARD – advances position in bytecode by oparg bytes

  • Other opcodes perform type checks or have callbacks

  • Many of the ones with type checks can be coaxed into usage by first building an object via one of the BUILD_X opcodes

  • Once address space is know, opcodes with callbacks are not dangerous


Python fin
python.fin()

  • Bugs in Python exist and are easy to find

  • Data structures and general metadata is easy to abuse

  • Byte-code is position independent and thus easy to make

    • Because of its PIC nature– argument stack exists elsewhere

    • Ownership can be transferred with one or the other, but made more difficult

  • Hardest part of returning into byte-code is ASLR

  • Python is really helpful there.


What about perl
What about PERL?

  • PERL has bugs too

  • Reading PERL is an exercise in patience

    • Friend: ‘I still maintain that PERL was not written.. It was found.. On a crashed UFO’

  • Yeah, it is that bad

  • Be careful when looking into the abyss..


Ugh wtf
Ugh, wtf?

  • Just an example (from 5.8.8):

    int

    perl_parse(PTHXx_, XSINIT_t xsinit, int argc, char **argv, char **env)

    {

    […]

    #ifdef PERL_FLEXIBLE_EXCEPTIONS

    CALLPROTECT(aTHX_ pcur_env, &ret,

    MEMBER_TO_FPTR(S_vparse_body),

    env, xsinit);

    #else

    JMPENV_PUSH(ret)

    #endif


Are you kidding me
Are you kidding me?

  • If PERL_FLEXIBLE_EXCEPTIONS is defined…

    #define CALLPROTECT CALL_FPTR(PL_protect)

    #define CALL_FPTR(fptr) (*fptr)

    #define PL_protect (aTHX->Tprotect)

    #define aTHX PERL_GET_THX

    #define aTHX_ aTHX,

    #ifdef USE_5005THREADS

    #define PERL_GET_THX ((struct perl_thread *)PERL_GET_CONTEXT)

    #else

    #ifdef MULTIPLICITY

    #define PERL_GET_THX ((PerlInterpreter *)PERL_GET_CONTEXT)

    #endif

    #endif


Larry wall is trying to kill me
Larry Wall is trying to kill me

  • And some more…

    #ifndef PERL_GET_CONTEXT

    #define PERL_GET_CONTEXT ((void *)NULL)

    #define PERL_GET_CONTEXT Perl_get_context()

    #define MEMBER_TO_FPTR(name) name

  • So, on conditional compilation expands to:

    perl_get_context()->Tprotect((struct perl_thread *)Perl_get_context(),

    pcur_env,

    &ret,

    S_Vparse_body,

    env,

    xsinit);


You outsourced to the guy who wrote procmail didn t you
You outsourced to the guy who wrote procmail didn’t you?!

  • If PERL_FLEXIBLE_EXCEPTIONS is not defined…

    #define JMPENV_PUSH(v) JMPENV_PUSH_ENV(*(JMPENV*)pcur_env, v)

    #define JMPENV_PUSH_ENV(ce, v) \

    STMT_START { \

    if (!(ce).je_noset) { \

    DEBUG_1(Perl_deb(aTHX_ “Setting up jumplevel %p, was %p\n”, \

    cl, PL_top_env)); \

    JMPENV_PUSH_INIT_ENV(ce, NULL) \

    EXCEPT_SET_ENV(ce, PerlProc_setjmp((ce).je_buf, SCOPE_SAVES_SIGNAL_MASK)); \

    (ce).je_noset = 1; \

    } \

    else \

    EXCEPT_SET_ENV(ce, 0); \

    JMPENV_POST_CATCH_ENV(ce); \

    (v) = EXCEPT_GET_ENV(ce); \

    } STMT_END


Errata
sigh

  • Does do { […] } while(0) not work somewhere??

    #if defined(__GNUC__) && !defined(PERL_GCC_BRACE_GROUPS_FORBIDDEN) && !defined (__cplusplus)

    #define STMT_START (void) {

    #define STMT_END }

    #else

    #if (VOIDFLAGS) && (defined(sun) || defined(__sun__)) && !defined(__GNUC__)

    #define STMT_START if (1)

    #define STMT_END else (void)0

    #else

    #define STMT_START do

    #define STMT_END while (0)

    #endif

    #endif


Errata

  • We’re just gonna skip expanding Debug_1() and guess that it probably deals with debugging output…

    #define JMPENV_PUSH_INIT_ENV(ce, THROWFUNC) \

    STMT_START { \

    (ce).je_throw = (THROWFUNC); \

    (ce).je_ret = -1; \

    (ce).je_mustcatch = FALSE; \

    (ce).je_prev = PL_top_env; \

    PL_TOP_env = &(ce); \

    OP_REG_TO_MEM; \

    } STMT_END

    #define PL_top_env (aTHX->Ttop_env)

    #ifdef OP_IN_REGISTER

    #define OP_REG_TO_MEM PL_opsave = op

    […]

    #else

    #define OP_REG_TO_MEM NOOP

    #define PL_opsave (aTHX->Top_save)


Anyone wanna bet how many slides it takes to explain one line of perl
Anyone wanna bet how many slides it takes to explain one line of PERL?

  • Almost there …

    #define EXCEPT_SET_ENV(ce, v) ((ce).je_ret = (v))

    #define JMPENV_POST_CATCH_ENV(ce) \

    STMT_START { \

    OP_MEM_TO_REG; \

    PL_top_env = &(ce); \

    } STMT_END

    #define EXCEPT_GET_ENV(ce) ((ce).je_ret)


Huzzah one line expanded
Huzzah! One line expanded! line of PERL?

  • seven slides later..

  • Two of over a dozen possible conditional compilations were explored

  • We’ve successfully decoded *one line* of perl

  • Except we haven’t, now we have to find out where the function pointers get initialized…

  • And of course, discover where the heck op came from

  • I don’t think an hour is long enough to cover just PERL, much less PERL and Python


0xbadc0ded4
0xbadc0ded line of PERL?

  • Undisclosed & unpatched

    do { i_img *im = read_one_tiff(tif, 0); if (!im) break; if (++*count > result_alloc) { if (result_alloc == 0) { result_alloc = 5; results = mymalloc(result_alloc * sizeof(i_img *)); } else { i_img **newresults;result_alloc *= 2; newresults = myrealloc(results, result_alloc * sizeof(i_img *)); if (!newresults) { i_img_destroy(im); /* don't leak it */ break; } results = newresults; } } results[*count-1] = im; } while (TIFFSetDirectory(tif, ++dirnum));


0xbadc0ded5
0xbadc0ded line of PERL?

  • Undisclosed & unpatched

    static i_img *

    read_one_rgb_tiled(TIFF *tif, int width, int height, int allow_incomplete) { [...] uint32* raster = NULL; [...] uint32 tile_width, tile_height; i_color *line; [...] TIFFGetField(tif, TIFFTAG_TILEWIDTH, &tile_width); TIFFGetField(tif, TIFFTAG_TILELENGTH, &tile_height); [...]raster = (uint32*)_TIFFmalloc(tile_width * tile_height * sizeof (uint32)); [...]line = mymalloc(tile_width * sizeof(i_color)); for( row = 0; row < height; row += tile_height ) { for( col = 0; col < width; col += tile_width ) { /* Read the tile into an RGBA array */if (myTIFFReadRGBATile(&img, col, row, raster)) { [...]


0xbadc0ded6
0xbadc0ded line of PERL?

  • Undisclosed & unpatched

    i_img *

    read_one_rgb_lines(TIFF *tif, int width, int height, int allow_incomplete) { i_img *im; uint32* raster = NULL; uint32 rowsperstrip, row; i_color *line_buf; int alpha_chan; int rc; im = make_rgb(tif, width, height, &alpha_chan); [...] rc = TIFFGetField(tif, TIFFTAG_ROWSPERSTRIP, &rowsperstrip); [...] raster = (uint32*)_TIFFmalloc(width * rowsperstrip * sizeof (uint32)); [...] line_buf = mymalloc(sizeof(i_color) * width); for( row = 0; row < height; row += rowsperstrip ) { uint32 newrows, i_row;if (!TIFFReadRGBAStrip(tif, row, raster)) {