iOS多点连接的使用、协议逆向、安全性

http://www.2cto.com/kf/201411/352716.html

什么是多点连接?


多点连接简单说就是将设备两两进行连接,从而组成一个网络,见下图:
\

多点连接可以基于如下两种通道建立:
\   \

即:蓝牙与WiFi, 且“只具有蓝牙的设备”可以与“只具有WiF的设备”通信, 这一切都是透明的,开发者根本不需要关心:
\

个人感觉它的能力还是比较强大的。 既然能力这么强大,它可以用来做什么呢? MC只是提供了一种数据通道,具体用途还是要看业务、看大家的想象力, 下面列几个比较常见的用途: 传文件聊天室一台设备作为数据采集外设(比如:摄像头),将实时数据导到另一台设备上网络数据转发...

多点连接 API 的使用

 

SDK及版本信息

 

MultipeerConnectivity.frameworkiOS 7.0OS X 10.10

 

可以看到基于MC可以做到电脑与手机的通信。 了解了其能力与SDK相关信息后,下面我们看看工作流程: 使设备可被发现--->浏览设备,建立连接--->传输数据 。 关于使用大家可以看看参考资源与 MCDemo, 这里只是做一个代码导读。
1、初始化 MCPeerID 及 MCSession, MCPeerID 用来唯一的标识设备, MCSession 是通信的基础:
?
1
2
3
4
5
6
-( void )setupPeerAndSessionWithDisplayName:(NSString *)displayName{
     _peerID = [[MCPeerID alloc] initWithDisplayName:displayName];
     
     _session = [[MCSession alloc] initWithPeer:_peerID];
     _session.delegate = self;
}

2、广播设备,使设备可以被发现:
?
1
2
3
4
5
6
7
8
9
10
11
12
-( void )advertiseSelf:(BOOL)shouldAdvertise{
     if (shouldAdvertise) {
         _advertiser = [[MCAdvertiserAssistant alloc] initWithServiceType: @chat -files
                                                            discoveryInfo:nil
                                                                  session:_session];
         [_advertiser start];
     }
     else {
         [_advertiser stop];
         _advertiser = nil;
     }
}

3、浏览“局域网”中的设备,并建立连接:
?
1
2
3
-( void )setupMCBrowser{
     _browser = [[MCBrowserViewController alloc] initWithServiceType: @chat -files session:_session];
}

MCBrowserViewController实例化后,直接弹出,这个类内部会负责查找设备并建立连接。 对于有界面定制化需求的,也可以通过相关接口实现类似的功能。
4、发送消息:
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
-( void )sendMyMessage{
     NSData *dataToSend = [_txtMessage.text dataUsingEncoding:NSUTF8StringEncoding];
     NSArray *allPeers = _appDelegate.mcManager.session.connectedPeers;
     NSError *error;
     
     [_appDelegate.mcManager.session sendData:dataToSend
                                      toPeers:allPeers
                                     withMode:MCSessionSendDataReliable
                                        error:&error];
     
     if (error) {
         NSLog(@%@, [error localizedDescription]);
     }
     
     [_tvChat setText:[_tvChat.text stringByAppendingString:[NSString stringWithFormat: @I wrote:
%@
 
, _txtMessage.text]]];
     [_txtMessage setText:@];
     [_txtMessage resignFirstResponder];
}

发送消息时有个选项:MCSessionSendDataReliable,MCSessionSendDataUnreliable 但是不管是可靠还是不可靠,数据都是基于 UDP 进行传输的。
5、接收消息:
?
1
2
3
4
5
6
7
8
9
-( void )session:(MCSession *)session didReceiveData:(NSData *)data fromPeer:(MCPeerID *)peerID{
     NSDictionary *dict = @{ @data : data,
                            @peerID : peerID
                            };
     
     [[NSNotificationCenter defaultCenter] postNotificationName: @MCDidReceiveDataNotification
                                                         object:nil
                                                       userInfo:dict];
}

