diff options
Diffstat (limited to 'scripts/backorifice-brute.nse')
-rw-r--r-- | scripts/backorifice-brute.nse | 291 |
1 files changed, 291 insertions, 0 deletions
diff --git a/scripts/backorifice-brute.nse b/scripts/backorifice-brute.nse new file mode 100644 index 0000000..0699b6d --- /dev/null +++ b/scripts/backorifice-brute.nse @@ -0,0 +1,291 @@ +local bits = require "bits" +local brute = require "brute" +local creds = require "creds" +local nmap = require "nmap" +local shortport = require "shortport" +local stdnse = require "stdnse" +local string = require "string" +local table = require "table" + +description = [[ +Performs brute force password auditing against the BackOrifice service. The +<code>backorifice-brute.ports</code> script argument is mandatory (it specifies ports to run +the script against). +]] + +--- +-- @usage +-- nmap -sU --script backorifice-brute <host> --script-args backorifice-brute.ports=<ports> +-- +-- @arg backorifice-brute.ports (mandatory) List of UDP ports to run the script against separated with "," ex. "U:31337,25252,151-222", "U:1024-1512" +-- +-- This script uses the brute library to perform password guessing. A +-- successful password guess is stored in the nmap registry, under the +-- <code>nmap.registry.credentials.backorifice</code> table for other BackOrifice +-- scripts to use. +-- +-- @output +-- PORT STATE SERVICE +-- 31337/udp open BackOrifice +-- | backorifice-brute: +-- | Accounts: +-- | michael => Valid credentials +-- | Statistics +-- |_ Perfomed 60023 guesses in 467 seconds, average tps: 138 +-- + +-- Summary +-- ------- +-- x The Driver class contains the driver implementation used by the brute +-- library +-- x The backorifice class contains the backorifice client implementation +-- + +author = "Gorjan Petrovski" +license = "Same as Nmap--See https://nmap.org/book/man-legal.html" +categories = {"intrusive", "brute"} + + +-- This portrule succeeds only when the open|filtered port is in the port range +-- which is specified by the ports script argument +portrule = function(host, port) + + local ports = stdnse.get_script_args(SCRIPT_NAME .. ".ports") + if not ports then + stdnse.verbose1("Skipping '%s' %s, 'ports' argument is missing.",SCRIPT_NAME, SCRIPT_TYPE) + return false + end + + -- ensure UDP + ports = ports:gsub("^[U:]*", "U:") + return port.protocol == "udp" and shortport.port_range(ports)(host, port) and + not(shortport.port_is_excluded(port.number,port.protocol)) +end + +local MAGICSTRING ="*!*QWTY?" +local backorifice = +{ + new = function(self, host, port) + local o = {} + setmetatable(o, self) + self.__index = self + o.host = host + o.port = port + return o + end, + + --- Initializes the backorifice object + -- + initialize = function(self) + --create socket + self.socket = nmap.new_socket("udp") + self.socket:set_timeout(self.host.times.timeout * 1000) + return true + end, + + --- Attempts to send an encrypted PING packet to BackOrifice service + -- + -- @param password string containing password for encryption + -- @param initial_seed number containing initial encryption seed + -- @return status, true on success, false on failure + -- @return err string containing error message on failure + try_password = function(self, password, initial_seed) + --initialize BackOrifice PING packet: |MAGICSTRING|size|packetID|TYPE_PING|arg1|arg_separat|arg2|CRC/disregarded| + local PING_PACKET = MAGICSTRING .. string.pack("<I4 I4 B zz", 19, 0, 1, "", "") + local seed, status, response, encrypted_ping + + if not(initial_seed) then + seed = self:gen_initial_seed(password) + else + seed = initial_seed + end + + encrypted_ping = self:BOcrypt(PING_PACKET,seed) + + status, response = self.socket:sendto(self.host, self.port, encrypted_ping) + if not(status) then + return false, response + end + status, response = self.socket:receive() + + -- The first 8 bytes of both response and sent data are + -- magicstring = "*!*QWTY?", without the quotes, and since + -- both are encrypted with the same initial seed, this is + -- how we verify we are talking to a BackOrifice service. + -- The statement is optimized so as not to decrypt unless + -- comparison of encrypted magicstrings succeeds + if status and response:sub(1,8) == encrypted_ping:sub(1,8) + and self:BOcrypt(response,seed):match("!PONG!(1%.20)!.*!") then + local BOversion, BOhostname = self:BOcrypt(response,seed):match("!PONG!(1%.20)!(.*)!") + self:insert_version_info(BOversion,BOhostname,nil,password) + return true + else + if not(status) then + return false, response + else + return false,"Response not recognized." + end + end + end, + + --- Close the socket + -- + -- @return status true on success, false on failure + close = function(self) + return self.socket:close() + end, + + --- Generates the initial encryption seed from a password + -- + -- @param password string containing password + -- @return seed number containing initial seed + gen_initial_seed = function(self, password) + if password == nil then + return 31337 + else + local y = #password + local z = 0 + + for x = 1,y do + local pchar = string.byte(password,x) + z = z + pchar + end + + for x=1,y do + local pchar = string.byte(password,x) + if (x-1)%2 == 1 then + z = z - (pchar * (y-(x-1)+1)) + else + z = z + (pchar * (y-(x-1)+1)) + end + z = z % 0x7fffffff + end + z = (z*y) % 0x7fffffff + return z + end + end, + + --- Generates next encryption seed from given seed + -- + -- @param seed number containing current seed + -- @return seed number containing next seed + gen_next_seed = function(self, seed) + seed = seed*214013 + 2531011 + seed = seed & 0xffffff + return seed + end, + + --- Encrypts/decrypts data using BackOrifice algorithm + -- + -- @param data binary string containing data to be encrypted/decrypted + -- @param initial_seed number containing initial encryption seed + -- @return data binary string containing encrypted/decrypted data + BOcrypt = function(self, data, initial_seed ) + if data==nil then return end + local output = {} + + local seed = initial_seed + + for i = 1, #data do + local data_byte = string.byte(data,i) + + --calculate next seed + seed = self:gen_next_seed(seed) + --calculate encryption key based on seed + local key = bits.arshift(seed,16) & 0xff + + local crypto_byte = data_byte ~ key + output[i] = string.char(crypto_byte) + if i == 256 then break end --ARGSIZE limitation + end + return table.concat(output, "") + end, + + insert_version_info = function(self,BOversion,BOhostname,initial_seed,password) + if not self.port.version then self.port.version={} end + if not self.port.version.name then + self.port.version.name ="BackOrifice" + self.port.version.name_confidence = 10 + end + if not self.port.version.product then self.port.version.product ="BackOrifice trojan" end + if not self.port.version.version then self.port.version.version = BOversion end + if not self.port.version.extrainfo then + if not password then + if not initial_seed then + self.port.version.extrainfo = "no password" + else + self.port.version.extrainfo = "initial encryption seed="..initial_seed + end + else + self.port.version.extrainfo = "password="..password + end + end + self.port.version.hostname = BOhostname + if not self.port.version.ostype then self.port.version.ostype = "Windows" end + nmap.set_port_version(self.host, self.port) + nmap.set_port_state(self.host,self.port,"open") + end +} + +local Driver = +{ + new = function(self, host, port) + local o = {} + setmetatable(o, self) + self.__index = self + o.host = host + o.port = port + return o + end, + + connect=function(self) + --only initialize since BackOrifice service knows no connect() + self.bo = backorifice:new(self.host,self.port) + self.bo:initialize() + return true + end, + + disconnect = function( self ) + self.bo:close() + end, + + --- Attempts to send encrypted PING packet to BackOrifice service + -- + -- @param username string containing username which is disregarded + -- @param password string containing login password + -- @return brute.Error object on failure + -- creds.Account object on success + login = function( self, username, password ) + local status, msg = self.bo:try_password(password,nil) + if status then + if not(nmap.registry['credentials']) then + nmap.registry['credentials']={} + end + if ( not( nmap.registry.credentials['backorifice'] ) ) then + nmap.registry.credentials['backorifice'] = {} + end + table.insert( nmap.registry.credentials.backorifice, { password = password } ) + return true, creds.Account:new("", password, creds.State.VALID) + else + -- The only indication that the password is incorrect is a timeout + local err = brute.Error:new( "Incorrect password" ) + err:setRetry(false) + return false, err + end + end, + +} + +action = function( host, port ) + + local status, result + local engine = brute.Engine:new(Driver,host,port) + + engine.options.firstonly = true + engine.options.passonly = true + engine.options.script_name = SCRIPT_NAME + + status, result = engine:start() + + return result +end |