教你编程实现TFTP协议.docx
《教你编程实现TFTP协议.docx》由会员分享,可在线阅读,更多相关《教你编程实现TFTP协议.docx(13页珍藏版)》请在冰豆网上搜索。
教你编程实现TFTP协议
教你编程实现TFTP协议
Scuhkr/古开元四川大学信息安全研究所
前言:
不论你是热衷于黑客技术的爱好者,还是从事编写网络应用程序的程序员,都需要掌握熟练的编程能力,以及具备扎实的TCP/IP协议的扎实基础.很多初学者都有这样的经历,要么是能编一些复杂算法的程序,却不能结合到实际的应用中,要么就是三卷W.RichardStevens的圣经《TCP/IP详解》熟记于胸,却无法用熟悉的编程语言将其实现.这真是件很令人郁闷的事.出于这样的目的,本文就结合TCP/IP协议中的TFTP协议来分析,并简单实现了Windows下TFTP的服务器和客户端.
(致编者:
请务必加上以下声明)
注:
本文大部分理论引用于和中国协议分析网)
一、协议分析:
TFTP(TrivialFileTransferProtocol)即简单文件传送协议,是TCP/TP协议族中的一个用来在客户机与服务器之间进行简单文件传输的协议,提供不复杂的,开销不大的文件传输服务.为了保持简单和短小,TFTP使用UDP协议,且默认端口号为69,它不提供可靠的数据流传输服务,也不提供存取授权与认证机制,使用超时重传方式来保证数据的到达.与FTP相比,TFTP的大小要小的多,它主要用于小文件的传输,只能从文件服务器上获得或写入文件,不能列出目录.更多的资料可以参考RFC1350.
下图一给出了5种TFTP报文格式。
图一5种TFTP报文格式
结合图一,我们可以看到,TFTP报文的头两个字节表示操作码。
对于读请求(RRQ)和写请求(WRQ),文件名字段说明客户要读或写的位于服务器上的文件。
这个文件字段以0字节作为结束。
模式字段是一个ASCII码串netascii或octet(大小写可任意组合),同样以0字节结束。
netascii表示数据是以成行的ASCII码字符组成,以两个字节—回车字符后跟换行字符(称为CR/LF)作为行结束符。
这两个行结束字符在这种格式和本地主机使用的行定界符之间进行转化。
octet则将数据看作8bit一组的字节流而不作任何解释。
每个数据分组包含一个块编号字段,它以后要在确认分组中使用。
以读一个文件作为例
子,TFTP客户需要发送一个读请求说明要读的文件名和文件模式(mode)。
如果这个文件能被这个客户读取,TFTP服务器就返回一个块编号为1的数据分组。
TFTP客户又发送一个块编号为1的ACK。
TFTP服务器随后发送块编号为2的数据。
TFTP客户发回块编号为2的ACK。
重复这个过程直到这个文件传送完。
除了最后一个数据分组可含有不足512字节的数据,其他每个数据分组均含有512字节的数据。
当TFTP客户收到一个不足512字节的数据分组,就知道它收到最后一个数据分组。
在写请求的情况下,TFTP客户发送WRQ指明文件名和模式。
如果该文件能被该客户写,TFTP服务器就返回块编号为0的ACK包。
该客户就将文件的头512字节以块编号为1发出。
服务器则返回块编号为1的ACK。
最后一种TFTP报文类型是差错报文,它的操作码为5。
它用于服务器不能处理读请求或写请求的情况。
在文件传输过程中的读和写差错也会导致传送这种报文,接着停止传输。
差错编号字段给出一个数字的差错码,跟着是一个ASCII表示的差错报文字段,可能包含额外的操作系统说明的信息。
根据图一及上面的分析,我们可以得到以下定义:
#defineTFTP_RRQ01//读请求
#defineTFTP_WRQ02//写请求
#defineTFTP_DATA03//数据包
#defineTFTP_ACK04//确认包
#defineTFTP_ERROR05//错误代码
头文件结构也能很容易得到:
typedefstructtftphdr{
USHORTtu_opcode;//操作码
union
{
USHORTtu_block;//块号
USHORTtu_code;//错误码
chartu_stuff[1];//请求包填充物
}th_u;
charth_data[1];//数据或错误字符串
}TFTP_HDR,*PTFTP_HDR;
二、TFTP服务器的实现
不知道大家对两年前的冲击波病毒Msblast.exe是否还有印象,该病毒通过RPC远程溢出漏洞溢出目标主机后,会绑定目标主机的4444端口,然后利用windows系统自带的tftp客户端程序发送下载消息,目标主机通过tftp下载病毒再运行病毒,反复循环导致更多的计算机受到感染。
因为windows系统并没有默认的TFTP服务端程序,所以此时的关键就在于Msblast.exe病毒本身就是个tfpt服务器。
至于TFTP服务器的实现,以前ww0830在《黑防》介绍过,为了满足大多数读者的要求,及关系到本文的完整性,我这里也给出具体实现,注意,本TFTP服务端只实现了供客户端程序下载的功能,更多的功能读者可以自己完善。
下面给出TFTP实现核心代码及详细注释
(注:
以下所有代码均在winxpsp1+vc6.0环境下编译通过,因为主要理解思路,省略了错误处理等步骤,完整源代码见附件内容):
初始化winsock库,注意在要加上ws2_32.lib库文件:
WSADATAWSAData;
WSAStartup(MAKEWORD(2,2),&WSAData);
创建套接字
SOCKETsock=INVALID_SOCKET;
sock=socket(AF_INET,SOCK_DGRAM,0);//这里用的是UDP协议,所以协议类型为//SOCK_DGRAM
初始化本地主机地址信息:
structsockaddr_inServerAddr;
ServerAddr.sin_family=AF_INET;
ServerAddr.sin_addr.s_addr=inet_addr(MyIp);//MyIp为本主机IP
ServerAddr.sin_port=htons(69);//TFTP服务器默认端口号
绑定套接字
bind(sock,(structsockaddr*)&ServerAddr,sizeof(structsockaddr_in))
初始化远程地址信息
structsockaddr_inClient;
Client.sin_family=AF_INET;
Client.sin_port=htons(INADDR_ANY);
Client.sin_addr.s_addr=inet_addr(RemoteIp);//远程IP地址
定义TFTP首部缓冲区
TFTP_HDRRecvBuff;//接收的数据缓冲区
TFTP_HDRLocalBuff;//本地数据缓冲区
接收数据,定义数据块大小为512字节:
intalen=sizeof(Client);
recvfrom(sock,(char*)&RecvBuff,512,0,(structsockaddr*)&Client,&alen);
获得客户端的请求操作码
LocalBuff.tu_opcode=ntohs(RecvBuff.tu_opcode);
打开本地文件
fp=fopen("abc.txt","rb");//假设abc.txt为服务器文件
读本地文件
fread(FileBuff,sizeof(char),512,fp);
填充TFTP首部
SendBuff.tu_opcode=htons(3);//操作码3=data
SendBuff.th_u.tu_block=htons(time);//块号
memcpy(&(SendBuff.th_data),FileBuff,i);//数据
发送文件给客户机(接收文件或数据类似)
sendto(sock,(char*)&SendBuff,i+4,MSG_DONTROUTE,(structsockaddr*)&Client,sizeof(Client));
整个简单的TFTP服务器这样就完成了,是不是很简单呐。
带外说一说,这里如果要模拟冲击波病毒的传播方式,程序最开始有个随机扫描的代码:
WSAStartup(MAKEWORD(2,2),&WSAData);
SOCKADDR_INaddr;
sock=socket(AF_INET,SOCK_STREAM,0);
addr.sin_family=AF_INET;
addr.sin_port=htons(80);//通过80端口是否开放判断对方主机是否存活
addr.sin_addr.s_addr=inet_addr(RemoteIp);//这里的RemoteIp应该通过一
//个随机生成网段内IP地址的函数得到
if(connect(sock,(structsockaddr*)&addr,sizeof(addr))<0)
{
closesocket(sock);
WSACleanup();
return;//连接失败,停止扫描
}
//通过gethostsocket()获取连出去的ip
structsockaddr_inhostname;
intnamelen=sizeof(hostname);
char*source;
getsockname(sock,(structsockaddr*)&hostname,&namelen);
source=inet_ntoa(hostname.sin_addr);
这一段代码的作用是循环连接网段内存活主机的80端口,如果发现某台主机存活,则蠕虫自身生成的TFTP服务器则接受被冲击波病毒溢出的客户端下载请求,复制自身到对方主机,如此恶性循环,导致蠕虫成级数性的扩散。
三、TFTP客户端的实现
通常情况下,windows2K以上操作系统都可以使用系统自带的TFTP客户端程序,但自从Msblast.exe蠕虫爆发后,很多用户都关掉了TFTP的默认使用。
对于很多学习入侵和渗透的人来说,这真是个噩耗^_^。
所以,通常很多木马或后门的设计者都在他们的作品里加入了FTP,Http下载,Email或TFTP客户端的功能,以加强对肉鸡的控制。
下面,我们就来分析一下TFTP的客户端实现。
TFTP客户端的实现主要体现在对TFTP各种数据包首部字段的分析及填充上。
下面给出了部分核心功能的伪代码,(具体代码可以参看附件里的源代码:
该代码作者为helloworld1,在此对他所提供的源代码表示万分感谢)
填充(读/写)请求字段,根据图一,可以得到下面的读/些请求数据包格式:
图二:
RRQ/WRQ包格式
于是,定义一个makereq()函数来填充请求字段:
intmakereq(chartype,intmode,char*filename,char*buffer,intsize)
{
intpos=0;//位置
inti=0;
chars[32]="";
if(mode==TFTP_NETASCII)//传输模式
strcpy(s,"netascii");
else
strcpy(s,"octet");
buffer[pos]=0;
pos++;
buffer[pos]=type;//Opcode=01(RRQ)or02(WRQ)
pos++;
for(i=0;i{
buffer[pos]=filename[i];
pos++;
}
buffer[pos]=0;//0
pos++;
for(i=0;i{
buffer[pos]=s[i];
pos++;
}
buffer[pos]=0;//0
pos++;
returnpos;//返回请求包头长度
}
下面为ACK包格式及填充地段函数makeack():
图三:
ACK包格式
intmakeack(unsignedshortnum,char*buffer,intsize)
{
intpos=0;
buffer[pos]=0;
pos++;
buffer[pos]=TFTP_ACK;//Opcode=04
pos++;
buffer[pos]=(char)(num>>8);//块号2个字节
pos++;
buffer[pos]=(char)num;
pos++;
returnpos;
}
同理,DATA包格式及填充函数makedata()如下:
图四:
DATA包格式
intmakedata(intnum,char*data,intdatasize,char*buffer,intbufsize)
{
intpos=0;
buffer[pos]=0;
pos++;
buffer[pos]=TFTP_DATA;//Opcode=03
pos++;
buffer[pos]=(char)(num>>8);//Block#块号
pos++;
buffer[pos]=(char)num;
pos++;
memcpy(&buffer[pos],data,datasize);//Data数据
pos=pos+datasize;
returnpos;
}
实现下载文件,cmd[][256]为自定义的参数数据结构,pcount为参数个数:
voidgetfile(charcmd[][256],intpcount)
{
charsendbuf[1024]={0};//发送的请求包
charrecvbuf[1024]={0};//接收的数据缓冲区
sockaddr_inaddr;//本机网络地址信息
sockaddr_infrom;//服务器网络地址信息
intfromlen=0;
intret=0;
intlen=0;
fd_setfdr;//套接字的“读”集
intretry=0;//重试次数
structtimevaltimeout={5,0};//超时设置5秒
intstat=0;
intlastdata=0;
FILE*file;
intflen=0;//总的数据或文件大小
intc;
if(pcount!
=1)
{
printf("usage:
getfilename\n");
return;
}
if((file=fopen(cmd[1],"r"))!
=NULL)//判断所下载的文件是否已存在
{
printf("File%salreadyexits,overwrite?
y/n",cmd[1]);
while(true)
{
c=getch();
if('Y'==toupper(c))//覆盖
{
printf("\n");
fclose(file);
break;
}
elseif('N'==toupper(c))//不覆盖,返回
{
printf("\n");
fclose(file);
return;
}
}
}
if((file=fopen(cmd[1],"w+b"))==NULL)
{
printf("Can'tcreatefile\n");
return;
}
//填充读(下载)请求字段
len=makereq(TFTP_RRQ,fMode,cmd[1],sendbuf,sizeof(sendbuf));
addr.sin_family=AF_INET;
from.sin_family=AF_INET;
addr.sin_port=htons(69);
addr.sin_addr.s_addr=inet_addr(desthost);
ret=sendto(sock,sendbuf,len,0,(sockaddr*)&addr,sizeof(addr));
while(true)
{
FD_ZERO(&fdr);
FD_SET(sock,&fdr);
ret=select(sock,&fdr,NULL,NULL,&timeout);
if(SOCKET_ERROR==ret)
{
printf("Socketerror\n");
fclose(file);
return;
}
elseif(0==ret)//超时
{
if(MAX_RETRY==retry)//达到了尝试次数MAX_RETRY=3
{
printf("TimeOut\n");
fclose(file);
return;
}
sendto(sock,sendbuf,len,0,(sockaddr*)&addr,sizeof(addr));
retry++;
}
else
{
if(FD_ISSET(sock,&fdr))
{
retry=0;
fromlen=sizeof(sockaddr);
//接收数据或文件
ret=recvfrom(sock,recvbuf,sizeof(recvbuf),0,(sockaddr*)&from,&fromlen);
if(TFTP_ERROR==recvbuf[1])
{
fclose(file);
printf("Error%d:
%s\n",recvbuf[3],&recvbuf[4]);
return;
}
if(0==stat)
{
addr.sin_port=from.sin_port;
stat=1;
}
if(TFTP_DATA==recvbuf[1])
{
lastdata=recvbuf[2]*256+recvbuf[3];
//填充确认字段
len=makeack(lastdata,sendbuf,sizeof(sendbuf));
sendto(sock,sendbuf,len,0,(sockaddr*)&addr,sizeof(addr));
//#defineTFTP_NOTEND_DATALEN512+2+2
//后面的2+2是确认数据包头的操作码(2个字节)+块号(2个字节)
if(ret{
//已经是最后的包块
fwrite(&recvbuf[4],1,ret-4,file);
flen=flen+ret-4;
fclose(file);
printf("total%dbytereceived\n",flen);
return;
}
else
{
//不是最后的包块
fwrite(&recvbuf[4],1,512,file);
flen=flen+512;
printf("%dbytereceived\r",flen);
}
}
}
}
}
}(致编者:
后门6个括号…呵呵)
代码的注释很清楚,其实结合文章开始的理论及图,再实现就很简单了。
至于上传(put)文件和下载类似,由于篇幅的原因,大家可以参看源代码。
实现效果:
首先,tftpServer.exe为我们第2部分介绍实现的TFTP服务端程序,在该程序的同级目录下放一个名为“abc.txt”的文件,运行tftpd.exe。
然后,运行第3部分介绍的TFTP客户端程序tftpClient.exe,并输入命令
#getabc.txt
实验截图如下:
图五:
tftpServer.exe
图六:
tftpClient.exe
总结:
对于学习网络知识也好,学习黑客技术也罢,对于TCP/IP协议的掌握是重中之重,本文讨论虽然只是简单讨论TFTP协议的相关理论及实现,但稍微复杂一点的FTP协议,POP3协议,SNMP协议也都可以通过类似的分析来实现的。
本人水平有限,本文给出都是思路,实现都简单的不能再简单。
如有不对之处,请大家给予批评指正。
招商银行一卡通号码:
002821026284
本人地址:
成都市四川大学望江校区东区三教五楼信息安全研究所
本人姓名:
古开元。
邮编:
610064