Since quite a few people asked me how I created iCalFix, I thought it was time to share some details with you. There’s technical content ahead, so in case that’s not your cup of tea, I hid it behind a cut. While it’s OSX-specific, it might still be interesting for non-OSX programmers – the whole process of writing plug-ins for Cocoa applications is rather interesting, regardless what platform you work on.
So how does it all work? The most important piece in the puzzle is the fact that almost all Apple Applications are using ObjectiveC – a message based programming language. That means no function calls, just message dispatches. It’s not that important to plugin development, but crucial to reverse engineering. Every message is dispatched through one central function, by name.
The question then is, how do you get to read the messages? We need to somehow hook into a running application. When I started this project, I thought about a rather convoluted solution using mach_inject – it allows you to inject arbitrary code into a running application. I thought I’d use that combined with mach_override to override the central dispatch function objc_sendmsg.
A quick trip to the extendamac mailing list on SourceForge, and the resident gurus had set me straight, though. Objective C already has message logging functionality built in. All I needed was to call instrumentObjcMessageSends(YES); inside iCal’s application space.
There are, as far as I know, two choices to do that. Either you use mach_*, which is a tricky business, or you write an input manager – all OSX applications load input managers – which is a slightly less tricky business.
This is where a second major piece comes in – SIMBL, the Smart InputManager Bundle Loader. It’s a piece of software that uses an input manager to load user-written plugins. All the heavy lifting is done in there, and you just need to create a bundle (sort of a DLL, for you Windows guys) with one entry point.
The code to activate message logging then boiled down to:
+ (void) install
{
instrumentObjcMessageSends(YES);
}
Compile that as a bundle, and SIMBL will make sure it gets loaded at startup. All messages the hooked up sends from then on are logged. As you’ll soon find out, Cocoa Apps send a lot of messages, so prepare for some digging through multi-megabyte text files.
After a little while, you will have figured out which message does what. Once you are there, you need to somehow take over or intercept those messages to do your business. Since ObjectiveC is a relatively dynamic language, it’s easy to replace a method on a class with your own method – just what we want to do. The CocoaDev website has a great explanation of MethodSwizzling.
Now we’re almost there – we hooked the critical messages, but we actually need to modify data on objects to achieve any visible effect. This is where our third helper comes into play – class-dump, which can be downloaded here.
class-dump reads an executable and presents you with an API declaration that you can simply include in your source. Once you do that, you can call all the object modification functions you want. In short, you become a part of the executable you hooked.
All this allowed me to create iCalFix with less than 100 lines of code. It’s rapidly growing, since I’m busy adding new features, but the core hack is extremely simple – thanks to a lot of hard work done by others.
If you’d like even more detail, Mike Solomon, the author of SIMBL, has a great page on reverse engineering Cocoa programs.
Tags: osx, cocoa, SIMBL, objective-c, plugin, reverse engineering
Cant get iCalFix to work. Keeps giving me an error message stating that I have accidentally installed iCalFix in my SIMBL folder, even though I have not.
??????????????????
Awesome! It’s a pitty Apple made such a stupid design decision. If I recall correctly, this is a feature that they actually removed from earlier versions.
Ideally, the preferences should allow the user to choose one of two behaviors: use the preferences defined alarm time, or use the previously used alarm time. The later really is just allowing the user to change the preferences setting in-situ when setting an alarm as opposed to using the preferences. While cool, I favor the current default behavior, but could see where someone might like the be able to chose the other.
Thanks for the cool plug-in… you’ve got a beer on me.
Users: Give this guy a little cash for his troubles. It won’t cover his time but it is a sincere way to say thank you.