小 忆 博 客
有梦想,有精彩

MacOS App代码提权详解

目录

  1. 1.通过ServiceManagement注册LaunchdDaemon
    1. 1.在xcode中编译项目 (Product > Build或者command+b)
    2. 2.使用终端进入项目根目录,运行以下命令:
    3. 3.clean项目(Product > Clean),然后再次编译(Product > Build)。
    4. 4.在项目根目录下,终端执行:
    5. 5.运行编译的APP。
  2. 2.AuthorizationExecuteWithPrivileges函数
  3. 3.使用AppleScript提权
  4. 4.自创的提权方法(思路)

最近一段时间开发公司一款MacOS平台的App时需要用到管理员权限,于是乎上网查询了MacOS App使用代码提权的方式,目前主要有以下几种:

1.通过ServiceManagement注册LaunchdDaemon

这种方法是目前苹果官方推荐的一种提权方式,官方也提供了一个SMJobBless的Demo,但是需要用苹果开发者账号编译(我会在后面的文章单独介绍一种不需要开发者账号编译的方式,也能通过这种方式达到高权限的需求,在本文末尾会提供思路!),而且使用起来很复杂。带来的好处是,将高权限任务封装到独立的子程序,将按需调用,不会让整个程序处于高权限的状态会相对安全一些,子程序便能轻松实现开启自启、常驻后台、高权限的需求。

我通过研究官方SMJobBless的Demo,发现其实是通过launchd工具加载一个与Daemon程序相关的标准的plist文件,由于launchd需要高权限运行,所以启动的子程序自然而然也是高权限运行。程序运行之后,会将子程序放到/Library/PrivilegedHelperTools这个目录,需要的plist文件会被放到/Library/LaunchDaemons,这样launchd加载plist时,会去启动子程序。

在SMJobBless的Demo中是通过ServiceManagement这个框架的API来完成提权操作的,而这框架在10.6就出现了,所以不需要担心兼容性的问题。本文在这里不详细介绍如何使用相关API了,只是在这里简单说说编译方法吧!在Demo中的ReadMe.txt中虽然讲述的很清楚,但是新版xcode编译之后的目录有所不同。(注意:程序虽然编译成功,但在默认的SMJobBlessHelper-Launchd.plist中并没有RunAtLoad这一属性,所以launchd不会启动子程序,只会将子程序放和plist文件到相关目录,如需启动子程序,需在SMJobBlessHelper-Launchd.plist中添加RunAtLoad属性,Boolean类型,值为YES)

1.在xcode中编译项目 (Product > Build或者command+b)

2.使用终端进入项目根目录,运行以下命令:

1
./SMJobBlessUtil.py setreq <SMJobBlessApp.app:path> SMJobBlessApp/SMJobBlessApp-Info.plist SMJobBlessHelper/SMJobBlessHelper-Info.plist

<SMJobBlessApp.app:path>是指在xcode左边的Navigator的product下的编译的APP的路径

脚本运行成功会输出:

1
2
SMJobBlessApp/SMJobBlessApp-Info.plist: updated
SMJobBlessHelper/SMJobBlessHelper-Info.plist: updated

3.clean项目(Product > Clean),然后再次编译(Product > Build)。

4.在项目根目录下,终端执行:

1
./SMJobBlessUtil.py check <SMJobBlessApp.app:path>

脚本运行成功没有输出任何东西,说明成功了。

5.运行编译的APP。

这时会弹出需要输入密码的认证提示框,输入密码之后,xcode在控制台打印Job is available!,App上有显示The Helper Tool is available!字样,表示成功运行了。

官方Demo:SMJobBless.zip

2.AuthorizationExecuteWithPrivileges函数

这个函数是Security.framework中的一函数,使用很方便,而且还有一个封装非常好的库STPrivilegedTask,接口和NSTask几乎一样。但是AuthorizationExecuteWithPrivileges函数在MacOS 10.7的时候就开始被弃用了,在新版的10.13.3中发现某些时候会提权失败,所以不推荐使用这种方法,在本文也不做过多介绍!

3.使用AppleScript提权

AppleScript是苹果独有的脚本语言,通过OC或者Swift都可以调用AppleScript,在这里提供一个OC写的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
-(NSDictionary*)doAppleScript:(NSString*)cmd{
cmd=[NSString stringWithFormat:@"do shell script \"%@\" with administrator privileges",cmd];
NSAppleScript *script= [[NSAppleScript alloc] initWithSource:cmd];
NSDictionary *scriptError = nil;
NSAppleEventDescriptor *descriptor = [script executeAndReturnError:&scriptError];
NSMutableDictionary *dicResult=[NSMutableDictionary dictionary];
if(scriptError) {
[dicResult setObject:@NO forKey:@"id"];
[dicResult setObject:[scriptError objectForKey:NSAppleScriptErrorMessage] forKey:@"result"];
} else {
NSAppleEventDescriptor *unicode = [descriptor coerceToDescriptorType:typeUnicodeText];
NSData *data = [unicode data];
NSString *result = [[NSString alloc] initWithCharacters:(unichar*)[data bytes] length:[data length] / sizeof(unichar)];
[dicResult setObject:@YES forKey:@"id"];
[dicResult setObject:result forKey:@"result"];
}
return dicResult;
}

AppleScript方法固然简单,但是还有一些缺点,比如每次需要管理员权限时,都需要提示输入密码,会让用户感觉你总是在获取权限,造成用户对你程序的不信任,在执行需要等待结果的命令时,会造成UI卡住,一直占有线程,所以建议在子线程中运行!

4.自创的提权方法(思路)

提供一个通过AppleScript启动launchd子程序的思路(后面文章单独做介绍),这样也可以实现高权限,常驻后台的需求。

AppleScript可以运行shell脚本(方法3提供的OC语言的方法就可以执行脚本),而shell脚本可以传入参数,知道这两点,我们就可以在自己项目中建立子程序和需要plist文件,通过AppleScript执行管理员权限运行shell脚本,shell脚本把plist文件和子程序移动到方法1中的所提到的目录中,这样是不是可以达到一样的方法呢?感兴趣的童鞋可以试试!