windows PPTP协议代码实现
la0gke 发表于 北京 二进制安全 674浏览 · 2024-08-12 04:17

1、协议大致过程

  1. tcp三次握手
  2. 控制连接隧道创建
  3. 链接控制协议配置
  4. 认证
  5. 数据交互

2、相关库分析

在我的代码编写过程中,主要用到两个python库:socketscapy

首先说明,两个库都很强大,为什么不用一个那?当然是因为我代码能力较菜,分别有解决不了的问题。那么,我在编写过程中分别用了各自库的哪些?

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服务端依然照常运行,并没有出现崩溃。

看了,没有我想的这么简单,还是需要深入分析。

0 条评论
某人
表情
可输入 255

没有评论