最近在研究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];
[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
|
BOOL DTRenameInstanceSelector(Class _class, SEL _oldSelector, SEL _newSelector)
{
Method method = nil ;
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重新编辑 ]