2008年1月13日星期日

矿大联创Linux客户端源码分析(二)

矿大的Linux客户端已经成功的移植到本校。一旦完成了,我就开始变得慵懒,连分析代码都有点要放弃的念头了。做事情要有始有终,后面的代码我就稍稍简单点分析——其实很大程度上也是因为我自己也不太懂,嘿嘿。

另外,最搞笑的一点就是,在本校,根本就用不着加密算法,因此我已经把MD5加密那部分完全删除了,最难的部分最终成了最简单的部分了。myerr.c和myerr.h我就不分析了,没啥味道,只分析sendpacket.c和linkthread.c

linkthread.c

/* This file contains functions which are used to implement the authentication */
#include <libnet.h>
#include <pcap.h>
#include <stdio.h>
#include "global.h"
#include "myerr.h"
#include "linkthread.h"
#include "sendpacket.h"
#include "configfile.h"

extern Config_Info *config_info;

#define TRUE 1
#define FALSE 0
unsigned char l_localMAC[6];//本机的MAC地址
unsigned char l_destMAC[6];//服务器的MAC地址.

extern int state_global;
libnet_t *l=NULL; //定义一个lib_net_t结构体(lib_net结构体的别名),在libnet-structures.h中有该结构体的详细信息
pcap_t *p=NULL; // 定义一个pcap_t结构体 (lib_pcap结构体的别名),在pcap-int.h中定义
int p_fd; //定义一个整数

static int init_daemon(void); //声明init_daemon 函数原型,函数定义在后面

void
init(void)
{
char l_errbuf[LIBNET_ERRBUF_SIZE];
char filter_buf[256];
struct libnet_ether_addr *l_ether_addr;
struct bpf_program filter_code;
u_int32_t p_netmask;
char p_errbuf[PCAP_ERRBUF_SIZE];

load_config(); //加载配置信息
sprintf(filter_buf,"ifconfig %s up",config_info->interface); // 生成启动网卡的命令
system(filter_buf); // 启动网卡
if((l=libnet_init(LIBNET_LINK, config_info->interface,l_errbuf))==NULL)
err_quit("libnet_init: %s\n", l_errbuf); //初始化libnet环境,如果出错,打印错误信息并退出。

if((p=pcap_open_live(config_info->interface,65536,0, 500, p_errbuf))==NULL){
err_msg("pcap_open_live: %s\n",p_errbuf);
// 初始化抓包过程。从eth0抓取,最大长度为65535,非混杂模式,等待500ms,
// 并传入一个错误信息缓存区用于存储错误信息 。如果失败,打印错误并退出。
}
p_fd=pcap_fileno(p); //从pcap_open_live()获得的p中读取抓到的包的文件描述符编号
//we can pselect() it in the following code.

if((l_ether_addr=libnet_get_hwaddr(l))==NULL){
err_msg("unable to get local mac address :%s\n",libnet_geterror(l));
} //获取本机MAC地址,如果出错,打印错误信息并退出。
memcpy(l_localMAC,l_ether_addr,sizeof(l_localMAC)); //把MAC地址的值存入l_locamMAC数组

snprintf(filter_buf,sizeof(filter_buf),FILTER_STR, l_localMAC[0],l_localMAC[1],
l_localMAC[2],l_localMAC[3],l_localMAC[4],l_localMAC[5]);
// FILTER_STR = "ether[12:2]=0x888e and ether dst %02x:%02x:%02x:%02x:%02x:%02x"
// 最后filter_buf的内容为 ether[12:2]=0x888e and ether dst 00:0A:2F:42:09:CF 字符串
// 这里的MAC地址是我乱编的,实际会是本机的MAC地址。
if(pcap_compile(p, &filter_code,filter_buf, 0, p_netmask)==-1){
err_msg("pcap_compile(): %s", pcap_geterr(p));
} // 处理广播包。如果发生错误,打印错误信息并退出。
if(pcap_setfilter(p, &filter_code)==-1){
err_msg("pcap_setfilter(): %s", pcap_geterr(p));
} // 指定过滤程序。如果发生错误,打印错误信息并退出。
pcap_freecode(&filter_code); // avoid memory-leak
//处理由pcap_compile()分配的bpf内存

}

void
release_network()
{
pcap_close(p); //关闭抓包
libnet_destroy(l); //关闭libnet环境
}