消息的接收是通过 MCSession 的回调方法进行的。 MCSession的回调方法非常重要, 设备状态的改变、消息的接收、资源的接收、流的接收都是通过这个回调进行通知的。

6、发送资源,资源可以是本地的URL,也可以是 Http 链接:
?
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
32
33
34
35
36
37
38
39
40
-( void )actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex{
     if (buttonIndex != [[_appDelegate.mcManager.session connectedPeers] count]) {
         NSString *filePath = [_documentsDirectory stringByAppendingPathComponent:_selectedFile];
         NSString *modifiedName = [NSString stringWithFormat:@% @_ %@, _appDelegate.mcManager.peerID.displayName, _selectedFile];
         NSURL *resourceURL = [NSURL fileURLWithPath:filePath];
         
         dispatch_async(dispatch_get_main_queue(), ^{
             NSProgress *progress = [_appDelegate.mcManager.session sendResourceAtURL:resourceURL
                                                                             withName:modifiedName
                                                                               toPeer:[[_appDelegate.mcManager.session connectedPeers] objectAtIndex:buttonIndex]
                                                                withCompletionHandler:^(NSError *error) {
                                                                    if (error) {
                                                                        NSLog( @Error : %@, [error localizedDescription]);
                                                                    }
                                                                    
                                                                    else {
                                                                        UIAlertView *alert = [[UIAlertView alloc] initWithTitle: @MCDemo
                                                                                                                        message: @File was successfully sent.
                                                                                                                       delegate:self
                                                                                                              cancelButtonTitle:nil
                                                                                                              otherButtonTitles: @Great !, nil];
                                                                        
                                                                        [alert performSelectorOnMainThread: @selector (show) withObject:nil waitUntilDone:NO];
                                                                        
                                                                        [_arrFiles replaceObjectAtIndex:_selectedRow withObject:_selectedFile];
                                                                        [_tblFiles performSelectorOnMainThread: @selector (reloadData)
                                                                                                    withObject:nil
                                                                                                 waitUntilDone:NO];
                                                                    }
                                                                }];
             
             //NSLog(@*** %f, progress.fractionCompleted);
             
             [progress addObserver:self
                        forKeyPath: @fractionCompleted
                           options:NSKeyValueObservingOptionNew
                           context:nil];
         });
     }
}

可以通过 NSProgress查询相关状态。
7、接收资源:
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
-( void )session:(MCSession *)session didStartReceivingResourceWithName:(NSString *)resourceName fromPeer:(MCPeerID *)peerID withProgress:(NSProgress *)progress{
     
     NSDictionary *dict = @{ @resourceName  :   resourceName,
                            @peerID        :   peerID,
                            @progress      :   progress
                            };
     
     [[NSNotificationCenter defaultCenter] postNotificationName: @MCDidStartReceivingResourceNotification
                                                         object:nil
                                                       userInfo:dict];
     
     
     dispatch_async(dispatch_get_main_queue(), ^{
         [progress addObserver:self
                    forKeyPath: @fractionCompleted
                       options:NSKeyValueObservingOptionNew
                       context:nil];
     });
}


协议逆向


协议分析时,我们是基于WiFi进行分析,因为这样便于抓包。 抓到的数据包如下图:
\

可以看到主要是基于如下几个协议:
width=350

Bonjour在法语中是 Hello 的意思,即:主要用来做服务发现。 STUN主要用来做端口映射,便于两台设备直接建立连接。 剩下的两个协议未知:一个基于TCP,一个基于UDP。 基于 TCP 的,我们看下TCP Stream:
\

注意下图中红框部分:
\

这是某种握手机制,首先是交换设备ID,然后会基于Binary Plist 交换信息。 首先提取plist,提取plist时要参考 tcp stream 中的起始字节与结束字节, 将 plist 提出来后, 会看到一共交换了三个plist:
plist-1:
\

MCNearbyServiceInviteIDKey:MCEncryptionOption—>1, MCEncryptionNone—>0;
MCNearbyServiceMessageIDKey:序号
MCNearbyServiceRecipientPeerIDKey:接收者的PeerID
MCNearbyServiceSenderPeerIDKey:发送者的PeerID

