Didier Stevens

Saturday 17 January 2009

Playing With Authenticode and MD5 Collisions

Filed under: Encryption,Hacking — Didier Stevens @ 15:11

Back when I researched Microsoft’s code signing mechanism (Authenticode), I noticed it still supported MD5, but that the signtool uses SHA1 by default.

You can extract the signature from one program and inject it in another, but that signature will not be valid for the second program. The cryptographic hash of the parts of the program signed by Authenticode is different for both programs, so the signature is invalid. By default, Microsoft’s codesigning uses SHA1 to hash the program. And that’s too difficult to find a collision for. But Authenticode also supports MD5, and generating collisions for MD5 has become feasible under the right circumstances.
illustration-ab

If both programs have the same MD5 Authenticode hash, the signature can be copied from program A to program B and it will remain valid. Here is the procedure I followed to achieve this.

I start to work with the goodevil program used on this MD5 Collision site. goodevil is a schizophrenic program. It contains both good and evil in it, and it decides to execute the good part or the evil part depending on some data it caries. This data is different for both programs, and also makes that both programs have the same MD5 hash.

The MD5 collision procedure explained on Peter Selinger’s page will generate 2 different programs, good and evil, with the same MD5 hash. But this is not what I need. I need 2 different programs that generate the same MD5 hash for the byte sequences taken into account by the Authenticode signature. For a simple PE file, the PE Checksum (4 bytes) and the pointer to the digital signature (8 bytes) are not taken into account (complete details here). That shouldn’t be a surprise, because signing a PE file changes these values.

So let’s remove these bytes from PE file goodevil.exe, and call it goodevil.exe.stripped. The hash for goodevil.exe.stripped is the same as the Authenticode hash for goodevil.exe.

20090116-235321

20090116-235400

Now I can compute an MD5 collision for goodevil.exe.stripped, as explained on Peter Selinger’s page. (I could also have modified the MD5 collision programs to skip these fields, but because this is just a one shot demo, I decided not to).

After about an hour, I have 2 new files, good.exe.stripped and evil.exe.stripped, both with the same MD5 hash. I transform them back to standard-compliant PE files by adding the checksum and pointer bytes I removed (giving me good.exe and evil.exe). Now the MD5 hashes are different again, but not the Authenticode MD5 hashes (Authenticode disregards the PE checksum and the signature pointer when calculating the hash).

OK, now I sign good.exe with my own cert. But there’s a little change in the code signing procedure I explained in this other post.

One difference is that I select custom signing:

20090116-232242

This allows me to select MD5 hashing in stead of the default SHA1:

20090116-232318

OK, now good.signed.exe is signed:

20090117-000846

20090117-001029

The signature is valid, and of course, the program still works:

20090117-001333

Let’s summarize what we have. Two programs with different behavior (good.exe and evil.exe), both with the same MD5 Authenticode hash, one with a valid Authenticode signature (good.signed.exe), the other without signature.

Now I extract the signature of good.signed.exe and add it to evil.exe, saving it with the name evil.signed.exe. I use my digital signature tool disitool for this:

disitool.py copy good.signed.exe evil.exe evil.signed.exe

illustration-aa

This transfers the signature from program good.signed.exe (A) to evil.signed.exe (A’). Under normal circumstances, the signature transferred to the second program will be invalid because the second program is different and has a different hash. But this is an exceptional situation, both programs have the same Authenticode hash. Hence the signature for program evil.signed.exe (A’) is also valid:

20090117-001029

evil.signed.exe executes without problem, but does something else than good.signed.exe:

20090117-004549

This demonstrates that MD5 is also broken for Authenticode code signing, and that you shouldn’t use it. But that’s not a real problem, because Authenticode uses SHA1 by default (I had to use the signtool in wizard mode and explicitly select MD5 hashing). In command-line mode (for batching or makefiles), the signtool provides no option to select the hashing algorithm, it’s always SHA1. And yes, SHA1 is also showing some cracks, but for Authenticode, you have no other choice.

You can download the demo programs and code signing cert here.

Blog at WordPress.com.