Authenticode is a Microsoft code signing technology designed to guarantee the origin and integrity of an application. The core principle of its integrity verification system is code immutability. In other words, once an application is signed, its code cannot change without breaking the envelope integrity. This way the user is guaranteed that the only code they are executing is the one created by the software publisher that signed it.
Authenticode is a very well-designed system. Its plethora of verification checks --- before and after the code is signed --- take into consideration a huge number of possible misuses. However, both Authenticode and the executable format it is signing are extremely complex in their design. And the more moving parts a system has, the more likely it is that one of them will fail. Breaking the Authenticode security model equals attacking the complexity it is designed to protect.
A typical portable executable file consists of headers followed by an arbitrary number of sections and extra data appended just after them. This extra appended data is called an overlay, and it is not a part of the image once the file is loaded into memory. Overlay is where the Authenticode signature resides when the executable image is signed. Since an Authenticode signature contains a validation hash of the content that precedes it, no code can be changed without breaking the envelope integrity.
Due to the specifics of the portable executable format, two very small regions are excluded from the integrity check: the checksum of the portable executable (a four-byte region), and the information about the Authenticode signature (an eight-byte structure containing its location and size). Neither of those regions has anything to do with the application code.
The location and size of the digital certificate are intentionally skipped because the Authenticode signature needs to be modified after the file is signed. Even though it sounds counterintuitive, those expansions enhance the quality and the overall security of the Authenticode signature. Expansion enables concepts such as cross-signing for time validation, and dual-signing for hash algorithm strength improvements, among other things. However, this also makes it easy to expand the region after the signature without breaking the envelope integrity.
This expansion possibility still doesn’t matter, as everything after the Authenticode signature is still just overlay- part of the file that will never be loaded into memory. The content that cannot host, influence, or replace the main application code. That’s how the portable executable (PE) format should work. Yet there’s a but lurking in here.
The portable executable format is the most complex executable format in use today. Its documentation and implementation differ in many aspects. The implementation, or rather the operating system application loading procedure, has been made fault-tolerant for backward compatibility reasons. Those reasons make it possible to create an application that can, in fact, load the overlay into memory and make it part of the image. Once that is possible, all the previous security and integrity validation checks relying on these assumptions are broken. The executable code can be changed after signing with its signature integrity intact.
Programming is an exercise in handling edge cases. Regardless of what the program calculates, there are always exceptions that apply to cases of the finitely large and the discreetly small. How these edges get handled often defines the security boundary around which the systems get designed. Breaking the Authenticode security model is attacking the handling of the discreetly small.
Consider the following portable executable file as an example implementation of this attack.
This small portable executable file is a valid and PECOFF standard-compliant image. It's rather small in size; with headers and a single section, it is exactly 1,024 bytes long. The complete image even fits in a single smallest allocation unit. However, there’s a big problem with this kind of image.
The operating system loader mistakenly maps additional data, found after the first section, in memory. Because of this, the overlay becomes part of the image as well. In terms of its location, it gets placed perfectly - into the region of memory that can be executed. Thus the barrier between the image and its overlay gets broken, and the assumptions made by the Authenticode signing process are violated.
Since the Authenticode signature can be expanded, everything behind it also becomes part of the image in memory. This allows arbitrary unverified code and data to be placed there, making the creation of the following portable executable possible.
This application is created in two phases: the first before signing, and the second after. Alternatively, these phases can be named "preparation" and "execution".
Before the file is signed, it is prepared so that the application expects to find code and dependency information in the region of the file that doesn’t yet exist. Those memory pointers are pointing to nowhere at this point. But they are carefully calculated to ensure that the expanded region would be able to host them when the image is signed.
After the file is signed, the Authenticode signature is expanded, and the necessary code and data get placed into the precalculated locations. This completes the application and makes it an executable image that can run.
Since the code is placed in the region of the file not covered by Authenticode integrity checks, it can be arbitrarily changed at will.
For demonstration purposes, such an application is created and signed with a self-signed certificate. Certificate type makes no difference for this attack, as the Authenticode signature can be expanded by an arbitrary number of bytes. Consequently, there’s enough room to fit any kind of certificate, including the extended validation certificate, complete with the optional cross-sign timestamping if necessary.
The following image shows this file, its hash, the Authenticode signature validation response, and the behavior of the application when it is launched.
The application can further be modified so that the properties of the message dialog and the text it is showing are different. Furthermore, making such modifications doesn’t break the envelope integrity because of the reasons stated earlier.
The following image shows that, even though the application code has been changed, its Authenticode hash remains the same.
These are two different files, and they exhibit different behavior when executed. However, they do share the same Authenticode integrity hash. This clearly shows that Authenticode integrity can be bypassed to allow application code changes.
While interesting, this attack isn’t very practical. Because there’s a preparation phase involved, no existing Authenticode signature can be modified without breaking the envelope integrity. Every application signed before this publication remains just as secure as it ever was. Following the best development practices and secure development lifecycle procedures will still result in organizations publishing safe code.
ReversingLabs solutions understand the dangers of extended Authenticode signatures, and they actively prohibit whitelisting when such cases are encountered. More importantly, they alert the developers that such behavior introduces risk, and that it should be avoided. Those notifications are just a small piece of a larger picture in which ReversingLabs solutions enhance secure code development.
Code signing is here to stay, and doing it properly is more important than ever.
Windows Vista (not affected)
Windows 7 (not affected)
06/03/2019 - Contacted Microsoft MSRC
06/13/2019 - Contacted Microsoft MSRC again
08/19/2019 - Contacted Microsoft via Twitter
08/30/2019 - Microsoft confirms the issue and will work on bugfix in a future release
signed.A.exe - 4CE45465ADD315A46B81B9B40B858E1FCD5DBF9F8A6E9693BE91254A967DF3D2
signed.B.exe - F63DDBD342319F91460B46B6DF072B43289028A4D110054A61AFF600BFC798A4