目录
0x00 wireshark
注意目标ip中 只要是是主机号全部是1的地址 全部是广播地址,例如:
192.168.2.255
10.10.255.255
10.255.255.255
255.255.255.255 这些都是广播地址。
wireshark 中黑色和红色往往表示数据包有错误
0x01 Tftpd32
Tfpd32 运行起来之后,就开启了一个文件共享服务器
我们的任务是要写一个客户端,可以从这个ftp服务器上下载文件。
0x02 客户端怎样从服务器下载文件呢?
首先,下载的过程是这样的:
- 创建一个空文件:f = open("test.jpg","bw")
- 向里面写数据:
- 关闭文件
while True:
recvData = udpSocket.recvfrom(1024)
if 没有数据了:
break
else:
f.write(recvData)
f.close()
但是,服务器怎么知道客户端要那个文件呢?客户端怎么告诉服务器自己要那个文件呢?这就要用到tftp协议(即简单文件传输协议)了,tftp协议是tcp/ip协议族中的一个用来在客户端和服务器之间进行简单文件传输的协议,它基于udp实现,tftp服务器默认打开udp69号端口。
具体下载过程:
首先,客户端向tftp服务器的udp69端口发送一个请求包(读请求(下载请求)或者写请求(上传请求)),服务器如果批准该请求,则会开启一个新的端口(随机)进行数据的传输
比如说服务器收到一个读请求,即客户端请求下载文件,那么服务器在找到需要下载的文件后,会立即打开该文件,然后从文件中读取数据到内存中,然后将读取到的数据通过tftp协议发送给客户端,如果文件的总大小较大,那么服务器会分多次发送,每次会从文件中读取512个字节的数据发送过来。
因为发送的次数有可能很多,为了让客户端对接受到的数据进行排序,服务器会在发送那512个字节数据的时候,多发两个字节的数据,用来存放序号(序号从1开始),并且将序号放在512个字节数据的前面,
当服务器中不存在客户端请求下载的文件时,服务器会发给客户端一个错误信息。为了能够区分服务器发送的包表示错误信息还是表示数据包,在序号(即块编号)的最前面 又添加了两个字节 表示 操作码:
操作码 | 功能 |
1 | 读请求(下载请求) |
2 | 写请求(上传请求) |
3 | 表示该包内容为数据(即DATA) |
4 | 确认码(即ACK) |
5 | 错误 |
因为用udp模块发包,只管一个劲的发包,而不在乎对方是否收到(UDP协议接受方不需要回复ack),所以数据包是否发送成功不能确定,所以TFTP协议为了弥补udp协议的不足,规定为了让服务器知道客户端已经成功收到了刚刚发送的那个数据包,当客户端收到一个数据包的时候,必须向服务器发送一个ack包,表示已收到
同时为了标记数据已经发送完毕,tftp协议规定,当客户端接收到的数据小于516(2个字节操作码+2个字节序号+512个字节的数据)时,就意味着服务器发送完毕了。
总结:
tftp协议通信中涉及到的四种数据包:读写请求包(客户端),数据包(客户端或者服务器),ack包(客户端或者服务器),错误包
其中读写请求包中的模式默认为 octet。ack包的块编号和收到数据包的块编号相对应。
tfpt通信过程图解:
注意:UDP 69端口只接受 读写请求包,ack包会发送给 服务器新开的随机端口。
那么如何实现用python发送请求包呢?
首先让我们复习下大端模式和小端模式。
- 小端模式就是低位字节放在内存的低地址端,高位字节放在内存的高地址端。例如:0x11 22 中11 为高位字节,22位低位字节。那么22就会先被存储在低地址位,22就会被存储在高位地址。
- 大端模式就是高位字节放在内存的低地址端,低位字节放在内存的高地址端。
一般而言,pc机中用小端模式,但是有些服务器中却使用大端模式。这样一来,PC机中用小端模式存储的数据发送给服务器,服务器用大端模式存储,就出现了乱套。因此,网络协议中规定 只要这个数据是多个字节组成的,那么就必须将该数据 转化 为大端模式 来发送。
import struct
from socket import *
#构造 下载请求包
sendData = struct.pack("!H8sb5sb",1,"test.jpg",0,"octet",0)
#"!H8sb5sb" 是占位符,H表示占两个字节对应 1,8s表示占8个字节的位置(对应test.jpg),b表示占一个字节对应0,5s表示占5个字节对应octet. !表示组织该数据 按照网络的格式组织,即大端模式组织。该函数的返回值就是我们组织好的数据。
udpSocket = socket(AF_INET,SOCK_DGRAM)
udpSocket.sendto(sendData,("192.168.43.4",69))
那么如何将从服务器发来的数据解包呢?
import struct
from socket import *
#构造 下载请求包
sendData = struct.pack("!H8sb5sb",1,"test.jpg",0,"octet",0)
data = struct.unpack('!HH',sendData[:4])
#截取sendData的前4个字节,并对其解包,返回值为一个元组
print(data)
0x03完整程序:
tftp客户端.py
import struct
from socket import *
import sys
if len(sys.argv) != 2:
print(len(sys.argv))
print("Usage:tftp.py ip")
exit()
ip = sys.argv[1]
#print(ip)
udpSocket = socket(AF_INET,SOCK_DGRAM)
sendData = struct.pack("!H8sb5sb",1,"test.jpg",0,"octet",0)
udpSocket.sendto(sendData,(ip,69))
already_recv_num = 0
while True:
data,info = udpSocket.recvfrom(1024)
#print(data)
#print(info)
op_num = struct.unpack("!HH",data[:4])
op = op_num[0]
num = op_num[1]
if op == 5:
print("error")
break
elif op == 3:
if len(data) < 516:
print("receive successfully")
f.close()
udpSocket.close()
break
if num == 1:
f = open("test.jpg",'a')
if already_recv_num+1 == num:
f.write(data[4:])
already_recv_num += 1
print("already received data num:%d",already_recv_num)
ack = struct.pack("!HH",4,num)
udpSocket.sendto(ack,info)