這個項(xiàng)目因時間成本過高最終沒有繼續(xù)推進(jìn)了,這里把已經(jīng)取得的一些進(jìn)展備注一下,有興趣的同學(xué)可以了參考下。
項(xiàng)目主要在這三個方向上進(jìn)行了探索:客戶端-服務(wù)器通信協(xié)議、客戶端發(fā)起通信流程、服務(wù)端返回數(shù)據(jù)的解析流程。前兩個流程在本項(xiàng)目中只進(jìn)行了一些簡單的探索。相比較來說,第三個流程探索的更為精細(xì)與深入。
客戶端-服務(wù)器通信
QZone這個APP沒有像普通的其它資訊類應(yīng)用采用常見的http/https協(xié)議進(jìn)行客戶端與服務(wù)器的通信。而是使用了TCP協(xié)議,這一點(diǎn)可以通過Wireshark來得到驗(yàn)證。整個TCP連接建立的入口位于[WnsSDK startWnsConnection]中。WnsSDK是騰訊云向開發(fā)者推出的一個通信方案,但就我在此次逆向經(jīng)驗(yàn)推測,QZone中使用的Wns應(yīng)該是經(jīng)過嘗試定制的版本或者說是內(nèi)部版本,其基本實(shí)現(xiàn)與開放給開發(fā)者的SDK版本有著很大的不同。當(dāng)然作為參考,SDK及其開發(fā)文檔還是有著很高的價值。
通信的過程中還使用了騰訊自己特有的JCE序列化協(xié)議(功能類似于protobuf),騰訊出身的同學(xué)也許知道該協(xié)議,QZone的同學(xué)很可能有該協(xié)議的生成工具。有了該協(xié)議的生成工具,那么就可以通過classdump導(dǎo)出的頭文件反寫出整個QZone使用的序列化文件。
網(wǎng)絡(luò)請求主要類結(jié)構(gòu)
WnsSDK只是負(fù)責(zé)一些中轉(zhuǎn)的工作,GRNetReqContext是網(wǎng)絡(luò)請求上下文的緩存類,GRNetworkEngine類是整個通信流程的核心類。下圖給出了整個網(wǎng)絡(luò)請求的大致流程:

hook sendBizData:與handleBizData:并打印傳入的參數(shù)能夠發(fā)現(xiàn):兩個流程的參數(shù)類型為WnsBizSendData/WnsBizRecvData,WnsBizSendData類有data與bizDelegate兩個屬性值,WnsBizRecvData也有一個屬性data,很自然地就會猜測data可能就是jce對象序列化后的二進(jìn)制數(shù)據(jù),而bizDelegate通過打印值獲悉其是GRNetworkEngine對象。
如此可以推測客戶端將JCE對象序列化成二進(jìn)制數(shù)據(jù)后通過TCP協(xié)議(socket實(shí)現(xiàn))發(fā)送到服務(wù)器,接收到服務(wù)器返回的數(shù)據(jù)后交由GRNetworkEngine處理,由其負(fù)責(zé)將二進(jìn)制數(shù)據(jù)反序列化為JCE對象。
客戶端發(fā)起通信請求
這個流程沒有深入探索,只能提供下入口與方向,具體細(xì)節(jié)還需要深入[WnsSDK sendBizData:]的實(shí)現(xiàn)。這里給出hook sendBizData:函數(shù)時的一些打印日志:

圖中command同樣也是WnsBizSendData中的屬性值,它應(yīng)該是標(biāo)識本次請求的數(shù)據(jù)結(jié)構(gòu),值QzoneNewService.getActiveFeeds猜測就是獲取當(dāng)前feeds流的請求。
服務(wù)器返回數(shù)據(jù)的解析
WnsBizRecvData的頭文件結(jié)構(gòu)如下:
@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應(yīng)該就是從服務(wù)器返回的二進(jìn)制數(shù)據(jù),而seqno應(yīng)該是某一標(biāo)識,通過ida對handleBizData的數(shù)據(jù)處理過程進(jìn)行分析,將其轉(zhuǎn)換成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對象的主體被解析出來,下面的流程只是對該對象的一些屬性進(jìn)行補(bǔ)充。debugserver-lldb打印的JCE類名如下圖:

WnsBizRecvData中的data被轉(zhuǎn)化成了UniAttribute對象,這個對象實(shí)質(zhì)上就是一個由string-data構(gòu)成的字典類型。通過debugserver-lldb能夠?qū)崟r的打印出該對象的內(nèi)容如下:

打印[requset elementRequests]內(nèi)容如下:

可以猜測其實(shí)質(zhì)上就是通過key值來標(biāo)識數(shù)據(jù)對應(yīng)的JCE結(jié)構(gòu),通過遍歷將UniAttribute對象中的數(shù)據(jù)轉(zhuǎn)換成具體的JCE對象。當(dāng)然這些諸如ModeEntryContent/hostQboss/hostQzmall具體代表著什么,只能靠推測與進(jìn)一步驗(yàn)證了。
解析得到的對象會通過notifyResponder:response:error:requestInfo:busiserverip:接口發(fā)送出去。跟蹤該接口的實(shí)現(xiàn),能夠確定對象最終會進(jìn)入到QzoneNewFeedManager中的responseDicWithRsp:Req:Parameters:complete:作進(jìn)一步的處理。
最終傳入到QzoneNewFeedManager中處理的feed流數(shù)據(jù)類似于下圖,也就是說至此數(shù)據(jù)依然不可讀,而如何轉(zhuǎn)換成可讀的明文數(shù)據(jù),就需要參考responseDicWithRsp:Req:Parameters:complete:處理過程了。

結(jié)尾
這里只是記錄自己在項(xiàng)目中所發(fā)現(xiàn)的一些東西,很遺憾不能從總體上給出一個完善的逆向結(jié)論。