19 Feb 2006

circumventing ichat's g3 restriction (part 2)


Last week, I started a post about how I wanted to override ichat's restriction that prevented 600MHz or slower G3's from using any sort of video conferencing even though they are capable of doing it. So here is the second, and final part of that post that should you how to go about actually implementing a solution.

A quick recap on what we want to do. we've found out that we need to override either ProcessorSpeedMHz or [VCRules canSupportH264Decode]. What I found out later was that [VCRules canSupportH264Decode] doesn't help at all because it is only used on G4s.Tthe quickest way (probably not the safest way) to fool iChat into thinking we have a processor capable of doing video conferencing is to just report a fake result for ProcessorSpeedMHz.

Side Note: Overriding Objective-C methods



Replacing Objective-C methods and C functions are different.

For replacing Objective-C methods, all we really need to do is to modify the message lookup for Objective-C messages. This is done very easily using Method Swizzling. As usual, you need to determine whether you want to replace a class method or an instance method. But the idea is the same. There are a couple of good places for information about this on the net, which is CocoaDev and Mike Solomon's Cocoa Reverse Engineering wiki page.

To explain this quickly, what it involves is getting the selector for the original method and the replacement method, and calling class_getInstanceMethod() or class_getClassMethod() to grab a handle of the method you want to replace, and then just swap the method implementation pointers. In our case, if we were to replace [VCRules canSupportH264Decode], this is a class method, so our Method Swizzling function would look something like this (modified from the code in CocoaDev):


BOOL ClassMethodSwizzle(Class aClass, SEL orig_sel, SEL alt_sel)
{
Method orig_method = nil, alt_method = nil;

// First, look for the methods
orig_method = class_getClassMethod(aClass, orig_sel);
alt_method = class_getClassMethod(aClass, alt_sel);

// If both are found, swizzle them
if ((orig_method != nil) && (alt_method != nil))
{
char *temp1;
IMP temp2;

temp1 = orig_method->method_types;
orig_method->method_types = alt_method->method_types;
alt_method->method_types = temp1;

temp2 = orig_method->method_imp;
orig_method->method_imp = alt_method->method_imp;
alt_method->method_imp = temp2;
return YES;
}
return NO;
}


and you would invoke it in this way:


success = ClassMethodSwizzle([VCRules class],
@selector(canSupportH264Decode),
@selector(canSupportH264DecodeUnleashed));


where canSupportH264DecodeUnleashed is our custom function. You can call the original method also from your code, but counter-intuitively, you need to call it by the canSupportH264DecodeUnleashed selector because you've swapped them around.

For more information, I would encourage you to check out the Wiki page on CocoaDev for more information on Method Swizzling.

Step 3. Overriding C Functions in Mac OS X



Now, with C functions, things are a little different. C is not based on message passing like Objective C, you can't just go and swap the implementation pointers and pat yourself on the back. you have to do something more sinister. That evilness is called mach_override. mach_override is a part of the mach_star suite of tools that allow you to take control and inject code into other processes. It is really quite evil, but we're only going to use the mach_override bit and not mach_inject.

Note that this method only works on PowerPC at the moment, so if you have an Intel Mac, you're not going to be able to use this method. Developers are frantically looking for methods to emulate this hack, but I suspect it will be a while before you see this same technique resurfacing. Probably another reason (not) to get an Intel Mac if you don't want haxies working ;)

The idea is, if you have code running on the process already, you can swap out functions are runtime and replace them with your own. In our case, we want to override ProcessorSpeedMHz(), so we'll go ahead and use this method.

First thing you'll need to do is to download the mach_star bundle of joy, and copy over the mach_override.* files into your project. Now, we have to figure out how we want to use it. Do we want to call the original ProcessorSpeedMHz() at all, or do we just ignore it?

My approach is to ignore the original implementation, as that means less evil things to worry about like re-entering the overridden process. Since ProcessorSpeedMHz() is quite easy to write anyway, Apple has a sample implementation of a similar type of query but for processor type.

Therefore, we would just define our own ProcessorSpeedMHz() in the following way:


int ProcessorSpeedMHz();

int unleashedProcessorSpeedMHz()
{
OSErr err;
long speed;
err = Gestalt (gestaltProcClkSpeed, &speed );
if (err)
return 0;

if (speed < 600)
return 601;
return speed;
}


Nothing really special with this implementation here except that the output of Gestalt requires 8 bytes, so I assumed it wanted a long. We also make sure we have a reference to the internal ProcessorSpeedMHz function by just defining a pointer to it on the first line. All this function does is if the speed is less than 600, then make it 601, otherwise, just return the speed. This would make it work for all CPUs, even you kickass 2.0GHz in case you accidentally install it on those.

Swapping the functions out is a piece of cake with the mach_override tools, all we need is the following code:


mach_error_t err = mach_override_ptr((void *)&ProcessorSpeedMHz,
(void *)&unleashedProcessorSpeedMHz,
(void **)NULL);


All that is doing is swapping the implementation of the two functions. Once that is done, any further calls to ProcessorSpeedMHz will actually execute unleashedProcessorSpeedMHz.

Step 4. Getting that code loaded into iChat



So we have the mechanisms and the implementation to swap the function implementation, now we need to load our code into there. There are always more than one way of doing things. I've chosen on generic way of doing it, which is by using SIMBL. What SIMBL does is load itself into every process that gets started via the InputManager feature of Mac OS X, and then load a plugin that is registered for that application.

There are other ways of doing this, such as using Accessibility Features, writing your own InputManager loader, or even with some apps, they already have a plugin loading mechanism, so once you figure that out, you could inject your own code. However, the nice thing with SIMBL is that all that ugly evilness is taken care for you, and all you need to do is build your project as a Cocoa Bundle in Xcode and add this bit to your Info.plist:


<key>NSPrincipalClass</key>
<string>iChatUnleash</string>
<key>SIMBLTargetApplications</key>
<array>
<dict>
<key>BundleIdentifier</key>
<string>com.apple.iChat</string>
<key>MaxBundleVersion</key>
<string>429</string>
<key>MinBundleVersion</key>
<string>429</string>
</dict>
</array>


What that does is tell SIMBL to load your bundle only with an application identifier "com.apple.iChat", and for safety, only use it with iChat bundle version 429. You can find that out by just looking at the "About iChat .." menu item when the application is running.

Once you have you injecting code in there, and it compiles, you put it into ~/Library/Application Support/SIMBL/Plugins/ and SIMBL will do the hard work for you. Of course, you need to have it installed.

Finished



That's all there is to it. I'm putting up the Xcode project that does exactly what I've said here so you can have a play around with it. It will be available here called iChatUnleash. Use at your own risk, any sort of code injecting is pretty evil, but I suppose if you know what you're doing, you'll be alright.


You can reply to me about this on Twitter: