--- -- A minimal RDP (Remote Desktop Protocol) library. Currently has functionality to determine encryption -- and cipher support. -- -- -- @author Patrik Karlsson -- @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("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("