1、协议大致过程
- tcp三次握手
- 控制连接隧道创建
- 链接控制协议配置
- 认证
- 数据交互
2、相关库分析
在我的代码编写过程中,主要用到两个python库:socket、scapy
首先说明,两个库都很强大,为什么不用一个那?当然是因为我代码能力较菜,分别有解决不了的问题。那么,我在编写过程中分别用了各自库的哪些?
2.1、Socket
这个库是一个网络编程的底层库,可以用来实现大部分的网络协议,应用场景很广泛,并且稳定。由于PPTP协议从始至终需要保持一个完整的会话,送一这里主要用到了Socket的会话功能。
缺点就是数据包的生成方面,需要一个比特一个比特的组装数据包。不仅需要使用者熟悉每种协议构成,还比较繁琐。
先建立和服务端链接
self.Socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
if timeout:
self.Socket.settimeout(timeout)
self.Socket.connect((self.IP, self.Port))
之后就可以向服务端发送byte格式的数据了。
self.Socket.send(ctlmsgbuf)
发送数据完成后,可以直接接收服务端响应的数据。需要循环接收,直接没有数据为止。
datas = b""
while True:
data = self.Socket.recv(1024)
if len(data) > 0:
datas += data
else:
break
后边的数据报都是用self.Socket来进行发送和接收。
2.2、scapy
Scapy是一种用于计算机网络的数据包处理工具,由Philippe Biondi用Python编写。[3][4]它可以伪造或解码数据包,通过网络发送它们,捕获它们,并匹配请求和响应。它还可以用于处理扫描、跟踪路由、探测、单元测试、攻击和网络发现等任务。可以说在网络方面功能很强大,这里主要用来生成各种自定义的数据报,非常方便。
TCP链接建立之后,下一个该Start-Control-Connection-Request请求了。scapy自带的有pptp类,并且根据RFC进行了实现,可以直接用来生成该请求的数据报文。
导入:
from scapy.all import *
from scapy.layers.pptp import *
使用之前,先要看一下怎么用,这是该函数可以接收的参数,基本都有默认值。需要自定义那个参数,直接修改即可,比如每个控制连接会绑定一个Call ID。
>>> ls(PPTPStartControlConnectionRequest)
len : LenField = ('156')
type : ShortEnumField = ('1')
magic_cookie : XIntField = ('439041101')
ctrl_msg_type : ShortEnumField = ('1')
reserved_0 : XShortField = ('0')
protocol_version : ShortField = ('256')
reserved_1 : XShortField = ('0')
framing_capabilities : FlagsField = ('<Flag 0 ()>')
bearer_capabilities : FlagsField = ('<Flag 0 ()>')
maximum_channels : ShortField = ('65535')
firmware_revision : ShortField = ('256')
host_name : StrFixedLenField = ("b'linux'")
vendor_string : StrFixedLenField = ("b''")
配置完之后,用raw函数生成原始数据,之后传递给socket向服务端发送。
raw_data = raw(PPTPStartControlConnectionRequest(magic_cookie=0x1a2b3c4d))
self.Socket.send(raw_data)
发送完之后,该接收服务端的响应数据报了。同样,接收的数据报也是byte格式的原始数据,还需要通过PPTPStartControlConnectionReply函数对数据报进行解析。根据响应判断有没有报错,以及传递一些参数。
PPTPCtlr = PPTPStartControlConnectionReply(raw_datas_respone)
同样,不熟悉使用的情况下,需要看一下有哪些参数。
>>> ls(PPTPStartControlConnectionReply)
len : LenField = ('156')
type : ShortEnumField = ('1')
magic_cookie : XIntField = ('439041101')
ctrl_msg_type : ShortEnumField = ('2')
reserved_0 : XShortField = ('0')
protocol_version : ShortField = ('256')
result_code : ByteEnumField = ('1')
error_code : ByteEnumField = ('0')
framing_capabilities : FlagsField = ('<Flag 0 ()>')
bearer_capabilities : FlagsField = ('<Flag 0 ()>')
maximum_channels : ShortField = ('65535')
firmware_revision : ShortField = ('256')
host_name : StrFixedLenField = ("b'linux'")
vendor_string : StrFixedLenField = ("b''")
比如,判断有没有错误
if PPTPCtlr.error_code != 0:
print("Error!")
有时候也需要用到Scapy发送数据报,主要有两种发送方式:send、sr
send
这种只能发送,没有接收。需要用sniff过滤数据报,选择自己需要的。
发送
send(pkt)
处理相应包,先对需要的数据报进行捕获,使用sniff函数进行捕获。这里的filter只能处理网络层及之上层的数据报。
sniff(filter="PPP_LCP_Configure",stop_filter=packet_handler)
- filter:这个参数可以设置BPF(Berkeley Packet Filter)过滤规则,只捕获满足条件的数据包。例如:sniff(filter="tcp and host 192.168.1.1 and port 80", count=10)。
- iface:这个参数可以指定网络接口,如:sniff(iface="eth0", count=10)。
- prn:这是一个函数,它会被应用到每个捕获到的数据包上。通常用于数据包的处理或打印,如:sniff(prn=lambda x:x.summary(), count=10)。
- stop_filter:也是一个函数,它决定何时停止捕获,如下面的例子会在捕获到目的端口为80的包时停止:sniff(stop_filter=lambda x: x.haslayer(TCP) and x[TCP].dport == 80, count=10)。
- count:捕获数据报的数量,达到数量即停止捕获。如果未设置,则根据stop_filter返回True即停止。如果stop_filter也没有设置,则一直捕获。
packet_handler
捕获的数据报的处理函数,对每一个捕获的数据报进行打印,处理,修改等等操作。这里用来捕获两个数据报,分别是LCP requests、LCP ACK。并获取响应的LCP requests数据报的选项类型为19的data。之后返回True停止捕获
def packet_handler(pkt):
global LcpConReq,LcpConAck,Laa
# if pkt.haslayer(PPTP): # 检查数据包是否包含PPTP层
print(pkt.summary())
if pkt.haslayer(PPP_LCP_Configure) and pkt[PPP_LCP_Configure].code == 1:
LcpConReq = 1
if LcpConAck == 1:
LcpConReq = 2
option = pkt[PPP_LCP_Configure].options[-1]
if option.type == 19 and option.data != b'':
Laa = option.data
print("Configure-Request details:", option.data)
if pkt.haslayer(PPP_LCP_Configure) and pkt[PPP_LCP_Configure].code == 2:
LcpConAck = 1
if LcpConReq == 1:
LcpConAck = 2
print("Configure-Ack details:", pkt[PPP_LCP_Configure])
if LcpConReq==2 or LcpConAck ==2:
LcpConAck, LcpConReq = 0, 0
print("[+] Sending Set-Link-Info")
return True
sr
sr
函数(Send and Receive)是Python库Scapy中的主要函数之一,用于创建和发送数据包,然后接收响应。
reqreplist, Unanswered = sr(pkt,multi=True,timeout=1)
响应(response
)是包含两个列表的元组。第一个列表包含收到的所有响应数据包(包含请求和响应数据报对列表),第二个列表包含未收到响应的数据包。
sr
函数还有其他可用的参数:
-
timeout
:设置接收响应的超时时间(以秒为单位),如sr(packet, timeout=5)
。 -
retry
:设置在没有收到响应的情况下重试的次数,如sr(packet, retry=2)
。 -
filter
: 这将会设定一个 BPF (Berkeley Packet Filter) 字符串,用于过滤流量。只有匹配该字符串规则的回复包才会被接收。 -
iface
: 选择特定的网络接口发送和接收数据包。如果不设定,会使用默认的网络接口。 -
prn
: 指定一个函数,该函数会在每个接收到的数据包上被调用。这可以用于实时处理或显示数据包。 -
verbose
: 设置是否显示详细输出。如果设为0,则静默模式,即不会在控制台打印发送和接收的数据包的详细信息。 -
retry
: 这是一个整数参数,用于在收不到响应时重新发送数据包。如果你设置retry=2
,Scapy会在初次发送后,如果没有收到响应,再重新发送两次。 -
multi
: 通常,sr()
在收到第一个回应后就会停止等待更多的回应。但是如果你设置了multi=True
,Scapy会继续监听,直到超时。 -
store_unanswered
: 默认情况下,Scapy会将未回应的请求包存储在unanswered
列表中。如果你不需要这些信息,设置store_unanswered=False
可以节省存储空间。 -
inter
: 这是在连续发送数据包之间的间隔时间,单位为秒。例如,如果你设置inter=0.5
,Scapy在发送每个数据包后会暂停半秒。 -
loop
: 设置该参数为一个正整数,Scapy会将待发送的数据包列表循环发送指定的次数。
一些用法:
answered, unanswered = sr(IP(dst="ip_address")/ICMP())
for snd,rcv in answered:
print(snd.src, snd.dst, snd.summary())
print(rcv.summary())
3、演示
下边是一个PPP LCP配置演示。
还有一点,运行这个脚本需要管理员权限。
4、PPTP模糊测试工具编写
接着,参照大佬PPT,开始进行模糊测试工具编写
1、创建与服务器的连接
2.1. Start-Control-Connection-Request . . . . . . . . . . . . . 10
2.2. Start-Control-Connection-Reply . . . . . . . . . . . . . . 12
2、为此连接创建一些调用
2.7. Outgoing-Call-Request . . . . . . . . . . . . . . . . . . 19
2.8. Outgoing-Call-Reply . . . . . . . . . . . . . . . . . . . 22
2.9. Incoming-Call-Request . . . . . . . . . . . . . . . . . . 25
2.10. Incoming-Call-Reply . . . . . . . . . . . . . . . . . . . 28
2.11. Incoming-Call-Connected . . . . . . . . . . . . . . . . . 29
3、创建一些随机执行以下操作的线程
3.1、向服务器发送Call相关消息(create/destroy/setting)
create
2.7. Outgoing-Call-Request . . . . . . . . . . . . . . . . . . 19
2.9. Incoming-Call-Request . . . . . . . . . . . . . . . . . . 25
destroy
2.12. Call-Clear-Request . . . . . . . . . . . . . . . . . . . 31
2.13. Call-Disconnect-Notify . . . . . . . . . . . . . . . . . 32
setting
2.15. Set-Link-Info . . . . . . . . . . . . . . . . . . . . . . 35
PPP LCP Configuration Request
PPP LCP Configuration Ack
ppp LCP Configuration Reject
3.2、向服务器发送control相关信息(create/destroy)
create
2.1. Start-Control-Connection-Request . . . . . . . . . . . . . 10
destroy
2.3. Stop-Control-Connection-Request . . . . . . . . . . . . . 15
3.3、关闭连接
s.close()
[FIN, ACK]
[ACK]
[RST, ACK]
以上单步的函数都已经完成。接着,开始尝试触发大佬的漏洞。主要两个线程,都是用来结束Call
线程一对应函数:Call-Disconnect-Notify
线程二对应函数:Call-Clear-Request
我也用python的多线程库对功能进行了实现,如下分别对应线程一和线程二
这里用的ThreadPoolExecutor函数进行多线程运行。
然而结果并没有按照想象的那样发生。VPN服务端依然照常运行,并没有出现崩溃。
看了,没有我想的这么简单,还是需要深入分析。
没有评论