Presentation is loading. Please wait.

Presentation is loading. Please wait.

EuSecWest website references IOActive No longer IOActive employee

Similar presentations


Presentation on theme: "EuSecWest website references IOActive No longer IOActive employee"— Presentation transcript:

1

2 EuSecWest website references IOActive No longer IOActive employee
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

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

4 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

5 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

6 Goals & Prior Art Goals: Stephan Esser Mark Dowd
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

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

8 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

9 But interpreted languages don’t have buffer overflows…
More common than expected CVE : Multiple integer overflows in imageop module CVE : Signedness issues in PyString_FromStringAndSize() CVE : 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

10 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

11 Ethics of 0-day Arguments would be easier to take serious if contracts didn’t have clauses like this:

12 When good APIs go bad Patched in CVS, broken in Python versions up to 2.5.2 Also in PyBytes_FromStringAndSize() & PyUnicode_FromStringAndSize() PyObject * PyString_FromStringAndSize(const char *str, Py_ssize_t size) { register PyStringObject *op; assert(size >= 0); if (size == 0 && (op = nullstring) != NULL) { […] } if (size == 1 && str != NULL && (op = characters[*str & UCHAR_MAX]) != NULL) { } 73 /* Inline PyObject_NewVar */ op = (PyStringObject *)PyObject_MALLOC(sizeof(PyStringObject) + size);

13 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) \ (assert((n) <= PY_SIZE_MAX / sizeof(type)) , \ ((type *) PyMem_Malloc((n) * sizeof(type)))) 88 #define PyMem_NEW(type, n) \ (assert((n) <= PY_SIZE_MAX / sizeof(type) ) , \ ((type *) PyMem_MALLOC((n) * sizeof(type)))) 91 92 #define PyMem_Resize(p, type, n) \ (assert((n) <= PY_SIZE_MAX / sizeof(type)) , \ ((p) = (type *) PyMem_Realloc((p), (n) * sizeof(type)))) 95 #define PyMem_RESIZE(p, type, n) \ (assert((n) <= PY_SIZE_MAX / sizeof(type)) , \ ((p) = (type *) PyMem_REALLOC((p), (n) * sizeof(type))))

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

15 0xbadc0ded Reported and patched in CVS, versions up to are vulnerable 768 static PyObject * 769 PyZlib_unflush(compobject *self, PyObject *args) 770 { int err, length = DEFAULTALLOC; PyObject * retval = NULL; unsigned long start_total_out; 774 775 if (!PyArg_ParseTuple(args, "|i:flush", &length)) return NULL; if (!(retval = PyString_FromStringAndSize(NULL, length))) return NULL; […] 783 start_total_out = self->zst.total_out; 784 self->zst.avail_out = length; self->zst.next_out = (Byte *)PyString_AS_STRING(retval); 786 Py_BEGIN_ALLOW_THREADS err = inflate(&(self->zst), Z_FINISH);

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

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

18 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])) { [...]

19 Goal 0: memory corruption bugs
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..

20 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 0x in PyEval_EvalFrameEx […] #3 0x in PyEval_EvalCodeEx […] #4 0x a2 in PyEval_EvalCode […] #5 0x a969e in PyRun_FileExFlags […] #6 0x a9930 in PyRun_SimpleFileExFlags […] #7 0x in Py_Main […] #8 0x00002b9d5bab2b74 in __libc_start_main […] #9 0x b89 in _start ()

21 Bytecode flow overview

22 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

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

24 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

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

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

27 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; } While PyCodeObject’s represent a segment of byte code, PyFrameObject’s kind of keep track of the current state of execution, they determine where the byte code is, where variables are, and so on. PyFrameObject’s are allocated on the heap, thus meaning that a heap overflow can be a stack overflow, although as we’ll see later a stack overflow can be a stack overflow as well. Before we get into all of that though, we should note one interesting feature of the frame creation and destruction process. In addition to the free list, there is a member in the PyCodeObject called ‘co_zombieframe’, which acts as a cache of sorts. As we can see, if there is a zombie frame present, then certain presumptions are made– most importantly that the pointer to the PyCodeObject in the frame is re-used. Sure, a verification is done via assert(), but that gets compiled out. So what does this mean exactly? Basically, we can have a heap overflow and hit a PyFrameObject, overwriting its pointer to the PyCodeObject, then when we go out of scope get onto the code objects zombie frame, then when we enter that code object again, our frame will get reused. We’ll see how that comes into play shortly, but for now, lets take a look and see how the byte code is processed exactly.

28 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

29 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) { The PyEval_EvalFrame() and PyEval_EvalFrameEx() functions are the main-processing loops of the interpreter, the former is a wrapper to PyEval_EvalFrameEx(). In PyEval_EvalFrameEx() a few local variables are first initialized, first_instr is set to point to the co_code member of the PyCodeObject, and the next instruction is set to point to the first_instr plus the f_lasti member of the current frame plus one. This seems a bit odd at first, as it seems like we would always miss the first instruction, however PyFrame_New() initializes the f_lasti variable to -1, so upon entry next_instr just points to first_instr. We then enter an infinite for() loop and the opcode is obtained via the NEXTOP() macro, which simply returns the next byte in the byte codes stream and increments the pointer. We’ll discuss the encoding of opcodes more shortly, but for now just know that opcodes can potentially take an oparg, which is a 16-bit modifier of sorts. So now we have a decent understanding of the call stack- the control variables used to determine where in the byte code we are local variables to PyEval_EvalFrameEx(), thus in order to hit any of those pointers we need a stack overflow. However, it’s more likely, and actually far more common for use to be able to overwrite the data next_instr points to, than to modify next_instr itself. Another common occurrence is that we overwrite the data that stack_pointer points to. Before we really delve into that though, let’s take another look at the opcodes and their format.

30 Important variables first_instr: next_instr: stack_pointer:
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 stack_pointer: Derived from f->f_stacktop Determines next argument to given opcode Makes up data stack

31 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 */ Retval is typically taken either from the argument stack, or from the bytecode stream, depending on context. b_handler member is just an integer, PyTryBlock is stored in the PyFrameObject Allows potential jumps forward/backwards in the bytecode, potentially outside of bytecode into user controlled data

32 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

33 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…

34 opcodes Python opcodes are a single char
As of 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) LOAD_CONST ('PsychoAlphaDiscoBetaBioAquaDoLoop') 3 PRINT_ITEM 4 PRINT_NEWLINE 5 LOAD_CONST (None) 8 RETURN_VALUE

35 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; The MAKE_FUNCTION opcode pops a pointer to a PyCodeObject off of the argument stack and then creates a new function from it, using the current frames globals for its globals. The rest of the function really deals with setting up function defaults and isn’t a huge concern for us. Note that at the end of the function the return function is pushed onto the argument stack for us. It should be fairly obvious that the majority of the functionality for the opcode takes place in PyFunction_New(). Thankfully, the PyFunction_New() function is pretty forgiving and can only fail if it can’t allocate memory, although if your PyCodeObject has a bad co_consts or co_name member you can get a segfault, aside from that, it pretty much succeeds always. So what we know is, we need to control the stack_pointer variable so that when v gets its assignment we control the PyCodeObject, and that’s basically it here.

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

37 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; } Once we’ve set the stage, all that’s really left is for us is to call the function we just created, this is accomplished via the CALL_FUNCTION opcode, the PCALL() macro is just used for statistics and isn’t really overly important. From there the stack_pointer variable is saved and the call_function() function is called. As we can see in here, there’s really no way to fail, so let’s take a look at call_function() and see what constraints it places upon us.

38 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); } [...] First section of code deals with C-based function’s, we won’t hit it after calling MAKE_FUNCTION because we don’t have the right type, but for the time being we want to avoid this area anyways– it uses a callback and we’d require an executable section of memory.

39 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); This is the code path taken when the function is function object was created by the MAKE_FUNCTION opcode, specifically we enter into fast_function(). fast_function() is an optimization that inlines a lot of the PyEvalFrameEx() code, after setting up the environment it returns into PyEval_EvalCodeEx() which ultimately re-enters PyEval_EvalFrameEx() and the main byte-code processing loop, we win.

40 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

41 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

42 Returning into bytecode in AppEngine doesn’t make much sense?
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

43 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; Obtains stdout object for us, bypasses typechecks, pops an argument directly from stack_pointer and creates a tuple from it, also allowing type check bypasses and finally prints it out, with no real way to fail

44 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

45 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

46 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

47 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

48 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

49 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

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

51 Reading PERL is an exercise in patience
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..

52 Just an example (from 5.8.8):
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

53 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

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

55 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

56 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 #define STMT_START do #define STMT_END while (0) #endif

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

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

59 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

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

61 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)) { [...]

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


Download ppt "EuSecWest website references IOActive No longer IOActive employee"

Similar presentations


Ads by Google