拆解wechatPC CDN部分

@vrqq  October 16, 2019
参考Github项目:wechat4iPad

Part0 下断点分析
首先mars网络明文数据已知了,我们分析CDN部分。
打开随便一个抓包工具,例如Wireshark,或者Microsoft Message Analyzer(快不能用了)这种,然后封堵https:443端口,使网络降级,然后我们发现请求端口为http:80。
在wechatwin.dll::offset_C529F0处下断点,看注释我们得知这个函数原型:

class Protocol::CBaseRequest { //看内存 size=0x20 allocated, 0x18 used. 也许为了8-aligned
    void* vftable[1] = {...}; //0x0
    int16 var_0x4;
    struct vector< struct_info > {
        void *begin;    //0x8
        void *end;    //0xC
        void *allocated_end;    //0x10
    }var_header;
    void var_address_0x14;
    bool __thiscall ReqToBuf_UseHttp(Autobuffer *output, char* server_ip, char*/string* path, unknown|int? [out]);
}

在return处停留便可以截获output,经比对与抓到的包一致。

基础知识
这个CDN网络其实是传统的http-server,用户发起请求,服务器返回数据,没了。

1. 何时从CDN下载

在mars网络,下载缩略图thumb,或者传文件时候下载文件,都会发来一个cdn-fileid,举例:
在Mars网络中,有一Task{ cgi="/cgi-bin/micromsg-bin/newsync" },其返回数据包micromsg::NewSyncResponse中包含了下面内容:(返回数据包经解密后为proto编码)

<?xml version="1.0"?>
<msg>
        <appmsg appid="wx96..." sdkver="0">
                <title>file.rar</title>
                <appattach>
                        <totallen>251066</totallen>
                        <attachid>@cdn_30...00_eb..2c_1</attachid>
                        <emoticonmd5 />
                        <fileext>rar</fileext>
                        <cdnattachurl>30...00</cdnattachurl>
                        <cdnthumbaeskey />
                        <aeskey>eb...2c</aeskey>
                        <encryver>0</encryver>
                        <filekey>1...0@chatroom2...</filekey>
                </appattach>
                <md5>6e...1a</md5>
        </appmsg>
</msg>

省略了不用的部分,我们从这里获取<cdnattachurl />以及`这两部分。

2.得知CDN服务器IP

在mars网络上提起Task{ cgi="/cgi-bin/micromsg-bin/getcdndns" },返回数据中有四组CDN服务器,延续上面的例子,我们使用最后一组DNS,即下述dnsinfo7

message GetCDNDnsResponse {
    message CDNDnsInfo {
        repeated google.protobuf.StringValue frontip_list = 6;
        UniversalPayload dns_authkey = 8;
    };
    CDNDnsInfo dnsinfo2 = 2;
    CDNDnsInfo dnsinfo3 = 3;
    CDNDnsInfo dnsinfo4 = 4;
    CDNDnsInfo dnsinfo7 = 7;
}

在此处记下frontip_list为DNS的服务器IP,记下dns_authkey这个参数,后面请求会用到

3.发起HTTP Request

http请求的body格式如下(省略了不重要部分)

00,00,00,03 + "ver" + 00,00,00,01 + "1" ...
00,00,00,07 + "authkey" + 00,00,00,45 + "{dns_authkey}" //第二步得到的cdnkey,总是0x45长
... "rangestart" ... "0"
... "rangeend" ... "65535" 

其中有两个kv-pair: rangestart=0, rangeend=65535,即如果遇到较大的文件,会在此分段请求,最大64kb一个包。

使用curl形象生动又开心,把制作好的reqbody.bin存起来,先获取一段

curl -v -X POST --header "Accept: */*" --header "Content-Type: application/octet-stream" --header "User-Agent: MicroMessenger Client" --data-binary "@reqbody.bin" {front_ip[0]}/download -o netresp.bin

返回包如下格式同上(服务器主动close connection很节能)

... "ver" ... "0"
... "filedata" ... "<netresp.bin>"

然后写段python 3先把第一个文件片解了

import os
from Crypto.Cipher import AES

fin = open("netresp.bin", "rb");
fout = open("plain.bin", "wb");
enc_data = fin.read()
print('Open file size = ', fin.tell())

#把part1中的aeskey粘过来,字符串长度32,每两个组成一个16进制数,得到一个len=16的key
key = bytes.fromhex('eb...2c')
print('Decrypted Key: size=', len(key), " value=", key)
decryptor = AES.new(key, AES.MODE_ECB)
plain = decryptor.decrypt(enc_data)
print('Plain data size = ', len(plain))
fout.write(plain)

对每段文件分别结完,把plain.bin拼接即可得到file.rar。

附,一些汇编中找key小技巧:

  • 比如我们想找this->offset_0x7B4这个变量,这个7B4很蹊跷啊,直接全文搜索,找到相应汇编,看上下文即可找到源头。
  • 搜索出来 mov ecx, esi基本上就是调用了(ecx代表__thiscall时候的this),但是看到个特别的可以留意是不是constructor哦!
  • 想做一个内存跟踪插件,肯定太影响运行速度了。
  • 汇没事干也不会operator new,看到以后火速找这个是啥结构体,对应多大,下断点看内存,稳。
  • 多打标机错了不怕,大段看反汇编,小段直接看汇编比较快。
  • 留意自有struct Autobuffer以及std::string在内存中的样子。

添加新评论