Apple IPC : DO Basic

MacOS IPC 之 DO

简介

DO全称是Distributed Objects,从字面上来看意思很好理解,分布式对象。这是一种IPC方式,简单易用,实现的效果就是:通过launchd和一个proxy object,任何的进程都可以访问到server中的DO对象,也可以调用这个对象的方法,从而实现IPC。下面是一个流程图:

(图来自pj0的博客,ianbeer的文章)

示例代码

server.m

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#import <objc/Object.h>
#import <Foundation/Foundation.h>

@interface VendMe : NSObject
- (oneway void) foo: (int) value;
@end

@implementation VendMe
- (oneway void) foo: (int) value;
{
NSLog(@"%d", value);
}
@end

int main (int argc, const char * argv[]) {
VendMe* toVend = [[VendMe alloc] init];
NSConnection *conn = [NSConnection defaultConnection];
[conn setRootObject:toVend];
[conn registerName:@"com.foo.my_test_service"];
[[NSRunLoop currentRunLoop] run];
return 0;
}

client.m

1
2
3
4
5
6
7
8
9
#import <Cocoa/Cocoa.h>

int main(int argc, char** argv){
id theProxy = [[NSConnection
rootProxyForConnectionWithRegisteredName:@"com.foo.my_test_service"
host:nil] retain];
[theProxy foo:123];
return 0;
}

Makefile

1
2
3
4
5
all:
clang client.m -o client -framework Foundation
clang server.m -o server -framework Foundation
clean:
rm server client

运行效果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# run server
╰─$ ./server
2019-08-10 22:27:07.545 server[28274:2677291] 123
2019-08-10 22:27:08.897 server[28274:2677291] 123
2019-08-10 22:27:09.662 server[28274:2677291] 123
2019-08-10 22:27:10.172 server[28274:2677291] 123

# run client times...
╭─muhe@muheMacBookPro ~/Downloads/DO_Study
╰─$ ./client
╭─muhe@muheMacBookPro ~/Downloads/DO_Study
╰─$ ./client
╭─muhe@muheMacBookPro ~/Downloads/DO_Study
╰─$ ./client
╭─muhe@muheMacBookPro ~/Downloads/DO_Study
╰─$ ./client
╭─muhe@muheMacBookPro ~/Downloads/DO_Study
╰─$

效果看起来很简单,类似socket通信那种效果,server跑起来等待连接,client连上去,然后通过launchd和proxy obj调用了server里的方法,参数是client传递的,看起来像是client执行了一个函数,其实真正代码执行的是server。

OC的语法虽然很奇怪,但是问题不大,还是能看懂:

  • server:

通过NSConnection注册了一个用于DO的对象,设置的对象是VendMe,注册名是com.foo.my_test_service,用来标示这个服务。随后使用Runloop使得server能随时处理事件但并不退出,关于Runloop的分析本文不会涉及。

  • client

client的逻辑很简单,通过NSConnection连接目标服务,然后通过proxy obj调用他的foo方法,并传递一个123的参数给他。

大概逻辑就这个样子。

逆向分析

为什么要提及这部分,apple大部分服务都不开源,而且用的IPC方式都不确定,逆向的时候需要想办法确定,并且找到关键函数,比如上例中的foo函数,找到处理函数,方便代码审计。

这里只需要关注服务端(废话),所以直接贴F5后的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int __cdecl main(int argc, const char **argv, const char **envp)
{
VendMe *v3; // rax
VendMe *v4; // ST28_8
void *v5; // ST20_8
void *v6; // rax

v3 = objc_msgSend(&OBJC_CLASS___VendMe, "alloc", envp);
v4 = objc_msgSend(v3, "init");
v5 = objc_msgSend(&OBJC_CLASS___NSConnection, "defaultConnection");
objc_msgSend(v5, "setRootObject:", v4);
objc_msgSend(v5, "registerName:", CFSTR("com.foo.my_test_service"));
v6 = objc_msgSend(&OBJC_CLASS___NSRunLoop, "currentRunLoop");
objc_msgSend(v6, "run");
return 0;
}

oc这些函数调用基本都变成objec_msgSend(a,b,xxx)了,不过不太影响看,我的理解就是
a.b(xxx),大概这个样子,知道这行代码在做什么,对于逆向已经够了。

关键点在于找DO的对象以及和他绑定的方法。通过上面的方法,能找到:v4-->v3-->OBJC_CLASS___VendMe,那么就是OBJC_CLASS___VendMe这个对象了,下面是找和他绑定的方法。

直接找到的是:

1
2
3
4
5
__objc_data:0000000100001198 _OBJC_CLASS_$_VendMe __objc2_class <offset _OBJC_METACLASS_$_VendMe, \
__objc_data:0000000100001198 ; DATA XREF: __objc_classlist:0000000100001060↑o
__objc_data:0000000100001198 ; __objc_classrefs:classRef_VendMe↑o
__objc_data:0000000100001198 offset _OBJC_CLASS_$_NSObject, \
__objc_data:0000000100001198 offset __objc_empty_cache, 0, offset VendMe_$classData>

然后根据classData找到 :


1
2
3
4
5
__objc_const:00000001000010D8 VendMe_$classData __objc2_class_ro <0, 8, 8, 0, 0, offset aVendme, \
__objc_const:00000001000010D8 ; DATA XREF: __objc_data:_OBJC_CLASS_$_VendMe↓o
__objc_const:00000001000010D8 offset _OBJC_INSTANCE_METHODS_VendMe, 0, 0, 0, 0> ; "VendMe"
__objc_const:00000001000010D8 __objc_const ends
__objc_const:00000001000010D8

最后

1
2
3
4
__objc_const:00000001000010B8 _OBJC_INSTANCE_METHODS_VendMe __objc2_meth_list <18h, 1>
__objc_const:00000001000010B8 ; DATA XREF: __objc_const:VendMe_$classData↓o
__objc_const:00000001000010C0 __objc2_meth <offset sel_foo_, offset aVv2008i16, \ ; -[VendMe foo:] ...
__objc_const:00000001000010C0 offset __VendMe_foo__>

依靠对classData的引用看。

其实也可以的知道了DO对象的对象名之后直接搜函数列表,比如:VendeMe:。 这个是在有符号的情况下,不过符号这个看运气了,不一定有 符号。

VendeMe:foo

1
2
3
4
void __cdecl -[VendMe foo:](VendMe *self, SEL a2, int a3)
{
NSLog(CFSTR("%d"), (unsigned int)a3);
}

用户的参数从a3开始算。

这类服务漏洞挖掘

主动fuzz:找到处理函数,确定类型,然后主动写客户端fuzz。

被动fuzz:hook处理点,但是要一个一个hook,然后修改数据。

代码审计:找处理函数,人肉看逻辑。

引用

revisiting-apple-ipc