Expert: Miami

Tuesday, August 14, 2018

Alright, I am breaking a 3-year-posting-slumber here. Don't get too excited, I am probably not going to post regularly but I will try and share some security and/or allocator related thoughts here.

One of the novelties introduced by C++14 was sized delete operators. Taking an extra size_t parameter, those are meant for efficiency purposes, allowing to avoid a potentially costly lookup of the size of a chunk, to quote N3536:

Modern memory allocators often allocate in size categories, and, for space efficiency reasons, do not store the size of the object near the object. Deallocation then requires searching for the size category store that contains the object. This search can be expensive, particularly as the search data structures are often not in memory caches.

And this is indeed the case. While someone can directly call the sized delete operator, it's usually up to the compiler to the heavy lifting, specifying the command line flag -fsized-deallocation; but it is usually enabled for -std=c++14 and above (see gcc c++ dialect options).

So what happens on the allocator side when the sized deallocation function is used? The allocator usually has fast path function that will use the size provided to look up where the chunk will end up (see tc_free_sized for tcmalloc, je_sdallocx for jemalloc). That's great, no size to compute for a given pointer, it's faster. But it implies that the compiler gets it right all the time (or that a programmer doesn't blindly call the sized operator with a wrong size, or that a malicious user doesn't pass a mismatched pointer to a sized deallocation function), otherwise the deleted chunk ends up in the wrong bin/freelist/*, and when it's later returned to fulfill an allocation, something bad is likely to happen.

My catastrophic thinking self expected this was going to go wrong at some point, but as far as I can tell, there was nothing much in the world of exploitable bugs related to this, except for the early implementation hiccups.

ASan's allocator has an optional check for this, and so does Scudo (an allocator I work on): if the size passed to the deallocation doesn't match the one of the chunk being deallocated, kill things as something is terribly wrong somewhere (but do not trust the size passed in any case - so much for efficiency 😕).

But then a few days ago, it was pointed out that the Intel Compiler was totally messing up the sized deallocation (see the compiled code). The consequences of this are entirely dependent on the allocator being used at runtime, and it looks like for most this could just result in some wasted memory (a large chunk ending up in a smaller bin), but that likely requires some additional digging (TODO(cryptoad) I guess). Anyway, if you compiled anything with ICC 18.0.0 in C++14 mode, update your compiler and recompile your binaries!

The reporter found the issue using Scudo, and it makes me somewhat happy that the check found a meaningful justification. Anyway, if you have examples of a sized deallocation gone wrong, feel free to chime in.

Thursday, August 6, 2015

Here is another issue in avast!, in the GUI AvastUI.exe. It allowed arbitrary code execution within the context of that trusted process, and as such EoP, self-protection bypass, etc. Exploit is provided. It was fixed about a year ago by the avast! crew.

Foreword

It's been a while since I had used a shatter attack for an interesting purpose! Trendy about 10 years ago (according to Wikipedia), they allowed privilege escalation thanks to core components of Windows like with MS02-071. They are mostly extinct due to Windows now restricting the messages sent to more privileged processes, or isolation of services in session 0. An old but very good presentation of the excellent Brett Moore explains them in detail.

But the problem resurfaces when a process attempts to introduce home-made integrity levels, while functioning as the current logged in user (and at the same IL). This new security boundary can be shattered thanks to Windows messages.

Description

Since we are running in the same context as AvastUI.exe, we can pretty much send any window message to its windows. This appears to be something that the developers didn't think about. For example, the window corresponding to the window class asw_av_tray_icon_wndclass accepts quite a bit of user messages. The following piece of code handles the message 0x83fd:

As you can see, this handler will interpret wParam as a function pointer and lParam as its first and only argument and call it. This obviously becomes an issue when the message is sent by a 3rd party application as it pretty much guarantees code execution within the AvastUI.exe process.

This call primitive is ideal to execute a function like LoadLibrary. We have to make the first parameter point to a string locating the DLL on the drive. Given that we are local, and that Windows doesn't do per-process randomization of DLLs, we already know the address of LoadLibraryA.

But one has to be a bit imaginative to know how to place the string into the AvastUI.exe process memory at a known location. One of the solutions that I found (that restricts the path to the DLL to *44* bytes), is to use a functionality that would put memory under our control at a known offset into

the .data section of AvastUI.exe. This requires some interaction with the named pipe \\.\pipe\snx_sdesktop_pipe. The process AvastUI.exe creates 10 of those, and reads from them in the following code:

.text:0054E159 mov ecx, [ebp+var_30]

.text:0054E15C push 0 ; lpOverlapped

.text:0054E15E shl ecx, 4

.text:0054E161 add ecx, [ebp+var_30]

.text:0054E164 lea eax, [ebp+var_8]

.text:0054E167 push eax ; lpNumberOfBytesRead

.text:0054E168 push 44 ; nNumberOfBytesToRead

.text:0054E16A lea eax, (g_NamedPipeStructures+4)[ecx*4]

.text:0054E171 push eax ; lpBuffer

.text:0054E172 push g_NamedPipeStructures[ecx*4] ; hFile

.text:0054E179 call ds:ReadFile

What I called g_NamedPipeStructures is located in the .data section of AvastUI.exe and is an array of 10 structures containing the handle to the pipes followed by a 44 byte array receiving the information read from the pipe.

In order to know where this structure is located in AvastUI.exe, we load the binary within our process and locate the structure thanks to a code signature. If there is no address space collision that would trigger a remapping elsewhere, that address will be the same in the remote process. We then open the 10 named pipes and write the DLL path to them to make sure all the structures will be filled with our data. Then we locate the window, and send it the window message with LoadLibraryA as wParam and the 1st structure address as lParam. This will load the DLL within the AvastUI.exe process.

In my exploit, the DLL in question will spawn a cmd.exe and call the IOCTL to make it trusted. Obviously raising a cmd.exe to trusted doesn't make much sense in a real world exploitation scenario, this is just more of a visual example.

Tuesday, August 4, 2015

Here is a new bug, this time in English. Since most of the logic issues have been dealt with, this one will be a memory corruption, with exploit. Once again, it was patched about a year ago by the avast! team.

Summary

Description

The ashTaskEx.dll implements an RPC interface that is bound to a local ncalrpc endpoint, this interface being 908d4c23-138f-4ac5-af4a-08584ae7c67b v1.0. Most of the functions offered by this interface do not enforce any specific checks and are accessible by unprivileged local users. Those functions are processed within the AvastSvc.exe binary, which runs as SYSTEM.

The function with opcode 8 of this interface has the following IDL prototype (note that the function name is mine, not a symbol):

Edit: opcode 7 has the exact same vulnerability, with a different GUID check, and the exploit below is for that function.
If the comparison succeeds, it will process to copying the second string into a stack buffer:

As you can see here, the destination buffer var_214 is located on the stack, and can hold at most 0x210 bytes before reaching the stack cookie. The copy operation looks like a an inlined wcscpy. There is no check on the length of the string prior to copy.

This results in a stack overflow condition, that can be exploited to achieve code execution and EoP to SYSTEM. Note that the /GS cookie check has to be bypassed to achieve this, which requires exploiting the exception handler or disclosing memory.

A heap overflow will also happen in the subfunction called by tskexStartRescueDiscToolkitImpl if the string we sent is too large, but not large enough to reach the end of the stack. It only allocates 0x4e8 bytes for the structure the string is copied in:

Remote exploitation

While this bug is a default local EoP on avast! Free, if the Chest remote RPC endpoint (ncacn_ip_tcp) is enabled (either in avast! Endpoint Protection or by playing with the .ini files), then this bug becomes an RCE. See the following MSDN entry about this:

"Be Wary of Other RPC Endpoints Running in the Same Process"
http://msdn.microsoft.com/en-us/library/windows/desktop/aa373564(v=vs.85).aspx

Exploit

Here are some explanations:

we exploit a stack overflow in an LPC interface offered by ashTaskEx.dll;

this function is protected by a /GS cookie, so the usual route is to go through overwriting the exception handler, which on newer platforms requires to use a handler in a binary not protected by SafeSEH (this assumes that we overflow enough to get a memory access violation prior to the cookie being checked);

algo.dll is not SafeSEH protected. algo.dll is shipped with definitions, so I attempted my best to do something decently generic that will locate the latest version of algo.dll by looking up some registry keys and entries in the .INI files;

we want the overwritten exception handler to point to a gadget into algo.dll that somewhat restores the stack pointer to somewhere under our control. Luckily the DLL contains quite a lot of add esp,const & retn that will do that (with const in a ~800h-~1000h range);

we load algo.dll in our process, and look for that gadget. It is to be noted that given how Windows works, the base address of algo.dll in our process will be the same than in AvastSvc.exe unless we are quite unlucky;

at this point, we just have to build a ROP chain that will do something interesting;

since we are local, I decided to do something that would LoadLibrary a DLL under my control. To do so, I make one of the registers point to one of the strings sent into the RPC request (the one that didn't overlow) with some basic additions, copy it in some safe place (the .data section of algo.dll), restore a register to LoadLibraryW and trigger a push & call combination that will load the library as SYSTEM;

the library just creates a cmd.exe as SYSTEM on WinSta0 (you need to click a dialog to see it but at this point you see that it's won);

DeepScreen might be annoying and block access to the files, so run it without parameters for the first time to just load the DLL in the current process, and once DeepScreen is happy, run it again with 'run' as parameter to trigger the overflow. The irony here is that the overflow can happen within the DeepScreen sandbox, even if the original ends up being blocked!

Some constants that you might need to adjust based on your platform:

FillMemory( pbBuffer, 0x1000, 'A' );

Our overflowing buffer will be 0x1000 bytes. In most cases it's enough to go past the end of the stack and trigger an AV, but sometimes there is another page (or several) after the stack and that size might have to be increased.

Here, we require that esp+0x818 at the time of the exception handling lands at 0x20c from the beginning of our buffer. The other requirement is that our second string is at 0x3dc (-0xfffffc24) bytes from ebp at the time of the exception handling. Those are pretty much the only things that can differ from one platform to another given the same ashTaskEx.dll version.

We restore eax from ebp, restore ecx from the stack, subtract ecx from eax, withsome trash ending up in ebx. Then we set eax, esi and ebp so that we can call a memcpy gadget that copies our string into the .data section of the algo.dllbinary. We then call LoadLibraryW on our DLL, and ExitProcess gracefully.

I spent some time quite a while ago looking into avast! and, after about a year, I am going to post about the issues that were found, and fixed back then. The whole project was pretty fun, avast! offers a lot of functionalities and as such a ton of components to look into. Identifying the security boundaries and attack surface required a decent understanding of the product: services, opened ports, LPC or RPC interfaces, kernel drivers and their IO controls or filters, browser components, various parsers, "self-defense", etc.

A decent number of issues were found, and Igor and the avast! bug bounty team fixed them promptly, and extensively - I think they did a great job at not concentrating on the specific issues submitted but thinking about the bigger picture, variants and remediation.

I will start with one of the juicier ones, as it allowed RCE from a browser.

avast! Client-Side Remote Code Execution

Summary

Description

avast! offers a SafeZone functionality that creates a separate desktop, and runs processes in a sandboxed environment on this desktop. This SafeZone component comes with a SafeZone Browser that basically is Google Chrome. The SafeZone component is not available in the Free version of avast!, as the browser is not installed, and the UI interface doesn't allow to switch to the SafeZone. Yet, the core functionalities of the SafeZone are present and the SafeZone can be launched, through LPC, the local HTTP Daemon, or browser addons.

There are two issues that can be leveraged to achieve code execution, both of them resulting from an injection in the command line of the SafeZone browser. In order to demonstrate the issue, we will use the avastBHO.SwitchToSafezone API in Javascript for the IE BHO. This is accessible when the extension is enabled (default), and for all websites except a few local exceptions.

Let's start with the Free version of avast!. In this version, the SafeZone browser is not installed, so when trying to switch to the SafeZone, Windows will attempt to parse the command line to find the binary. Here is the output of ProcessMonitor illustrating this:

As you can see, this becomes an issue because each component of the command line will end up being considered a directory at some point. It is trivial to go up in the tree using the usual ..\ sequence:

This will download and execute stage1.exe, still in the SafeZone. At this point, we can execute whatever we want, but are still restrained to the sandboxed environment of the SafeZone (file system, registry and privileges).

The avastBHO object can become unreachable after some attempts, and IE has to be relaunched.

Escaping the SafeZone

For the sake of completeness, I wrote up a quick SafeZone escape through Windows messages. Doing a CreateProcess from stage1.exe or equivalent still has us stuck in the SafeZone file system. Since we can switch desktop easily, the provided escape just switches back to the Default desktop, pops-up the Windows Run dialog by sending a WM_HOTKEY message, sets the content of the edit box to the command to execute, and presses OK. It can be a bit racey at times, and could use some Q&A, but it's working often enough.

What about Chrome/Firefox/etc?

Extensions for Chrome and Firefox appear to offer the same SwitchToSafezone functionality but I am actually not sure on how to script them or if they are even accessible. Those extensions merely wrap HTTP requests to the local HttpDaemon running in AvastSvc.exe on port 27275, which uses Google's libprotobuf-lite. A switch to SafeZone can be triggered by issuing command 7 to that server, and this vector is also vulnerable to the command injection.

What about non-free versions of avast!?

Now for avast! Premier and other versions including the browser, we cannot use the ..\ trick as it will not succeed since the binary exists, but we can inject an argument to Chrome. The argument that matters is the --load-extension one, which will attempt to load an extension, and accepts UNC paths, for example you can try: