这个项目因时间成本过高最终没有继续推进了,这里把已经取得的一些进展备注一下,有兴趣的同学可以了参考下。
项目主要在这三个方向上进行了探索:客户端-服务器通信协议、客户端发起通信流程、服务端返回数据的解析流程。前两个流程在本项目中只进行了一些简单的探索。相比较来说,第三个流程探索的更为精细与深入。
客户端-服务器通信
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:处理过程了。
结尾
这里只是记录自己在项目中所发现的一些东西,很遗憾不能从总体上给出一个完善的逆向结论。