Issue validating signature of EFI PE executable
Don't want to file a bug for this because I'm not sure if it's a LIEF problem or a deviation from the standard of the EFI implementation of PE executables but here it goes:
There is this PE file (a EFI bootloader):
The certificate that validates this file is (not needed in this case because what fails is the signature validation but just in case):
From Python hooks, when I do:
import lief
pe = lief.parse('fwupdx64.efi')
signature = pe.signatures[0]
print(signature.check())
I get:
OK
I understand that signature.check() is not validating the actual hash on the signature and the file, so, when I do:
print(pe.verify_signature())
I get:
VERIFICATION_FLAGS.BAD_DIGEST | VERIFICATION_FLAGS.BAD_SIGNATURE
There is a common Linux tool used to validate this types of bootloaders called sbverify which code is here.
This tool uses OpenSSL to perform all the signature operations. This tools validates the signature hash correctly. The output is:
sbverify --cert cert.pem fwupdx64.efi
warning: gap in section table:
.rela : 0x0000b400 - 0x0000c400,
/4 : 0x0000c470 - 0x0000c670,
warning: gap in section table:
/4 : 0x0000c470 - 0x0000c670,
.sbat : 0x0000c800 - 0x0000ca00,
gaps in the section table may result in different checksums
warning: data remaining[53120 vs 63976]: gaps between PE/COFF sections?
Signature verification OK
It throws a bunch of warnings regarding sections. I know there are some issues with the linkers for PE files regarding EFI. They do some hacks that may be confusing LIEF.
What I'm trying to understand are the following:
- Is this a LIEF bug?
- If it is not, what are the differences of this PE file that make it fail validation with LIEF?
- Is there a way I can fix the PE file to be able to validate it with LIEF? Or can I do something different withing LIEF to validate it?
At least if you could point me in any directions on how to continue with this issue, it would be useful.
Thanks in advance!
Also, why signature.check() didn't catch the VERIFICATION_FLAGS.BAD_SIGNATURE and pe.verify_signature() did? I understand the VERIFICATION_FLAGS.BAD_DIGEST is only part of verify_signature but I understand that the other code should be returned by check(). Correct me if I'm wrong please.
Interesting, I never thought about this test for an EFI binary. I'll take a look when I'll back from vacation
Re,
I investigated the issue. First, signature.check() verifies the integrity of the signature itself. You can have a consistent signature while having a binary that have been modified. On the other hand, pe.verify_signature()
verifies both: that signature is valid AND that the binary integrity is valid according to the signature.
Secondly, I checked the binary you attached with other PE signature tools and it seems that the output of LIEF is correct. I suppose that sbverify only performs signature.check() and not the complete verification (even though sections seem implied).
I close the issue as it seems the correct behavior but feel free to re-open it if you find that something is still wrong.
sbverify only performs signature.check() and not the complete verification
In fact, it does the complete verification. If I change any random byte on the binary, sbverify will tell me.
After a bit of research, I have come to the following conclusion regarding this:
There were hystorical issues on the toolchains used to create EFI PE executables that made the validation with regular tools not work in some cases. Instead of changing the already-compiled bootloader binaries, they added exceptions on the checkers (sbverify, tianocore, etc) to support them.
These exception I think that only involves non-executable and/or relocation sections. It may be worth investigating if supporting this types of files is something desired for LIEF. I don't know if adding them would be considered a deviation of the authenticode standard.
LMK if this is something you would like to see added and maybe I can prepare a patch.
Ok my bad for sbverify. Definitely if you can draft a patch it would help to think about how it can be integrated along the with regular authenticode.
Ok. I need to understand several things still. But I'm interested in contributing this. Let's keep this open for me, please, and I will come back when I have something.
Any update on this?