你好,歡迎來到IOS教程網

 Ios教程網 >> IOS編程開發 >> IOS開發綜合 >> iOS多點連接的使用、協議逆向、安全性

iOS多點連接的使用、協議逆向、安全性

編輯:IOS開發綜合

 

什麼是多點連接?


多點連接簡單說就是將設備兩兩進行連接,從而組成一個網絡,見下圖:
\

多點連接可以基於如下兩種通道建立:
\ \

即:藍牙與WiFi, 且“只具有藍牙的設備”可以與“只具有WiF的設備”通信, 這一切都是透明的,開發者根本不需要關心:
\

個人感覺它的能力還是比較強大的。 既然能力這麼強大,它可以用來做什麼呢? MC只是提供了一種數據通道,具體用途還是要看業務、看大家的想象力, 下面列幾個比較常見的用途: 傳文件聊天室一台設備作為數據采集外設(比如:攝像頭),將實時數據導到另一台設備上網絡數據轉發...

多點連接 API 的使用

 

SDK及版本信息

 

MultipeerConnectivity.frameworkiOS 7.0OS 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進行分析,因為這樣便於抓包。 抓到的數據包如下圖:
\

可以看到主要是基於如下幾個協議:
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 附反編譯結果:
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:
-- 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 的攻擊面。

  1. 上一頁:
  2. 下一頁:
蘋果刷機越獄教程| IOS教程問題解答| IOS技巧綜合| IOS7技巧| IOS8教程
Copyright © Ios教程網 All Rights Reserved