plist-2:

\

MCNearbyServiceAcceptInviteKey:是否接收连接
MCNearbyServiceConnectionDataKey

plist-3:
\

MCNearbyServiceConnectionDataKey

如上只是说了plist的内容, 但是在 tcp stream 中我们还看到了设备ID, 设备ID是如何生成的呢? 通过代码逆向可以得到一个大概的结论: 设备ID在 -[MCPeerIDInternal initWithIDString:pid64:displayName:] 中实现, 基本策略是: IDString: 随机,base36pid64:随机displayName:外部传入,如:”Proteas-iPhone5s” 设备间交换ID时需要进行序列化, 序列化的方法为:-[MCPeerID serializedRepresentation] 总结起来就是:PeerID = 基于pid64生成前 9 byte + displayName 附反编译结果:
?
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
void * -[MCPeerID initWithDisplayName:]( void * self, void * _cmd, void * arg2) {
     STK33 = r5;
     STK35 = r7;
     sp = sp - 0x28 ;
     r5 = arg2;
     arg_20 = self;
     arg_24 = * 0x568f0 ;
     r6 = [[&arg_20 super ] init];
     if (r6 != 0x0 ) {
             if ((r5 == 0x0 ) || ([r5 length] == 0x0 )) {
                     r0 = [r6 class ];
                     r0 = NSStringFromClass(r0);
                     var_0 = r0;
                     [NSException raise:*_NSInvalidArgumentException format: @Invalid displayName passed to %@];
             }
             else {
                     if ([r5 lengthOfBytesUsingEncoding: 0x4 ] >= 0x40 ) {
                             r0 = [r6 class ];
                             r0 = NSStringFromClass(r0);
                             var_0 = r0;
                             [NSException raise:*_NSInvalidArgumentException format: @Invalid displayName passed to %@];
                     }
             }
             arg_8 = r6;
             arg_C = r5;
             r8 = CFUUIDCreate(*_kCFAllocatorDefault);
             CFUUIDGetUUIDBytes(&arg_10);
             r11 = (arg_1C ^ arg_14) << 0x18 | (arg_1C ^ arg_14) & 0xff00 | 0xff00 & (arg_1C ^ arg_14) | arg_1C ^ arg_14;
             r10 = 0xff00 & (arg_10 ^ arg_18) | ((arg_10 ^ arg_18) & 0xff00 ) << 0x8 | arg_10 ^ arg_18 | arg_10 ^ arg_18;
             r5 = _makebase36string(r11, r10);
             if (*_gVRTraceErrorLogLevel < 0x6 ) {
                     asm{ strd       r4, r5, [sp] };
                     VRTracePrint_();
             }
             else {
                     if (*(int8_t *)_gVRTraceModuleFilterEnabled != 0x0 ) {
                             asm{ strd       r4, r5, [sp] };
                             VRTracePrint_();
                     }
             }
             r4 = [NSString stringWithUTF8String:r5];
             free(r5);
             CFRelease(r8);
             r0 = [MCPeerIDInternal alloc];
             var_0 = r10;
             arg_4 = arg_C;
             r0 = [r0 initWithIDString:r4 pid64:r11 displayName:STK- 1 ];
             r6 = arg_8;
             r6->_internal = r0;
     }
     r0 = r6;
     Pop();
     Pop();
     Pop();
     return r0;
}
 
[[MCPeerIDInternal alloc] initWithIDString:_makebase36string(...) pid64:r11 displayName:STK- 1 ]

前面的 plist 中有 Data Key,我们没有做过多说明, 接下来我们大概看看 Data Key 的生成:
\

在初始化一个多点连接的 Session 时,我们可以指定 加密 方式, 这个加密方式是个枚举类型: MCEncryptionOptional = 0
MCEncryptionRequired = 1
MCEncryptionNone = 2
从上图可以看出加密方式会影响Data Key, 但是完全通过抓包来分析 Data Key 是比较耗时的, 而且很可能会有遗漏。 通过代码逆向,我们找到负责 Data Key 生成的类:
\

