Preventing systematic piracy in Cocoa with Code Signing

Why sign your code?

Any Cocoa application that uses public key encryption to handle licensing and registration, like my app DateLine, is open to systematic cracks. This has historically been almost unavoidable due to certain features of the Cocoa runtime, like runtime swizzling, and classdumping.

Exactly how these cracks are worked is somewhat mysterious. The one thing I do know is that DateLine (and therefore CodexFab) has been cracked. A causal search on Google for "DateLine[k]" reveals that there are thousands of people who are circumventing my carefully crafted licensing scheme, people who obviously find $4.95 too much to ask for access to the handful of advanced features that are unlocked with a license. If you are a software author using AquaticPrime, CocoaFob, or a similar scheme, I suggest you do a quick search for "MyApp[k]" yourself, you may well be surprised.

Many indie developers seem unworried by the knowledge that their hard work is being plundered in this way. I tend to agree with them on some level. Pirates are users too, and there is at least the hope that if all these people are exposed to your app, some of them may be convinced to purchase at some future time. There's nothing like piracy to spread your app far and wide, and no exposure is bad exposure, to coin a phrase. The argument goes that most of those people weren't going to buy your app anyway, so you're not actually losing any revenue.

Philosophy aside, the engineer in me finds this whole situation a little distasteful. My innocent code is being filleted by some unknown hacker, and that just sucks. Wouldn't it be great if there was an easy way to prevent this sort of thing?

Well, now there is.

Leopard (and Snow Leopard, and iPhoneOS) introduces code signing. Code signing is a way of ensuring that the code your users execute is exactly the code that you shipped. Apple introduced it into iPhone OS in order to ensure that all apps installed on iTouch devices are approved by Apple through the App Store. So, if you are an iPhone developer, you are already familiar with the codesign utility.

On the Mac, code signing is entirely optional (at this point in history, anyway), so many developers are not yet familiar with it. You can read up on the subject in Apple's Documentation.

One obvious use for code signing would be to check at runtime that your app bundle contains exactly the code, classes and resources that you built, and that no-one has interfered with or modified (ie. cracked) it since.

What you need

The first thing you'll need is a Certificate. This is essentially the private RSA key that your code signatures will be built and checked against. Apple provide great documentation on creating your own local certificate here.

Next you will need to add a new Shell Script build phase to your distribution build target, where you run the codesign tool against your app. This will generate all the necessary hash files, which are kept in a folder named _CodeSignature in your app bundle.

codesign -s CertificateName path/to/app/bundle

Lastly, and this is the undocumented part, you will need a method that calls the codesign tool and checks that your app bundle is intact.

I wrote a simple class method which is called in my App Delegate's +intialize method, which I have included below as an example to get you started.

How ever these cracks are being applied, they must be patching the app bundle somehow. Code signing ensures that modified app bundles are easily detected and dealt with.

+ (void) codesignCheck {
	NSTask * task = [[NSTask alloc] init];
	NSPipe * newPipe = [NSPipe pipe];
	NSFileHandle * readHandle = [newPipe fileHandleForReading];
	NSData * inData;
	NSString * tempString;
	[task setCurrentDirectoryPath:NSHomeDirectory()];
	[task setLaunchPath:@"/usr/bin/codesign"];
	NSArray *args = [NSArray arrayWithObjects:@"-v", [[NSBundle mainBundle] bundlePath], nil];
	[task setArguments:args];
	[task setStandardOutput:newPipe];
	[task setStandardError:newPipe];
	[task launch];
	inData = [readHandle readDataToEndOfFile];
	tempString = [[NSString alloc] initWithData:inData encoding:NSASCIIStringEncoding];
	NSLog(@"%@", tempString);
	if ([tempString rangeOfString:@"missing or invalid"].length > 0 ||
		[tempString rangeOfString:@"modified"].length > 0) {
		// Someone has messed with our app!
		[task release];
		[tempString autorelease];
		NSAlert * alert = [NSAlert alertWithMessageText:@"The application is damaged." defaultButton:@"Quit" alternateButton:nil otherButton:nil informativeTextWithFormat:@"Please download a fresh copy from our website."];
		[alert runModal];
		[[NSApplication sharedApplication] performSelector:@selector(terminate:) withObject:nil];
		return;
	}
	[task release];
	[tempString autorelease];
}

Awesome!

Very nice idea. How did I not come up with this? :P

Submitted by David Schiefer (not verified) on Thu, 10/28/2010 - 17:52.
-kill

Hey guys,
I've read somewhere there is a -kill flag, one can pass to the code sign command.
This is supposed to be a hint to the OS on what to do with the launching app if it sees the binary was modified.
So it will kill the app.

This would have the advantage that replacing the if condition with YES or NO with a hex editor would really be obsolete.

Of corse keygens could still be made and therefor, server checking must be implemented.

Assuming this is 10.6 only ?
So what - let's push forward !

Submitted by Anonymous (not verified) on Thu, 06/10/2010 - 00:41.
Wouldn't this approach be

Wouldn't this approach be vulnerable if the cracker simply bypasses the if() that checks if the app was tampered with? I might be wrong (I'm quite new to cocoa and all), but the way I see it, this approach is the same with a boolean guard.

Why not use the kill flag of codesign?

kill Forces the signed code's kill flag to be set when the code begins execution. Code with the
kill flag set will die when it loses its identity. It is therefore safe to assume that code
marked this way will have a valid identity while alive.

Submitted by Anonymous (not verified) on Wed, 06/02/2010 - 22:14.
Hey guys, I just read this

Hey guys,

