A few days ago I published an article detailing how a second bug, in the schannel TLS handshake handling, could allow an attacker to trigger the DecodeSigAndReverse heap overflow in an application that doesn’t support client certificates. I had stated I was not familiar with ECC signatures and was unsure of how to trigger the exploit; However, a few hours research fixed that.

BeyondTrust’s post implies they triggered the overflow by randomly modifying the ECC signature, though I believe this is unlikely and was just a safer alternative to disclosing exactly how to trigger the exploit. It was possible for me to achieve remote code execution with either ASLR or DEP disabled, but on a system with both it would prove quite a challenge, thus I’m not too worried about detailing exactly how to trigger the overflow.

DecodeSigAndReverse

We already know the function in which the overflow occurs, so I decided to work backwards from there. This function is responsible for decoding the ASN.1 (DER) encoded ECC signature and returning it to be verified.

The first thing that is done here is the ECC signature is passed to CryptDecodeObject in order to calculate the total size of the decoded signature, which is used to allocate some memory using SPExternalAlloc (LocalAlloc Wrapper). CryptDecodeObject will always handle the signature correctly, with the returned size being sufficient.

CryptDecodeObject is now called again, but this time it is passed a pointer to the allocate memory in which to copy the decoded signature. The “cmp ebx, 2Fh” checks the signature type (X509_ECC_SIGNATURE) and will direct the code to the left.

The decoded signature is pointed to by an ECC_SIGNATURE header, which is 12 bytes in size an looks something like this.

What R and S are doesn’t really matter here, all we need to know is they are extremely large integers. Our ECC structure now contains the size of each integer and a pointer to where it’s stored.

The 2 memcpy operations should be pretty obvious now, the first one copies rSize bytes from R to some allocated memory, then the second copies sSize bytes of S to the same memory directly after R; If there’s going to be an overflow It’s going to be in the second memcpy. What we don’t yet know is the size of the destination memory or how it’s allocated.

All I had to do to find where the memory gets allocated was to look at the call graph, find the function responsible for coding DecodeSigAndReverse, then scout it for the “Dst” parameter.

This is where everything goes right (or wrong if you’re Microsoft). _BCryptGetProperty is being passed “KeyLength” to… Drum roll please…. get the key length. Directly below that length is being divided by 8 (converted from bits to bytes) then doubled; this is due to the fact the signature length is (should be) double the key length. Just before the call to DecodeSigAndReverse we can see that the destination buffer is also allocated on the heap.

So back at the 2 memcpys now with knowledge of the destination buffer size, we can see exactly what triggers the heap overflow. If we use a key size of 256 bit (32 bytes), then the function is expecting a 512 bit (64 byte) signature, any more will overflow the heap and when it’s freed cause a crash.

There are very few constraints on the signature, due to the fact the whole thing is just 2 massive integers. As long as we maintain a valid ASN.1 (DER) encoding and the signature is of valid size, we can write arbitrary data to the heap header resulting in an access violation or even remote code execution when the system tries to free the memory.