Digitally signing ELF binaries.
Ok, I’m still working on getting XMLNode ready and out as a library, but in the mean time, here’s something that’s ready, willing, and waiting.
The Intro:
A while back a project I was working on had a major architecture change. We went to a completely plugin based system. We needed a way to verify that these plugins were from a trusted source (us), unmodified, and uncorrupted. Sounds like a perfect case for digital signatures!
The Problem:
These plugins were all linux shared objects. Linux uses the ELF binary standard for all of it’s executable binary types, including executables, shared objects, and core files. The issue was how to incorporate a digital signature in such a way as to tie it to the shared object. It also needed to be straightforward enough that it didn’t run the risk of corrupting the binary. The last requirement was that it had to be lgpl since I wanted to open-source it, but it would be used for a non-gpl program.
The Solution:
Upon reading the elf spec, I found it would be fairly straight-forward to create a new section in the binary, and just put the signature there. Technically this wasn’t quite the correct and proper solution. The truly proper solution would be to insert the signature into the “notes” section. Anyway, that was a bit harder to do, and not really necessary, so I created a new section (valid and allowed in the ELF spec) to house the signature.
The Implementation:
I grabbed the very useful libcrypt library from openssl, and proceeded to find out how to programatically create RSA keys, store those keys in a format that could be used in source code, sign and verify blocks of data. Once I had that all figured out, I tackled reading and writing ELF sections in a binary. Then to tie it all together, I made a nice little app to sign binaries, and a static library that provided functions to verify signatures in a given binary. I couldn’t use the provided ELF interface because it was gpl, however there were some lower level data structures that I could use. (Thanks lgpl!)
The process for signing a binary is basically this:
- Read in the “data” and “text” sections of the binary, these are the compiled code sections.
- Sign a buffer of those combined sections with the private RSA key.
- Write out the resulting signature in a new section.
To verify:
- Read in the “data” and “text” sections of the binary.
- Read in the “lsesig” section.
- Verify the combined buffer with the signature that was read in using the public RSA key.
This effectively produces a signed binary that can be easily verified, but not easily tampered with. If you remove the signature, then the verification will fail, if you change the compiled code, the signature won’t match.
As long as you don’t distribute the private key, or the singing program, then you can verify that the shared objects you are loading came from you, weren’t tampered with, and weren’t corrupted. (Or at least didn’t have corruption in any of the code sections of the binary)
In theory this could be incorporated in the kernel to allow signed modules. It could also be used to periodically verify objects on a given system. Who knows what else it could be used for, but hey, it does what I need!
To get your hands on this uber-useful library, go to http://sourceforge.net/projects/signelf
-Synwan