I just read this post about codesigning and thought it could bring an end to the cracks but I have to say this approach is pretty lame actually. Codesigning the app with codesign -s is nice and that a folder is created and the executable modified and so on but why do I have to do the checking myself, I thought the system is clever enough to detect whether there were changes or not. I mean this is like generating an md5 checksum and then doing a check inside the app with an 'isEqualtoData' or 'isEqualToString' which can be cracked in less than a minute.

I mean this here is the best part of all:
if ([tempString rangeOfString:@"missing or invalid"].length > 0 ||
[tempString rangeOfString:@"modified"].length > 0) {
// Someone has messed with our app!

Did you know that modifying those two conditions is easy as pie for crackers? This just doesn't make sense and it isn't any secure… so why in god's name should I codesign my app, it doesn't help at all.

Submitted by Anonymous (not verified) on Sun, 05/23/2010 - 16:54.
Another solution

It seems like Security.framework in the Mac OS X 10.6 Snow Leopard allows to validate bundles using new APIs: http://stackoverflow.com/questions/1815506/how-to-obtain-codesigned-appl...
Is it acceptable solution? Thanks.

Submitted by Vadim Shpakovski (not verified) on Fri, 03/12/2010 - 21:31.
Re; Another solution

For 10.6+ apps, that is probably the best way to go. I am still supporting 10.5+ in my code, so I needed to roll my own. But from what I can see, that stackoverflow page looks like a good solution.

Cheers,

Alex

Submitted by alex on Tue, 03/16/2010 - 03:40.
Why not just check terminationStatus?

Why don't you just call "NSTask terminationStatus" and check for a zero return value? Man page says "codesign -v" shall return non-zero if the verify fails.

Submitted by Anonymous (not verified) on Wed, 12/30/2009 - 08:21.
Re: Why not just check terminationStatus?

Hi,

I don't simply check termination status because you will often need to build versions of the application that are not codesigned, for testing purposes. In fact Apple recommends that you do *not* codesign every build, only your distribution builds. This is why I add the shell script to the Distribution build. To be clear, and I didn't explain this well, the Distribution Build Target is a new target that I have in all my projects, which is distinct from the default Release target.

Anyway, for non Distribution builds, you will want your codeSignCheck method to allow your app to continue running.

It would probably be more sensible to specifically handle the error that codesign returns when your build is not codesigned instead, rather than as I have done in my sample code above.

Cheers,

Submitted by alex on Thu, 12/31/2009 - 10:57.
Ways around the check

First of all, wouldn't the whole idea be broken by replacing /usr/bin/codesign with a more "friendly" version?

And, if the crackers manage to go around other checks, what would prevent them from (for example) stripping the sign check code from your app?

Submitted by labria (not verified) on Thu, 12/17/2009 - 03:23.
Re: Ways around the check

Excellent questions, thanks.

1. Sure, this might indeed work. I will have to do some experiments. This may possibly work around codesign; alternatively if codesign itself is protected in some way, it could mess up your Mac's ability to run any codesigned apps at all. Interesting.
If this technique can indeed work around codesign, I still think it is a different proposition than a systematic crack. Each user would have to patch the codesign in their system, a more serious commitment to piracy than simply downloading a torrent of a cracked app bundle. That said, there are jailbroken iPhones, this could be similarly a "jailbreak" for Mac OS.

2. Simply stripping the code signatures is not enough. They would need to modify the executable, and as far as I can see, the location of the sign check code within it is well obfuscated, perhaps randomised, by the codesign utility. If there is a simple, systematic way to hack this, Apple's whole codesign scheme is seriously flawed. I assume that they have thought seriously about these questions and implemented solutions - how effective these are, time will tell.

Thanks for your comments.

Submitted by alex on Sat, 12/19/2009 - 03:32.
Update

Since I wrote this article, experimentation revealed that simply removing the _CodeSignature folder from the app bundle was enough to work around my implementation, as codesign returns a different error in this circumstance.

I have since added a second test to my if statement to remedy this, however this exposes a weakness in my approach, in that I am testing for specific keywords returned by the codesign task. I do not claim to have exhaustively tested this approach, and there may be further return values to test for. Please use this code as the basis for your own explorations, but understand that this is just a first draft approach to this subject. i will update this page as I learn more.

Submitted by alex on Fri, 12/04/2009 - 06:39.
Question

Does this protection work if someone removes the code signature from your application and then signs your cracked application with another certificate?

Submitted by Anonymous (not verified) on Thu, 12/03/2009 - 19:01.
Re: Question

From my experiments, once an app has been signed, the executable is modified in some way by codesign and it cannot be signed again. So unless there is a systematic way to remove codesign's mods to the executable, this method seems to be very secure.

Submitted by alex on Fri, 12/04/2009 - 06:21.
CodeSigning

Great Post ... Thanks! I have struggled with how to best cope with this issue myself. I hope apple will continue to help us with this element of software delivery. And since we live in a kakistocray and are surrounded by kleptocrats and law breakers we're on our own.

Submitted by Rick (not verified) on Thu, 12/03/2009 - 16:09.

Post new comment

The content of this field is kept private and will not be shown publicly.
Enter the code shown in the image:

CAPTCHA
This question is for testing whether you are a human visitor and to prevent automated spam submissions.
12 + 3 =
Solve this simple math problem and enter the result. E.g. for 1+3, enter 4.

Donate!





If you like what you find here and wish to support further development of this site, please donate via PayPal. No account required.

Syndicate

Syndicate content

User login

CAPTCHA
This question is for testing whether you are a human visitor and to prevent automated spam submissions.
4 + 6 =
Solve this simple math problem and enter the result. E.g. for 1+3, enter 4.
Enter the code shown in the image: