有關QZone信息流逆向的一些總結

這個項目因時間成本過高最終沒有繼續推進了,這里把已經取得的一些進展備注一下,有興趣的同學可以了參考下。

項目主要在這三個方向上進行了探索:客戶端-服務器通信協議、客戶端發起通信流程、服務端返回數據的解析流程。前兩個流程在本項目中只進行了一些簡單的探索。相比較來說,第三個流程探索的更為精細與深入。

客戶端-服務器通信

QZone這個APP沒有像普通的其它資訊類應用采用常見的http/https協議進行客戶端與服務器的通信。而是使用了TCP協議,這一點可以通過Wireshark來得到驗證。整個TCP連接建立的入口位于[WnsSDK startWnsConnection]中。WnsSDK是騰訊云向開發者推出的一個通信方案,但就我在此次逆向經驗推測,QZone中使用的Wns應該是經過嘗試定制的版本或者說是內部版本,其基本實現與開放給開發者的SDK版本有著很大的不同。當然作為參考,SDK及其開發文檔還是有著很高的價值。

通信的過程中還使用了騰訊自己特有的JCE序列化協議(功能類似于protobuf),騰訊出身的同學也許知道該協議,QZone的同學很可能有該協議的生成工具。有了該協議的生成工具,那么就可以通過classdump導出的頭文件反寫出整個QZone使用的序列化文件。

網絡請求主要類結構

WnsSDK只是負責一些中轉的工作,GRNetReqContext是網絡請求上下文的緩存類,GRNetworkEngine類是整個通信流程的核心類。下圖給出了整個網絡請求的大致流程:


網絡請求流程

hook sendBizData:與handleBizData:并打印傳入的參數能夠發現:兩個流程的參數類型為WnsBizSendData/WnsBizRecvData,WnsBizSendData類有data與bizDelegate兩個屬性值,WnsBizRecvData也有一個屬性data,很自然地就會猜測data可能就是jce對象序列化后的二進制數據,而bizDelegate通過打印值獲悉其是GRNetworkEngine對象。

如此可以推測客戶端將JCE對象序列化成二進制數據后通過TCP協議(socket實現)發送到服務器,接收到服務器返回的數據后交由GRNetworkEngine處理,由其負責將二進制數據反序列化為JCE對象。

客戶端發起通信請求

這個流程沒有深入探索,只能提供下入口與方向,具體細節還需要深入[WnsSDK sendBizData:]的實現。這里給出hook sendBizData:函數時的一些打印日志:


sendBizData打印日志

圖中command同樣也是WnsBizSendData中的屬性值,它應該是標識本次請求的數據結構,值QzoneNewService.getActiveFeeds猜測就是獲取當前feeds流的請求。

服務器返回數據的解析

WnsBizRecvData的頭文件結構如下:

@interface WnsBizRecvData : NSObject
@property(nonatomic) int webappChangeTime; 
@property(retain, nonatomic) NSString *webappIp; 
@property(nonatomic) int tlvIndex; 
@property(nonatomic) _Bool isFinish;
@property(nonatomic) _Bool isFirst; 
@property(nonatomic) _Bool isTlvMode; 
@property(nonatomic) long long seqno; 
@property(retain, nonatomic) NSData *data; 
@end

下面的解析過程中會用到的有data、seqno,毫無疑問data應該就是從服務器返回的二進制數據,而seqno應該是某一標識,通過ida對handleBizData的數據處理過程進行分析,將其轉換成OC語言后如下:

