老谭笔记

利用Mac OSX注入技术编写插件/外挂的实现

之前就有朋友在我的博客留言问我是否研究过OSX的注入技术,前不久,有幸与新浪的一位技术总监聊天又提到这个话题,这样一来,我对注入不感兴趣都不行了。

其实早在他们提及这个话题之前,我就尝试过相关的技术研究,当然我所谓的研究也不过是在学习怎样使用别人已经造好的”车轮”,对于OSX的注入已经有非常有名的开源库叫做mach_inject(GitHub跳转),但因为这个项目更新并不及时,加上作者写的代码非常有技术泛儿,所以第一次Clone下来使用并不顺畅,出现了很多错误,于是就搁置在一边了,直到这两天上班比较空闲,我又搬出来折腾了,终于把整个过程理顺了,并成功的注入了Finder。

首先解释一下何为注入,注入就是从外部把可执行的代码片段载入到目标进程的技术,大家肯定对SQL注入不陌生吧,其实这个概念就是相通的,对于桌面程序,如OSX上运行的程序,我们合理的利用注入技术就可以方便的为第三方程序编写插件/外挂。比如目前大名鼎鼎的DropBox的OSX客户端程序就是利用了注入将DropBox与Finder合理的整合在了一起,直接在Finder中就可以对本地和远程的文件进行操作,如下图:

dropbox

当然注入只是第一步,要实现像DropBox这样给文件和文件夹打上标识、在右键菜单中添加自定义的功能,还需要很长的路要走,首先要对OSX的编程非常了解,然后需要经过很多的注入测试来了解目标程序本身的逻辑,然后利用比如RunTime的特性加入这些高级功能。

下面就通过一个我今天编写的实例Demo来完整的走一遍注入的全过程(相关代码在末尾),下图是整个工程的目录:

mach_inject_1

其中mach_inject、mach_inject_bundle、mach_inject_bundle_stub是GitHub上Clone下来的mach_inject原工程的三个目录,我们的工程最终需要依赖于mach_inject_bundle,它的编译结果是一个framework,我们只需要这个framework就足以,但我们可以了解一下这三个目录的关联:mach_inject_bundle工程依赖于mach_inject_bundle_stub工程编译结果,而这两个工程中都会使用到mach_inject中的代码,本文提供的测试工程我已经在原工程中对工程配置进行了一些修改,并且修改了几处编译会出错的代码(这几处修改花费了我不少的时间)。

mach_inject_finder是我们注入Finder程序所有逻辑的工程,其中包括两个Target,第一个Target叫做mach_inject_finder_plugin,它是一个插件,目前里面就只有一个主类,该类所负责的工作就是注入到Finder之后要做的所有事情(该测试工程最终结果是改变Finder所有窗口的Title为“已经注入成功!”这句话,并在控制台打印出窗口的视图层级关系图),下面贴出该类的所有代码,非常的简单:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
+ (id)enumView:(NSView *)aView
{
NSMutableArray *array = [NSMutableArray array];
NSArray *subViews = [aView subviews];
for (NSView *subView in subViews)
{
id result = [self enumView:subView];
if (result)
{
[array addObject:result];
}
}
return [NSDictionary dictionaryWithObject:array forKey:NSStringFromClass(aView.class)];
}
+ (void)load
{
NSArray *windows = [NSApp windows];
if ([windows count] > 0)
{
for (NSWindow *aWindow in windows)
{
[aWindow setTitle:@"已经注入成功!"];
//此为Finder的窗口
if ([NSStringFromClass(aWindow.class) isEqualToString:@"TBrowserWindow"])
{
NSLog(@"视图的层级关系图:%@",[self enumView:aWindow.contentView]);
}
}
}
}

这个工程另一个Target叫做mach_inject_finder,它所负责的就是调用mach_inject_bundle.framework中的方法,并将mach_inject_finder_plugin做为参数传递给framework,实现注入,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//获取finder的pid
NSArray *finderApps = [NSRunningApplication runningApplicationsWithBundleIdentifier:@"com.apple.finder"];
if ([finderApps count] == 0) return;
NSRunningApplication *finderApplication = [finderApps objectAtIndex:0];
pid_t process_id = [finderApplication processIdentifier];
//开始注入
NSString *injectedBundlePath = [[NSBundle mainBundle] pathForResource:@"mach_inject_finder_plugin"
ofType:@"bundle"];
assert( injectedBundlePath );
mach_error_t err = mach_inject_bundle_pid( [injectedBundlePath fileSystemRepresentation],
process_id );
assert(!err);

最后还有一个工程叫做mach_inject_finder_loader,并且它也是我们这个测试程序的入口,它的原理更简单,就是mach_inject_finder的一个启动器,因为要使用注入,程序就必须通过ROOT权限打开,所以这个启动器的原理就是通过STPrivilegedTask来使用ROOT权限打开mach_inject_finder程序而已。

PS:不能对同一进程多次注入,所以测试工程运行一次之后想要再次测试就需要将Finder进程重启才行。

测试工程最终结果截图(见Window的Title变化):

mach_inject_2

整个工程代码下载:mach_inject_finder