2012 saw a spate of vulnerabilities in the Remote Desktop protocol implementation in Microsoft Windows.
One of these issues was MS12-020; a ‘double free’ issue in releasing memory allocated from the handling of channel join requests. A ‘double free’ issue occurs when a program attempts to release memory that has already been released, thus they can lead to undefined behaviour or the execution of arbitrary code.
In this case, a remote attacker can join channels not assigned to their user identifier, causing multiple pointers to the same channel object in memory, which lacks reference counting.
When the attacker disconnects, the list of channels is walked, and the allocated memory released. The result turned out to be a ‘double free’ with a very tight use-after-free potential.
The issue can be summarised as follows:
- Send one user request. The server replies with a user id (call it A) and a channel for that user.
- Send another user request. The server replies with another user id (call it B) and another channel.
- Send a channel join request with requesting user set to A and requesting channel set to B. If the server replies with a success message, we conclude that the server is vulnerable.
- In case the server is vulnerable, send a channel join request with the requesting user set to B and requesting channel set to B to prevent the chance of a crash.
The conditions on lines 260 and 264 are curious in that they are identical. The semantics of the ASSERT statement is to realise a BSOD (Blue Screen of Death) should the condition fail to hold, albeit in debug builds of the code.
As such, if the condition on line 260 fails to hold, then the code at line 264 will never be executed. So why the same condition?
The only explanation is in a failure to copy and paste, where the developer copied line 260 and failed to change the condition to the correct one. This permitted the condition to succeed and the inadvertent behaviour to be performed.
The unexpected behaviour was to permit a remote user to join a channel belonging to another user. This facilitated the ‘double free’, since the user will have a pointer in their channel list pointing to the channel of the other user. Should the user disconnect, the channel list will be freed, which would free every item in the list and deallocate the pointer to the channel that is still in the channel list of the other user.
Looking at the code in question and understanding the semantics of the issue, it is clear that the above code should have been something along the lines of:
That is, the channel identifier should have been checked against the one assigned to the user.
This was later confirmed via an analysis of the patch. The analysis said: “rdpwd!MCSChannelJoinRequest(). There were some problems in handling channel id. Note that each channel seems to have a corresponding entry in the link list.”
The patch changed a condition to dereference the pointer stored in the register ‘esi’; this is the pointer ‘pUA’ in the code given above. Accessing the pointer at an offset of 12-bytes, corresponding to the value ‘UserID’ in the UserAttachment structure.
As it happens, the author of the analysis was close to the root cause of the issue!
What he described as “there were some problems in handling channel id”, was the problem.
And the problem was a copy and paste.