之前就有朋友在我的博客留言问我是否研究过OSX的注入技术,前不久,有幸与新浪的一位技术总监聊天又提到这个话题,这样一来,我对注入不感兴趣都不行了。
其实早在他们提及这个话题之前,我就尝试过相关的技术研究,当然我所谓的研究也不过是在学习怎样使用别人已经造好的”车轮”,对于OSX的注入已经有非常有名的开源库叫做mach_inject(GitHub跳转),但因为这个项目更新并不及时,加上作者写的代码非常有技术泛儿,所以第一次Clone下来使用并不顺畅,出现了很多错误,于是就搁置在一边了,直到这两天上班比较空闲,我又搬出来折腾了,终于把整个过程理顺了,并成功的注入了Finder。
首先解释一下何为注入,注入就是从外部把可执行的代码片段载入到目标进程的技术,大家肯定对SQL注入不陌生吧,其实这个概念就是相通的,对于桌面程序,如OSX上运行的程序,我们合理的利用注入技术就可以方便的为第三方程序编写插件/外挂。比如目前大名鼎鼎的DropBox的OSX客户端程序就是利用了注入将DropBox与Finder合理的整合在了一起,直接在Finder中就可以对本地和远程的文件进行操作,如下图:
当然注入只是第一步,要实现像DropBox这样给文件和文件夹打上标识、在右键菜单中添加自定义的功能,还需要很长的路要走,首先要对OSX的编程非常了解,然后需要经过很多的注入测试来了解目标程序本身的逻辑,然后利用比如RunTime的特性加入这些高级功能。
下面就通过一个我今天编写的实例Demo来完整的走一遍注入的全过程(相关代码在末尾),下图是整个工程的目录:
其中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变化):
转自:http://www.tanhao.me/code/1005.html?replytocom=960