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

结尾

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,743评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,296评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,285评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,485评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,581评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,821评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,960评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,719评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,186评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,516评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,650评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,329评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,936评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,757评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,991评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,370评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,527评论 2 349

推荐阅读更多精彩内容

  • 国家电网公司企业标准(Q/GDW)- 面向对象的用电信息数据交换协议 - 报批稿:20170802 前言: 排版 ...
    庭说阅读 10,930评论 6 13
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,633评论 18 139
  • 简介 用简单的话来定义tcpdump,就是:dump the traffic on a network,根据使用者...
    保川阅读 5,946评论 1 13
  • “真自由训练营”是由幸福进化进化俱乐部发起的元习惯提升类产品,活动具体内容请详见:http://blog.hidd...
    非凡说阅读 363评论 2 0
  • 一年前的今天是我十九岁人生最记忆深刻的日子。高考成绩出来的那一刻,其实我内心是平静的,我很清楚我高三那种行尸走肉...
    不爱说话的痞子阅读 659评论 4 9