和现代语言一样,Object-C是一种反射式语言,他可以在运行时查看和修改自己 的行为。反射机制允许程序将指令看成数据,也允许在运行的时候对自己进行修改。Object-C 运行司环境不仅可以让一个程序创建和调用临时的方法,还可以实时创建类和方法。Object-C实现了smalltalk 消息队列框架,使得其中的方法实际上并不是传统意义上的呗调用,而是被发送消息。因此,如果你知道该收听哪个电台,就能监听到这些消息,从而了解程序中正在发生什么。另一方面,如果你知道怎么去发送消息,那就可以开始操作你的Object-C软件的运行时环境,使得它的行为发生改变,从而达到你的目的。
首先,先来了解Object-C在运行时是怎么做的,又发生了什么。先来看看下面的列子
@interface SaySomething:NSObject
- (void)say:(NSString *)phrase;
@end
@implementation SaySomething
- (void)say:(NSString *)phrase
{
printf("%s\n",[phrase UTF8String]);
}
@end
int main(void){
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc]init];
SaySomething *saySomething = [[SaySomething alloc]init];
[saySomething say:@"hello world!!"];
[saySomething release];
[pool release];
return 0;
}
没有问题,进行编译
$ clang -arch armv7 -isysroot `xcrun --sdk iphoneos --show-sdk-path` -o HelloWorld HelloWorld.m -framework Foundation -lobjc
这个是最新的编译器,用的是clang编译器。
$ file HelloWorld
HelloWorld: Mach-O executable arm
当然,这个程序是没法在计算机中运行的,签名后,拷贝到iPhone中才能运行,但是可以在计算机中使用strings 命令查看。
$ strings HelloWorld
hello world!!
UTF8String
say:
alloc
init
release
SaySomething
v12@0:4@8
在苹果操作系统中,可执行文件、动态链接库、扩展文件和核心转储(core dump)文件都是采用了一种名为Mach-O 的文件格式。上面我们用file命令已经查看过。它由三部分组成:头部、一系列的加载指令,以及数据段。在Mach-O 头部中给出了目标体系结构,如:x86-64 或者arm,还给出了一些标志位,以及解析出问价剩下的字段信息。加载指令描诉了问价的结构,以及在加载时在虚拟内存中该如何布局,还包含了一些其他的信息。如(用于动态链接的)符号表的位置和其他要被加载的共享库的名字等。最后,数据段中包含在加载指令中描述的实际要加载到内存中的短,其中包含了实际的代码和其他数据。
用tool命令来显示程序依赖的动态库,也就是程序加载时链接的共享库:
$ otool -L HelloWorld
HelloWorld:
/System/Library/Frameworks/Foundation.framework/Foundation (compatibility version 300.0.0, current version 1242.12.0)
/usr/lib/libobjc.A.dylib (compatibility version 1.0.0, current version 228.0.0)
/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1226.10.1)
/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation (compatibility version 150.0.0, current version 1242.13.0)
上面红色的那一行就是Object-C库,也就是我们在编译时使用了-lobjc标识链接给应用的。这个库提供了所有Object-C环境的基本功能,如信息、类约定、协议支持、对象管理等。我们在一开始就介绍,Object-C 是一种反射式语言,它的底层工作都是动态的。那具体是怎么工作的呢,再来看一段代码
objc_msgSend(
objc_msgSend(objc_getClass("NSAutoreleasePool"),NSSelectorFromString(@"alloc")),NSSelectorFromString(@"init")
);
objc_msgSend(objc_msgSend(objc_msgSend(objc_getClass("SaySomething"),NSSelectorFromString(@"alloc")),NSSelectorFromString(@"init")),NSSelectorFromString(@"say:"),@"hello world");
这说明,所有到say something 类及其方法的映射都必须保存在可执行文件中,让Object-C的反射机制能发挥作用。在这个例子中调用objc_getClass 方法后想say something 发送了一个消息:alloc、init、say。
在编译好的程序里存在一个到所有类、方法和Object-C其他的组件,用于运行时换进的操作,那如何提取出这个映射的内容呢,这就要用到一个神奇:class-dump,这在我的iOS应用逆向工程(二)有它的介绍和使用说明
$ class-dump HelloWorld
//
// Generated by class-dump 3.5 (64 bit).
//
// class-dump is Copyright (C) 1997-1998, 2000-2001, 2004-2013 by Steve Nygard.
//
#pragma mark -
//
// File: HelloWorld
// UUID: 8FE08112-5E4D-36E6-8B46-E1654DAF5DB3
//
// Arch: armv7
// Source version: 0.0.0.0.0
// Minimum iOS version: 9.2.0
// SDK version: 9.2.0
//
// Objective-C Garbage Collection: Unsupported
//
@interface SaySomething : NSObject
{
}
- (void)say:(id)arg1;
@end
这些就是刚刚写的那个程序的头文件,说明被编译进程序的每一个类及其相关的方法、实例变量、属性等。
在iOS 应用逆向工程(三) 中介绍过怎么把微信的反编译出来。