参考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在内存中的样子。