存档

2010年3月 的存档

关于库文件的搜索路径问题

三月 29th, {2010 1 条评论 9,237 人阅读过  

项目需要用到sip协议,虽然以前折腾飞信的时候用的也是sip,但不是标准的sip,自己去写一套标准的sip协议栈既浪费时间,又没有什么意思,于是想到了osip和exosip。

装了osip和exosip这两个库,在linux下安装下来就非常简单了,典型的三步曲就可以搞定。

写了个小程序测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <eXosip2/eXosip.h>
#include <netinet/in.h>
 
int main(int argc , char* argv[])
{
	int i;
	int port = 5060;
	TRACE_INITIALIZE(6 , stdout);
 
	i = eXosip_init();
 
	if(i != 0)
		return -1;
	i = eXosip_listen_addr(IPPROTO_TCP , NULL , port , AF_INET , 0);
 
	if(i != 0 )
	{
		eXosip_quit();
		fprintf(stderr , "can not initialize transport layer");
		return -1;
	}
	return 0;
}

编译:gcc -o exosip exosip.c -leXosip2 -DENABLE_TRACE

编译通过了,执行./exosip的时候却会报错:

./exosip: error while loading shared libraries: libeXosip2.so.4: cannot open shared object file: No such file or directory

./exosip: error while loading shared libraries: libosipparser2.so.4: cannot open shared object file: No such file or directory

./exosip: error while loading shared libraries: libosip2.so.4: cannot open shared object file: No such file or directory

很显然,程序找不到需要的这三个共享库,查了一个/usr/lib和/lib这两个目录里面果然没有,再查了一个/usr/local/lib,果然是安装在了那里面,以往都会在编译时设置环境变量LD_LIBRARY_PATH或者直接在/usr/lib目录下创建相关库文件的符号链接,想想太麻烦了,干嘛不设置一下库文件的搜索路径。

库文件搜索路径是在/etc/ld.so.conf中定义的,只需要在最后面加上一行/usr/local/lib就可以了

实际上程序在运行时并不是通过检查这个文件来定位所需要的头文件的,而是通过/etc/ld.so.cache这个文件来定位的,这个文件并不是明文的文本文件,它是ldconfig命令通过检查/etc/ld.so.conf生成的缓存文件,如果通过检查ld.so.conf中的路径来搜索库文件,执行效率未免太低,于是就生成一个缓存文件,通过它来定位所需要库文件。

ld.so.conf修改完之后必须用ldconfig来更新ld.so.cache,否则ld.so.conf改了也没用,另外ldconfig命令需要用root权限执行。

分类: Linux 标签: , ,

飞信2010身份验证过程及算法详述

三月 14th, {2010 23 条评论 65,766 人阅读过  

本人还是在校学生,由于滤涉世未深,对互联网还心存敬畏,没能像当初分析2006飞信协议的大哥那样洒脱,出于各种顾虑,当初在写飞信登录协议相关的文章的时候,都没详细地阐述登录过程,那篇文章当初也只是做为自己业余爱好的一个总结,却没想会受到大家如此的关注,很多人留言或发邮件问我具体的身份验证过程及算法,我也无暇一一详细解答,后来想了想,互联网应该是一个开放的环境,大家对飞信的关注其实也是为了给更多人提供方便或者学习交流,我想我也无须吝啬,就在这里把详细地身份验证过程阐述一下,仅用于学习交流,希望大家把它用在正途。

另外,我本人也在用业余时间写了一个Linux环境下的飞信,期待大家的加入,有兴趣的可以在这里留言。

具体身份验证流程我在下面这篇文章里面说得比较详细了,有需要的话可以先阅读一下这篇文章:

http://basiccoder.com/fetion2010-auth-algorithm.html

在里面有两个重要的过程:

1.密码的处理方式。

密码并不是以明文的方式进行加密的,而是用SHA1做了散列之后才进行加密的,散列的过程与2008略有不同,文字表达能力有限,就贴几个C函数吧:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
char* hash_password_v1(const unsigned char* b0 , int b0len
                                   , const unsigned char* password , int psdlen) 
{
	unsigned char* dst = (unsigned char*)malloc(b0len + psdlen + 1);
	unsigned char tmp[20];
	char* res;
	memset(tmp , 0 , sizeof(tmp));
	memset(dst , 0 , b0len + psdlen + 1);
	memcpy(dst , b0 , b0len);
	memcpy(dst + b0len , password , psdlen);
	SHA_CTX ctx;
	SHA1_Init(&ctx);
	SHA1_Update(&ctx , dst , b0len + psdlen );
	SHA1_Final(tmp , &ctx);
	free(dst);
	res = hextostr(tmp , 20);
	return res;
}
char* hash_password_v2(const char* userid , const char* passwordhex) 
{
	int id = atoi(userid);
	char* res;
	unsigned char* bid = (unsigned char*)(&id);
	unsigned char ubid[4];
	int bpsd_len;
	unsigned char* bpsd = strtohex(passwordhex , &bpsd_len);
	memcpy(ubid , bid , 4);
	res = hash_password_v1(ubid , sizeof(id) , bpsd , bpsd_len);
	free(bpsd);
	return res;
}
char* hash_password_v4(const char* userid , const char* password)
{
	const char* domain = "fetion.com.cn:";
	char *res , *dst;
	unsigned char* udomain = (unsigned char*)malloc(strlen(domain));
	unsigned char* upassword = (unsigned char*)malloc(strlen(password));
	memset(udomain , 0 , strlen(domain));
	memcpy(udomain , (unsigned char*)domain , strlen(domain));
	memset(upassword , 0 , strlen(password));
	memcpy(upassword , (unsigned char*)password , strlen(password));
	res = hash_password_v1(udomain , strlen(domain) , upassword , strlen(password));
	free(udomain);
	free(upassword);
	if(userid == NULL || strlen(userid) == 0)
	{
		return res;
	}
	dst = hash_password_v2(userid , res);
	free(res);
	return dst;
}

第三个函数是用来对密码进行散列处理的函数,输入是userid和密码,这个函数返回一个40字节的16进制字符串。

另外还有两个函数是在unsigned char串和char串之间相互转换:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
unsigned char* strtohex(const char* in , int* len) 
{
	unsigned char* out = (unsigned char*)malloc(strlen(in)/2 );
	int i = 0 , j = 0 , k = 0 ,length = 0;
	char tmp[3] = { 0 };
	memset(out , 0 , strlen(in) / 2);
	while(i < (int)strlen(in))
	{
		tmp[k++] = in[i++];
		tmp[k] = '\0';
		if(k == 2)
		{
			out[j++] = (unsigned char)strtol(tmp , (char**)NULL , 16);
			k = 0;
			length ++;
		}
	}
	if(len != NULL )
		*len = length;
	return out;
}
char* hextostr(const unsigned char* in , int len) 
{
	char* res = (char*)malloc(len * 2 + 1);
	int i = 0;
	memset(res , 0 , len * 2 + 1);
	while(i < len)
	{
		sprintf(res + i * 2 , "%02x" , in[i]);
		i ++;
	};
	i = 0;
	while(i < (int)strlen(res))
	{
		res[i] = toupper(res[i]);
		i ++;
	};
	return res;
}

然后是计算response过程:

1
2
3
/*这当然是不符合语法的,这样写大家应该就能明白*/
unsigned char* response_tmp = (unsigned char*)nonce
          + strtohex(hashed_password) + strtohex(aedkey);

最后把response_tmp通过服务器发过来的公钥进行RSA加密后得到response,传回服务器就可以通过验证了。

具体的信令格式都是明文,大家自己抓包看就好了,我在这里也就不多说了。

分类: Protocol 标签: , ,

应用层协议消息处理

三月 10th, {2010 没有评论 7,124 人阅读过  

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

前几天在用自己写的飞信,突然发现把配置文件都删了之后再次登录会需要下载头像,头像下载的速度相比于官方飞信来说实在是慢,其实最初没有考虑这个问题,想得是一个个小图片从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 标签:

写C程序时犯的超低级致命错误

三月 7th, {2010 4 条评论 8,288 人阅读过  

这几天一直在用C写程序,对于C/C++的内存管理方面的机制总是很小心,每次malloc之后都记得一定要free,可却犯了一个最初始的低级错误。

写了一个这样的函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
unsigned char* strtohex(const char* in , int* len) 
{
	unsigned char* out = (unsigned char*)malloc(strlen(in)/2 );
	int i = 0 , j = 0 , k = 0 ,length = 0;
 	char tmp[2] = { 0 };
	memset(out , 0 , strlen(in) / 2);
	while(i < (int)strlen(in))
	{
		tmp[k++] = in[i++];
                tmp[k] = '\0';
		if(k == 2)
		{
			out[j++] = (unsigned char)strtol(tmp , (char**)NULL , 16);
			k = 0;
			length ++;
		}
	}
	if(len != NULL )
		*len = length;
	return out;
}

函数很简单,功能就是把一个16进制的字符串转换成unsigned char 数组,每两个字节转换成一个字节的unsigned char

strtol函数的功能是将一个字符串转换成一个长整型,存在第二个参数里面,然后返回转换结果,第三个参数是进制数,这里是16进制。

看上去没什么问题,但这个函数却给我带来了灾难性的后果,经常在free的时候会Segmentation fault ,仔细地检查各种malloc,各种长度都没有问题,可Segmentation fault 却时而出现时而不出现,当时就意识到自己犯了致命的错误,可却找不出在哪里,gdb也查不出问题所在。

后来有一次调用这个函数做转换的时候猛然间发现对同一个字符串有时候的转换结果会不一样,仔细地检查了一下这个函数终于发现了错误的根源:

	char tmp[2] = { 0 };

这里定义了一个2字节的字符数组,在存储的是一个2字节的16进制字符串,居然忘了里后的一个\0,需要3个字节才够,真是晕了,即使在malloc的时候也会记得多malloc一个字节存储\0,在这里却大意了,狠狠地警示一下自己。

分类: C/C++ 标签:

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

三月 1st, {2010 61 条评论 24,400 人阅读过  

周末简单抓包分析了一下飞信的登录协议,昨天晚上一直延续到现在都在研究它的身份验证的算法,身份验证的过程大体搞清楚了,跟旧版本的形式差不多,最大的变化是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 标签: , ,