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控制信息

报文结构大致上如下:

BugScan插件编写高(gǎo)级(jī)教程

第二节 PPTP报文分析:

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

  1. start-control-connection-request : 由PPTP客户端发出,请求建立控制连接。PPTP隧道要求在发送任何其他PPTP消息之前,先建立一条控制连接。我们要发给服务器的报文就是这个家伙了。所以要好好看看这个家伙到底是个什么鬼。 start-control-connection-request报文格式如下: BugScan插件编写高(gǎo)级(jī)教程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的一些版本信息。得到报文之后要得出结论,就要了解这个报文是什么样子,什么含义。要不然你收到报文也不知道是什么东西。 报文格式如下: BugScan插件编写高(gǎo)级(jī)教程 就说点和前面报文不一样的地方:

    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有关的报文了,如下图: BugScan插件编写高(gǎo)级(jī)教程

    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里面抓包到二进制数据差不多了。如下图: BugScan插件编写高(gǎo)级(jī)教程

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

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

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

HostName和VendorString的数据可以变的,这不用管。主要是来检验下前面的数据。 wireshark里面的数据如下: BugScan插件编写高(gǎo)级(jī)教程 再看我们的: BugScan插件编写高(gǎo)级(jī)教程 看出问题了没有?

没有看出来?那我再来发一个图: BugScan插件编写高(gǎo)级(jī)教程

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

>>> struct.pack("H",1)
'\x01\x00'
>>> struct.pack("!H",1)
'\x00\x01'

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

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

  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"])

    BugScan插件编写高(gǎo)级(jī)教程 现在对比下是不是就正确了呢? 本次教程中,我采用了第二种办法。

第三节 数据包发送及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,开启抓包,然后运行我们的插件

BugScan插件编写高(gǎo)级(jī)教程

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

BugScan插件编写高(gǎo)级(jī)教程

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

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

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

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

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

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

上一篇:项目中 2个或者多个EF模型 表名称相同会导致生成的实体类 覆盖的解决方法


下一篇:【转】通过Hibernate将数据 存入oracle数据库例子