zoukankan      html  css  js  c++  java
  • BugScan插件编写高(gǎo)级(jī)教程

    声明:本文最先发布在:http://q.bugscan.net/t/353 转载请注明出处

    有问题可以和我交流

    第一章 前言:

    在Bugscan群里混了一个月,个人对插件开发理解算是有一定见解,每天看群里朋友为了要一个邀请码很痛苦,之前一直想写插件编写教程,正打算开工的时候被@Line@牛抢先发了,那我就直接来一发高级教程吧。

    这里要声明一点:标题叫高级教程,不是说@Line@牛发的教程就低级了,只要是用心写的插件,都是高级的,没有高低级之分。本教程主要是针对 通(mei)!用(yong)!型(chu)! 插件的编写。

    在阅读本教程之前,要有一定计算机网络基础,你可能会看到一半看不下去,我只能说,我会尽最大能力讲清楚,讲简单。好了我废话真多。进入正题。

    本次我们以我编写的 PPTP-Version 插件为例,讲解如何开发一个通用型插件。前面基本和Bugscan没有关系,如果你已经对网络协议了解的很深可以直接跳过。

    编写通用弄插件主要用到的是socket,简单的来说,就是你用socket给服务器发一个数据包,然后服务器会给你返回一个数据包过来,你再解读返回的数据包,然后得出你的结论就可以了。你要发数据包,就要了解相关的协议的知识,so...有学习成本了。

    第二章 PPTP知识准备:

    本章内容主要参考http://blog.csdn.net/zhaqiwen/article/details/10083025这篇博文。

    第一节 什么是PPTP

    下面我直接抄了下专业解释,看看就好,了解下基本知识,不想了解直接略过。

    PPTP将PPP(Point-to-Point Protocol)帧封装进IP数据报中,通过IP网络如Internet或其他企业专用Intranet等发送。PPTP通过PPTP控制连接来创建、维护、终止一条隧道,并使用通用路由封装GRE(Generic Routing Encapsulation)对PPP帧进行封装。封装前,PPP帧的有效载荷即有效传输数据一般会经过加密、压缩或是两者的混合处理。PPTP协议假定在PPTP客户机和PPTP服务器之间有连通且可用的IP网络。因此如果PPTP客户机本身已经是某IP网络的组成部分,那么即可通过该IP网络与PPTP服务器取得连接.MPPE只提供连接加密,而不提供端-端加密。端-端加密属于应用层的加密技术,如果应用中要求实现端-端加密,则可在PPTP隧道建立之后,使用IPSec对两端的IP数据流进行加密处理。基于Internet的PPTP服务器即使用PPTP协议的VPN服务器,它的一个接口在Internet上,另一个接口在Intranet上。

    PPTP控制连接建立在PPTP客户机IP地址和PPTP服务器IP地址之间,PPTP客户机使用动态分配的TCP端口号,而PPTP服务器则使用保留TCP端口号1723(这里我提醒下,端口是可以改的,当然一般人都不会改,一定要记住端口号后面有用)。PPTP控制连接携带PPTP呼叫控制和管理信息,用于维护PPTP隧道,其中包括周期性地发送回送请求和回送应答消息,以期检测出客户机与服务器之间可能出现的连接中断。PPTP控制连接数据包包括一个IP报头,一个TCP报头和PPTP控制信息

    报文结构大致上如下:

    alt text

    第二节 PPTP报文分析:

    这里只说和我们编写插件有关的报文。

    1. start-control-connection-request : 由PPTP客户端发出,请求建立控制连接。PPTP隧道要求在发送任何其他PPTP消息之前,先建立一条控制连接。我们要发给服务器的报文就是这个家伙了。所以要好好看看这个家伙到底是个什么鬼。 start-control-connection-request报文格式如下: alt text Length是长度

      PPTP Message Type:pptp信息类型,1是控制信息,2是管理信息

      Magic Cookie:恒为0x1a2b3c4d,目的是保证数据流向相同

      Control Message Type: 控制信息类型,1为start-control-connection-request,2是start-control-connection-reply,7是Outgoing-Call-Request

      Reserved:保留字段,恒为0

      Protocol Version: PPTP版本号值为1.0

      Framing Capabilities:帧类型1代表同步,2代表异步。

      Bearer Capabilities : 指出承载性能

      Maximum Channels :该 PPTP服务器 可以支持的个人 PPP 会话总数。

      Firmware Revision : 若由 PPTP服务器发出,则包括发出 PPTP服务器时的固件修订本编号;若由 PPTP客户端 出发,则包括 PPTP客户端 PPTP 驱动版本。

      Host Name : 包括发行的 PPTP服务器 或 PPTP客户端的 DNS 名称。

      Vendor Name : 包括特定供应商字串,指当请求是由 PPTP客户端 提出时,使用的 PPTP服务器 类型或 PPTP客户端软件类型。

    2. start-control-connection-reply:由PPTP服务器发出,回应start-controlconnection-request消息。这个报文里面就包函了Server的一些版本信息。得到报文之后要得出结论,就要了解这个报文是什么样子,什么含义。要不然你收到报文也不知道是什么东西。 报文格式如下: alt text 就说点和前面报文不一样的地方:

      Result Code:表示建立channal是否成功的结果码,值为1表示成功,值为2表示通用错误,暗示着有问题。值为3表示channal已经存在,值为4表示请求者未授权,值为5表示请求的PPTP协议版本不支持。

      Error Code:表示错误码,一般值为0,除非Result Code值为2,不同的错误码表示不同的含义。

      我们在收到报文之后,重点看的部分是Firmware Revision字段和Vendor String字段。

    第三节 抓包看看PPTP到底是什么

    1. 在windows下新建一个pptp的连接,这里要是不会就直接百度吧。
    2. 开启wireshark 抓包软件,sniffer也可以,工具的操作我就不缀述了。
    3. 连接PPTP,连接完成之后,停止wireshark。
    4. 在抓到的数据包窗口下的filter中输入pptp,然后回车,就看到了pptp有关的报文了,如下图: alt text

      Request报文:
      Point-to-Point Tunnelling Protocol
      
        Length:156
        Message type:Ctrol Message(1)
        Magic Cookie: 0x1a2b3c4d (correct)
        Control Message Type: Start-Control-Connection-Request (1)
        Reserved: 0000
        Protocol version: 1.0
        Reserved: 0000
        Framing Capabilities: Asynchronous Framing supported (1)
        Bearer Capabilities: Analog access supported (1)
        Maximum Channels: 0
        Firmware Revision: 0
        Host Name: 
        Vendor Name: Microsoft
      Reply报文:
      Point-to-Point Tunnelling Protocol
        Length: 156
        Message type: Control Message (1)
        Magic Cookie: 0x1a2b3c4d (correct)
        Control Message Type: Start-Control-Connection-Reply (2)
        Reserved: 0000
        Protocol version: 1.0
        Result Code: Successful channel establishment (1)
        Error Code: None (0)
        Framing Capabilities: Unknown (0)
        Bearer Capabilities: Unknown (0)
        Maximum Channels: 1
        Firmware Revision: 1
        Host Name: local
        Vendor Name: linux

      看到这里也许有些人已经晕了,有些人已经看出来怎么做了。没错,看到返回包中VendorName的值了吗?我们要读取的也就是这里了。

    第三章 插件编写:

    第一节 报文格式的构造

    终于要开工写代码了,我这人真的话很多,废话也很多。哈哈,来咬我啊233

    报文格式的构造我们可以直接看着上面报文结构来进行构造。 有两种办法,一种是自己创建一个pptp的类,还有一种是使用dict字典类型,其实都一样,都一样,不要纠结使用什么,但自己一定要思路清晰。这里我用dict来实现,代码如下:

        pptp={"Length":156,"MessageType":1,"MagicCookie":0x1a2b3c4d,
            "ControlMessageType":1,"Reserved":0,"ProtocolVersion":1,
            "FramingCapabilities":1,"BearerCapabilities":1,
            "MaximumChannels":0,"FirmwareRevision":0,
            "HostName":"Bugscan","VendorName": "medicean"}

    什么?为什么这么写?为什么字段的值是这些?你没有仔细看前面的哟,自行面壁去。这里我要说的有:

    1. Reserved明明报文里面有2个字段,为什么这里只有一个了?没错,这又不是真正要发送的报文,不要着急。
    2. HostName和VendorName怎么是个字符串,不是二进制?没错,这又不是真正要发送的报文,不要着急。
    3. 还有问题吗?233

    第二节 二进制流操作 及 struct 简介

    我们用struct模块来处理二进制数据,怎么说呢?就是你第一节有的问题我们在第二节解决他。构造真正的pptp报文。不讲理论了,直接写代码吧。

    首先要导入struct模块。然后处理数据。

    import struct
    payload=struct.pack('!HHLHHHHLLHH64s64s',pptp["Length"],pptp["MessageType"],pptp["MagicCookie"],pptp["ControlMessageType"],pptp["Reserved"],pptp["ProtocolVersion"],pptp["Reserved"],pptp["FramingCapabilities"],pptp["BearerCapabilities"],pptp["MaximumChannels"],pptp["FirmwareRevision"],pptp["HostName"],pptp["VendorName"])

    上面的代码就是我们要做的事情,但!是!这段代码是有!问!题!的!先不慌说问题在哪,这段代码执行之后,payload就同我们wireshark里面抓包到二进制数据差不多了。如下图: alt text

    来解释下这段代码吧。 Struct.pack方法就是把数据按照前面的格式给格式化了,就好比是C语言中的printf一样,前面有个格式化字串。 比如说:

    >>> struct.pack("i",1)
    'x01x00x00x00'
    >>> struct.pack("b",1)
    'x01'

    好了具体自己看struct相关的文章,这里说下我们的模式串,'!HHLHHHHLLHH64s64s',这是严格按照我们一开始给的报文来写的,是按照原字节数来的意思,H是unsigned short 占2个字节,也就是16个bit,就是之前报文图片中一行宽度的一半,L是unsigned long 占4个字节,就是占一行的长度。现在把这段模式串和报文图逐个字段大小比比。 alt text 这样就行了吗?一般来说这样就可以了,但是这里还是出了点小问题:

    HostName和VendorString的数据可以变的,这不用管。主要是来检验下前面的数据。 wireshark里面的数据如下: alt text 再看我们的: alt text 看出问题了没有?

    没有看出来?那我再来发一个图: alt text

    没错,Protocol Version 这里出了点问题是为什么呢?我们先来看一段代码:

    >>> struct.pack("H",1)
    'x01x00'
    >>> struct.pack("!H",1)
    'x00x01'

    看到了吧?我们使用的模式串中是以!开头的,所有我们的结果是x00x01但是wireshark中的却是x01x00,很多时候就是有这种细节上的问题会让你死都找不出来为什么,不是说这东西直接发过去一定会有问题,只是说,要做就做严谨,否则出问题了,找都找不到在哪。

    解决方案有很多种,我这里提供两种思路:

    1. 把第一节中,pptp["ProtocolVersion"]的值(当前是1),改为0x0100,也就是255。这样pack后就没有问题了。

    2. 或者是修改模式串,把'!HHLHHHHLLHH64s64s'中的第六个(ProtocolVersion的顺序)H改成B,然后后面加个x也就是填充个x00来凑。原代码就变为:

      payload=struct.pack('!HHLHHBxHLLHH64s64s',pptp["Length"],pptp["MessageType"],pptp["MagicCookie"],pptp["ControlMessageType"],pptp["Reserved"],pptp["ProtocolVersion"],pptp["Reserved"],pptp["FramingCapabilities"],pptp["BearerCapabilities"],pptp["MaximumChannels"],pptp["FirmwareRevision"],pptp["HostName"],pptp["VendorName"])

      alt text 现在对比下是不是就正确了呢? 本次教程中,我采用了第二种办法。

    第三节 数据包发送及socket简介

    说简单点Socket是提供给我们编写网络程序的一个套接字,我们可以用它来发送网络报文。Socket有三种分别是SOCK_STREAM(流式套接字),SOCK_DGRAM(面向连接套接字),SOCK_RAW(原始套接字)。什么意思呢?Stream是直接接到TCP报文下面的,DGRAM是接到UDP或者TCP报文下面,而RAW是直接接到IP报文下面的。你知道这个暂时就可以用了。一般我们编写扫描插件,用到socket的多是模仿客户端程序,也就不用去看服务器编程了。

    流程如下:

    1. 创建套接字

      s=socket.socket(family, type)

      family字段取值AF_UNIX 用于本机进程通信

      AF_INET 用于IPv4通信.

      type 字段取值就是上面说的三种了。

    2. 使用socket的connect方法连接服务器。对于AF_INET家族,连接格式如下:

      s.connect((host,port))

      s是我们第一步创建的对象。这里参数是python的一个tuple类型数据,其中port一定要是整数,不能写字符串,host可以是主机名,也可以是IP地址。

    3. 发送数据

      s.send(payload)

    4. 接收数据

      data=s.recv(1024)

      1024是缓冲区大小,你可以自行设置

    5. 关闭socket

      s.close()

    看了socket的简单用法,是不是觉得很简单呢?

    分析下,pptp报文是TCP报文中的数据部分,所以我们本次使用Stream(流式socket)

    来来来,和我一起写代码了。

        import socket
        host='172.18.19.90'#服务器地址
        port=1723   #端口号
        s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
        s.connect((host,port))#连接对应主机和端口
        s.send(payload)
        data=s.recv(1024)
        s.close()

    这样就可以发送request报文,并且接到reply报文了。这段代码还是有点小问题

    有没有想过?如果对方没有打开1723端口,要么对方直接ban了你的IP,再或者节点在扫描的时候断网了,对方宕机了,地震了机房塌方了,没有交电费了...

    反正就是你连接通信过程中中断了。这里socket会抛出异常,然后就导致我们的程序意外退出。

    你说那不正好吗反正没有开服务就不会有信息输出了呀。怎么说呢,打开一个socket连接之后,一定要关闭了,不关闭就会学浪费系统资源,这是一个习惯。还有一点,如果程序异常,交给上级处理,上级会直接把错误输出到屏幕上并且退出脚本,就是最近经常看到的那个wvs.py的错误,相信很多人都见过了吧,是不是看了那种错误很不爽?写程序有时就要这么处女座233

    废话太多,我直接就写改进的代码上来了,其实就是加了异常捕获,没有什么特别的地方。

    s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    try:
            socket.setdefaulttimeout(20)#设置超时时间
            s.connect((host,port))#连接对应主机和端口
            s.send(payload)
            data=s.recv(1024)
        except Exception :
            pass
        finally:
            s.close()

    嗯,这样我们的插件就相对比较好了。

    第四节 reply报文解读

    第三节中,我们收到了服务器返回的reply报文,收到的肯定都是进制,不解读根本不知道是什么鬼,什么鬼啊什么鬼。

    解读二进制,想到什么东西了吗?没错就是第二节中讲的struct模块,我们能使用pack方法把数据打包成二进制,当然也能有个unpack方法把它还原成我们能看懂的东西。

    上代码:

    reply=struct.unpack('!HHLHHBxHLLHH64s64s',data)

    我去就这么简单?没错就这么简单。

    reply是什么格式呢?我直接给你回答了吧,是list格式的(也就是[]这玩意儿)

    reply[0]就是第一个模式字符H对应的内容

    reply[1]就是第二个模式字符H对应的

    reply[3]就是第三个模式字符L对应的内容。

    还记得我们在模式串中有个x吗?这个x在解包后有没有对应的呢?你要自己调试比对了,我也不能给你个确定答案,至少我看到的是没有对应的。

    if reply[0]==156 and reply[3]==2:#长度是156字节并且Control Message Type是2
        output='%s/tcp open pptp %s (Firmware Revision %s)'%(port,reply[12],reply[10])
        output.replace('x00','')#把不必要的空数据去掉
        security_note(output)#security_note是bugscan的sdk中的输出方法

    第五节 封装并嵌入sdk中

    把我们检测pptp信息的代码封装到一个方法中去,看上去好看点:

    def getPPTPVersion(host,port):
        s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
        pptp={"Length":156,"MessageType":1,"MagicCookie":0x1a2b3c4d,"ControlMessageType":1,"Reserved":0,"ProtocolVersion":1,"FramingCapabilities":1,"BearerCapabilities":1,"MaximumChannels":0,"FirmwareRevision":0,"HostName":"Bugscan","VendorName": "medicean"}
        payload=struct.pack('!HHLHHBxHLLHH64s64s',pptp["Length"],pptp["MessageType"],pptp["MagicCookie"],pptp["ControlMessageType"],pptp["Reserved"],pptp["ProtocolVersion"],pptp["Reserved"],pptp["FramingCapabilities"],pptp["BearerCapabilities"],pptp["MaximumChannels"],pptp["FirmwareRevision"],pptp["HostName"],pptp["VendorName"])
        try:
            socket.setdefaulttimeout(20)#超时
            s.connect((host,port))#连接对应主机和端口
            s.send(payload)
            data=s.recv(1024)
            reply=struct.unpack('!HHLHHBxHLLHH64s64s',data)
            if reply[0]==156 and reply[3]==2:#长度是156字节并且Control Message Type是2
                output='%s/tcp open pptp %s (Firmware Revision %s)' % (port,reply[12],reply[10])
                output=output.replace('x00','')#把不必要的空数据去掉
                security_note(output)
        except Exception :
            pass
        finally:
            s.close()

    是不是每句话都可以看懂了呢? 然后再来看sdk:

    def assign(service, arg):
        if service == "ip":#平时写多了cms?这里是ip哟
            return True, arg
    def audit(arg):
        #这里调用我们刚刚写的方法
    
    if __name__ == '__main__':#插件本地测试入口
        from dummy import *
        audit(assign('ip', '127.0.0.1')[1])

    简单说下,assign是主框架调度插件时的方法,bugscan扫描时候,主程序先启动, 然后检测目标的开放的service,一般有ip,www,然后就是目标的cms指纹了,以某个论坛为例子来说, 主程序扫描完后发现目标的service有ip,www,discuz,然后就会以一个任务队列去调用插件,现在有 一个扫描wordpress插件,按理来讲他是不应该被调度的,因为肯定不会有这样的漏洞存在。所以就有了 这个assign方法,做到精确调度插件,极大程度上提高扫描速度。

    然后就是这个audit了,给主程序提供统一的调用插件的方法名,就好比是个接口,主程度才不管你插件 里面怎么写的,我只管调这个方法就行了。

    好了,把我们自己的方法再整合进去,差不多就快完工了。

    #!/usr/bin/env python
    # -*- coding: utf-8 -*-
    #__author__ = 'Medici.Yan'
    #探测对方主机pptp版本信息
    import socket,struct
    def assign(service, arg):
        if service == "ip":
            return True, arg
    def audit(arg):
        #pptp默认是1723端口,但是有些系统可能会修改端口
        for i in range(1720,1725):
            getPPTPVersion(arg, i)
    
    def getPPTPVersion(host,port):
        s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
        pptp={"Length":156,"MessageType":1,"MagicCookie":0x1a2b3c4d,"ControlMessageType":1,"Reserved":0,"ProtocolVersion":1,"FramingCapabilities":1,"BearerCapabilities":1,"MaximumChannels":0,"FirmwareRevision":0,"HostName":"Bugscan","VendorName": "medicean"}
        payload=struct.pack('!HHLHHBxHLLHH64s64s',pptp["Length"],pptp["MessageType"],pptp["MagicCookie"],pptp["ControlMessageType"],pptp["Reserved"],pptp["ProtocolVersion"],pptp["Reserved"],pptp["FramingCapabilities"],pptp["BearerCapabilities"],pptp["MaximumChannels"],pptp["FirmwareRevision"],pptp["HostName"],pptp["VendorName"])
        try:
            socket.setdefaulttimeout(20)#超时
            s.connect((host,port))#连接对应主机和端口
            s.send(payload)
            data=s.recv(1024)
            reply=struct.unpack('!HHLHHBxHLLHH64s64s',data)
            if reply[0]==156 and reply[3]==2:#长度是156字节并且Control Message Type是2
                output='%s/tcp open pptp %s (Firmware Revision %s)' % (port,reply[12],reply[10])
                output=output.replace('x00','')#把不必要的空数据去掉
                security_note(output)
        except Exception :
            pass
        finally:
            s.close()
    if __name__ == '__main__':
        from dummy import *
        audit(assign('ip', '127.0.0.1')[1])

    第四章 测试及收尾

    插件写完了,我们最好是在本地测试下哦。

    打开wireshark,开启抓包,然后运行我们的插件

    alt text

    看到了吧,我们的Bugscan字符出现在报文里面了,说明就是我们自己发的包哟。 再来看看返回的结果:

    alt text

    嗯,完全正确,没有问题了。当然最好是再找个windows的pptp服务器去测试下,我找过了,不过ip地址 不是私网的,这里就不放图片了。

    然后呢,上传到平台上吧。如果你能上传私有插件,先上传成私有插件,自己去扫一下一个已知的网站,看 看看结果是什么样子的,最后什么都没有问题了,你就可以直接在群里私密管理员@半块西瓜皮@了,很快就会通过的。

    最后呢,再说一些要注意的地方吧:

    1. 在自己编写扫描插件的时候,一定要注意不要写成利用工具了。比如有个注入漏洞,不要直接把管理员密码给爆出来,我们检测漏洞,并不是在写利用工具。

    2. 不要对对方系统造成影响。这个怎么说呢,比如说对方系统有安全机制,有个页面有漏洞, 你的插件给对方页面中写了phpinfo(),然后你要删除的时候,触发了安全机制删除不了了,这样把对方的敏感信息 直接泄漏出来是非常不好的。

    好了,就写到这里吧,抛砖引玉,大家自由发挥喽。

  • 相关阅读:
    CF833 A The Meaningless Game
    [Noip2016]蚯蚓 (单调队列)
    [NOI2003]逃学的小孩 (贪心+树的直径+暴力枚举)
    [POI2014]FAR-FarmCraft (树规+贪心)
    洛谷P2566 [SCOI2009]围豆豆(状压dp+spfa)
    [POJ1852] Ants(思维题)
    树的深度(我觉得没毛病)
    HDU
    剑指offer相关问题
    CC150相关问题
  • 原文地址:https://www.cnblogs.com/mediciyan/p/4366175.html
Copyright © 2011-2022 走看看