这里可以作为分析 Data Key 的起点, 有需要的兄弟可以进行深入分析。
上面我们都是在说基于 TCP 的未知协议, 接下来我们看看基于 UDP 的未知协议。 UDP数据流:
\

具体一个UDP数据包:
\

可以看出它是在 DTLS 之上做了封装, 我们只要抛弃到 0xd0 就可以让 Wireshark 进行识别分析。 这里需要说下 BH-US 大会上没有公布具体的工具与方法, 我处理的方法是写一个 Custom Protocol Dissector:
?
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
-- Apple Mutipeer Connectivity Custom DTLS Protocl
 
-- cache globals to local for speed.
local format = string.format
local tostring = tostring
local tonumber = tonumber
local sqrt = math.sqrt
local pairs = pairs
 
-- wireshark API globals
local Pref = Pref
local Proto = Proto
local ProtoField = ProtoField
local DissectorTable = DissectorTable
local Dissector = Dissector
local ByteArray = ByteArray
local PI_MALFORMED = PI_MALFORMED
local PI_ERROR = PI_ERROR
 
-- dissectors
local dtls_dissector = Dissector.get(dtls)
 
apple_mcdtls_proto = Proto(apple_mcDTLS, Apple Multipeer Connectivity DTLS, Apple Multipeer Connectivity DTLS Protocol)
function apple_mcdtls_proto.dissector(buffer, pinfo, tree)
     local mctype = buffer( 0 , 1 ):uint()
     if mctype == 208 then
         pinfo.cols.protocol = AppleMCDTLS
         pinfo.cols.info = Apple MC DTLS Payload Data
         local subtree = tree:add(apple_mcdtls_proto, buffer(), Apple MC DTLS Protocol)
         subtree:add(buffer( 0 , 1 ),Type:  .. buffer( 0 , 1 ):uint())
         local size = buffer:len()
         subtree:add(buffer( 1 , size - 1 ), Data:  .. tostring(buffer))
         dtls_dissector:call(buffer( 1 ):tvb(), pinfo, tree)
     end
end
 
local function unregister_udp_port_range(start_port, end_port)
     if not start_port or start_port <= 0 or not end_port or end_port <= 0 then
         return
     end
   udp_port_table = DissectorTable.get(udp.port)
   for port = start_port,end_port do
     udp_port_table:remove(port, apple_mcdtls_proto)
   end
end
  
local function register_udp_port_range(start_port, end_port)
     if not start_port or start_port <= 0 or not end_port or end_port <= 0 then
         return
     end
     udp_port_table = DissectorTable.get(udp.port)
     for port = start_port,end_port do
         udp_port_table:add(port, apple_mcdtls_proto)
     end
end
 
register_udp_port_range( 16400 , 16499 )

在 Wireshark 中使用自定义协议进行处理后:
\

这里识别出协议后,我们不做继续分析, 但是评估安全性时,比如在手机上 kill 调 ssl 后, 可以在 DTLS 的 Payload 中看到明文数据。

安全性分析


前文中也提到了,安全性的控制是在初始化 MCSession 时控制的, 默认是使用 MCEncryptionOptional, 但是当有一方是 MCEncryptionNone 时会发生降级,即:通信不加密。
\

但是当双方都是 MCEncryptionOptional,通信也是不安全的, 可能发生中间人攻击:
\

实施中间人攻击首先要识别出基于 TCP 一些数据包, 如上图中的浅色部分,数据包都是有特点的, 因此是可以识别的。 但是没有演示中间人攻击的原因是, plist文件中的数据貌似是有关联关系,简单的将0改为1, 并不会将 false 改成 true,会造成 plist 无效, 因此实施中间人攻击时可能需要将整个 plist 都截获后, 修改,再发送。

其他

目前没有逆向出整个通信协议,但是如果想将一些外设模拟成 MC 设备,需要进一步逆向出整个协议。MultipeerConnectivity 链接了 IOKit,因此可能间接得暴露出 IOKit 的攻击面。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值