博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
iOS多用连接、反向协议、安全
阅读量:7060 次
发布时间:2019-06-28

本文共 13124 字,大约阅读时间需要 43 分钟。

资源

  1. WWDC-2013-Session-708

  2. BlackHat-US-2014-“It Just (Net)works”

  3. Understanding Multipeer Connectivity Framework in iOS 7 - Part 1 & 2

  4. MCDemo.zip: 

什么是多点连接?

多点连接简单说就是将设备两两进行连接。从而组成一个网络,见下图:
多点连接能够基于例如以下两种通道建立:
    
即:蓝牙与WiFi。
且“仅仅具有蓝牙的设备”能够与“仅仅具有WiF的设备”通信,
这一切都是透明的,开发人员根本不须要关心:
个人感觉它的能力还是比較强大的。

既然能力这么强大。它能够用来做什么呢?
MC仅仅是提供了一种数据通道,详细用途还是要看业务、看大家的想象力。
以下列几个比較常见的用途:
  1. 传文件
  2. 聊天室
  3. 一台设备作为数据採集外设(比方:摄像头),将实时数据导到还有一台设备上
  4. 网络数据转发
  5. ...

多点连接 API 的使用

SDK及版本号信息

  1. MultipeerConnectivity.framework
  2. iOS 7.0
  3. OS X 10.10

能够看到基于MC能够做到电脑与手机的通信。
了解了其能力与SDK相关信息后,以下我们看看工作流程:
使设备可被发现--->浏览设备,建立连接--->数据传输 。
关于使用大家能够看看參考资源与 MCDemo,
这里仅仅是做一个代码导读。
1、初始化 MCPeerID 及 MCSession,
MCPeerID 用来唯一的标识设备,
MCSession 是通信的基础:
-(void)setupPeerAndSessionWithDisplayName:(NSString *)displayName{    _peerID = [[MCPeerID alloc] initWithDisplayName:displayName];        _session = [[MCSession alloc] initWithPeer:_peerID];    _session.delegate = self;}
2、广播设备,使设备能够被发现:
-(void)advertiseSelf:(BOOL)shouldAdvertise{    if (shouldAdvertise) {        _advertiser = [[MCAdvertiserAssistant alloc] initWithServiceType:@"chat-files"                                                           discoveryInfo:nil                                                                 session:_session];        [_advertiser start];    }    else{        [_advertiser stop];        _advertiser = nil;    }}
3、浏览“局域网”中的设备。并建立连接:
-(void)setupMCBrowser{    _browser = [[MCBrowserViewController alloc] initWithServiceType:@"chat-files" session:_session];}
MCBrowserViewController实例化后,直接弹出,这个类内部会负责查找设备并建立连接。
对于有界面定制化需求的,也能够通过相关接口实现类似的功能。
4、发送消息:
-(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:\n%@\n\n", _txtMessage.text]]];    [_txtMessage setText:@""];    [_txtMessage resignFirstResponder];}
发送消息时有个选项:MCSessionSendDataReliable,MCSessionSendDataUnreliable
可是无论是可靠还是不可靠。数据都是基于 UDP 进行传输的。

5、接收消息:
-(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 链接:
-(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、接收资源:
-(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进行分析,由于这样便于抓包。
抓到的数据包例如以下图:
能够看到主要是基于例如以下几个协议:
Bonjour在法语中是 Hello 的意思,即:主要用来做服务发现。
STUN主要用来做port映射。便于两台设备直接建立连接。
剩下的两个协议未知:一个基于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:] 中实现,
基本策略是:
  1. IDString: 随机,base36
  2. pid64:随机
  3. displayName:外部传入。如:”Proteas-iPhone5s”
设备间交换ID时须要进行序列化,
序列化的方法为:-[MCPeerID serializedRepresentation]
总结起来就是:PeerID = 
基于pid64生成前 9 byte + displayName
附反编译结果:
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 时,我们能够指定加密方式,
这个加密方式是个枚举类型:
  1. MCEncryptionOptional = 0
  2. MCEncryptionRequired = 1
  3. MCEncryptionNone = 2
从上图能够看出加密方式会影响Data Key,
可是全然通过抓包来分析 Data Key 是比較耗时的。
并且非常可能会有遗漏。
通过代码逆向。我们找到负责 Data Key 生成的类:
这里能够作为分析 Data Key 的起点,
有须要的兄弟能够进行深入分析。

上面我们都是在说基于 TCP 的未知协议,
接下来我们看看基于 UDP 的未知协议。
UDP数据流:
详细一个UDP数据包:
能够看出它是在 DTLS 之上做了封装。
我们仅仅要抛弃到 0xd0 就能够让 Wireshark 进行识别分析。
这里须要说下 BH-US 大会上没有发布详细的工具与方法。
我处理的方法是写一个 Custom Protocol Dissector:
-- Apple Mutipeer Connectivity Custom DTLS Protocl-- cache globals to local for speed.local format = string.formatlocal tostring = tostringlocal tonumber = tonumberlocal sqrt = math.sqrtlocal pairs = pairs-- wireshark API globalslocal Pref = Preflocal Proto = Protolocal ProtoField = ProtoFieldlocal DissectorTable = DissectorTablelocal Dissector = Dissectorlocal ByteArray = ByteArraylocal PI_MALFORMED = PI_MALFORMEDlocal PI_ERROR = PI_ERROR-- dissectorslocal 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)    endendlocal 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)  endend 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)	endendregister_udp_port_range(16400, 16499)
在 Wireshark 中使用自己定义协议进行处理后:
这里识别出协议后,我们不做继续分析。
可是评估安全性时。比方在手机上 kill 调 ssl 后。
能够在 DTLS 的 Payload 中看到明文数据。

