存档

文章标签 ‘协议分析’

应用层协议消息处理

三月 10th, {2010 没有评论 3,129 人阅读过  

最近基于应用层协议写了不少程序,着实也遇到了一些问题,很多东西真正动手实践过其实自己以前的理解都是错的。

前几天在用自己写的飞信,突然发现把配置文件都删了之后再次登录会需要下载头像,头像下载的速度相比于官方飞信来说实在是慢,其实最初没有考虑这个问题,想得是一个个小图片从http服务器上下载下来理应需要一定的时间,前两天觉得不对翻源码看了看,原来在每个头像加载完成之后我用了select来检查socket状态是否可读,并设定了一秒的超时时间,这样不管图片下载得多快,下载完成后都要等1s才能进入下一个循环,以前不知道怎么判断数据是否读取完成,就采用了select的方法,如果socket中没有数据并处于阻塞状态,select read失败就表明数据接收完成,其实这种方法是很不好的。

很多应用层的协议头部都会有一个长度字段,不管是明文的还是非明文的,用来标识载核数据的长度,因为常用的传输层协议像TCP是基于字节流的协议,而不是像SCTP那样基于消息,这样就增加了在应用层处理的难度,于是很多应用层协议便会加上长度字段,无论HTTP,SIP这些明文的协议还是像RTP这种非明文的协议,都会有Length字段,这样在读取到消息头的时候,就知道消息体有多长,并且明文协议通常会在消息头和消息体之间用\r\n\r\n分隔,这样就可以判断读取的数据是否是一条完整的消息,如果不是则可以继续读取缺少的字节数。

当然,很多时候消息并不是交互式的到来,而且推送式的,像SIP里面的notification,用户没有发送请求的时候消息一样会被推过来,当多条消息一起推送过来的时候,由于内核把收到的数据放到缓冲区里面,套接字再从缓冲区里面读取数据,这样读取到的数据可能是多条消息连在一起,这样便需要用Length字段来将消息分开。

其实TCP和UDP的这种面向字节流的机制在消息处理上还是比较麻烦的,而SCTP采用的面向消息的机制确实很不错,在消息处理上着实很方便,只可惜现在的SCTP应用还不是很广泛。

分类: Protocol 标签:

飞信2010身份验证算法成功破解

三月 1st, {2010 61 条评论 13,982 人阅读过  

周末简单抓包分析了一下飞信的登录协议,昨天晚上一直延续到现在都在研究它的身份验证的算法,身份验证的过程大体搞清楚了,跟旧版本的形式差不多,最大的变化是2010不再是将密码的散列值进行传输,而是使用RSA非对称加密之后再进行传输。

在SipC注册的过程中,客户端生成一个CNOUCE,然后发给服务器,服务器自己生成一对密钥,私钥自己保留,公钥放到key里面发给客户端,随同公钥一起发给客户端的还有服务器生成的key值,和一个数据签名signature。

客户端的加密过程如下:

客户端将三个字段的UTF8数组合并成一个byte[](在C里面也就是unsigned char*),然后对这个byte[]进行RSA加密,加密使用了PKCS#1 1.5的padding方式,查了一下.net只支持两种padding,相比于no padding来说更安全一些,而且它每次加密之后的结果都是不一样的,另外使用这种Padding进行加密时,要加密的数据至少要比密钥的模长短至少11个字节,否则就会加密失败。

下面说一下是将哪三个字段加密了,第一个字段是服务器发过来的nonce,第二个字段是密码,第三个字段是一个aeskey。

nonce这个没什么好说的,就是Sipc注册的时候返回来的nonce值,直接放在前面就可以了。

password是这个是困扰我时间最长的,飞信在计算response值的时候,把这三个字段当成16进制字符串,也就是类似于”ACFDF768597″这种,它将它们转换成UTF8数组,每两个字节转换成一个字节的UTF8数组,但原始密码肯定不可能是这种标准的16进制字符串,必然是做过某种处理,注意到在发送验证信令的后面附上了一个值algorithm=”SHA1-sess-v4″,然后我意识到密码可能是经过SHA1散列处理了,事实上确实如此,只不过不如我想象的那么简单。在linux下用openssl提供的加密算法来测试,返回的却一直都是Unaccpectable,一直以为是加密算法出了问题,反复修改测试各种加密算法,无数次失败后终于决定回到windows下改用.net测试,用飞信用到的.net framework里提供的加密算法进行加密,确保加密算法没有问题,庆幸自己多掌握了几门语言,虽然写得不如别人漂亮,但能看懂对我来说就足够了。

用.net写了一个sipc验证的程序,结果总是提示密码错误,也就是说密码的处理上确实存在问题,折腾了两三个小时终于弄明白飞信对密码都动了哪些手脚。它将字符串fetion.com.cn:password的utf8字符串进行SHA1做散列,然后得出一个16进制字符串,SHA1是20个字节的,16进制字符串有40个字节,然后再将这个字符串前面再附上一个user-id,再做散列,得到的字符串就是最后要加密的密码,user-id这个东西在旧版本里面我就注意到,但事实上它并没有起到什么作用,新版本里面居然把它用到身份验证里面来了,至于user-id的获取,这个连看也不用看,肯定在SSI登录的完成的时候会在返回的数据包中包含。

再就是aeskey这个字段,这真是个让我哭笑不得的字段,从名字上看上去好像是用到了AES对称加密,事实上在生成这个字段的时候也确实用到了.net关于AES算法的Rijndael类,冒然贴一段代码:

1
2
3
4
5
6
7
public static string GenerateKey()
{
     Rijndael rijndael = Rijndael.Create();
     rijndael.KeySize = 0x100;
     rijndael.GenerateKey();
     return BinaryToHex(rijndael.Key);
}

简单地说一下,第一句话创建一个Rijndael对象,第二句话指定密钥的长度为256个字节,GenerateKey()这个方法在MSDN里面解释是说生成一个随机密钥,仔细查看了.net关于Rijndael的代码,发现它在它的父类中声明,是个抽象方法,而Rijndael类并没有对这个方法进行重写,也就是说这里调用的这个方法是个空方法,我把这个方法注释掉以后再调用这个方法,仍然可以生成随机字符串。最有意思的地方是这里并于AES的只有这几句话,根本没有涉及到任何加密的代码,也就是说飞信开发人员需要一个64字节的随机字符串附在密码后面,一起加密,这样使要加密字符串更长一些,加密的强度也就更大一些,而飞信开发人员嫌麻烦,懒得再重新写一个生成这样一个随机字符串的方法,于是就借用AES里面的随机密钥,其实整个身份验证过程跟AES没有半点关系,即使我把这个aeskey换成了一个固定值如:“4A026855890197CFDF768597D07200B346F3D676411C6F87368B5C2276DCEDD2”也一样能够验证通过,而光是这个名字就给了我很大的误导。

总而言之,飞信2010身份验证算法已经搞定了,在windows下用.net测试也通过了,接下来如何移植到C+openssl就是以后的事了,并于openssl实现这些相关的算法有时间再写吧。

声明:本文纯粹用于学习,不出于任何商业利益或者有任何损坏飞信利益的行为,涉及内容比较抽象,靠此文不可能写出飞信登录程序,希望权威部门不要找我麻烦。

分类: Protocol 标签: , ,

飞信2010登录协议分析

二月 27th, {2010 17 条评论 11,579 人阅读过  

飞信2010出了有一段时间了,但到现在也只是beta版,所以就没怎么关注它,今天下载了一个分析了一下它的协议,才发现飞信迟迟不推出新版本是因为它的协议有了比较大的变动,似乎想要把我们这些山寨货赶尽杀绝,也就是传说中的V4版本的协议,很多信令后面都加上了V4标志,这也意味着想要适应2010,协议部分就必须重写。

飞信的登录协议比以前的版本有了显著的变化,最明显的变化在于它对登录信息的加密处理上,旧版本的飞信登录信息的加密让人看起来有点小儿科,把几个字段这样加起来做散列,然后再那样加起来做散列,来来回回折腾最后得出一个response传回服务器验证。

2010的登录信息处理采用了非对称加密RSA算法。先贴几个抓出来的协议包吧:
1.SSI登录
SSI登录仍然采用了https登录,登录服务器应该没变,还是uid.fetion.com.cn。登录参数变了,加上必须的参数uri如下:

https://uid.fetion.com.cn/ssiportal/SSIAppSignInV4.aspx?mobileno=1348888888&&domains=fetion.com.cn%3bm161.com.cn%3bwww.ikuwa.cn&&v4digest-type=2&v4digest=password

注意:这里的密码也不再是v3里面的明文密码了,呵呵。

其中demains里面有三个域名,第一个应该是必须的,后两个不知道是哪家第三方的公司的域名,估计可以去掉,v4digest-type是密码类型,有如下定义:

1
2
3
4
5
6
public enum PasswordTypeEnum
{
    V3,
    V4Temp,
    V4
}

v4digest就是密码了,然后还有一些可选参数像algorithm之类的我也就没细看它的功能。

2.sipC注册

R fetion.com.cn SIP-C/4.0
F: 916098834
I: 1
Q: 1 R
CN: 1CF1A05B2DD0281755997ADC70F82B16
CL: type=”pc” ,version=”3.6.1900″

这是sipC注册信息,CN就是旧版本里的cnouce , CL就是客户端信息了。服务器收到后回复信令:

SIP-C/4.0 401 Unauthoried
F: 916098834
I: 1
Q: 1 R
W: Digest algorithm=”SHA1-sess-

v4″,nonce=”389CE97D594B290366DCD4CC2DA5368D”,key=”D84CA40D849FEF3B45F02BBA6900D2A1B3010001″,signature=”62F5BA9D9146D477CDECAE1FD47098D0CABBFD31D683B5223B5D52″

为了节约篇幅key和signature的内容我都删去了一部分,key就是共享密钥,这个用来对验证信息做非对称加密用的,signature是签名,用来对密钥信息做验证的。

3.sipC验证

R fetion.com.cn SIP-C/4.0
F: 916098834
I: 1
Q: 2 R
A: Digest

response=”5041619340F10D101149E45A8E2343294E8D5F4F5086C24703EE00CAA4DF8F9D2951E9F285C458F556

7454F5C35084A8CB7C5E3AA8D31D1FC3B9DD7CF395631E5EDE73E2E683D54C9E521443D31792E18B81CA26542BF5

79F662459CD5F6E4C0EC29767B6C5F775C10AF19B544B6957AC98FF6A420C7486BE68837A576036118″,algorith

m=”SHA1-sess-v4″
AK: ak-value
L: 426

消息体里面的xml信息就不贴出来了。最关键的部分是通信sipC注册返回的信息来计算这个response,计算response共需要用到4个参数,publickey,nouce,password,aeskey.

publickey刚才注册返回的key值。

nouce 注册返回的nouce值。

password用户密码。

aeskey 用户通过 Rijndael算法生成的AES串。

response的计算规则是:

1.str = nouce + password + aeskey

2.对str进行RSA非对称加密,RSA算法具体细节我不懂,不过查看了一个微软MSDN了也大体知道是怎么工作的,它在计算的时候需要Exponent(e,公钥指数),Modulus(n , 模),D (d , 私钥指数),不过我看飞信在传递RSA参数的时候只是指定了e和n,e是截取了公钥的前256的字符,n是截取了公钥的256个以后的字符。

3.通过2计算出来的参数对str进行加密,得到的就是response值。

将验证信令发送到服务器之后服务器会返回用户的详细信息,这与之前的版本之比也是一大改进,省去了再额外构造信令去GetPersonalInfo

另外,新版本的协议在获取用户信息上改动也很大,sipC验证完成后用户个人信息和联系人列表就会随返回消息一同发过来,旧版本会先发送GetContactList信息获取用户列表,然后再构造一个庞大的Subscription去订阅这些用户的消息,新版本将这些完全简化了,发送如下一条信令就可以订阅所有用户的信息,用户信息就会被一条一条地推送过来。

SUB fetion.com.cn SIP-C/4.0
F: 916098834
I: 8
Q: 1 SUB
N: PresenceV4
L: 95

<args>
<subscription self="v4default;mail-count" buddy="v4default" version="320595567" />
</args>

格式如下:

BN 916098834 SIP-C/4.0
N: PresenceV4
I: 1
L: 190
Q: 5 BN

<events>
    <event type="PresenceChanged">
        <contacts>
            <c id="601820518"><pr di="" b="0" d="" dt="" dc="0"/></c>
            <c id="464997176"><pr di="" b="0" d="" dt="" dc="0"/></c>
        </contacts>
    </event>
</events>

将字段名称也简化了,这样有助于在数据传输时候节省字节数,效率会更高一些。

OK,重要的变化差不多也就这些了,真要动手去写的话也够我忙活一阵子的了,现在没那么多时间了,要老老实实专心做项目了,2010到现在也只是个beta版,我先静观其变吧,免得做无用功。

分类: Protocol 标签: , ,

飞信文件传输协议简述

二月 3rd, {2010 没有评论 3,922 人阅读过  

今天切回win分析了一下飞信的一些零散的功能的协议,觉得好不容易切过来就抢包看了看文件传输的协议。状态机我就不细划了,简单的地写一下几个重要的点:
当发起文件传输请求的时候,客户端向服务器发起ShareContent的请求:

fetion.com.cn SIP-C/2.0
F: 916098834
I: 3
Q: 0 O
K: ShareContent
T: sip:572003969@fetion.com.cn;p=4599
L: 460

1
2
3
4
5
6
7
8
9
<share-content id="6b984ea5-2e66-48f4-acb3-60513160162a">
<caps modes="block;relay;p2p;p2pV2;relayV2;p2pV3;scV2" max-size="2097151" />
<client outer-ip="" inner-ip="59.64.128.137:1429;" port="1428" />
<fileinfo>
<transmit type="p2p" session-id="xz4BBcV6b984ea52e6648f4acb360513160162a" />
<file name="Desktop.rar" size="526697" url="" 
md5="1a0edaa0a213be4a28d415594a150053" id="6b984ea5-2e66-48f4-acb3-60513160162a" file-type="unknown" />
</fileinfo>
</share-content>

这里面包含几个重要的字段,session-id,id,md5
md5显而易见是对要传输的文件做md5散列运算得出的字符串,用于在接收端对文件内容的完整性进行校验。
id 其实是Guid,也就是全局唯一标识符,它指在一台机器上生成的数字,在同一时空中不会有另一台机器上的数字与它相同。
官方飞信是能C#开发的,GUID 的格式为“xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx”,其中每个 x 是 0-9 或 a-f 范围内的一个十六进制的数字。例如:337c7f2b-7a34-4f50-9141-bab9e6478cc8 即为有效的 GUID 值。 查看了一下飞信生成Guid用了一句话:

System.Guid.NewGuid().ToString("D");

session-id的生成是这样的,把id中的‘-’去掉,然后在前面加上”xz4BBcV”这个字符串,C#代码如下:

1
2
3
4
5
6
7
public string SessionId
{
    get
    {
        return ("xz4BBcV" + this.SessionGuid.ToString().Replace("-", string.Empty));
    }
}

filetype字段的定义

1
2
3
4
5
public enum ShareContentFileType
{
    Unknown,
    DirectSendImage
}

接收端接收文件时向服务器发送如下信息:

IN 916098834 SIP-C/2.0
I: 1
Q: 4 IN
F: sip:572003969@fetion.com.cn;p=4599
L: 201

1
2
3
<share-content action="accept">
<file id="c36b9ea6-7704-4860-a323-9470db7fab74" />
<client prefer-types="FFFFFFF" inner-ip="3B408089" net-type="7" udp-inner-port="1497" tcp-port="1496" /></share-content>

id就不用说了,在同一个会话中都使用一个id,prefer-types是指接收端希望使用的传输协议,共有如下几种类型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public enum ShareContentType
{
    All = 0xfffffff,
    None = 0,
    P2P = 0xff00,
    RelayAndBlock = 1,
    TCP = 0x100,
    UDP = 0x200,
    V2All = 0xff0000,
    V2HttpRelay = 0x200000,
    V2TcpClient = 0x20000,
    V2TcpRelay = 0x100000,
    V2TcpServer = 0x10000,
    V2Udp = 0x40000
}

在这里使用FFFFFFF表示可以使用所有的类型进行传输,后来分析了一下发现其实飞信文件传输用的一直都是P2P。
OK,这些问题搞清楚了文件传输的实现就是时间的问题了。

分类: Protocol 标签: ,