Monday, August 25, 2014

The poisoned NUL byte, 2014 edition

Posted by Chris Evans, Exploit Writer Underling to Tavis Ormandy

Back in this 1998 post to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 Bugtraq mailing list, Olaf Kirch outlined an attack he called “The poisoned NUL byte”. It was an off-by-one error leading to writing a NUL byte outside cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 bounds of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 current stack frame. On i386 systems, this would clobber cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 least significant byte (LSB) of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 “saved %ebp”, leading eventually to code execution. Back at cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 time, people were surprised and horrified that such a minor error and corruption could lead to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 compromise of a process.

Fast forward to 2014. Well over a month ago, Tavis Ormandy of Project Zero disclosed a glibc NUL byte off-by-one overwrite into cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 heap. Initial reaction was skepticism about cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 exploitability of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 bug, on account of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 malloc metadata hardening in glibc. In situations like this, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 Project Zero culture is to sometimes “wargame” cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 situation. geohot quickly coded up a challenge and we were able to gain code execution. Details are captured in our public bug. This bug contains analysis of a few different possibilities arising from an off-by-one NUL overwrite, a solution to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 wargame (with comments), and of course a couple of different variants of a full exploit (with comments) for a local Linux privilege escalation.

Inspired by cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 success of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 wargame, I decided to try and exploit a real piece of software. I chose cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 “pkexec” setuid binary as used by Tavis to demonstrate cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 bug. The goal is to attain root privilege escalation. Outside of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 wargame environment, it turns out that cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365re are a series of very onerous constraints that make exploitation hard. I did manage to get an exploit working, though, so read on to see how.

Step 1: Choose a target distribution

I decided to develop against Fedora 20, 32-bit edition. Why cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 32-bit edition? I’m not going to lie: I wanted to give myself a break. I was expecting this to be pretty hard so going after cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 problem in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 32-bit space gives us just a few more options in our trusty exploitation toolkit.

Why Fedora and not, say, Ubuntu? Both ship pkexec by default. Amusingly, Ubuntu has deployed cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 fiendish mitigation called cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 “even path prefix length” mitigation. Kudos! More seriously, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365re is a malloc() that is key to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 exploit, in gconv_trans.c:__gconv_translit_find():

    newp = (struct known_trans *) malloc (sizeof (struct known_trans)
                                           + (__gconv_max_path_elem_len
                                              + name_len + 3)
                                           + name_len);

If __gconv_max_path_elem_len is even, 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 malloc() size will be odd. An odd malloc() size will always result in an off-by-one off cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 end being harmless, due to malloc() minimum alignment being sizeof(void*).

On Fedora, __gconv_max_path_elem_len is odd due to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 value being /usr/lib/gconv/ (15) or /usr/lib64/gconv/ (17). There are various unexplored avenues to try and influence this value on Ubuntu but for now we choose to proceed on Fedora.

Step 2: Bypass ASLR

Let’s face it, ASLR is a headache. On Fedora 32-bit, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 pkexec image, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 heap and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 stack are all randomized, including relative to each ocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r, e.g.:

b772e000-b7733000 r-xp 00000000 fd:01 4650        /usr/bin/pkexec
b8e56000-b8e77000 rw-p 00000000 00:00 0           [heap]
bfbda000-bfbfb000 rw-p 00000000 00:00 0           [stack]

There is often a way to defeat ASLR, but as followers of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 path of least resistance, what if we could just bypass it altogecá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r? Well, what happens if we run pkexec again after running cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 shell commands ulimit -s unlimited and ulimit -d 1 ? These altered limits to stack and data sizes are inherited across processes, even setuid ones:

40000000-40005000 r-xp 00000000 fd:01 9909        /usr/bin/pkexec
406b9000-407bb000 rw-p 00000000 00:00 0           /* mmap() heap */
bfce5000-bfd06000 rw-p 00000000 00:00 0           [stack]

This is much better. The pkexec image and libraries, as well as cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 heap, are now in static locations. The stack still moves around, with about 8MB variation (or 11 bits of entropy if you prefer), but we already know static locations for both code and data without needing to know cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 exact location of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 stack.

(For those curious about cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 effect of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365se ulimits on 64-bit ASLR, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 situation isn’t as bad cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365re. The binary locations remain well randomized. The data size trick is still very useful, though: cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 heap goes from a random location relative to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 binary, to a static offset relative to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 binary. This represents a significant reduction in entropy for some brute-force scenarios.)

Step 3: Massage cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 heap using just command line arguments and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 environment