安全性分析

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

可是当两方都是 MCEncryptionOptional,通信也是不安全的。
可能发生中间人攻击:
实施中间人攻击首先要识别出基于 TCP 一些数据包。
如上图中的浅色部分。数据包都是有特点的。
因此是能够识别的。

可是没有演示中间人攻击的原因是,
plist文件里的数据貌似是有关联关系,简单的将0改为1,
并不会将 false 改成 true,会造成 plist 无效,
因此实施中间人攻击时可能须要将整个 plist 都截获后,
改动。再发送。

其它

  1. 眼下没有逆向出整个通信协议,可是假设想将一些外设模拟成 MC 设备,须要进一步逆向出整个协议。
  2. MultipeerConnectivity 链接了 IOKit。因此可能间接得暴露出 IOKit 的攻击面。

版权声明:本文博主原创文章,博客,未经同意不得转载。

你可能感兴趣的文章
RMI客户端调用
查看>>
oracle修改主键列类型
查看>>
在SecureCRT中不能使用rz命令
查看>>
影响网站排名的30个因素
查看>>
J2EE搭建之八 运行第一个JSP
查看>>
c++filt
查看>>
ExtJS中get、getDom、getCmp、getBody、getDoc的使用
查看>>
Android Studio的使用
查看>>
我的友情链接
查看>>
【云图】【支付宝】如何在支付宝服务窗上增加家乐福门店分布图?
查看>>
WebLogic缓存
查看>>
针对百度搜索上线的极光算法,我们应该怎样应对?
查看>>
WordPress文件结构
查看>>
Java程序员从笨鸟到菜鸟之(七十一)细谈struts2(十三)struts2实现文件上传和下载详解...
查看>>
Feign http 请求跟踪—乱码及连接池
查看>>
python unittest库 官方网站
查看>>
shell脚本安装 nfs-server
查看>>
5G超新时代,点燃了25G和100G光模块市场
查看>>
程序员最怕的四个字:通宵发布!| 程序员有话说
查看>>
没有公网IP怎样访问异地视频监控
查看>>