Privilege Escalation using the Use-After-Free (UAF) vulnerability in the Linux Kernel

Source: Internet
Author: User
Tags cve

Privilege Escalation using the Use-After-Free (UAF) vulnerability in the Linux Kernel

Last month, the CVE-2016-0728 Local Elevation of Privilege Vulnerability let everyone's eyes again focused on Linux kernel security. Like CVE-2015-3636, CVE-2015-7312, and CVE-2014-2851, CVE-2016-0728 is a Use-After-Free (UAF) type vulnerability. We know that the culprit of UAF is Dangling pointer ). After the allocated memory is released, its pointer does not change to NULL because the memory is released, but continues to point to the released memory. However, not all lost pointers are dangerous, and they are also benign and malignant:

· Benign lost pointer: This pointer will no longer be used.

· Vicious lost pointer: This pointer is also used to read and write the released memory.

UAF can be triggered only by a vicious lost pointer (considering the definition of UAF ). Unless otherwise stated, all the lost pointers in this article are vicious lost pointers. Let's look at an example first.

Struct Object1_struct{         int flag;         void (*func1)();         char message[256];}OBJECT1; Struct Object2_struct{         int flag;         int flag2;         char welcome[256];}OBJECT2;  pObject1 = (OBJECT1 *) malloc( sizeof(OBJECT1));// …initialization…// …pass values…// … use ……free(pObject1);…pObject2 = (OBJECT2 *) malloc( sizeof(OBJECT2));…if(pObject1 != NULL)         pObject1->pfunc1();

In the above Code, pObject1 is a lost pointer, because it is not set to NULL after release, and the member function it points to in the subsequent code is called again. The following two situations may occur:

· Although the memory space has been recycled by the system, it has not been used for another use. The data in the memory is still the original data. This call may return normal results.

· The memory space has been moved for other users. If it happens to be stored in the object2 object (pObject1 and pObject2 point to the same address ). Note that the new data and the original data are not of the same type. In this case, call pObject1-> pfunc1 (); to interpret an object2 object in the object1 structure, so that the EIP can jump to a strange address (the address is the flag2 value in object2 ).

If the value of flag2 is an unexecutable address, the system generates an accessviolation error.

If the flag2 value is an executable address, the EIP will jump to and execute it. In this way, If attackers have the opportunity to carefully construct the flag2 in Object2, they can successfully implement the Program jump.

To sum up, using a UAF vulnerability requires three phases:

1. Get a lost pointer first.

2. Fill in the released memory area with specially constructed data.

3. Use the pointer again to redirect the eip to the filled data.

If the UAF vulnerability exists in the kernel, and the attacker can trigger the preceding three steps from the user space, the attacker will have the opportunity to execute the Code designed by himself in the kernel to gain higher permissions. Of course, the difficulty is that attackers can only use limited APIs to operate kernel objects in user space. This is a game rule developed by the designer. We just need to use these limited game rules to trigger vulnerabilities and achieve our goal: Elevation of Privilege!

Phase 1 generates a lost pointer

The Linux kernel uses the garbage collection mechanism to release kernel objects. Each object has a reference counter (refcount ). During object initialization, refcount is set to 1. When the operation involved in the object is executed, add refcount to 1 and reduce refcount to 1 at the end of the operation. In addition, when object B is referenced by object, add 1 to refcount of object B, end the reference, and subtract 1 from refcount. When the refcount value is 0, the system knows that the object is no longer in use and automatically releases it. For example, when a file system is installed on the system, it cannot be released. When the file system is no longer used, the refcount is changed to 0, so it can be released. The above operations can be expressed simply by these three functions. See https://www.kernel.org/doc/Documentation/kref.txt for details

void kref_init(k)               k->refcount = 1;void kref_get(k)                k->refcount++;void kref_put(k, release)      if (!--k->refcount) release(k); void release(k) {    struct my_obj *obj = container_of(k, struct my_obj, refcount);    kfree(obj);} 

By tracking the usage of kernel objects, the garbage collection mechanism solves the UAF problem in a sense: only when an object is determined to be no longer in use can it be released. That is to say, even if a lost pointer is generated, it is also a benign lost pointer that does not trigger UAF.

However, at this time, the operation on refcount is crucial. Imagine that if the operation is not added, the operation should not be reduced. In this case, the refcount may be accidentally changed to 0 when the object is still in use, resulting in the memory space being released, forming a vicious lost pointer. The following are two examples of this situation:

No reduction of the time, CVE-2016-0728 (http://perception-point.io/2016/01/14/analysis-and-exploitation-of-a-linux-kernel-vulnerability-cve-2016-0728)

This vulnerability exploits the keyring object

Find the keyring named $ name; if (not found) {create a keyring named $ name; kref_init (keyring) // refcount = 1 ;} else // found kref_get (keyring) // refcount ++; if (found the keyring in use in this session) goto error2 uses keyringkref_put (keyring); // if (! -- Refcount) release (k); error2: return

 

From the above pseudo code, we can find that as long as we enter the keyring name in use in this session, the program will skip kref_put (keyring);, resulting in only increasing or decreasing refcount. So how can we change refcount to 0? Considering that the refcount type is int, we can use the integer overflow method: Let the above Code be repeatedly run 0 × 100000000 times, so that the refcount overflow will eventually become 0.

To put it bluntly, CVE-2016-0728 authors claim that the vulnerability affects 66% of android devices. However, to trigger this vulnerability, you must call the keyctl API for at least 4.2 billion times... Even the Core i7 PC took half an hour to run the 4.2 billion times, some people on the phone for a night's progress of 0.018% (http://p011ux.net/2016-01-27 ), are all android 66% devices super standby?

Should not reduce 1 when minus 1, CVE-2015-3636 (https://www.blackhat.com/docs/us-15/materials/us-15-Xu-Ah-Universal-Android-Rooting-Is-Back-wp.pdf)

When you use ICMP socket and AF_UNSPEC as parameters and call connect (), the system will jump directly to disconnect (), delete the hash of the current sock object, and reduce refcount once. The pseudo code is as follows.

In the hash list, search for the sock object sk's haslif (found) {Delete the hash from the hash list; kref_put (sk );}

If you call connect () again with the same parameters, the if statement returns FALSE because the hash has been deleted. However, due to a program bug, in a specific case, TRUE is actually returned, resulting in an additional reduction in refcount. Therefore, an attacker can set refcount to 0 by creating an ICMP socket and calling three connect () (the first connect () to generate a hash) consecutively, to release the sock object. POC:

Int sockfd = socket (AF_INET, SOCK_DGRAM, IPPROTO_ICMP); // refcount = 1; structsocksockaddr addr = {. sa_family = AF_INET}; int ret = connect (sockfd, & addr, sizeof (addr); // refcount ++; Create hashstructsockaddr _ addr = {. sa_family = AF_UNSPEC}; ret = connect (sockfd, & _ addr, sizeof (_ addr); // Delete hash; refcount --; ret = connect (sockfd, & _ addr, sizeof (_ addr); // The bug causes the hash to be deleted; refcount --; The refcount value is 0.

This vulnerability can be triggered with only four APIs, much more practical than the CVE-2016-0728.

Phase 2 fill data

At this stage, we need to fill in the memory zone released in the previous stage with self-constructed data. Similarly, due to permission restrictions, we cannot directly write data into the memory in the user space and can only use APIs for the purpose.

Since the released memory is to be recycled and reused, if we use APIs to create a new kernel object, and this kernel object is "exactly" allocated to the memory just released, isn't that our goal? However, the memory allocation mechanism is so complicated. How can new objects be allocated to the memory just released as we wish? Here we will talk about SLAB and SLUB.

SLAB is a memory management mechanism. To improve efficiency, SLAB requires the system to temporarily retain the released kernel object space so that the next application does not need to be initialized and allocated again. For example, a farmer builds a chicken cage to hold the chicken. When the chicken in the cage is sold, the farmer does not immediately destroy the cage and use it for another chicken. However, the SLAB mechanism is very picky about the types of kernel objects. Only objects of the same type and size can reuse their space. For example, a chicken cage can only hold chickens, but not ducks, although the chicken and duck are almost big. In this way, we release sock object A and create sock object B immediately. Then, B is very likely to reuse the memory occupied by. This mechanism has indeed helped us to take a big step towards our goal, but unfortunately it is not perfect. Even if we create sock object B, we may not have the opportunity to modify all the members of object B to write arbitrary data.

Compared with SLAB, SLUB has no restrictions on object types. Two objects can reuse the same memory as long as they are of the same size, regardless of whether the types are the same. In this way, both chicken and duck can be put in the same cage. That is to say, we release sock object A and then create object B immediately. As Long As A and B are of the same size (regardless of B type), B is very likely to reuse A's memory. Since B can be of any object type, we certainly want to select an object type that is easy to use. The following conditions must be met:

· The user space can control the object size

· User space can write data to this object at will

A message object is a good example that fully meets the preceding two conditions. We can use msgsnd () or sendmmsg () API in the user space to construct the kernel message object B, as long as the message size plus the size of the message header is equal to the size of object, this message may reuse the memory occupied by. The POC of CVE-2016-0728 uses this method. Https://gist.github.com/PerceptionPointTeam/18b1e86d1c0f8531ff8f)

Phase 3

As long as we use "reasonable" data to overwrite the memory, this phase is very simple. We only need to use the lost pointer so that the EIP can jump to the code we designed.

For example, the keyring object contains a function pointer pointing to the revoke () function to cancel a keyring. You can use the keyctl (KEY_REVOKE, key_name) API to call revoke (). Then, when we override the keyring object, we can control the function pointer pointing to revoke () and point it to the prepared elevation code. However, where should I put the Elevation of Privilege code?

Return-to-User (ret2usr)

The simplest way is to put the elevation code in the user space. In this way, after modifying the function pointer to the elevation code, we will hijack the EIP from the kernel to the user space. This is the easiest way, because

· Attackers have full control over the user space.

· There are many restrictions on entering the kernel from the user space, but there are almost no restrictions on entering the user space from the kernel.

For example, the poc of the CVE-2016-0728 creates a false revoke function in the user space, modifying the function pointer to let the EIP jump to this false revoke function.

void userspace_revoke(void * key) {    commit_creds(prepare_kernel_cred (0));}

This false revoke function uses a very traditional kernel authorization code. By calling commit_creds to modify the data structure of the Process creds, uid = 0 and gid = 0, the root permission is obtained.

 

However, many comments on github complain that the POC has not been successfully revoked in actual tests. Why?

· No permission to read the addresses of commit_creds and prepare_kernel_cred: Attackers need the addresses of commit_creds and prepare_kernel_cred to construct the elevation code. The address is stored in the/proc/kallsyms file. However, normal users may not be able to read the file. As a result, the authorization code cannot be constructed.

· Blocked by SMAP, SMEP/PXN: as I said just now, "There are many restrictions on entering the kernel from the user space, but there are almost no restrictions on entering the user space from the kernel ." This sentence was still true five years ago. The past experiences have brought you great security risks by allowing the kernel to enter the user space, so we have proposed the SMAP, SMEP/PXN mechanism. SMAP prevents the kernel from accessing user space data at Will (only when the RFLAGS. AC flag is 1 ). SMEP prohibits the kernel from executing user space code (PXN is an ARM version of SMEP ). Currently, most mainstream systems support SMAP, SMEP, and PXN. Therefore, ret2usr is a sunset technology. (Of course, we can also modify the startup options and add the nosmep and nosmap parameters to disable SMAP and SMEP)

Construct kernel ROP (Return Oriented Programming)

SMEP/PXN only blocks the kernel from executing user space code. In this case, we can still allow the EIP to jump freely in the kernel, that is, let the kernel execute the kernel code. Therefore, we can use kernel drop to execute the elevation code: Look for the kernel gadget (the ready-made code snippet ending with RET in the kernel). For example, we want to implement moveax, ebx, then we can look for mox eax, ebx; ret in the kernel. Suppose we found mox eax, ebx; ret in 0xffffffff8143ae19, then this gadget is: 0xffffffff8143ae19. Finally, combine the selected gadget into the ROP chain.

For example, commit_creds (prepare_kernel_cred (0); In x64 (compared with x86, function parameter transfer is no longer fully dependent on the stack, and function parameters must be saved to registers ), equivalent

Pop % rdi; ret

NULL

Prepare_kernel_cred address

Mov % rax, % rdi; ret

Commit_creds address

We can find the corresponding gadget in the kernel memory and splice them together. Then, the EIP is redirected to and executed using the lost pointer. How to use Linux kernel drop, please refer to https://cyseclabs.com/page? N = 17012016

Using physmap

Physmap is a very large continuous virtual memory space managed by the kernel (in x64 systems, physmap addressing space can reach 64 TB !). To improve efficiency, the space address and RAM address are directly mapped. The problem is that Physmap is so large that RAM is much smaller. As a result, any RAM address can find its corresponding virtual memory address in physmap. On the other hand, we know that the virtual memory of the user space will also be mapped to RAM. In this case, two virtual memory addresses (one in Physmap and the other in user space) are mapped to the same RAM address. That is to say, the code of the data we create in the user space is likely to be mapped to the physmap space.

Based on this theory, we can use mmap () to map the elevation code to the memory in the user space, and then find the corresponding copy in physmap, modify the EIP and jump to the copy. Because physmap itself is in the kernel space, SMAP, SMEP/PXN will not play a role. This method was first proposed in ret2dir: RethinkingKernel Isolation.

Own yourAndroid! The Yet Another Universal Root article introduces the new usage of physmap. In fact, this usage is implementation phase 2, filling data. Since physmap is introduced here, by the way, the central idea of this method is to use the phying of user space to physmap to fill SLAB/SLUB. In the kernel space, physmap and SLAB/SLUB are very close. Attackers first create a large number of sock objects to "raise" the SLAB/SLUB address, assign a new sock object to physmap. Subsequently, attackers can map data to physmap by calling mmap () in user space to overwrite sock objects.

1. CVE-2014-2851group_info UAF Exploitation, ttps: // cyseclabs.com/page? N = 02012016

2. LinuxKernel drop, https://cyseclabs.com/page? N = 17012016

3. ret2dir: Rethinking Kernel Isolation, http://www.cs.columbia.edu /~ Vpk/papers/ret2dir.sec14.pdf

4. analysisand exploitation of a linux kernel vulnerability (CVE-2016-0728), http://perception-point.io/2016/01/14/analysis-and-exploitation-of-a-linux-kernel-vulnerability-cve-2016-0728/

5. PXN Protection Technology Research and bypass, http://drops.wooyun.org/tips/7764

6. Ownyour Android! Yet Another Universal Root, https://www.blackhat.com/docs/us-15/materials/us-15-Xu-Ah-Universal-Android-Rooting-Is-Back-wp.pdf

 

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.