62

errata

  • Upload
    bina

  • View
    40

  • Download
    0

Embed Size (px)

DESCRIPTION

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 - PowerPoint PPT Presentation

Citation preview

Page 1: errata
Page 2: 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

Page 3: 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. *

Page 4: errata

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

Page 5: errata

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

Page 6: errata

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

Page 7: errata

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.

Page 8: errata

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

Page 9: errata

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

Page 10: errata

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

Page 11: errata

Ethics of 0-day

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

Page 12: errata

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);

Page 13: errata

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))))

Page 14: errata

0xbadc0ded

□ Reported, but currently unpatched

static intunicode_resize(register PyUnicodeObject *unicode, Py_ssize_t length){

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

staticPyUnicodeObject *_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);

Page 15: errata

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;774775776 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_THREADS788 err = inflate(&(self->zst), Z_FINISH);

Page 16: errata

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));

Page 17: errata

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);

Page 18: errata

ya dig?

□ Currently undisclosed & unpatchedstatic 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])) {

[...]

Page 19: errata

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..

Page 20: errata

Python Call stack

□ Simple test program:#!/usr/bin/pythonimport 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.6gdb> 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 ()

Page 21: errata

Bytecode flow overview

Page 22: errata

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

Page 23: errata

PyCodeObject

/* Bytecode object */

typedef struct {PyObject_HEADint 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;

Page 24: errata

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

Page 25: errata

PyFrameObject

typedef struct _frame {PyObject_VAR_HEADstruct _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_stacktopPyObject *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;

Page 26: errata

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

voidframe_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;

}

Page 27: errata

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;

}

Page 28: errata

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

Page 29: errata

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) {

Page 30: errata

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

Page 31: errata

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 */

Page 32: errata

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

Page 33: errata

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…

Page 34: errata

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

Page 35: errata

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;

Page 36: errata

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;

Page 37: errata

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; }

Page 38: errata

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); } [...]

Page 39: errata

calling the call_function() functionstatic 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);

[…]

Page 40: errata

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

Page 41: errata

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

Page 42: 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

Page 43: errata

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;

Page 44: errata

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

Page 45: errata

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

Page 46: errata

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

Page 47: 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

Page 48: errata

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

Page 49: errata

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

Page 50: errata

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.

Page 51: errata

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..

Page 52: errata

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

Page 53: errata

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

Page 54: errata

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);

Page 55: errata

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

Page 56: 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

Page 57: 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)

Page 58: errata

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)

Page 59: errata

Huzzah! One line expanded!

□ 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

Page 60: errata

0xbadc0ded□ 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));

Page 61: errata

0xbadc0ded

□ 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)) {

[...]

Page 62: errata

0xbadc0ded

□ 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)) {