美文网首页
有关QZone信息流逆向的一些总结

有关QZone信息流逆向的一些总结

作者: 秦砖 | 来源:发表于2017-11-01 14:57 被阅读177次

这个项目因时间成本过高最终没有继续推进了,这里把已经取得的一些进展备注一下,有兴趣的同学可以了参考下。

项目主要在这三个方向上进行了探索:客户端-服务器通信协议、客户端发起通信流程、服务端返回数据的解析流程。前两个流程在本项目中只进行了一些简单的探索。相比较来说,第三个流程探索的更为精细与深入。

客户端-服务器通信

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信息

结尾

这里只是记录自己在项目中所发现的一些东西,很遗憾不能从总体上给出一个完善的逆向结论。

相关文章

  • 有关QZone信息流逆向的一些总结

    这个项目因时间成本过高最终没有继续推进了,这里把已经取得的一些进展备注一下,有兴趣的同学可以了参考下。 项目主要在...

  • iOS 逆向工程

    由于最近工作需要,学习了一些有关iOS逆向开发的知识。主要资料还是来自狗神的《iOS应用逆向工程 第二版》,这本书...

  • 有关逆向protobuf数据的一些心得

    protobuf是一套非常优秀的序列化与反序列化协议,因此在平时的逆向过程中,与之打交道是不可避免的。如何能够在浩...

  • 看《逆向盈利》读书笔记

    最近看完了一本书,这本书叫【逆向盈利】,感觉还是不错的。以下是读这本书的读书笔记,做一些总结分享。 本书书名【逆向...

  • 腾讯公益H5:QQ反家暴自拍行动

    案例赏析链接:https://qzs.qzone.qq.com/qzone/qzact/act/external/...

  • 有关产品的一些总结

    呐,这回谈的还是产品,因为只对这个了解一些。网上曾说过,一千个读者就有一千个哈姆雷特,产品也是一样,而且抛开个人...

  • 有关Map的一些总结

    1.HashMap 的数据结构是什么样的? 我们知道在Java中最常用的两种结构是数组和模拟指针(引用),几乎所有...

  • 360信息流投放的大致总结

    简单总结一下360信息流,看图说话,360信息流包括四个位置,PC信息流,优品广告,场景橱窗,PC标准展示,有没有...

  • 逆向WeChat获取用户输入时密码

    wechat逆向工程探索与学习期间遇到了一些坑并且总结了经验,分享需求:在非越狱的iphone环境,wechat用...

  • iOS 逆向指南:界面分析

    前言: 写几篇文章总结一下 iOS 逆向的整个流程,逆向初学者可以作为入门指南。内容包括逆向工具和环境配置、踩坑点...

网友评论

      本文标题:有关QZone信息流逆向的一些总结

      本文链接:https://www.haomeiwen.com/subject/szybpxtx.html