int
link_thread()
{
int ret;
fd_set read_set; //来源。我不懂这是什么。typedef struct fd_set { int fds_bits[1]; } fd_set;
struct pcap_pkthdr *pkt_hdr; //pcap_pkthdr结构体的定义在pcap.h中。
const u_char *pkt_data;
int isFirstPacketFromServer=1;
int count_search_server=0;
int count_send_name=0;
int count_send_password=0;
int count_wait_for_refresh=0;
int count_logout=0;
struct timespec timeout; //声明timespec结构体
char buffer[16];
enum
{
SEARCHING_SERVER,
SEND_NAME,
SEND_PASSWORD,
ONLINE,
UNLINK
}linkstate; //连线状态枚举
timeout.tv_sec=1;timeout.tv_nsec=0;
linkstate=SEARCHING_SERVER; //开始连线,搜索服务器。
SendFindServerPacket(l); //发送搜索广播包 。此函数在sendpacket中定义。
while(TRUE)
{
/* 获取服务器数据 */

FD_ZERO(&read_set); //初始化文件描述符组
FD_SET(p_fd,&read_set); //将read_set的值填充入p_fd
ret=pselect(p_fd+1,&read_set,NULL,NULL,&timeout,NULL); //不太明白
switch(ret) //这段switch是应对出错时应做的动作。正常情况下,这段代码是不会被执行到的。一旦进入这段代码,基本上也就连不上了。
{
case -1: //出错
puts("error occur when pselect!");
return(-1);
break;
case 0:
// 没有收到数据包
//此处不明白。如果收到服务器响应呢?
switch (linkstate)
{

// 寻找服务器阶段

case SEARCHING_SERVER:
if(++count_search_server >= config_info->response_time_out) //如果无法得到服务器响应,则退出。这里,我把config_info->response_time_out改成了config_info->retry_times。总感觉用response_time_out没来由。关键是看不懂上面的时间函数。
{
un_link();
return 1;
}
else //第一次进入循环时,如果没有收到响应,则进入这里,触发再一次发送广播包;发送后跳出,回到循环起始,接受服务器响应。
{
SendFindServerPacket(l);
continue;
}
/* 发送用户名阶段 */

case SEND_NAME:
if(++count_send_name >= config_info->retry_times)
{
count_send_name=0;
SendFindServerPacket(l);
}
SendNamePacket(l,pkt_data);
continue;
/* 发送密码阶段 */

case SEND_PASSWORD:
if(++count_send_name >= config_info->retry_times)
{
puts("send password packet time out!");
count_send_password=0;
SendFindServerPacket(l);
}
SendPasswordPacket(l,pkt_data);
continue;
/* 认证成功,在线阶段 */

case ONLINE:
if(++count_wait_for_refresh >= config_info->refresh_time_out)
{
if(config_info->auto_retry)
linkstate=SEARCHING_SERVER;
else
;
}
else
continue;
continue;
case UNLINK:
if(++count_logout >= config_info->retry_times)
{
un_link();
return 0;
}
else
continue;
default:
continue;
} //判断link_state的那个switch结束。
} //判断ret的那个switch结束。即,除非pselect出错,或没有受到服务器的包,否则跳过。
//第一次即收到数据包,或重试后收到SendFindServerPacket(l)触发的服务器响应后,执行这里;
if((pcap_next_ex(p,&pkt_hdr,&pkt_data))!=1) continue; //如果读取数据包出错,则跳出,回到循环起始。
//读取pcap_t结构体p,使pkt_data指向p的数据部分,使pkt_hdr指向p的pcap_pkthdr结构体,pcap_pkthdr在pcap.h中定义。
//而pkd_data指向的位置,正是服务器返回的那个数据包包头!(查阅抓包结果就知道了。)

//通过比较MAC地址来确定这个数据包是不是服务器发来的,是则分析之,否则忽略掉
if ((!isFirstPacketFromServer)&&(memcmp(l_destMAC,pkt_data+6,6)!=0)) continue;

switch( pkt_data[0x12] ) //分析EAP包类型
{
case 0x01: //表示请求
switch( pkt_data[0x16] )
{
case 0x01: //要求发送用户名
if (linkstate == ONLINE)//在线状态, 服务器要求更新连接(约每30s更新一次)
count_wait_for_refresh=0;//忽略认证成功后的心跳测试包
//置等待更新时间为零

else if(linkstate == SEARCHING_SERVER){ //正在联接
count_search_server=0;
linkstate=SEND_NAME;
if (isFirstPacketFromServer) //获得服务器的MAC地址
{
memcpy( l_destMAC, pkt_data+6, 6);
isFirstPacketFromServer=0;
}
++count_send_name;
}
(void)SendNamePacket(l, pkt_data);
break;
case 0x52: //发送密码(没有加密算法-___-|||)//本校为0x52
if(linkstate != SEND_NAME)continue; //忽略认证成功后的心跳测试包
count_send_name=0;
linkstate=SEND_PASSWORD;
++count_send_password;
(void)SendPasswordPacket(l, pkt_data);
break;
}
break;
case 0x03: //认证成功
if(linkstate != SEND_PASSWORD) continue;
count_send_password=0;
linkstate=ONLINE;
//调用ifup来获得网络配置,这需要3到10秒的时间
sprintf(buffer,"ifconfig %s down",config_info->interface);
system(buffer);
sprintf(buffer,"ifconfig %s up",config_info->interface);
system(buffer);
//上面重启网卡的命令考虑修改为system("dhclient");
//大功告成,终于连好了
init_daemon();//转为后台守护进程,联网成功。
break;
case 0x04: //连接失败了

switch (linkstate)
{
case SEARCHING_SERVER:
//不在上网时段?
fputs("Server freeze your connectiong\n", stdout);
break;
case ONLINE:
//服务器切断连接(没钱啦?到断网时间了?)
fputs("Server cut down your connectiong!!!\n", stderr);
break;
case SEND_NAME:
case SEND_PASSWORD:
fputs("Username of Password error!!!\n", stdout);
//用户名错啦?密码错啦?没钱啦?
break;
case UNLINK:
break;
}//end switch
un_link();
return 1;
}// end switch
}//end while
}

