怎样对Cocoa程序编写逆向工程程序(外挂/补丁)

最近在研究iPhoto的插件的开发,顺便研究了一下逆向工程的技术,这里给出自己的心得以供参考~ 
 
英文名应该叫做 Cocoa Reverse Engineering.不知道怎么翻译,参考babylon的翻译(来自Wikipedia) 
 
Reverse engineering 
逆向工程
 
 
Reverse engineering (RE) is the process of discovering the technological principles of a device or object or system through analysis of its structure, function and operation. It often involves taking something apart and analyzing its workings in detail, usually to try to make a new device or program that does the same thing without copying anything from the original. 
逆向工程,通过对某种产品的结构、功能、运作进行分析、分解、研究后,制作出功能近似,但又不完全一样的产品过程。 
 
那Cocoa的反向工程是什么呢?就是对已有的Mach-O文件打补丁或者开发出framework中未公开的头文件。怎样进行呢,那我们开始吧~ 
翻译的一篇文档,加了额外的讲解。http://www.culater.net/wiki/moin.cgi/CocoaReverseEngineering 
1. 开始前的基础 
你得先知道什么是Cocoa和Objective-C,当然不需要很专业的那种。另外还需要知道怎样在XCode中创建Cocoa Bundle的工程。 
2. 选择你的目标 
我们假设你想破解Terminal.app来改变文本颜色,但没有代码你该怎么半呢?那就需要工具来查看编译过的Mach-O的文件,里面包含所有的元数据噢。 
3. 破解工具 
nm是Unix平台的工具。它可以反编译出C函数的名字,托管C++代码和Objective-C函数。 
strings也 很普遍。它能反编译出给定库的所有字符串。Oftentimes "secret" preference keys(啥意思?哪个大虾翻一下) will reveal themselves as you look at the strings within a binary. 
gdb (就不用说了阿,上英文)is well equipped to help you on your way. You can run any application from gdb and set breakpoints on Objective-C messages, just as you would with a C function. You can also do some noodling around to explore data structures at runtime. Very powerful, but not the easiest tool to use. 
class-dump (这个也无需说了阿,可以生成库的头文件)is your friend. In fact, get to know it like family. class-dump loads a given chunk of Objective-C code and generates a fairly convincing header file of the classes contained within. This will give you an excellent snapshot of the application and you can learn a lot from the information contained within. 
FScript FScriptAnywhere(Fscript 是一个脚本语言,FScriptAnywhere可以把Fcript安装到程序里,这样你就可以选择一个控件并查看它的属性和方法了,英语翻译水平不行, 大家辛苦下自己琢磨琢磨拉)是一个与Cocoa/Ojective-C紧密结合的类似Smalltalk的脚本语言。FScriptAnywhere是一 个SIMBL插件,它可以把Fscipt载入到任何一个Cocoa程序中。一旦载入之后,你就可以浏览次程序中的运行时对象-检查某个对象的值或者调用某 个类的方法等。很显然它是很强大的,但是它相当于破坏了程序的流程,就有可能破坏程序的数据。尤其要小心的是那些自动保存数据或者拥有复杂数据结构的程 序,比如iPhoto/Mail,紧记保存程序的数据在开始破解之前。 
SIMBL(这 个很重要噢,你开发的插件都是由它给Patch到程序里面的)是一个启动后载入标准bundle的一个framework。你只需编译一个标准Cooca Bundle(使用XCode默认工程),再添加一些特殊的键值(指定装载你的Bundle的目标程序,还有这个程序的最低和最高版本,文后会提到) 
4. 开始破解之前的工作 
这一步非常之重要,因为写破解程序总得有个目标对象吧,既破解某个或某几个对象的方法来添加我的动作,或者扩展方法。 
这里我把破解分为两种 
1)Framework 
这个其实是最简单的,你的工程只要引用这个Framework,然后dump出这个framework的似有头文件并添加你感兴趣的头文件到工程里,然后之后使用或者集成扩展一下就可以了。 
比如我之前写得控件重绘的教程,NSThemeFrame就是一个似有头文件,我继承之并重写了方法。 
2)Cocoa Application 
这 个比较困难,当然dump出它的头文件是肯定的第一步,因为它是一个程序,你的插件是被载入到其中的,所以你不可以对其进行link的操作,所以 framework破解的那套方法是不管用的。那我们就需要用到FcriptAnywhere了,将Fcript载入到目标程序中,选择你感兴趣的控件查 看其方法或者属性。 
其实这种破解的难点就是目标程序是一个黑盒,你只能自己反复测试实验才能拿到自己感兴趣的东西,就跟iPhone破解一样,你找到了iBoot的漏洞,那么就破解了,如果没找到,那么就破解不了。 
找到“漏洞”后的破解方法还是有个难点的,这个后文再讲。 
 
总之在破解之前得找到可以利用的漏洞,否则就继续不下去拉~ 
5. 怎样对程序打补丁/破解 
5.1)Posing 
相信学过Objective-C的童鞋都应高听说过这个名词,虽然用到的机会很少,但真得很有用,不过在10.6 64-bit中被去掉了,所以在10.6 64-bit中用不了拉~10.5 & 10.6 32-bit中还是可以的。 
Class posing 是Objective-C运行时态库中一个非常有用的技术。它允许你创建一个全功能的ClassA来伪装成ClassB,且ClassA是ClassB的自给且不能有自定义变量(不清楚的童鞋请补基础喽~) 
[[B class] poseAsClass:[A class]]; 
这个不想深入的讲解,可以参考Apple的关于class posing或者 +[NSObject poseAsClass:(Class)_class]的文档。\ 
Posing有一下几个缺点 
1. 只有在你Posing之后创建的对象才是你伪装的类,在这之前创建的对象还是原始的类。当然这个问题不大,不过还是最好知道这个当你试图找到你代码不工作的原因。 
2. 你不能在子类中添加类变量(这个很明显的跟继承的思想是不符合的)。我不是100%的确定Apple这样做的原因,只能猜测应该是跟对象的内存大小相关吧,得保证所有这个类型的对象大小一致。不过幸运的是,我们可以通过一下方法来解决: 
 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
static NSMutableDictionary* s_fakeIvars = nil;
+ (void) initialize
{
s_fakeIvars = [[NSMutableDictionary alloc] init];
}
- (id) init
{
self = [super init];
[s_fakeIvars setObject:[NSMutableDictionary dictionary] forKey:[NSNumber numberWithInt:(int)self]];
}
- (void) dealloc
{
[super dealloc];
// note: assumes 32-bit pointers
[s_fakeIvars removeObjectForKey:[NSNumber numberWithInt:(int)self]];
}

 
3. 如果你posing的类是似有的,比如你自己dump出来的。如果类的大小改变了(比如说目标程序更新了,这个类多了几个成员变量,那么你的plugin 里的类的大小就跟最新的不一致了),你的plugin就很有可能造成程序挂掉。(童鞋们应高明白的,类的大小是编译时确定的,所以类定义变了,那么运行时 类的大小就变了,这也是posing子类中不允许添加变量的原因,添加方法是没关系的,因为方式IMP表而已) 
4.如果这个程序安装了好几个破解插件,而这几个插件同时posing了一个class,那明显会挂的。 
5.2)方法重定向(Method Swizzling) 
原文作者也不记得第一次听到这个名词是什么时候了,但它确实是一个好的描述。简单来说,swizzing就是把某一个函数的实现跟另一个函数对换。另一种说法就是重命名。它其实并不是相它表面说得那样邪恶,一段示例代码足以说明一切了。 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
 * Renames the selector for a given method.
 * Searches for a method with _oldSelector and reassigned _newSelector to that
 * implementation.
 * @return NO on an error and the methods were not swizzled
 */
BOOL DTRenameInstanceSelector(Class _class, SEL _oldSelector, SEL _newSelector)
{
Method method = nil;
 
// First, look for the methods
method = class_getInstanceMethod(_class, _oldSelector);
if (method == nil)
 return NO;
method->method_name = _newSelector;
return YES;
}

 
这段代码就是重命名了一个类的实例方法,类方法并没有进行重命名。具体这段代码的介绍,使用和完善后文实战中给出。 
6.实现并编译代码 
找到了漏洞并掌握的了破解的方法,那么剩下的就是创建工程并实现编译了。这里采用SIMBL的解决方案(还有InputManager的方案,不过推荐SIMBL)。 
具体创建的注意事项呢请参考SIMBL的网站或者作者原文第七段。这里我给出了一个工程模板,解压附件Cocoa SIMBL Bundle.zip,并讲解压出来的文件夹拷贝到 
/Developer/Library/Xcode/Project\ Templates/Framework\ \&\ Library/Bundle(这是10.6上的位置,10.5类似吧应该?记不的了~好像有点区别的)。 
重启XCode,在创建新功程Bundle中就能看到了。这个模板替你做好了开始写破解代码之前的所有操作,你只需填写目标程序的identifier就可以了。 
具体使用方法后面实战帖子中给出。 
7.注意事项 
1. 首先要注意目标程序的更新,如果你dump出来的头文件不是最新,那么就有可能crash。所以最好设置好plugin所支持的最大和最小版本。 
2. 符号的混淆。既你的类,扩展类,C方法会和已有的重名。所以在你的类之前添加你自己的前缀吧。 
8.合理的plugin更新 
原作者写得很有意思,大家自己看看理解下吧~牛人阿~ 
Use version checking to turn off your plugins in applicati***** that just won't work - you don't want to cause other developers/Apple undue stress by breaking every application upgrade. Trust me - I learned the hard way. I'm sure there is a blacklist with my name on it near the Safari developers. 
 
 
 
呼,暂时告一段落。昨天同事请客吃饭喝酒,又唱了个通宵,早上回来睡了一个上午。起来忙 了午饭家里收拾了一下赶紧来写,理论教程算是写完了,接下来要写一篇实战教程,就以SvnX为例。因为再好的理论都是在实战中被验证的。这个实战教程明天 一定奉上,《以父之名》起誓~原本应该再接再厉一口气完成的,但老婆要去逛商场~要结婚拉,强迫被拉去买衣服~哈哈~

[ 此帖被yoyokko在2010-01-18 17:12重新编辑 ]
附件: Cocoa SIMBL Bundle.zip (50 K) 下载次数:108
原文地址:https://www.cnblogs.com/ligun123/p/2486912.html