Is a Code Signing Certificate Enough for Writing Secure Code?
Many companies develop and deliver applications and software for embedded devices in today’s complex world of IoT. You may be a developer in one of those companies and asking yourself “how do I secure my software?” “How do I prevent attackers from attacking company software and our reputation?” You search and find many security articles, most very complex, and you feel overwhelmed, saying to yourself, “We’re not even code-signing yet…” Fear not, we’re here to help. We’ve been down the learning curve ourselves. Let us tell you why code signing is a great place to start, but not the be all and end all of how to write secure code.
Code Signing is a technique by which section(s) of your application’s image are authenticated by a signature, if any section fails authentication the image is assumed to be ‘tampered with’ and the application is not allowed to execute. Code Signing protection essentially involves the generation of a mathematical hash or digest calculation over special sections of your application binary representing the actual ‘code’. The hash/digest calculation will always be the same as long as the code sections have not changed. If the hash/digest is different, the original code section(s) have been modified. A signature is then generated over hash/digest calculation and can only be generated, and subsequently verified by ‘special’ keys, typically asymmetric keys. The signature is performed over the hash/digest calculation to ensure that an attacker has not modified the hash/digest value. It would be difficult for an attacker to generate a new signature due to the nature of asymmetric keys, but not impossible.
Great, you’ve added ‘code-signing’ to your embedded applications and software. You are protected from attacks now, right? Well, yes it would be difficult for an attacker to modify your application without detection but there are other types of attacks which circumvent code signing… What? Believe it or not, an attacker can use your authenticated code against you! Code signing comes up short in a couple of areas. First, the asymmetric keys themselves can be compromised, either by a malicious employee, data leakage or brute force attacks. The second more interesting area, falls into the category of application control flow, or more commonly called Control Flow Integrity (CFI)
Control Flow Integrity
All modern day compiled applications and software have very predictable control flow, that is to say the software logic within the software has a known call graph e.g. what function is supposed to invoke what function etc. The application software call graph can be generated when the software is compiled and turned into binary code. This call graph can then be used when the application is running to ensure the call graph map is followed. Why is this important? Well attackers use several techniques where they attempt to alter the call graph flow. These attacks are generally called ‘bending’ attacks. Now wait a minute, if I have ‘code-signing’ in place there should be no way my code can be modified without the modification being detected! That’s correct, but as I mentioned earlier ‘your authenticated code can be used against you’.
Generally, every application has a call-stack, this is a special memory area where function arguments are placed so that the next function to be invoked in the call graph map can retrieve them. In addition to function arguments being written to the application stack, the invoking function return address is also written to the application call-stack. The application call-stack has an Achilles Heel which subjects itself to attackers attempting to modify the call-stack contents and calling your code in an unintended fashion. By essentially overwriting your application call-stack an attacker can take over the control graph of your application.
Return Oriented Programming
Return Oriented Programming, or ROP as referred to by the security community essentially involves an attacker re-writing the return function addresses on the application call-stack. Generally, this is the result of a buffer overflow attack. Once the application call-stack has been compromised, your application is at the mercy of the attacker. Now the fascinating part, the code the attacker executes is your original un-touched authenticated signed code.
ROP Gadgets are code sequences in your application which generally end with some type of return or jump instruction. There are many ROP Gadget generator programs out there today which take your application binary and generate a list of all ROP Gadget code sequences in your application. Essentially the ROP Gadget generator shows a dictionary of all ROP gadget sequences, all the ROP gadgets are authenticated code in your application, essentially signed code. Take for example an attacker wanting to make a system call to system call 5. Using ROP Gadgets an attacker would generate a list of all ROP sequences in your code-signed application binary. Within the list of ROP gadgets (sequences), the attacker my find one such sequence which increments a register and returns, another which makes a system call and returns etc. The attacker could then use an application call-stack buffer overflow attack and modify the stack to call the first ROP gadget 5 times, then modify the call-stack to invoke the ROP gadget which makes a system call. Attack complete! Code signing alone would not have prevented this attack.
In today’s world of IoT devices there are many security techniques one can deploy to protect applications, and code-signing is one such technique. But code-signing alone is not a solution, it takes a much more comprehensive form of defense to protect your applications and software, you need ‘security built from the inside out”!
Some time ago, I stumbled upon the term “Heisenbug.” What a perfect name for a bug! If you’re a coder, be forewarned this article may