zoukankan      html  css  js  c++  java
  • Nmap脚本文件分析(AMQP协议为例)

    Nmap脚本文件分析(AMQP协议为例)

    一、介绍

      上两篇文章 Nmap脚本引擎原理   编写自己的Nmap(NSE)脚本,分析了Nmap脚本引擎的执行过程,以及脚本文件的编写,这篇文章将以解析AMQP Server为例,介绍Nmap自带库的使用,以及上两篇文章中介绍不足的地方。

      转载请注明出处:http://www.cnblogs.com/liun1994/

    二、AMQP协议

      AMQP协议的全称为: Advanced Message Queuing Protocol,提供统一消息服务的应用层标准高级消息队列协议;是应用层协议的一个开放标准,为面向消息的中间件设计,基于此协议的客户端与消息中间件可传递消息,并不
    受客户端/中间件不同产品,不同开发语言等条件的限制。下图显示为AMQP的原理图,典型的生产消费消息模型:

     原理图1

    原理图2

      各组件的作用如下:
       
     1)Broker:接收和分发消息的应用,比如:RabbitMQ Server就是Message Broker。

        2)Connection: publisher/consumer和broker之间的TCP连接。断开连接的操作只会在client端进行,Broker不会断开连接,除非出现网络故障或broker服务出现问题。

        3)Exchange:message到达broker的第一站,根据分发规则,匹配查询表中的routing key,分发消息到queue中去。常用的类型有:direct (point-to-point), topic (publish-subscribe) and fanout (multicast)。

        4)Queue: 消息最终被送到这里等待consumer取走。一个message可以被同时拷贝到多个queue中。

      在RabbitMQ实现中服务器允许的端口:参考(http://www.rabbitmq.com/networking.html

    z

      在Nmap探测时,使用的正是AMQP 0-9-1 Client,因此我们探测时探测端口5672。

      遵循什么格式与AMQP Server交互?参考(http://www.rabbitmq.com/resources/specs/amqp-xml-doc0-9-1.pdf

      由上两幅图可以看出,AMQP客户端与服务器交互首先发送协议头:“AMQP0091”到服务器,服务器如果拒绝连接,则返回一个无效的协议头,Nmap的处理方式是根据返回的协议版本再次请求服务。

    三、脚本文件分析

       amqp-info.nse文件:

    -- amqp库提供检索AMQP服务信息的基本函数,目前支持AMQP0-9和AMQP0-8协议
    -- 这个库包含一个AMQP类,这个类包含于AMQP通信及核心函数。
    local amqp = require "amqp" 
    
    -- nmap模块是与nmap内部函数交互和数据结构化的API,API提供目标主机的详细信息
    -- 例如端口状态和版本探测结果;同时API也提供与Nsock交互的接口;文件中共48个函数。
    local nmap = require "nmap"
    
    -- 构建简要端口规则的函数,端口规则被多数scripts所使用,因此
    -- 这个模块提供最基本的测试,函数返回true or false。
    local shortport = require "shortport"
    
    -- 标准Nmap脚本引擎函数,这个模块包含各种实用的函数,由于太小不能以模块的形式给出。
    -- 该模块下有个module函数,作用跟Lua 5.1的module函数类似;
    -- 例如_ENV = stdnse.module("socks", stdnse.seeall)的作用就是定义一个以文件名socks
    -- 为变量的模块,这样方便我们修改,便于统一,印象笔记中有Lua moudle详解。
    local stdnse = require "stdnse"
    
    -- 从AMQP服务器上收集信息
    description = [[
    Gathers information (a list of all server properties) from an AMQP (advanced message queuing protocol) server.
    ]]
    
    -- 用于控制脚本的选择 nmap --script default,safe;只运行safe和discovery类别的脚本
    categories = {"default", "discovery", "safe", "version"}
    
    -- 端口规则,当这个函数返回true的时候,执行action函数.
    portrule = shortport.version_port_or_service(5672, "amqp", "tcp", "open")
    
    action = function(host, port)
    
      -- 调用amqp模块新建一个cli表,表里面包括好多属性,host/port/amqpsocket等。
      local cli = amqp.AMQP:new( host, port )
      
      -- 通过connect方法连接服务器,通过namp模块的connect()方法与底层取得联系
      local status, data = cli:connect()
      
      -- 如果status为nil或false,则返回下面的数据输出,data为error字符串,在nmap模块中可查到。
      if not status then return "Unable to open connection: " .. data end
      
      -- 如果连接成功,则进一步握手处理,数据解析也是在amqp.lua这个模块中解析
      status, data = cli:handshake()
      
      -- 如果status为nil或false则返回错误信息data
      if not status then return data end
    
      -- 断开连接
      cli:disconnect()
      
      -- 能够进行到这一步,说明没有错误出现,确定下来是AMQP协议了。
      port.version.name = "amqp"
      port.version.product = cli:getServerProduct()
      port.version.extrainfo = cli:getProtocolVersion()
      port.version.version = cli:getServerVersion()
     
      -- 设置host,port表为新的状态。
      nmap.set_port_version(host, port)
    
      -- server_properties表存储了握手的所有信息
      return stdnse.format_output(status, cli:getServerProperties())
      
      
      -- 综上所述,如果我们想修改交互过程的包,以及解析返回的数据;
      -- 关注amqp.lua模块的handshake()函数即可。
    end

        函数预览图:

      内嵌库amqp.lua文件:

    -- 该模块提供基本的检索AMQP服务器信息的函数,目前支持 AMQP 0-9和AMQP 0-8协议格式
    -- 该模块包含一个类,AMQP类,该类定义了AMQP交互过程所需要的属性。
    local bin = require "bin"
    local match = require "match"
    local nmap = require "nmap"
    local stdnse = require "stdnse"
    local table = require "table"
    
    -- 统一化定义模块,跟lua原始方式定义是一样的,只不过这样定义可以统一。
    _ENV = stdnse.module("amqp", stdnse.seeall);
    
    
    AMQP = {
    
      -- protocol versions sent by the server
      versions = {
        [0x0800] = "0-8",
        [0x0009] = "0-9"
      },
    
      -- version strings the client supports
      client_version_strings = {
        ["0-8"] = "x01x01x08x00",
        ["0-9"] = "x00x00x09x00",
        ["0-9-1"] = "x00x00x09x01"
      },
    
      -- setmetatable,self,__index等关键字,是Lua模拟类的操作;返回一个o对象。
      new = function(self, host, port)
        local o = {}
        setmetatable(o, self)
        self.__index = self
        o.host = host
        o.port = port
        o.amqpsocket = nmap.new_socket()
        -- nmap通过registry共享全局数据,如果参数里面有["0-8"]["0-9"]["0-9-1"]则设置为相应的字符串
        -- 如果没有则设置为["0-9-1"]对应的字符串
        o.cli_version = self.client_version_strings[nmap.registry.args['amqp.version']] or self.client_version_strings["0-9-1"]
        o.protover = nil
        o.server_version = nil
        o.server_product = nil
        o.serer_properties = nil
        return o
      end,
    
      --- Connects the AMQP socket
      connect = function(self)
        local data, status, msg
    
        status, msg = self.amqpsocket:connect(self.host, self.port, "tcp")
        return status, msg
      end,
    
      --- Disconnects the AMQP socket
      disconnect = function(self)
        self.amqpsocket:close()
      end,
    
      --- Decodes a table value in the server properties field.
      --
      -- @param tbl the decoded table
      -- @param tsize number, the table size in bytes
      -- @return status, true on success, false on failure
      -- @return error string containing error message if status is false
      -- @return decoded value
      decodeTable = function(self, tbl, tsize)
        local status, err, tmp, read, value
        read = 0
    
        while read < tsize do
          local key, value
    
          status, tmp = self.amqpsocket:receive_buf(match.numbytes(1), true)
          if ( not(status) ) then
            return status, "ERROR: AMQP:handshake connection closed unexpectedly while reading key length", nil
          end
          read = read + 1
    
          tmp = select( 2, bin.unpack("C", tmp) )
          status, key = self.amqpsocket:receive_buf(match.numbytes(tmp), true)
          if ( not(status) ) then
            return status, "ERROR: AMQP:handshake connection closed unexpectedly while reading key", nil
          end
          read = read + tmp
    
          status, tmp = self.amqpsocket:receive_buf(match.numbytes(1), true)
          if ( not(status) ) then
            return status, "ERROR: AMQP:handshake connection closed unexpectedly while reading value type for " .. key, nil
          end
          read = read + 1
    
          if ( tmp == 'F' ) then -- table type
            status, tmp = self.amqpsocket:receive_buf(match.numbytes(4), true)
            if ( not(status) ) then
              return status, "ERROR: AMQP:handshake connection closed unexpectedly while reading table size", nil
            end
    
            read = read + 4
            value = {}
            tmp = select( 2, bin.unpack(">I", tmp) )
            status, err, value = self:decodeTable(value, tmp)
            read = read + tmp
            table.insert(tbl, key .. ": ")
            table.insert(tbl, value)
          elseif ( tmp == 'S' ) then -- string type
            status, err, value, read = self:decodeString(key, read)
            if ( key == "product" ) then
              self.server_product = value
            elseif ( key == "version" ) then
              self.server_version = value
            end
            table.insert(tbl, key .. ": " .. value)
          elseif ( tmp == 't' ) then -- boolean type
            status, err, value, read = self:decodeBoolean(key, read)
            table.insert(tbl, key .. ": " .. value)
          end
    
          if ( not(status) ) then
            return status, err, nil
          end
    
        end
    
        return true, nil, tbl
      end,
    
      --- Decodes a string value in the server properties field.
      --
      -- @param key string, the key being read
      -- @param read number, number of bytes already read
      -- @return status, true on success, false on failure
      -- @return error string containing error message if status is false
      -- @return decoded value
      -- @return number of bytes read after decoding this value
      decodeString = function(self, key, read)
        local value, status, tmp
        status, tmp = self.amqpsocket:receive_buf(match.numbytes(4), true)
        if ( not(status) ) then
          return status, "ERROR: AMQP:handshake connection closed unexpectedly while reading value size for " .. key, nil, 0
        end
    
        read = read + 4
        tmp = select( 2, bin.unpack(">I", tmp) )
        status, value = self.amqpsocket:receive_buf(match.numbytes(tmp), true)
    
        if ( not(status) ) then
          return status, "ERROR: AMQP:handshake connection closed unexpectedly while reading value for " .. key, nil, 0
        end
        read = read + tmp
    
        return true, nil, value, read
      end,
    
      --- Decodes a boolean value in the server properties field.
      --
      -- @param key string, the key being read
      -- @param read number, number of bytes already read
      -- @return status, true on success, false on failure
      -- @return error string containing error message if status is false
      -- @return decoded value
      -- @return number of bytes read after decoding this value
      decodeBoolean = function(self, key, read)
        local status, value
        status, value = self.amqpsocket:receive_buf(match.numbytes(1), true)
        if ( not(status) ) then
          return status, "ERROR: AMQP:handshake connection closed unexpectedly while reading value for " .. key, nil, 0
        end
    
        value = select( 2, bin.unpack("C", value) )
        read = read + 1
    
        return true, nil, value == 0x01 and "YES" or "NO", read
      end,
    
      --- Performs the AMQP handshake and determines
      -- * The AMQP protocol version
      -- * The server properties/capabilities
      --
      -- @return status, true on success, false on failure
      -- @return error string containing error message if status is false
      handshake = function(self)
        local _, status, err, version, tmp, value, properties
        
        -- 遵循AMQP协议格式,向AMQP服务器发送连接信息
        status = self.amqpsocket:send( "AMQP" .. self.cli_version )
        if ( not(status) ) then
          return false, "ERROR: AMQP:handshake failed while sending client version"
        end
    
        status, tmp = self.amqpsocket:receive_buf(match.numbytes(11), true)
        if ( not(status) ) then
          return status, "ERROR: AMQP:handshake connection closed unexpectedly while reading frame header"
        end
    
        -- check if the server rejected our proposed version
        if ( #tmp ~= 11 ) then --如果tmp不等于11,如果连接成功字节会在11以上,11是固定头。
          -- 如果服务拒绝连接,那么返回8字节的数据。select函数是lua自带函数,2代表选择bin.unpack返回值的第二个参数
          -- bin.unpack,二进制解包; 目前推荐使用Lua5.3的string.unpack功能。
          -- 可参考http://www.lua.org/manual/5.3/manual.html#6.4.2
          -- http://www.lua.org/manual/5.3/manual.html#pdf-string.unpack
          -- > 表示大端存储,I表示读取四字节的无符号整数。
          if ( #tmp == 8 and select( 2, bin.unpack(">I", tmp) ) == 0x414D5150 ) then
            local vi, vii, v1, v2, v3, v4, found
            _, vi = bin.unpack(">I", tmp, 5)
            found = false
    
            -- check if we support the server's version
            for _, v in pairs( self.client_version_strings ) do
              _, vii = bin.unpack(">I", v)
              if ( vii == vi ) then
                version = v
                found = true
                break
              end
            end
    
            -- try again with new version string
            if ( found and version ~= self.cli_version ) then
              self.cli_version = version
              self:disconnect()
              status, err = self:connect()
    
              if ( not(status) ) then
                return status, err
              end
    
              return self:handshake()
            end
    
            -- version unsupported, _代表最后一个结束字符list[list.n]。
            -- >表示大端存储,C表示读取单字节的无符号整数。
            _, v1, v2, v3, v4 = bin.unpack(">CCCC", tmp, 5)
            return false, ("ERROR: AMQP:handshake unsupported version (%d.%d.%d.%d)"):format( v1, v2, v3, v4 )
          else -- 返回的不是AMQP...八个字节,证明不是AMQP协议
            return false, ("ERROR: AMQP:handshake server might not be AMQP, received: %s"):format( tmp )
          end
        end
        
        -- 上述过程结束之后,说明连接成功,收到11个以上的字节,开始解析协议。
        -- parse frame header
        local frametype, chnumber, framesize, method
        _, frametype, chnumber, framesize, method = bin.unpack(">CSII", tmp)
        stdnse.debug1("frametype: %d, chnumber: %d, framesize: %d, method: %d", frametype, chnumber, framesize, method)
    
        if (frametype ~= 1) then
          return false, ("ERROR: AQMP:handshake expected header (1) frame, but was %d"):format(frametype)
        end
    
        if (method ~= 0x000A000A) then
          return false, ("ERROR: AQMP:handshake expected connection.start (0x000A000A) method, but was %x"):format(method)
        end
    
        -- 解析11字节之后的字节,12,13字节分别是Major Version和 Minor Version
        -- parse protocol version
        status, tmp = self.amqpsocket:receive_buf(match.numbytes(2), true)
        if ( not(status) ) then
          return status, "ERROR: AMQP:handshake connection closed unexpectedly while reading version"
        end
        version = select( 2, bin.unpack(">S", tmp) )
        self.protover = AMQP.versions[version]
    
        if ( not(self.protover) ) then
          return false, ("ERROR: AMQP:handshake unsupported version (%x)"):format(version)
        end
    
        -- 解析服务属性,4字节的内容代表数据长度。
        -- parse server properties
        status, tmp = self.amqpsocket:receive_buf(match.numbytes(4), true)
        if ( not(status) ) then
          return status, "ERROR: AMQP:handshake connection closed unexpectedly while reading server properties size"
        end
    
        local tablesize = select( 2, bin.unpack(">I", tmp) )
        properties = {}
        -- 封装到decodeTable解析,在decodeTable里面会继续从receive_buf接收数据
        status, err, properties = self:decodeTable(properties, tablesize)
    
        if ( not(status) ) then
          return status, err
        end
    
        -- 解析mechanisms
        status, err, value, tmp = self:decodeString("mechanisms", 0)
        if ( not(status) ) then
          return status, err
        end
        table.insert(properties, "mechanisms: " .. value)
    
        -- 解析locales
        status, err, value, tmp = self:decodeString("locales", 0)
        if ( not(status) ) then
          return status, err
        end
        table.insert(properties, "locales: " .. value)
    
        self.server_properties = properties
        
        -- 解析内容设置到AMQP o这个对象里面,返回true
        return true
      end,
    
      --- Returns the protocol version reported by the server
      --
      -- @return string containing the version number
      getProtocolVersion = function( self )
        return self.protover
      end,
    
      --- Returns the product version reported by the server
      --
      -- @return string containing the version number
      getServerVersion = function( self )
        return self.server_version
      end,
    
      --- Returns the product name reported by the server
      --
      -- @return string containing the product name
      getServerProduct = function( self )
        return self.server_product
      end,
    
      --- Returns the properties reported by the server
      --
      -- @return table containing server properties
      getServerProperties = function( self )
        return self.server_properties
      end,
    }
    
    return _ENV;
    View Code

    四、总结

       1)目前500多种脚本文件大致的执行流程类似,因为在脚本中可以拿到socket连接,可以与服务进行通信,拿到banner信息进行解析。

       2)更高级的用法需要了解每一个NSE的语句,通过这个例子,其他NSE脚本也不难理解。

       3)Nmap整体架构指的学习,有时间分析分析源码有助于对其他工具的理解。

    五、参考文献

      http://www.cnblogs.com/frankyou/p/5283539.html   博客:RabbitMQ与AMQP协议详解

      http://www.rabbitmq.com/documentation.html    RabbitMQ Documentation

      http://www.rabbitmq.com/protocol.html          AMQP 0-9-1 协议文档

      

  • 相关阅读:
    实验五
    实验四
    实验三
    实验二
    寄存器(内存访问)
    实验一
    寄存器
    Mermaid 绘图总结
    电脑查看系统版本
    _ZNote_Chrom_插件_Chrom运行Android软件_APK
  • 原文地址:https://www.cnblogs.com/liun1994/p/7044473.html
Copyright © 2011-2022 走看看