這個項目因時間成本過高最終沒有繼續推進了,這里把已經取得的一些進展備注一下,有興趣的同學可以了參考下。
項目主要在這三個方向上進行了探索:客戶端-服務器通信協議、客戶端發起通信流程、服務端返回數據的解析流程。前兩個流程在本項目中只進行了一些簡單的探索。相比較來說,第三個流程探索的更為精細與深入。
客戶端-服務器通信
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:函數時的一些打印日志:
圖中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類名如下圖:
WnsBizRecvData中的data被轉化成了UniAttribute對象,這個對象實質上就是一個由string-data構成的字典類型。通過debugserver-lldb能夠實時的打印出該對象的內容如下:
打印[requset elementRequests]內容如下:
可以猜測其實質上就是通過key值來標識數據對應的JCE結構,通過遍歷將UniAttribute對象中的數據轉換成具體的JCE對象。當然這些諸如ModeEntryContent/hostQboss/hostQzmall具體代表著什么,只能靠推測與進一步驗證了。
解析得到的對象會通過notifyResponder:response:error:requestInfo:busiserverip:接口發送出去。跟蹤該接口的實現,能夠確定對象最終會進入到QzoneNewFeedManager中的responseDicWithRsp:Req:Parameters:complete:作進一步的處理。
最終傳入到QzoneNewFeedManager中處理的feed流數據類似于下圖,也就是說至此數據依然不可讀,而如何轉換成可讀的明文數據,就需要參考responseDicWithRsp:Req:Parameters:complete:處理過程了。
結尾
這里只是記錄自己在項目中所發現的一些東西,很遺憾不能從總體上給出一個完善的逆向結論。