// 断开连接
void
un_link()
{
SendEndCertPacket(l);
release_network();
free_config();
exit(EXIT_SUCCESS);
}

//使函数变成后台守护进程
int
init_daemon(void)
{
pid_t pid;

if ( (pid = fork()) < 0) /* fork two process */
return -1;
else if (pid) /* grandfa goes bye bye */
exit(EXIT_SUCCESS);

if ( setsid() < 0)
return -1;

signal(SIGHUP, SIG_IGN);
if ( (pid = fork()) < 0)
return -1;
else if(pid) /* father goes bye bye too */
exit(EXIT_SUCCESS);

close(0);
close(1);
if (open("/dev/null", O_RDONLY) == -1)
return -1;
if (open("/tmp/lknnu.log", O_WRONLY | O_APPEND | O_CREAT, S_IWUSR | S_IRUSR) == -1)
return -1;

return 0;
}

sendpacket.c

#include "sendpacket.h"

//这个文件实在是太搞笑了,我所做的工作,就是把所有与MD5相关的代码全部删除。联创那帮人太搞笑了,在NNU的客户端中居然完全没有加密算法。
//只是简单的发一些未经加密的包。不同用户之间的区别仅在于往数据包里面填充的用户名和密码的不同,晕死我了!本来以为改这个文件会很辛苦的。

//广播包,用于试探服务器反应。
int SendFindServerPacket(libnet_t *l)
{
uint8_t broadPackage[0x22] = { //联创广播包,用于寻找服务器,基于NNU抓包结果修改。
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x88,0x8E,0x01,0x01,
0x00,0x00,0x6C,0x69,0x6E,0x6B,0x61,0x67,0x65,0x00,0x00,0xC8,0x00,0x17,0x00,0x00,
0x00,0x00};

extern uint8_t l_localMAC[6];

memcpy( broadPackage+6, l_localMAC, 6 ); //填充MAC地址

fputs(">> Searching for server...\n",stdout);

return (libnet_write_link(l,broadPackage, 0x22)==0x22)?0:-1;
}

//发送用户名
int SendNamePacket(libnet_t *l, const u_char *pkt_data)
{
uint8_t broadPackage[0x22] = { //联创广播包,用于寻找服务器,基于NNU抓包结果修改。
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x88,0x8E,0x01,0x01,
0x00,0x00,0x6C,0x69,0x6E,0x6B,0x61,0x67,0x65,0x00,0x00,0xC8,0x00,0x17,0x00,0x00,
0x00,0x00};

extern uint8_t l_localMAC[6];

memcpy( broadPackage+6, l_localMAC, 6 ); //填充MAC地址

fputs(">> Searching for server...\n",stdout);

return (libnet_write_link(l,broadPackage, 0x22)==0x22)?0:-1;
}