- (void)handleBizData:(WnsBizRecvData*)recvData
{
    ?long long seqno = [recvData seqno];
    NSData* data = [recvData data];
    NSInteger length = [data length];
    BOOL finish = [recvData isFinish];
    NSNumber* seqnoNum = [NSNumber numberWithLong:seqno];
    NSDictionary* ctxMap = [[GRNetworkEngine sharedInstance] requestCtxMap];
    GRNetReqContext* context = [ctxMap objectForKey:seqnoNum];
    NSDictionary* dictionary = [NSDictionary dictionaryWithObjects:]
    id request = [context request];
    id serviceCmd = [request serviceCmd];
    if (context) {
        [request setReqStatus:2];
        [request setRecvTimestamp:[NSData timeIntervalSinceReferenceData]];
        NSString* rspValue = [self rspNameFromReq:reqest];
        Class class = NSClassFromString(rspValue);
        if (class) {
            if ([class isSubclassOfClass:([JceObjectV2 class])]) {
                UniAttribute* attri = [UniAttribute fromData:data];
                NSNumber* number = [NSNumber intValueWithName:@"ret" inAttributes:attri];
                NSString* msg = [NSString stringWithName:@"msg" inAttributes:attri];
                if (number == nil && class != [WnsHttpProxyRsp ?class]) {
                    ?NSString* shortCmd = [self shortCmdWithReq:request];
                    ?id object = [JceObjectV2 objectWithName:shortCmd inAttributes:attri];
                    [object setErrorCode:nil];
                    [object setRequest:request];
//                    [object appendTraceStr:];
                    if (shortCmd) {
                        NSDictionary* elements = [request elementRequests];
                        if ([elements count] > 0) {
                            NSMutableDictionary* response = [NSMutableDictionary dictionary];
                            [elements enumeraterKeysAndObjectsUsingBlock:^(id key, id object, BOOL finish){
                                id temp = [object objectWithName:inAttributes:attri];
                                if(temp != nil){
                                    [response setObject:temp forKey:attri];
                                }
                            }];
                            [object setElementResponces:response];
                            [self notifyResponder:context response:object error:nil requestInfo:nil busiserverip:nil];
                        }
                    }
                }
            }
        }
    }
}

這個過程中最重要的一步是[JceObjectV2 objectWithName:inAttribute:],在這個過程里JCE對象的主體被解析出來,下面的流程只是對該對象的一些屬性進行補充。debugserver-lldb打印的JCE類名如下圖:


Feeds流類名

WnsBizRecvData中的data被轉化成了UniAttribute對象,這個對象實質上就是一個由string-data構成的字典類型。通過debugserver-lldb能夠實時的打印出該對象的內容如下:


UniAttribute內容

打印[requset elementRequests]內容如下:
key-type鍵值對

可以猜測其實質上就是通過key值來標識數據對應的JCE結構,通過遍歷將UniAttribute對象中的數據轉換成具體的JCE對象。當然這些諸如ModeEntryContent/hostQboss/hostQzmall具體代表著什么,只能靠推測與進一步驗證了。
解析得到的對象會通過notifyResponder:response:error:requestInfo:busiserverip:接口發送出去。跟蹤該接口的實現,能夠確定對象最終會進入到QzoneNewFeedManager中的responseDicWithRsp:Req:Parameters:complete:作進一步的處理。

最終傳入到QzoneNewFeedManager中處理的feed流數據類似于下圖,也就是說至此數據依然不可讀,而如何轉換成可讀的明文數據,就需要參考responseDicWithRsp:Req:Parameters:complete:處理過程了。


末解密的feed信息

結尾

這里只是記錄自己在項目中所發現的一些東西,很遺憾不能從總體上給出一個完善的逆向結論。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 國家電網公司企業標準(Q/GDW)- 面向對象的用電信息數據交換協議 - 報批稿:20170802 前言: 排版 ...
    庭說閱讀 11,145評論 6 13
  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,923評論 18 139
  • 簡介 用簡單的話來定義tcpdump,就是:dump the traffic on a network,根據使用者...
    保川閱讀 5,987評論 1 13
  • “真自由訓練營”是由幸福進化進化俱樂部發起的元習慣提升類產品,活動具體內容請詳見:http://blog.hidd...
    非凡說閱讀 395評論 2 0
  • 一年前的今天是我十九歲人生最記憶深刻的日子。高考成績出來的那一刻,其實我內心是平靜的,我很清楚我高三那種行尸走肉...
    不愛說話的痞子閱讀 679評論 4 9