summaryrefslogtreecommitdiffstats
path: root/nselib/rdp.lua
diff options
context:
space:
mode:
Diffstat (limited to 'nselib/rdp.lua')
-rw-r--r--nselib/rdp.lua453
1 files changed, 453 insertions, 0 deletions
diff --git a/nselib/rdp.lua b/nselib/rdp.lua
new file mode 100644
index 0000000..e468a96
--- /dev/null
+++ b/nselib/rdp.lua
@@ -0,0 +1,453 @@
+---
+-- A minimal RDP (Remote Desktop Protocol) library. Currently has functionality to determine encryption
+-- and cipher support.
+--
+--
+-- @author Patrik Karlsson <patrik@cqure.net>
+-- @copyright Same as Nmap--See https://nmap.org/book/man-legal.html
+--
+
+local nmap = require("nmap")
+local stdnse = require("stdnse")
+local string = require "string"
+local asn1 = require "asn1"
+_ENV = stdnse.module("rdp", stdnse.seeall)
+
+-- Server Core Data 2.2.1.4.2
+PROTO_VERSION = {
+ [0x00080001] = " RDP 4.0 server",
+ [0x00080004] = " RDP 5.x, 6.x, 7.x, or 8.x server",
+ [0x00080005] = " RDP 10.0 server",
+ [0x00080006] = " RDP 10.1 server",
+ [0x00080007] = " RDP 10.2 server",
+ [0x00080008] = " RDP 10.3 server",
+ [0x00080009] = " RDP 10.4 server",
+ [0x0008000A] = " RDP 10.5 server",
+ [0x0008000B] = " RDP 10.6 server",
+ [0x0008000C] = " RDP 10.7 server",
+}
+
+-- T.125 Result enumerated type
+CONNECT_RESPONSE_RESULT = {
+ [ 0] = "rt-successful",
+ [ 1] = "rt-domain-merging",
+ [ 2] = "rt-domain-not-hierarchical",
+ [ 3] = "rt-no-such-channel",
+ [ 4] = "rt-no-such-domain",
+ [ 5] = "rt-no-such-user",
+ [ 6] = "rt-not-admitted",
+ [ 7] = "rt-other-user-id",
+ [ 8] = "rt-parameters-unacceptable",
+ [ 9] = "rt-token-not-available",
+ [10] = "rt-token-not-possessed",
+ [11] = "rt-too-many-channels",
+ [12] = "rt-too-many-tokens",
+ [13] = "rt-too-many-users",
+ [14] = "rt-unspecified-failure",
+ [15] = "rt-user-rejected",
+}
+
+-- requestedProtocols - flag - RDP_NEG_REQ - MS-RDPBCGR 2.2.1.1.1
+PROTOCOL_RDP = 0 -- Standard RDP Security
+PROTOCOL_SSL = 1 -- TLS 1.0, 1.1, 1.2
+PROTOCOL_HYBRID = 2 -- CredSSP (NLA). TLS flag should be set as well
+PROTOCOL_RDSTLS = 4 -- RDSTLS
+PROTOCOL_HYBRID_EX = 8 -- CredSSP (NLA) with Early User Auth PDU
+
+Packet = {
+
+ TPKT = {
+
+ new = function(self, data)
+ local o = { data = tostring(data), version = 3 }
+ setmetatable(o, self)
+ self.__index = self
+ return o
+ end,
+
+ __tostring = function(self)
+ return string.pack(">BBI2",
+ self.version,
+ self.reserved or 0,
+ (self.data and #self.data + 4 or 4))
+ ..self.data
+ end,
+
+ parse = function(data)
+ local tpkt = Packet.TPKT:new()
+ local pos
+
+ tpkt.version, tpkt.reserved, tpkt.length, pos = string.unpack(">BBI2", data)
+ tpkt.data = data:sub(pos)
+ return tpkt
+ end
+ },
+
+ ITUT = {
+
+ new = function(self, code, data)
+ local o = { data = tostring(data), code = code }
+ setmetatable(o, self)
+ self.__index = self
+ return o
+ end,
+
+ parse = function(data)
+ local itut = Packet.ITUT:new()
+ local pos
+
+ itut.length, itut.code, pos = string.unpack("BB", data)
+
+ if ( itut.code == 0xF0 ) then
+ -- X.224 - Data TPDU (DT)
+ itut.eot, pos = string.unpack("B", data, pos)
+ elseif ( itut.code == 0xD0 ) then
+ -- X.224 - Connection Confirm (CC)
+ itut.dstref, itut.srcref, itut.class, pos = string.unpack(">I2I2B", data, pos)
+ end
+
+ itut.data = data:sub(pos)
+ return itut
+ end,
+
+ __tostring = function(self)
+ local len, eot
+ if self.code == 0xF0 then
+ eot = "\x80"
+ len = 2
+ else
+ eot = ""
+ len = #self.data + 1
+ end
+ local data = string.pack("BB",
+ len,
+ self.code or 0)
+ .. eot
+ .. self.data
+
+ return data
+ end,
+
+ },
+
+ ConfCreateResponse = {
+
+
+ new = function(self)
+ local o = {}
+ setmetatable(o, self)
+ self.__index = self
+ return o
+ end,
+
+ parse = function(data)
+
+ local tag_decoder = {}
+
+ tag_decoder["\x0A"] = function( self, encStr, elen, pos )
+ return self.decodeInt(encStr, elen, pos)
+ end
+
+ local ccr = Packet.ConfCreateResponse:new()
+
+ local decoder = asn1.ASN1Decoder:new()
+ decoder:registerTagDecoders( tag_decoder )
+
+ local _, pos = decoder.decodeLength(data, 3)
+ local response_result, userdata
+ response_result, pos = decoder:decode(data, pos)
+ ccr.result = CONNECT_RESPONSE_RESULT[response_result]
+
+ ccr.calledConnectId, pos = decoder:decode(data, pos)
+
+ -- T.125 DomainParameters SEQUENCE
+ -- Not interested in its values now, just need to correctly parse
+ -- the block so we can arrive at userData
+ _, pos = decoder:decode(data, pos)
+
+ -- T.125 userData OCTO string
+ userdata, _ = decoder:decode(data, pos)
+
+ if userdata == nil then
+ return ccr
+ end
+
+ -- Hackery to avoid writing ASN.1 PER decoding. Skip over fixed length
+ -- T.124 ConnectData header. Decode the length since it can be multiple
+ -- bytes. Drops us where we need to be.
+ _, pos = asn1.ASN1Decoder.decodeLength(userdata, 22 )
+ local block_type, block_len
+ while userdata:len() > pos do
+ block_type, block_len = string.unpack("<I2I2", userdata, pos)
+ if block_type == 0x0c01 then
+ -- 2.2.1.42 Server Core Data - TS_UD_SC_CORE
+ local proto_ver = string.unpack("<I4",userdata, pos + 4)
+ ccr.proto_version = ("RDP Protocol Version: %s"):format(PROTO_VERSION[proto_ver] or "Unknown")
+ elseif block_type == 0x0c02 then
+ -- 2.2.1.4.3 Server Security Data - TS_UD_SC_SEC1
+ ccr.enc_level = string.unpack("B", userdata, pos + 8)
+ ccr.enc_cipher= string.unpack("B", userdata, pos + 4)
+ end
+ pos = pos + block_len
+ end
+ return ccr
+ end,
+ },
+
+}
+
+Request = {
+
+ ConnectionRequest = {
+
+ new = function(self, proto)
+ local o = { proto = proto }
+ setmetatable(o, self)
+ self.__index = self
+ return o
+ end,
+
+ __tostring = function(self)
+ local cookie = "mstshash=nmap"
+
+ local data = string.pack(">I2I2B",
+ 0x0000, -- dst reference
+ 0x0000, -- src reference
+ 0x00) -- class and options
+ .. ("Cookie: %s\r\n"):format(cookie)
+
+ if ( self.proto ) then
+ data = data .. string.pack("<BBI2I4",
+ 0x01, -- TYPE_RDP_NEG_REQ
+ 0x00, -- flags
+ 0x0008, -- length
+ self.proto -- protocol
+ )
+ end
+ return tostring(Packet.TPKT:new(Packet.ITUT:new(0xE0, data)))
+ end
+ },
+
+ MCSConnectInitial = {
+
+ new = function(self, cipher, server_proto)
+ local o = { cipher = cipher, server_proto = server_proto }
+ setmetatable(o, self)
+ self.__index = self
+ return o
+ end,
+
+ __tostring = function(self)
+
+ local data = stdnse.fromhex(
+ "7f 65" .. -- BER: Application-Defined Type = APPLICATION 101,
+ "82 01 94" .. -- BER: Type Length = 404 bytes
+ "04 01 01" .. -- Connect-Initial::callingDomainSelector
+ "04 01 01" .. -- Connect-Initial::calledDomainSelector
+ "01 01 ff" .. -- Connect-Initial::upwardFlag = TRUE
+ "30 19" .. -- Connect-Initial::targetParameters (25 bytes)
+ "02 01 22" .. -- DomainParameters::maxChannelIds = 34
+ "02 01 02" .. -- DomainParameters::maxUserIds = 2
+ "02 01 00" .. -- DomainParameters::maxTokenIds = 0
+ "02 01 01" .. -- DomainParameters::numPriorities = 1
+ "02 01 00" .. -- DomainParameters::minThroughput = 0
+ "02 01 01" .. -- DomainParameters::maxHeight = 1
+ "02 02 ff ff" .. -- DomainParameters::maxMCSPDUsize = 65535
+ "02 01 02" .. -- DomainParameters::protocolVersion = 2
+ "30 19" .. -- Connect-Initial::minimumParameters (25 bytes)
+ "02 01 01" .. -- DomainParameters::maxChannelIds = 1
+ "02 01 01" .. -- DomainParameters::maxUserIds = 1
+ "02 01 01" .. -- DomainParameters::maxTokenIds = 1
+ "02 01 01" .. -- DomainParameters::numPriorities = 1
+ "02 01 00" .. -- DomainParameters::minThroughput = 0
+ "02 01 01" .. -- DomainParameters::maxHeight = 1
+ "02 02 04 20" .. -- DomainParameters::maxMCSPDUsize = 1056
+ "02 01 02" .. -- DomainParameters::protocolVersion = 2
+ "30 1c" .. -- Connect-Initial::maximumParameters (28 bytes)
+ "02 02 ff ff" .. -- DomainParameters::maxChannelIds = 65535
+ "02 02 fc 17" .. -- DomainParameters::maxUserIds = 64535
+ "02 02 ff ff" .. -- DomainParameters::maxTokenIds = 65535
+ "02 01 01" .. -- DomainParameters::numPriorities = 1
+ "02 01 00" .. -- DomainParameters::minThroughput = 0
+ "02 01 01" .. -- DomainParameters::maxHeight = 1
+ "02 02 ff ff" .. -- DomainParameters::maxMCSPDUsize = 65535
+ "02 01 02" .. -- DomainParameters::protocolVersion = 2
+ "04 82 01 33" .. -- Connect-Initial::userData (307 bytes)
+ "00 05" .. -- object length = 5 bytes
+ "00 14 7c 00 01" .. -- object
+ "81 2a" .. -- ConnectData::connectPDU length = 42 bytes
+ "00 08 00 10 00 01 c0 00 44 75 63 61 81 1c" .. -- PER encoded (ALIGNED variant of BASIC-PER) GCC Conference Create Request PDU
+ "01 c0 d8 00" .. -- TS_UD_HEADER::type = CS_CORE (0xc001), length = 216 bytes
+ "04 00 08 00" .. -- TS_UD_CS_CORE::version = 0x0008004
+ "00 05" .. -- TS_UD_CS_CORE::desktopWidth = 1280
+ "20 03" .. -- TS_UD_CS_CORE::desktopHeight = 1024
+ "01 ca" .. -- TS_UD_CS_CORE::colorDepth = RNS_UD_COLOR_8BPP (0xca01)
+ "03 aa" .. -- TS_UD_CS_CORE::SASSequence
+ "09 04 00 00" .. -- TS_UD_CS_CORE::keyboardLayout = 0x409 = 1033 = English (US)
+ "28 0a 00 00" .. -- TS_UD_CS_CORE::clientBuild = 2600
+ "45 00 4d 00 50 00 2d 00 4c 00 41 00 50 00 2d 00 " ..
+ "30 00 30 00 31 00 34 00 00 00 00 00 00 00 00 00 " .. -- TS_UD_CS_CORE::clientName = EMP-LAP-0014
+ "04 00 00 00" .. -- TS_UD_CS_CORE::keyboardType
+ "00 00 00 00" .. -- TS_UD_CS_CORE::keyboardSubtype
+ "0c 00 00 00" .. -- TS_UD_CS_CORE::keyboardFunctionKey
+ "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " ..
+ "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " ..
+ "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " ..
+ "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " .. -- TS_UD_CS_CORE::imeFileName = ""
+ "01 ca" .. -- TS_UD_CS_CORE::postBeta2ColorDepth = RNS_UD_COLOR_8BPP (0xca01)
+ "01 00" .. -- TS_UD_CS_CORE::clientProductId
+ "00 00 00 00" .. -- TS_UD_CS_CORE::serialNumber
+ "18 00" .. -- TS_UD_CS_CORE::highColorDepth = 24 bpp
+ "07 00" .. -- TS_UD_CS_CORE::supportedColorDepths = 24 bpp
+ "01 00" .. -- TS_UD_CS_CORE::earlyCapabilityFlags
+ "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " ..
+ "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " ..
+ "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " ..
+ "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " .. -- TS_UD_CS_CORE::clientDigProductId
+ "00" .. -- TS_UD_CS_CORE::connectionType = 0 (not used as RNS_UD_CS_VALID_CONNECTION_TYPE not set)
+ "00") -- TS_UD_CS_CORE::pad1octet
+ -- TS_UD_CS_CORE::serverSelectedProtocol
+ .. string.pack("<I4", self.server_proto or 0) .. stdnse.fromhex(
+ "04 c0 0c 00" .. -- TS_UD_HEADER::type = CS_CLUSTER (0xc004), length = 12 bytes
+ "09 00 00 00" .. -- TS_UD_CS_CLUSTER::Flags = 0x0d
+ "00 00 00 00" .. -- TS_UD_CS_CLUSTER::RedirectedSessionID
+ "02 c0 0c 00") -- TS_UD_HEADER::type = CS_SECURITY (0xc002), length = 12 bytes
+ -- "1b 00 00 00" .. -- TS_UD_CS_SEC::encryptionMethods
+ .. string.pack("<I4", self.cipher or 0) .. stdnse.fromhex(
+ "00 00 00 00" .. -- TS_UD_CS_SEC::extEncryptionMethods
+ "03 c0 2c 00" .. -- TS_UD_HEADER::type = CS_NET (0xc003), length = 44 bytes
+ "03 00 00 00" .. -- TS_UD_CS_NET::channelCount = 3
+ "72 64 70 64 72 00 00 00" .. -- CHANNEL_DEF::name = "rdpdr"
+ "00 00 80 80" .. -- CHANNEL_DEF::options = 0x80800000
+ "63 6c 69 70 72 64 72 00" .. -- CHANNEL_DEF::name = "cliprdr"
+ "00 00 a0 c0" .. -- CHANNEL_DEF::options = 0xc0a00000
+ "72 64 70 73 6e 64 00 00" .. -- CHANNEL_DEF::name = "rdpsnd"
+ "00 00 00 c0" -- CHANNEL_DEF::options = 0xc0000000
+ )
+ return tostring(Packet.TPKT:new(Packet.ITUT:new(0xF0, data)))
+ end
+
+ },
+
+}
+
+Response = {
+
+ ConnectionConfirm = {
+
+ new = function(self)
+ local o = { }
+ setmetatable(o, self)
+ self.__index = self
+ return o
+ end,
+
+ parse = function(data)
+ local cc = Response.ConnectionConfirm:new()
+
+ cc.tpkt = Packet.TPKT.parse(data)
+ cc.itut = Packet.ITUT.parse(cc.tpkt.data)
+ return cc
+ end,
+
+ },
+
+ MCSConnectResponse = {
+ new = function(self)
+ local o = { }
+ setmetatable(o, self)
+ self.__index = self
+ return o
+ end,
+
+ parse = function(data)
+ local cr = Response.MCSConnectResponse:new()
+
+ cr.tpkt = Packet.TPKT.parse(data)
+ cr.itut = Packet.ITUT.parse(cr.tpkt.data)
+ if ( cr.itut.code == 0xF0 ) then
+ -- X.224 - Data TPDU (DT)
+ cr.ccr = Packet.ConfCreateResponse.parse(cr.itut.data)
+ end
+ return cr
+ end
+ }
+
+}
+
+Comm = {
+
+ -- Creates a new Comm instance
+ -- @param host table
+ -- @param port table
+ -- @return o instance of Comm
+ new = function(self, host, port)
+ local o = { host = host, port = port }
+ setmetatable(o, self)
+ self.__index = self
+ return o
+ end,
+
+ -- Connect to the server
+ -- @return status true on success, false on failure
+ -- @return err string containing error message, if status is false
+ connect = function(self)
+ self.socket = nmap.new_socket()
+ self.socket:set_timeout(5000)
+ if ( not(self.socket:connect(self.host, self.port)) ) then
+ return false, "Failed connecting to server"
+ end
+ return true
+ end,
+
+ -- Close the connection to the server
+ -- @return status true on success, false on failure
+ close = function(self)
+ return self.socket:close()
+ end,
+
+ -- Sends a message to the server
+ -- @param pkt an instance of Request.*
+ -- @return status true on success, false on failure
+ -- @return err string containing error message, if status is false
+ send = function(self, pkt)
+ return self.socket:send(tostring(pkt))
+ end,
+
+ -- Receives a message from the server
+ -- @return status true on success, false on failure
+ -- @return err string containing error message, if status is false
+ recv = function(self)
+ return self.socket:receive()
+ end,
+
+ -- Sends a message to the server and receives the response
+ -- @param pkt an instance of Request.*
+ -- @return status true on success, false on failure
+ -- @return err string containing error message, if status is false
+ -- pkt instance of Response.* on success
+ exch = function(self, pkt)
+ local status, err = self:send(pkt)
+ if ( not(status) ) then
+ return false, err
+ end
+
+ local _, data = self:recv()
+ if ( #data< 5 ) then
+ return false, "Packet too short"
+ end
+
+ local itut_code = string.byte(data, 6)
+ if ( itut_code == 0xD0 ) then
+ stdnse.debug2("RDP: Received ConnectionConfirm response")
+ return true, Response.ConnectionConfirm.parse(data)
+ elseif ( itut_code == 0xF0 ) then
+ return true, Response.MCSConnectResponse.parse(data)
+ elseif itut_code ~= nil then
+ stdnse.debug1(("comm:exch - Unknown itut_code: %s"):format(itut_code))
+ end
+ return false, "Received unhandled packet"
+ end,
+}
+
+return _ENV;