zoukankan      html  css  js  c++  java
  • iOS多用连接、反向协议、安全

    资源

    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: https://dl.dropboxusercontent.com/u/2857188/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:
    %@
    
    ", _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.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 都截获后,
    改动。再发送。


    其它

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

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

  • 相关阅读:
    15_门面模式
    14_责任链模式
    13_观察者模式
    12_状态模式
    11_策略模式
    10_命令模式
    09_适配器模式
    08_装饰者模式
    07_代理模式
    linux邮件服务器postfix配置实例
  • 原文地址:https://www.cnblogs.com/yxwkf/p/4812358.html
Copyright © 2011-2022 走看看