Following on from my Helper Applications sample project, I’ve created another sample which focusses on how to do code-injection.
In particular this sample makes and installs a helper application who’s job is to inject some code into another application (by default the Finder).
The injected code adds a menu to the application’s menubar, with a single item in it that just logs stuff to the console.
Once again the project is based on existing examples, but attempts to present things in a slightly cleaner / simpler way.
It uses Wolf Rentzsch’s mach_star to do the heavy lifting for the injection task.
You can find it on github.
Once again I should point out that this example pretty much ignores security when it comes to the inter-process communication between the host application, the injection helper, and the injected code. Which isn’t to say that it’s not useful, simply that you’d have to batten it down far tighter if you want to avoid opening up some potentially horrible security holes.
In the sample, the helper is launched on demand, and only does the injection in response to a command from something else. In a real-world scenario, I guess it would make more sense for it to watch for the target app to launch and do the injection then. It would also make more sense if the various parameters were embedded into the helper to lock it down. However, in it’s current form, I guess the sample could form the basis of some sort of application-enhancer style system allowing multiple client applications to inject code into multiple targets.
I’ve recently been looking at how to set up a helper application (one that either runs all the time as a background task, or on demand, potentially with system permissions).
Apple has a few samples in this area, but they’re not all that complete, sometimes not cocoa-centric, and I found them a bit confusing.
After a fair bit of puzzling it out I’ve created my own sample (a modified version of a couple of theirs), and I’ve put it up on github in the hope that it might help someone else. Here’s the read me file for the project:
This is based on Apple’s SMJobBless example, which shows how to cleanly install a helper tool that needs to run with privileges as a launchd task.
The biggest problem with this task is that the helper tool and the host application that’s going to install it have to be set up very carefully to have the right code-signing details and bundle ids.
If you wanted to change these in the SMJobBless example you had to do it in lots of different places - and it was easy to miss one.
This sample gets round that problem by setting three user-defined values at the project level, in the Settings.xcconfig file:
HELPER_ID = com.elegantchaos.helper.helper
HOST_ID = com.elegantchaos.helper.host
HELPER_SIGNING = 3rd Party Mac Developer Application: Sam Deane
You should set HELPER_ID to the bundle id that you want to use for your helper application.
You should set HOST_ID to the bundle id that you want to use for the host application - the one that installs the helper (and which probably makes use of it, although that’s not necessarily the case).
You should set HELPER_SIGNING to the code signing profile that you want to use to sign everything. Note that if this profile is associated with a bundle id pattern (eg. com.elegantchaos.*) then the HELPER_ID and HOST_ID settings must match the pattern, otherwise xcode will refuse to sign the applications.
The name of the profile is embedded in various places, and it’s important that they all match exactly what’s in the certificate. For this reason you need to specify an exact profile name here (like “3rd Party Mac Developer Application: Sam Deane”), rather than a wildcard (like “3rd Party Mac Developer Application: *”).
The tricky part of all of this is that the host application needs one plist, and the helper application two, and both of them make reference to the three values defined above.
What’s more, you can’t rely on Xcode’s variable substitution to fill in all of these values. Two of the plists are embedded in the __TEXT section of the helper application, which is a simple binary and therefore doesn’t live in a bundle. These are embedded with some special linker commands, and it appears that Xcode (and the underlying linker tools) don’t pre-process the lists in passing.
Even with the other plist (which is used for the host application) has a complication to it, because one of the special keys in it is a dictionary where a key needs to be replaced by the value of HELPER_ID. It appears that Xcode’s plist pre-processing doesn’t affect keys, only values, so you can’t use a variable in the key to fill in the correct value.
The way I’ve got round this is to add build scripts to both targets which take the original plists and perform the substitutions. The various linking stages then use the output of these scripts as their inputs.
In theory these derived plists should probably go into ${DERIVED_FILE_DIR}, so that they are hidden out of the way.
However, it seems that Xcode needs to be able to find them when the build starts, because they are set as the Info.plist to use for the targets. When I tried hiding them away in the build folder, Xcode couldn’t seem to cope, so instead I’ve added them to the project as “proper” resource files. Each time you build, they’ll be regenerated. Luckily though, Xcode (and git) don’t see them as having changed unless they actually do change (because you change the values of the variables mentioned above).
I’ve also expanded the example to illustrate one way to actually communicate with the helper application.
This example uses distributed objects, which is NOT a particularly secure way to do things. Assuming that your helper is running with enhanced priveleges of some sort, it obviously becomes a potential attack vector for things that are trying to take over your system. DO is big and powerful, and complicated, and thus hard to secure.
In reality you should probably use something simpler, like sending simple bytecode across a NSMachPort or NSSocketPort.
I’m very pleased to announce that Neu 1.2 is now live on the Mac App store, as well as available for download from this site.
This version has quite an extensive list of changes and improvements, including reorganised preferences, some new substitutions, and a substantial overhaul of the main UI.
Check out the release notes for full details.
Ambientweet 1.0.2 is now available from the Mac Application store.
This is a minor revision, with a fix to a bug that caused it to stall and stop refreshing tweets after an hour or so.
Let’s say that you are building with a modern SDK, but you are targeting some older platforms. For example, you’re using the iOS 5.0 SDK, but you want your app to run on iOS 4.2.
When you do this, you have to take care not to call routines which are only implemented on iOS 5. You can set your deployment target to 4.2, but that won’t prevent you from calling 5-only routines, they will simply be weak linked and will be nil on platforms that don’t support them.
Occasionally I slip up on this, and during testing I discover that I’m using a routine that doesn’t exist everywhere.
The simple solution when this happens is to stop using the routine in question, and find some workaround, but that’s a bit rubbish. After all, the routine has probably been added precisely because it’s useful.
Also, the routine, when it does exist, might be better than your workaround. Apple wrote it, so it must be good right? So you’d prefer to only use the workaround where necessary.
Finally, this is a temporary problem that will probably go away at some point. The routine is present on new systems, and eventually time will move on and you’ll stop supporting the ones where it is missing.
So rather than changing your code, how about finding a way to add the missing implementation when necessary? Luckily, Objective C is wonderfully dynamic, so this is entirely feasible. Here’s a little example taken from ECFoundation.
UIColor has a method getRed:green:blue:alpha: which only seems to be present in iOS 5 (although the header helpfully fails to mention this). How can we arrange to add an implementation for it, if it’s missing?
The first thing we want to do is add the replacement implementation. We can put this anywhere really, but for neatness it might as well go into a private category on the class we’re fixing up. The actual code here doesn’t really matter for the purposes of the example - the point is that you’ve now got a method implementation that you can install if you need to:
- (BOOL)ecGetRed:(CGFloat *)red green:(CGFloat *)green blue:(CGFloat *)blue alpha:(CGFloat *)alpha
{
const CGFloat* components = CGColorGetComponents(self.CGColor);
BOOL ok = components != nil;
if (ok)
{
*red = components[0];
*green = components[1];
*blue = components[2];
*alpha = components[3];
}
return ok;
}
So how to we do the installation? We only want to do it once, and we want to do it before the routine is called, otherwise it won’t be much use.
This sounds like a job for the +initialize method, but there’s a slight problem. What happens if you add an initialize method in a category and there’s already one defined in the system elsewhere. Presumably you get a name clash and one version won’t get called. Nasty.
Luckily, there’s also the +load method. This gets called super early, and has some interesting properties. Here’s a quote from a great Mike Ash article:
An interesting feature of +load is that it’s special-cased by the runtime to be invoked in categories which implement it as well as the main class. This means that if you implement +load in a class and in a category on that class, both will be called. This probably goes against everything you know about how categories work, but that’s because +load is not a normal method. This feature means that +load is an excellent place to do evil things like method swizzling.
What a piece of luck! The system frameworks will be loaded by the time it runs, so we can test for the routine’s presence, and if it’s not there, add it:
+(void)load
{
SEL apiSelector = @selector(getRed:green:blue:alpha:);
if (![self instancesRespondToSelector:apiSelector])
{
SEL replacementSelector = @selector(ecGetRed:green:blue:alpha:);
Method replacementMethod = class_getInstanceMethod(self, replacementSelector);
IMP replacementImplementation = method_getImplementation(replacementMethod);
const char* encoding = method_getTypeEncoding(replacementMethod);
class_addMethod(self, apiSelector, replacementImplementation, encoding);
}
}
[Updated Feb 2012 to fix some github links that have changed]