目录
Python socket
socket 编程就是为了更方便实现网络间主机通信而设计出来的编程模式,它提供了很多接口方法用于实现这个功能。到现在为止,socket API 已经得到绝大部分语言很好的支持,python 也不例外,在 python 中,有很多标准和第三方的库支持 socket 编程,比如我们可以利用 socket 模块编写一个通过 TCP/UDP 传输数据的应用,用 scapy 模块实现数据包的构造,用 urllib3/requests 模块实现 http 服务的构建。由于 python 语言本身的特性,实现这些基本功能比别的语言要容易的多,下面将分别介绍各模块的作用以及相关方法。
socket 模块
在 python 中,socket 模块的编程流程和 socket 编程是一样的,服务端需要创建套接字,绑定端口,开启监听,而客户端则是创建套接字后开始连接,使用 UDP 协议则是直接发送数据,两端在通信结束后要关闭套接字,整个流程中通信协议之间的连接、断开步骤在 socket 层面被简化,开发者无需再去关注 tcp 握手的步骤而是把注意力放在数据传输效率和解决数据传输中可能会出现的问题上。下面是 python socket 模块的编程流程图:
socket 模块提供了 Socket-API 的支持,利用里面的方法可以快速的建立 tcp/udp 服务,下面是模块里的一些方法:
方法 | 作用 |
---|---|
s=socket.socket([family[, type]]) | 创建套接字对象(所有网络编程的第一步),family=套接字类型(AF_UNIX/AF_INET);type=套接字类型(面向连接SOCK_STREAM/非连接SOCK_DGRAM) |
s.close() | 关闭套接字,用with语句创建socket对象的话可以不调用这个方法 |
s.bind((host,port)) | 绑定主机、端口到套接字,AF_INET下以元组(host,port)的形式表示地址 |
s.listen(num) | 开始TCP监听,num=可挂起的最大连接数(至少=1) |
s.accept() | 接受客户端连接,当listen()监听到连接请求时调用 |
s.connect((host,port)) | 向TCP服务器发起连接请求,若连接出错,返回socket.error错误 |
s.connect_ex((host,port)) | 同上,但是出错时返回出错码,而不是抛出异常 |
s.recv(bufsize,flag) | 接收TCP数据,返回一个字符串,bufsize=指定接收的最大数据量;flag=提供有关数据的其他信息(可选) |
s.send(msg) | 发送TCP数据,返回发送成功的字节数量,msg=要发送的数据 |
s.sendall() | 发送所有TCP数据,成功返回None,失败则抛出异常 |
s.recvfrom() | 接收UDP数据,返回(data,address),data是数据字符串,address是数据发送方的套接字地址 |
s.sendto(msg,(host,port)) | 发送UDP数据,msg=发送的内容,第二个参数是包含目标地址和端口的元组,函数返回发送的字节数 |
s.getpeername() | 返回连接中对方的地址,格式是元组:(host,port) |
s.getsockname() | 返回连接中自己的地址,格式同上 |
s.settimeout(timeout) | 设置套接字操作的超时时间,timeout=浮点数,单位:秒 |
s.gettimeout() | 返回当前超时期的值,单位:秒 |
s.fileno() | 返回套接字的文件描述符 |
s.setblocking(flag) | flag=false(非阻塞模式),flag=true(阻塞模式,默认);非阻塞模式下若recv()、send()方法调用时无法操作数据,会引起socket.error异常 |
s.makefile() | 创建与该套接字相关联的文件 |
利用 accept()、connect() 方法我们很快就能建立一个 tcp 连接,用 recvfrom()、sendto() 能够用 UDP 协议传输数据,下面用 socket 模块分别编写 tcp、udp 的服务端和客户端,实现最基本的数据传输:
TCP
客户端
import socket
def client():
with socket.socket(socket.AF_INET,socket.SOCK_STREAM) as s:
s.connect(('127.0.0.1',8888))
s.send(bytes('Hello'))
return
服务端
import socket
def server():
with socket.socket(socket.AF_INET,socket.SOCK_STREAM) as s:
s.bind(('127.0.0.1',8888))
s.listen()
conn,addr=s.accept() #建立连接
#数据处理
with conn:
print(addr)
while true:
data=conn.recv(1024)
print(data)
if not data:
break
return
UDP
客户端
import socket
def client():
data='Hello'
s=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
s.sendto(data.encode('utf-8'),('127.0.0.1',8888))
s.close()
服务端
import socket
def server():
s=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
s.bind(('',8888))
#数据处理
while True:
data,addr=s.recvfrom(1024)
print('来自 ',addr,' 的数据: ',data.decode('utf-8'))
s.close()
socketserver 模块
socketserver 是标准库里的一个现成的框架,进一步简化了 python 的套接字编程,它把 socket 底层的 API 都封装起来了,使用这个模块可以使开发者更加专心于事务逻辑,而不是套接字的各种细节。
socketserver 中有两种类:服务类、请求类。前者提供绑定、监听、运行等建立连接过程的方法,后者专注于如何处理用户发送的数据,其中类的继承关系如下:
如上图,有4个同步类,另外还有两个 Mixin 类用来支持异步,和同步类组合得到:
- class ForkingUDPServer(ForkingMixln,UDPServer):pass
- class ForkingTCPServer(ForkingMixln,TCPServer):pass
- class ThreadingUDPServer(ThreadingMixIn,UDPServer):pass
- class ThreadingTCPServer(ThreadingMixIn,TCPServer):pass
其中,thread 是多线程,fork 是多进程(Windows不支持)
下面来看一个例子:
import socketserver
'''
socketserver编程步骤:
1.创建一个socketserver类继承socketserver模块下的BaseRequestHandler类
2.重写父类方法handle,实现服务端业务逻辑
3.实例化server对象,传入本机ip,以及监听的端口号,还有新建的继承socketserver模块下的BaseRequestHandler类
4.激活服务端,无限循环处理请求
conn=与客户端的连接
addr=客户端地址
data=接收的数据
sends=服务端发送的数据
'''
class MySockServer(socketserver.BaseRequestHandler):
def handle(self):
while True:
conn=self.request
addr=self.client_address
data=str(conn.recv(1024),encoding='utf8')
print(data)
if data=='q':#客户端输入 q 断开连接
break
sends=bytes(input('服务端回复:'),encoding='utf8')
conn.sendall(sends)
conn.close()
#main函数:
if __name__ == '__main__':
print('开始运行...')
server=socketserver.ThreadingTCPServer(('127.0.0.1',8888),MySockServer)
server.serve_forever()
上面是一个用 socketserver 模块编写的 TCP 服务端例子,客户端和之前一样用 socket 模块的 connect() 就可以建立连接。可以看到服务端不再需要 bind()→listen()→accept(),服务端可以阻塞等到客户端连接后,直接开始通信。
下面列出一些相关的接口属性和方法:
方法 | 作用 |
---|---|
server_address | 服务器监听的地址,格式:(‘ip’,port) |
socket | 服务器监听的套接字对象 |
request_queue_size | 同时接受的请求上限 |
address_family | 套接字所属的协议簇(AF_INET/AF_UNIX) |
socket_type | 服务器使用的套接字类型(SOCK_STREAM/SOCK_DGRAM) |
timeout | 接收请求超时时间 |
handle_request() | 处理单个请求,同步执行 |
server_forever(poll_interval=0.5) | 处理请求,异步执行 |
shutdown() | 使serve_forever循环停止并等待结束 |
server_close() | 关闭服务器 |
finish_request(request,client_address) | 通过实例化RequestHandlerClass并调用它的handle()方法来处理请求 |
server_bind() | 由服务器构造函数调用,绑定套接字和地址,可被覆盖 |
verify_request(request,client_address) | 用于服务器访问控制,值=True,请求被处理;值=False,请求被拒绝 |
scapy 模块
scapy 是一个第三方模块,是个强大的交互式数据包处理程序,和爬虫框架 scrapy 只少了一个 r,它能够对数据包进行伪造或解包,包括发送数据包、 包嗅探、应答和反馈匹配等功能。
这个模块在 kali 里是自带的,但是在其他系统则需要自己用 pip 进行安装,命令:pip install scapy。安装完成后,有两种使用办法,一种是直接使用模块的交互shell,终端直接输入 scapy(Windows 下启动 scapy.bat),启动后的界面如下:
然后就可以直接构造和发送数据包了,要注意构造、发送包会用到 root 权限,因此要用 sudo 运行:
上面我们构造了一个 ICMP 数据包并发送给虚拟机网关,通过查看返回包的 ttl 值,我们可以知道目标系统是一个 Windows 系统(ttl=64为Linux系统,255则为类Unix/Unix系统,128则是WindowsNT)。
现在我们使用另一种办法,在脚本中导入 scapy 模块,在脚本中写好构造语句,然后执行脚本进行批量检测:
#!/usr/bin/python3
#coding:utf-8
from scapy.all import *
import sys
import ipaddress
#参数处理
if len(sys.argv)!=2:
print('用法:./main.py [目标地址段]')
sys.exit()
target=sys.argv[1]
#循环检测
for i in ipaddress.IPv4Network(target):
ip=str(i)
resp=sr1(IP(dst=ip)/ICMP(),timeout=0.5<