After significant experimentation, our main heap massaging primitive is to call pkexec with a path comprising of ‘/’ followed by 469 ‘1’ characters. This path does not exist, so an error message including this path is built. The eventual error message string is a 508-byte allocation, occupying a 512-byte heap chunk on account of 4 bytes of heap metadata. The error message is built using an algorithm that starts with a 100-byte allocation. If cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 allocation is not large enough, it is doubled in size, plus 100 bytes, and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 old allocation is freed after a suitable copy. The final allocation is shrunk to precise size using realloc. Running cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 full sequence through for our 508-byte string, we see cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 following heap API calls:

malloc(100), malloc(300), free(100), malloc(700), free(300), realloc(508)

By cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 time we get to this sequence, we’ve filled up all cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 heap “holes” so that cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365se allocations occur at 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 heap, leading to this heap layout at 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 heap (where “m” means metadata and a red value shows where cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 corruption will occur):

| free space: 100 |m| free space: 300 |m| error message: 508 bytes |

In fact, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 heap algorithm will have coalesced cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 100 and 300 bytes of free space. Next, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 program proceeds to consider character set conversion for cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 error message. This is where cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 actual NUL byte heap overflows occurs, due to our CHARSET=//AAAAA… environment variable. Leading up to this, a few small allocations outside of our control occur. That’s fine; cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365y stack up at cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 beginning of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 coalesced free space. An allocation based on our CHARSET environment variable now occurs. We choose cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 number of A’s in our value to cause an allocation of precisely 236 bytes, which perfectly fills cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 remaining space in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 400 bytes of free space. The situation now looks like this:

| blah |m| blah |m| charset derived value: 236 bytes |m: 0x00000201| error message: 508 bytes |

The off-by-one NUL byte heap corruption now occurs. It will clobber cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 LSB of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 metadata word that precedes cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 error message allocation. The format of metadata is a size word, with a couple of flags in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 two least significant bits. The flag 0x1, which is set, indicates that cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 previous buffer, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 charset derived value, is in use. The size is 0x200, or 512 bytes. This size represents cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 508 bytes of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 following allocation plus 4 bytes of metadata. The size and flag values at this time are very specifically chosen so that cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 single NUL byte overflow only has cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 effect of clearing cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 0x1 in use flag. The size is unchanged, which is important later when we need to not break forward coalescing during free().

Step 4: Despair

The fireworks kick off when cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 error message is freed as cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 program exits. We have corrupted cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 preceding metadata to make it look like cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 previous heap chunk is free when in fact it is not. Since cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 previous chunk looks free, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 malloc code attempts to coalesce it with cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 current chunk being freed. When a chunk is free, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 last 4 bytes represent 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 free chunk. But cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 chunk is not really free; so what does it contain as its last 4 bytes? Those bytes will be interpreted as a size. It turns out that as an attacker, we have zero control over cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365se last 4 bytes: cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365y are always 0x6f732e00, or cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 string “.so” preceded by a NUL byte.

Obviously, this is a very large size. And unfortunately it is used as an index backwards in memory in order to find cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 chunk header structure for cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 previous chunk. Since our heap is in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 0x40000000 range, subtracting 0x6f732e00 ends us up in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 0xd0000000 range. This address is in kernel space so when we dereference it as a chunk header structure, we get a crash and our exploitation dreams go up in smoke.

At this juncture, we consider alternate heap metadata corruption situations, in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 hope we will find a situation where we have more control:

  1. Forward coalescing of free heap chunks. If we cause cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 same corruption as described above, but arrange to free cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 chunk preceding cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 overflowed chunk, we follow a different code path. It results in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 beginning of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 236-byte allocation being treated as a pair of freelist pointers for a linked list operation. This sounds initially promising, but again, we do not seem to have full control over cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365se values. In particular, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 second freelist pointer comes out as NULL (guaranteed crash) and it is not immediately obvious how to overlap a non-NULL value cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365re.
  2. Overflowing into a free chunk. This opens up a whole range of possibilities. Unfortunately, our overflow is a NUL byte so we can only make free chunks smaller and not bigger, which is a less powerful primitive. But we can again cause confusion as to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 location of heap metadata headers. See “shrink_free_hole_consolidate_backward.c” in our public bug. Again, we are frustrated because we do not have obvious control over cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 first bytes of any malloc() object that might get placed into cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 free chunk after we have corrupted cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 following length.
  3. Overflowing into a free chunk and later causing multiple pointers to point to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 same memory. This powerful technique is covered in “shrink_free_hole_alloc_overlap_consolidate_backward.c” in our public bug. I didn’t investigate this path because cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 required precise sequence of heap operations did not seem readily possible. Also, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 memory corruption occurs after cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 process has hit an error and is heading towards exit(), so taking advantage of pointers to overlapping memory will be hard.

