Escaping Adobe Sandbox: Exploiting an Integer Overflow in Microsoft Windows Crypto Provider

We discussed this vulnerability during Episode 204 on 11 April 2023

I thought this was an excellent post when it came to explaining the exploitation strategy, and has it dealt with encrypted pointers the exploitation was pretty cool to see documented. However I did have some problems following on the actual vulnerability details.

Getting started though, this is a sandbox escape, it is assuming you have code execution within the Adobe Sandbox, and now the goal is to exploit the Adobe Sandbox Broker which is the middle-man between the sandbox and any outside information and is a more privileged application. Communication between the sandbox and the broker happens through a 2MB piece of shared memory, the broker tracks the state of its processing of any message in there, and the sandbox can send a signal to the broker when it has written something new to be processed.

Vulnerability

The broker provides a number of cross-calls which are calls exposed to the sandboxed process and end up calling external services. Of interest here is the Windows Crypto Provider, a set of libraries that provide common cryptographic algorithms. The broker provides a number of Crypto related cross-calls that call into the Crypto Provider Service (CryptSP). Specifically it is the CryptImportKey() cross-call that calls the CryptSP::CryptImportKey() method, eventually leading to ImportOpaqueBlob()the vulnerable function in rsaenh.dll. When importing a key, ImportOpaqueBlob will a couple values from the blob, which are used in some arithmetic to determine how large of a buffer to allocate. Without any integer overflow checks, a crafted blob could overflow that size to be allocated resulting in too small of an allocation and a linear write into adjacent memory

Exploitation

Being a security minded component, the usual security features are enabled like ASLR and DEP, there is also Control Flow Guard, and CryptSP context object, uses an encrypted pointer. Looking at the CryptSP context object that is necessary to make CryptSP calls. It is instantiated by a call to CryptAcquireContext() which returns the context object. The context object itself is a simple C structure containing a number of function pointers to exposed methods and the “real” provider object that is used internally, which is stored with an encrypted pointer. When it calls any of the provider methods, the first argument is always the encrypted pointer.

Arbitrary Function Call - This provides the first primitive of note, corrupting the context object you can corrupt the function pointer and encrypted pointer giving you control over a function call target and the first argument.

Identifying Corrupting Objects - The Broker provides access to the CryptGenRandom() cross-call, which is supposed to return a random value, but one can use the overflow to replace its function pointer with a function that returns a known value. Then call CryptGenRandom on all of the context objects in the shared memory block to see which one returns the known value. This is a nice trick to, atleast in part, avoid the need to break ASLR. Instead you spray many objects, and corrupt one at random, then detect which object you corrupted.

Lastly I like the trick used here to deal with the encrypted pointer as first argument setup. They target the CryptReleaseContext() cross-call, which calls CPReleaseContext with the encrypted pointer. Overwrite the CPReleaseContext function pointer with WinExec and the for the argument you overwrite the encrypted pointer object with the pointer to a previously corrupted context object with your desired string as the first contents. So you still use an encrypted object, its just that its encrypted and pointing to data you control, pretty cool. Sorry if I didn’t do this trick justice trying to explain it here, but I think they do a solid job in the original post. They also dive more into some of the spraying techniques and more details about exploitation that I’ve glossed over.