I was lucky enough to get a great tip from MalwareBytes’ Thomas Reed this week on the possibilities of code hijacking.
Thomas was kind enough to share details of a talk he gave at MacTech last year, in which he demonstrated how some 3rd party apps are susceptible to having their binaries replaced by a fake binary even when the original application is properly code signed with a valid developer’s signature.
The vulnerability lies not so much in the code signing itself, but in the mechanism for when and why it gets checked. In short, code signing is checked when an app is first launched, but after that, except in a few special situations, macOS’s security mechanisms pretty much ignore it. That means once an app has passed GateKeeper, it’s a ripe target for attackers to come in and replace the binary with one of their own.
In order to ensure the app on disk is still in fact the app that was downloaded and first launched, developers need to implement a check on each launch.
The key to it is what you specify in the entitlement constant. In this example, I’ve specified three things: that the code is signed by Apple, that is has the app’s bundle identifier and that it has the developer’s Team ID. Don’t forget to change my dummy values for your real ones in the code! You can get all these details for your app by running this in Terminal:
codesign --display -r- <path to your app>
With that information, the function verifies that the application in memory meets the requirements specified in the entitlement.
Call the function at some point after launch (e.g, when your main nib has loaded) and handle the boolean result appropriately. For example, if the function returns false, you might throw an alert like this one from DetectX Swift telling the user that the app is damaged and needs to be re-downloaded, and then terminate the app when they hit “OK”:
Let’s keep our code (and users!) safe everybody. 🙂