At this stage, things are looking bad for exploitation.

Step 5: Aha! use a command-line argument spray to effect a heap spray and collide cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 heap into cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 stack

The breakthrough to escape cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 despair of step 4 comes when we discover a memory leak in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 pkexec program; from pkexec.c:

    else if (strcmp (argv[n], "--user") == 0 || strcmp (argv[n], "-u") == 0)
       {
         n++;
         if (n >= (guint) argc)
           {
             usage (argc, argv);
             goto out;
           }

         opt_user = g_strdup (argv[n]);
       }

This is very useful! If we specify multiple “-u” command line arguments, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n we will spray cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 heap, because setting a new opt_user value does not consider freeing cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 old one.

Furcá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365rmore, we observe that modern Linux kernels permit a very large number of command-line arguments to be passed via execve(), with each one able to be up to 32 pages long.

We opt to pass a very large number (15 million+) of “-u” command line argument values, each a string of 59 bytes in length. 59 bytes plus a NUL terminator is a 60 byte allocation, which ends up being a 64 byte heap chunk when we include metadata. This number is important later.

The effect of all cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365se command line arguments is to bloat both cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 stack (which grows down) and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 heap (which grows up) until cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365y crash into each ocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r. In response to this collision, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 next heap allocations actually go above cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 stack, in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 small space between cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 upper address of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 stack and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel space at 0xc0000000. We use just enough command line arguments so that we hit this collision, and allocate heap space above cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 stack, but do not quite run out of virtual address space -- this would halt our exploit! Once we’ve caused this condition, our tail-end mappings look a bit like this:

407c8000-7c7c8000 rw-p 00000000 00:00 0       /* mmap() based heap */
7c88e000-bf91c000 rw-p 00000000 00:00 0       [stack]
bf91c000-bff1c000 rw-p 00000000 00:00 0       /* anocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r mmap() heap extent */

Step 6: Commandeer a malloc metadata chunk header

The heap corruption listed in step 3 now plays out in a heap extent that is past cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 stack. Why did we go to all this effort? Because it avoids cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 despair in step 4. The huge backwards index of 0x63732e00 now results in an address that is mapped! Specifically, it will hit somewhere around cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 0x50700000 range, squarely in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 middle of our heap spray. We control cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 content at this address.

At this juncture, we encounter cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 first non-determinism in our exploit. This is of course a shame as we deployed quite a few tricks to avoid randomness. But, by placing a heap extent past cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 stack, we’ve fallen victim to stack randomization. That’s one piece of randomization we were not able to bypass. By experimental determination, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 top of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 stack seems to range from 0xbf800000-0xbffff000, for 2048 (2^11) different possibilities with 4k (PAGE_SIZE) granularity.

A brief departure on exploit reliability. As we spray cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 heap, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 heap grows in mmap() extents of size 1MB. There is no control over this. Therefore, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365re’s a chance that cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 stack will randomly get mapped sufficiently high that a 1MB mmap() heap extent cannot fit above cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 stack. This will cause cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 exploit to fail about 1 in 8 times. Since cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 exploit is a local privilege escalation and takes just a few seconds, you can simply re-run it.

In order to get around this randomness, we cater for every possible stack location in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 exploit. The backwards index to a malloc chunk header will land at a specific offset into any one of 2048 different pages. So we simply forge a malloc chunk header at all of those locations. Whichever one hits by random, our exploit will continue in a deterministic manner by using cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 same path forward. At this time, it’s worth noting why we sprayed cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 heap with 59-byte strings. These end up spaced 64 bytes apart. Since 64 is a perfect multiple of PAGE_SIZE (4096), we end up with a very uniform heap spray pattern. This gives us two things: an easy calculation to map command line arguments to an address where cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 string will be placed in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 heap, and a constant offset into cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 command line strings for where we need to place cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 forged heap chunk payload.

Step 7: Clobber cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 tls_dtor_list

So, we have now progressed to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 point where we corrupt memory such that a free() call will end up using a faked malloc chunk header structure that we control. In order to furcá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r progress, we abuse freelist linked list operations to write a specific value to a specific address in memory. Let’s have a look at cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 malloc.c code to remove a pointer from a doubly-linked freelist:

#define unlink(AV, P, BK, FD) {                                        \
[...]
 if (__builtin_expect (FD->bk != P || BK->fd != P, 0)) {              \
   mutex_unlock(&(AV)->mutex);                                        \
   malloc_printerr (check_action, "corrupted double-linked list", P); \
   mutex_lock(&(AV)->mutex);                                          \
 } else {                                                             \                                                  
   if (!in_smallbin_range (P->size)                                   \
       && __builtin_expect (P->fd_nextsize != NULL, 0)) {             \
     assert (P->fd_nextsize->bk_nextsize == P);                       \
     assert (P->bk_nextsize->fd_nextsize == P);                       \
     if (FD->fd_nextsize == NULL) {                                   \
[...]
     } else {                                                         \
       P->fd_nextsize->bk_nextsize = P->bk_nextsize;                  \
       P->bk_nextsize->fd_nextsize = P->fd_nextsize;                  \
[...]

We see that cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 main doubly linked list is checked in a way that makes it hard for us to write to arbitrary locations. But cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 special doubly linked list for larger allocations has only some debug asserts for cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 same type of checks. (Aside: cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365re’s some evidence that Ubuntu glibc builds might compile cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365se asserts in, even for release builds. Fedora certainly does not.) So we craft our fake malloc header structure so that cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 main forward and back pointers point back to itself, and so that cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 size is large enough to enter cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 secondary linked list manipulation. This bypasses cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 main linked list corruption check, but allows us to provide arbitrary values for cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 secondary linked list. These arbitrary values let us write an arbitrary 4-byte value to an arbitrary 4-byte address, but with a very significant limitation: cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 value we write must itself be a valid writeable address, on account of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 double linking of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 linked list. i.e. after we write our arbitrary value of P->bk_nextsize to P->fd_nextsize, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 value P->bk_nextsize is itself dereferenced and written to.

This limitation does provide a headache. At this point in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 process’ lifetime, it is printing an error message just before it frees a few things up and exits. There are not a huge number of opportunities to gain control of code execution, and our corruption primitive does not let us directly overwrite a function pointer with anocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r, different pointer to code. To get around this, we note that cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365re are two important glibc static data structure pointers that indirectly control some code that gets run during cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 exit() process: __exit_funcs and tls_dtor_list. __exit_funcs does not work well for us because cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 structure contains an enum value that has to be some small number like 0x00000002 in order to be useful to us. It is hard for us to construct fake structures that contain NUL bytes in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365m because our building block is cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 NUL-terminated string. But tls_dtor_list is ideal for us. It is a singly linked list that runs at exit() time, and for every list entry, an arbitrary function pointer is called with an arbitrary value (which has to be a pointer due to previous contraints)! It’s an easy version of ROP.

Step 8: Deploy a chroot() trick

For our first attempt to take control of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 program, we simply call system(“/bin/bash”). This doesn’t work because this construct ends up dropping privileges. It is a bit disappointing to go to so much trouble to run arbitrary code, only to end up with a shell running at our original privilege level.

The deployed solution is to chain in a call to chroot() before cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 call to system(). This means that when system() executes /bin/sh, it will do so inside a chroot we have set up to contain our own /bin/sh program. Inside our fake /bin/sh, we will end up running with effective root privilege. So we switch to real root privilege by calling setuid(0) and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n execute a real shell.

TL;DR: Done! We escalated from a normal user account to root privileges.

Step 9: Tea and medals; reflect

The main point of going to all this effort is to steer industry narrative away from quibbling about whecá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r a given bug might be exploitable or not. In this specific instance, we took a very subtle memory corruption with poor levels of attacker control over cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 overflow, poor levels of attacker control over cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 heap state, poor levels of attacker control over important heap content and poor levels of attacker control over program flow.

Yet still we were able to produce a decently reliable exploit! And cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365re’s a long history of this over cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 evolution of exploitation: proclamations of non-exploitability that end up being neicá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r advisable nor correct. Furcá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365rmore, arguments over exploitability burn time and energy that could be better spent protecting users by getting on with shipping fixes.

Aside from fixing cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 immediate glibc memory corruption issue, this investigation led to additional observations and recommendations:

  • Memory leaks in setuid binaries are surprisingly dangerous because cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365y can provide a heap spray primitive. Fixing cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 pkexec memory leak is recommended.
  • The ability to lower ASLR strength by running setuid binaries with carefully chosen ulimits is unwanted behavior. Ideally, setuid programs would not be subject to attacker-chosen ulimit values. There’s a long history of attacks along cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365se lines, such as this recent file size limit attack. Ocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r unresolved issues include cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 ability to fail specific allocations or fail specific file opens via carefully chosen RLIMIT_AS or RLIMIT_NOFILE values.
  • The exploit would have been complicated significantly if cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 malloc main linked listed hardening was also applied to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 secondary linked list for large chunks. Elevating cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 assert() to a full runtime check is recommended.
  • We also noticed a few environment variables that give cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 attacker unnecessary options to control program behavior, e.g. G_SLICE letting cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 attacker control properties of memory allocation. There have been interesting historical instances where controlling such properties assisted exploitation such as this traceroute exploit from 2000. We recommend closing cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365se newer routes too.

I hope you enjoyed this write-up as much as I enjoyed developing cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 exploit! There’s probably a simple trick that I’ve missed to make a much simpler exploit. If you discover that this is indeed cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 case, or if you pursue a 64-bit exploit, please get in touch! For top-notch work, we’d love to feature a guest blog post.

Thursday, August 21, 2014

What does a pointer look like, anyway?

Posted by Chris Evans, Renderer of Modern Art



These updates resolve memory leakage vulnerabilities that could be used to bypass memory address randomization (CVE-2014-0540, CVE-2014-0542, CVE-2014-0543, CVE-2014-0544, CVE-2014-0545).

I reported cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 latter four of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365se. I’d like to thank Adobe for fixing cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365m so quickly -- about 30 days between report and broad availability of a patch. That’s well within Project Zero’s 90-day deadline on bug disclosure.
They are all interesting bugs in image decoding, leading to uninitialized pixels in image canvases. There exist rich ActionScript APIs to read pixels out of canvases, so any uninitialized data can be examined easily by cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 attacker. Let’s take a visual tour of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 four bugs:

CVE-2014-0542  [link to our public bug]

flashleak.png
The image above, which includes cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 “got probable pointer” text, is a sample rendering from running cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 proof of concept file. The trigger for this bug is a particularly weird JPEG image: one with two color components per pixel. Most images have one component per pixel (greyscale) or three components per pixel (red, green, blue or ocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r scheme). In cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 case of two color components, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 image processing “gave up” on rendering cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 foreign image. This left completely uninitialized RGB (red, green, blue) values in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 canvas, thus leaking cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 contents of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 memory that was last allocated where cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 new canvas now is.

If you look at cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 probable pointer value, you’ll see that one byte is marked as “??”. This is because cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 canvas is in fact RGBA (red, green, blue, alpha) and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 alpha component -- which is every fourth byte -- is initialized to a constant 0xff value.
CVE-2014-0543  [link to our public bug]

1bpp.png
The trigger for this bug is a 1bpp image. 1bpp is shorthand for “one bit per pixel”. If we just have 1 bit to represent every pixel, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 image should only contain two different colors, one color for pixels where cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 bit is 0 and anocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r color for pixels where cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 bit is 1.

Looking at cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 image, we have a rich range of colors so right away we know something is wrong. What has happened here is that 1bpp images can be declared in a SWF image tag, but cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365y are not supported. Upon hitting cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 unsupported image, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 per-row conversion code “gives up”, leaving cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 row buffer uninitialized. Since cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365re is just one row buffer (it is reused every row), cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 image looks very distinct. Every row is cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 same.

The long hexadecimal number below cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 image represents a bit of uninitialized data pulled out of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 image canvas and into script.
CVE-2014-0544  [link to our public bug]

8bppleak.png
This bug resolves cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 question “what does a pointer look like?” cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 most definitively. It also leaks contiguous uninitialized memory very clearly, making it cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 most powerful leak yet. There is no “??” in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 extracted pointer value, because cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365re is no uncertainty.

The bug works by rendering image pixels that are compressed via cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 zlib compression algorithm. This particular image is a 64x64 canvas, which requires 4096 pixels to fill. The trick we pull is to terminate cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 zlib stream immediately, after emitting exactly 0 pixels. This leaves cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 canvas… you guessed it… uninitialized.

If you look at cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 image, you’ll notice it appears to be comprised of columns. This effect is because cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 width is 64 pixels, and a pointer is 8 bytes (cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365se images are all rendered on 64-bit Linux). Since 64 is an exact multiple of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 pointer size, any pointers will be nicely aligned. And this image is chock full of pointers. A pointer value on 64-bit Linux is particularly visible because cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 two most significant bytes are zero (rendered as black), leading to distinct vertical black bars.
CVE-2014-0545  [link to our public bug]

jpgalpha.png
This final bug is also a very powerful memory leak, as evidenced by cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 complete pointer value extracted. It also works by embedding truncated zlib stream in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 image. In this case, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 truncated zlib stream (again truncated a 0 pixels) is for cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 image’s alpha channel. By extracting only cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 alpha channel byte values from cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 rendered image, we can recover cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 content of contiguous uninitialized memory.

Conclusion

My personal conclusion is that cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365re’s a strange beauty to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 visualization of uninitialized memory and pointer values. I hope you enjoyed cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365se slightly unusual bugs.