//发送用户名
int SendNamePacket(libnet_t *l, const u_char *pkt_data)
{
extern char *l_name;
extern uint8_t l_destMAC[6];
extern uint8_t l_localMAC[6];
int nameLen;
nameLen=strlen(l_name);//获取用户名长度
//用了这个很笨的方法区分随园和仙林的用户。
if (nameLen == 10) {
static uint8_t ackPackage[0x31] = { //应答包,用户名
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x88,0x8E,0x01,0x00,
0x00,0x00,0x02,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00};
const uint8_t pktTail[16] = {0x6C,0x69,0x6E,0x6B,0x61,0x67,0x65,0x00,0x00,0xC8,0x00,0x17,0x00,0x00,0x00,0x00};


memcpy(ackPackage,l_destMAC,6); //将目的MAC地址填入组织回复的包
memcpy(ackPackage+6,l_localMAC,6); //将本机MAC地址填入组织回复的包

ackPackage[0x12]=0x02; //code,2代表应答
ackPackage[0x13]=pkt_data[0x13]; //ID,与服务器回送包保持一致
*(short *)(ackPackage+0x10) = htons((short)(5+nameLen));//把用户名长度填入数据包
*(short *)(ackPackage+0x14) = *(short *)(ackPackage+0x10);//把用户名长度填入数据包
memcpy(ackPackage+0x17,l_name,nameLen); //填入用户名
memcpy(ackPackage+0x17+nameLen,pktTail,16); //填入包尾

fputs(">> Sending user name...\n",stdout);

return (libnet_write_link(l,ackPackage, 0x31)==0x31)?0:-1;
}
else if (nameLen == 12) {
static uint8_t ackPackage[0x33] = { //应答包,用户名
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x88,0x8E,0x01,0x00,
0x00,0x00,0x02,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00};

const uint8_t pktTail[16] = {0x6C,0x69,0x6E,0x6B,0x61,0x67,0x65,0x00,0x00,0xC8,0x00,0x17,0x00,0x00,0x00,0x00};


memcpy(ackPackage,l_destMAC,6); //将目的MAC地址填入组织回复的包
memcpy(ackPackage+6,l_localMAC,6); //将本机MAC地址填入组织回复的包

ackPackage[0x12]=0x02; //code,2代表应答
ackPackage[0x13]=pkt_data[0x13]; //ID,与服务器回送包保持一致
*(short *)(ackPackage+0x10) = htons((short)(5+nameLen));//把用户名长度填入数据包
*(short *)(ackPackage+0x14) = *(short *)(ackPackage+0x10);//把用户名长度填入数据包
memcpy(ackPackage+0x17,l_name,nameLen); //填入用户名
memcpy(ackPackage+0x17+nameLen,pktTail,16); //填入包尾

fputs(">> Sending user name...\n",stdout);

return (libnet_write_link(l,ackPackage, 0x33)==0x33)?0:-1;
}
else {
fputs("Wrong username, please check /etc/lknnu.conf.\n",stdout);
return(1);
}
}

//发送密码
//密码包我不想改了,如果你修改了默认的密码666666,请改回来,或使用任意长度为6字符的密码。
int SendPasswordPacket(libnet_t *l,const u_char *pkt_data)
{
static uint8_t ackPackage[0x2E] = { //应答包
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x88,0x8E,0x01,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x36,0x36,0x36,0x36,0x36,0x36,0x6C,0x69,
0x6E,0x6B,0x61,0x67,0x65,0x00,0x00,0xC8,0x00,0x17,0x00,0x00,0x00,0x00};

extern char *l_password;
extern uint8_t l_destMAC[6];
extern uint8_t l_localMAC[6];
int passwordLen;

passwordLen=strlen(l_password);

memcpy(ackPackage,l_destMAC,6); //将目的MAC地址填入组织回复的包
memcpy(ackPackage+6,l_localMAC,6); //将本机MAC地址填入组织回复的包

ackPackage[0x12] = 0x02; //code,2代表应答
ackPackage[0x13]=pkt_data[0x13]; //id,与服务器回送包保持一致
*(ackPackage+0x16) = *(pkt_data+0x16); //这里就是linkthread.c判断中的那个0x52
*(short *)(ackPackage+0x10) = htons((short)(passwordLen + 6)); //密码长度+6,不知道用意何在
*(short *)(ackPackage+0x14) = *(short *)( ackPackage+0x10 ); //填入和上面一样的值
ackPackage[0x17]=passwordLen; //在linkage前面填入密码长度

fputs(">> Sending password... \n",stdout);
return (libnet_write_link(l,ackPackage, 0x2E)==0x2E)?0:-1;
}

//断开连接的包,反正都一样。就算不发送,30秒后服务器会自动断开连接的。
int SendEndCertPacket(libnet_t *l)
{
extern uint8_t l_destMAC[6];
extern uint8_t l_localMAC[6];

static uint8_t ExitPacket[0x22]={
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x88, 0x8E, 0x01, 0x02,
0x00, 0x00, 0x6C, 0x69, 0x6E, 0x6B, 0x61, 0x67, 0x65, 0x00, 0x00, 0xC8, 0x00, 0x17, 0x30, 0x00,
0x00, 0x01};
memcpy( ExitPacket, l_destMAC, 6);
memcpy( ExitPacket+6,l_localMAC, 6 );
fputs(">> Logouting... \n",stderr);
return (libnet_write_link(l,ExitPacket,0x22)==0x22)?0:-1;
}

上面的那个sendpacket.c我还没有测试过,所以可能会有错误,请注意。

我做了一个南师联创的网站,用户Linux客户端的发布。在这里: http://linknnu.googlepages.com 好了,联创客户端的问题就此解决。嗯,有点开始迷上UNIX系统编程了,嘿嘿。

没有评论:

发表评论