errata □EuSecWest website references IOActive □No longer IOActive employee ▫Independent...
-
Upload
joella-goodman -
Category
Documents
-
view
218 -
download
3
Transcript of errata □EuSecWest website references IOActive □No longer IOActive employee ▫Independent...
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□ 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
□ 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?
□ 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:▫ 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
□ 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
□ 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…
□ 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
□ 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
□ Arguments would be easier to take serious if contracts didn’t have clauses like this:
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..
□ 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
□ 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);
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);
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));
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?
□ 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])) {
[...]
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
□ 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 ()
Bytecode flow overview
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
/* 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;
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
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;
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;
}
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..
□ 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()
□ 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
□ 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
#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..
□ 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 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
□ 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..
□ 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..
□ 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.
□ 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
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() 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);
[…]
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!
□ 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
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..
□ 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..
□ 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.
□ 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
□ 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
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
□ 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
□ 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()
□ 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?
□ 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?
□ 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?
□ 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
□ 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?!
□ 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
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
…
□ 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?
□ 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!
□ 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
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));
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)) {
[...]
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)) {