Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/lib/rex/proto/ntlm/message.rb
Views: 11704
# -*- coding: binary -*-1#2# An NTLM Authentication Library for Ruby3#4# This code is a derivative of "dbf2.rb" written by yrock5# and Minero Aoki. You can find original code here:6# http://jp.rubyist.net/magazine/?0013-CodeReview7# -------------------------------------------------------------8# Copyright (c) 2005,2006 yrock9#10# This program is free software.11# You can distribute/modify this program under the terms of the12# Ruby License.13#14# 2011-02-23 refactored by Alexandre Maloteaux for Metasploit Project15# -------------------------------------------------------------16#17# 2006-02-11 refactored by Minero Aoki18# -------------------------------------------------------------19#20# All protocol information used to write this code stems from21# "The NTLM Authentication Protocol" by Eric Glass. The author22# would thank to him for this tremendous work and making it23# available on the net.24# http://davenport.sourceforge.net/ntlm.html25# -------------------------------------------------------------26# Copyright (c) 2003 Eric Glass27#28# Permission to use, copy, modify, and distribute this document29# for any purpose and without any fee is hereby granted,30# provided that the above copyright notice and this list of31# conditions appear in all copies.32# -------------------------------------------------------------33#34# The author also looked Mozilla-Firefox-1.0.7 source code,35# namely, security/manager/ssl/src/nsNTLMAuthModule.cpp and36# Jonathan Bastien-Filiatrault's libntlm-ruby.37# "http://x2a.org/websvn/filedetails.php?38# repname=libntlm-ruby&path=%2Ftrunk%2Fntlm.rb&sc=1"39# The latter has a minor bug in its separate_keys function.40# The third key has to begin from the 14th character of the41# input string instead of 13th:)4243#this module defines the message class , useful for easily handling type 1/2/3 ntlm messages44454647module Rex48module Proto49module NTLM50class Message < Rex::Proto::NTLM::Base::FieldSet5152BASE = Rex::Proto::NTLM::Base53CONST = Rex::Proto::NTLM::Constants54CRYPT = Rex::Proto::NTLM::Crypt555657class << Message58def parse(str)59m = Type0.new60m.parse(str)61case m.type62when 163t = Type1.parse(str)64when 265t = Type2.parse(str)66when 367t = Type3.parse(str)68else69raise ArgumentError, "unknown type: #{m.type}"70end71t72end7374def decode64(str)75parse(Rex::Text::decode_base64(str))76end77end#self7879def has_flag?(flag)80(self[:flag].value & CONST::FLAGS[flag]) == CONST::FLAGS[flag]81end8283def set_flag(flag)84self[:flag].value |= CONST::FLAGS[flag]85end8687def dump_flags88CONST::FLAG_KEYS.each{ |k| print(k, "=", flag?(k), "\n") }89end9091def serialize92deflag93super + security_buffers.map{|n, f| f.value}.join94end9596def encode6497Rex::Text::encode_base64(serialize)98end99100def decode64(str)101parse(Rex::Text::decode_base64(str))102end103104alias head_size size105106def data_size107security_buffers.inject(0){|sum, a| sum += a[1].data_size}108end109110def size111head_size + data_size112end113114private115116def security_buffers117@alist.find_all{|n, f| f.instance_of?(BASE::SecurityBuffer)}118end119120def deflag121security_buffers.inject(head_size){|cur, a|122a[1].offset = cur123cur += a[1].data_size124}125end126127def data_edge128security_buffers.map{ |n, f| f.active ? f.offset : size}.min129end130131# sub class definitions132133Type0 = Message.define {134string :sign, {:size => 8, :value => CONST::SSP_SIGN}135int32LE :type, {:value => 0}136}137138Type1 = Message.define {139string :sign, {:size => 8, :value => CONST::SSP_SIGN}140int32LE :type, {:value => 1}141int32LE :flag, {:value => CONST::DEFAULT_FLAGS[:TYPE1] }142security_buffer :domain, {:value => "", :active => false}143security_buffer :workstation, {:value => "", :active => false}144string :padding, {:size => 0, :value => "", :active => false }145}146147class Type1148class << Type1149def parse(str)150t = new151t.parse(str)152t153end154end155156def parse(str)157super(str)158enable(:domain) if has_flag?(:DOMAIN_SUPPLIED)159enable(:workstation) if has_flag?(:WORKSTATION_SUPPLIED)160super(str)161if ( (len = data_edge - head_size) > 0)162self.padding = "\0" * len163super(str)164end165end166end167168Type2 = Message.define{169string :sign, {:size => 8, :value => CONST::SSP_SIGN}170int32LE :type, {:value => 2}171security_buffer :target_name, {:size => 0, :value => ""}172int32LE :flag, {:value => CONST::DEFAULT_FLAGS[:TYPE2]}173int64LE :challenge, {:value => 0}174int64LE :context, {:value => 0, :active => false}175security_buffer :target_info, {:value => "", :active => false}176string :padding, {:size => 0, :value => "", :active => false }177}178179class Type2180class << Type2181def parse(str)182t = new183t.parse(str)184t185end186end187188def parse(str)189super(str)190if has_flag?(:TARGET_INFO)191enable(:context)192enable(:target_info)193super(str)194end195if ( (len = data_edge - head_size) > 0)196self.padding = "\0" * len197super(str)198end199end200#create a type 3 response base on a type2201# This method is not compatible with windows 7 / 2008 r2202# to make it compatible avpair Time and SPN must be handle as in utils203def response(arg, opt = {})204usr = arg[:user]205pwd = arg[:password]206if usr.nil? or pwd.nil?207raise ArgumentError, "user and password have to be supplied"208end209210if opt[:workstation]211ws = opt[:workstation]212else213ws = ""214end215216if opt[:client_challenge]217cc = opt[:client_challenge]218else219cc = rand(CONST::MAX64)220end221cc = Rex::Text::pack_int64le(cc) if cc.is_a?(Integer)222opt[:client_challenge] = cc223224if has_flag?(:OEM) and opt[:unicode]225usr = Rex::Text::to_ascii(usr,'utf-16le')226pwd = Rex::Text::to_ascii(pwd,'utf-16le')227ws = Rex::Text::to_ascii(ws,'utf-16le')228opt[:unicode] = false229end230231if has_flag?(:UNICODE) and !opt[:unicode]232usr = Rex::Text::to_unicode(usr,'utf-16le')233pwd = Rex::Text::to_unicode(pwd,'utf-16le')234ws = Rex::Text::to_unicode(ws,'utf-16le')235opt[:unicode] = true236end237238tgt = self.target_name239ti = self.target_info240241chal = self[:challenge].serialize242243if opt[:ntlmv2]244ar = { :ntlmv2_hash => CRYPT::ntlmv2_hash(usr, pwd, tgt, opt),245:challenge => chal, :target_info => ti}246lm_res = CRYPT::lmv2_response(ar, opt)247ntlm_res = CRYPT::ntlmv2_response(ar, opt)248elsif has_flag?(:NTLM2_KEY)249ar = {:ntlm_hash => CRYPT::ntlm_hash(pwd, opt), :challenge => chal}250lm_res, ntlm_res = CRYPT::ntlm2_session(ar, opt)251else252lm_res = CRYPT::lm_response(pwd, chal)253ntlm_res = CRYPT::ntlm_response(pwd, chal)254end255256Type3.create({257:lm_response => lm_res,258:ntlm_response => ntlm_res,259:domain => tgt,260:user => usr,261:workstation => ws,262:flag => self.flag263})264end265end266267268Type3 = Message.define{269string :sign, {:size => 8, :value => CONST::SSP_SIGN}270int32LE :type, {:value => 3}271security_buffer :lm_response, {:value => ""}272security_buffer :ntlm_response, {:value => ""}273security_buffer :domain, {:value => ""}274security_buffer :user, {:value => ""}275security_buffer :workstation, {:value => ""}276security_buffer :session_key, {:value => "", :active => false }277int64LE :flag, {:value => 0, :active => false }278}279280class Type3281class << Type3282def parse(str)283t = new284t.parse(str)285t286end287288def create(arg, opt ={})289t = new290t.lm_response = arg[:lm_response]291t.ntlm_response = arg[:ntlm_response]292t.domain = arg[:domain]293t.user = arg[:user]294t.workstation = arg[:workstation]295296if arg[:session_key]297t.enable(:session_key)298t.session_key = arg[session_key]299end300if arg[:flag]301t.enable(:session_key)302t.enable(:flag)303t.flag = arg[:flag]304end305t306end307end#self308end309310public311#those class method have been merged from lib/rex/smb/utils312313#314# Process Type 3 NTLM Message (in Base64)315#316# from http://www.innovation.ch/personal/ronald/ntlm.html317#318# struct {319# byte protocol[8]; // 'N', 'T', 'L', 'M', 'S', 'S', 'P', '\0'320# byte type; // 0x03321# byte zero[3];322#323# short lm_resp_len; // LanManager response length (always 0x18)324# short lm_resp_len; // LanManager response length (always 0x18)325# short lm_resp_off; // LanManager response offset326# byte zero[2];327#328# short nt_resp_len; // NT response length (always 0x18)329# short nt_resp_len; // NT response length (always 0x18)330# short nt_resp_off; // NT response offset331# byte zero[2];332#333# short dom_len; // domain string length334# short dom_len; // domain string length335# short dom_off; // domain string offset (always 0x40)336# byte zero[2];337#338# short user_len; // username string length339# short user_len; // username string length340# short user_off; // username string offset341# byte zero[2];342#343# short host_len; // host string length344# short host_len; // host string length345# short host_off; // host string offset346# byte zero[6];347#348# short msg_len; // message length349# byte zero[2];350#351# short flags; // 0x8201352# byte zero[2];353#354# byte dom[*]; // domain string (unicode UTF-16LE)355# byte user[*]; // username string (unicode UTF-16LE)356# byte host[*]; // host string (unicode UTF-16LE)357# byte lm_resp[*]; // LanManager response358# byte nt_resp[*]; // NT response359# } type_3_message360#361def self.process_type3_message(message)362decode = Rex::Text.decode_base64(message.strip)363type = decode[8,1].unpack("C").first364if (type == 3)365lm_len = decode[12,2].unpack("v").first366lm_offset = decode[16,2].unpack("v").first367lm = decode[lm_offset, lm_len].unpack("H*").first368369nt_len = decode[20,2].unpack("v").first370nt_offset = decode[24,2].unpack("v").first371nt = decode[nt_offset, nt_len].unpack("H*").first372373dom_len = decode[28,2].unpack("v").first374dom_offset = decode[32,2].unpack("v").first375domain = decode[dom_offset, dom_len]376377user_len = decode[36,2].unpack("v").first378user_offset = decode[40,2].unpack("v").first379user = decode[user_offset, user_len]380381host_len = decode[44,2].unpack("v").first382host_offset = decode[48,2].unpack("v").first383host = decode[host_offset, host_len]384385return domain, user, host, lm, nt386else387return "", "", "", "", ""388end389end390391392393#394# Process Type 1 NTLM Messages, return a Base64 Type 2 Message395#396def self.process_type1_message(message, nonce = "\x11\x22\x33\x44\x55\x66\x77\x88", win_domain = 'DOMAIN',397win_name = 'SERVER', dns_name = 'server', dns_domain = 'example.com', downgrade = true)398399dns_name = Rex::Text.to_unicode(dns_name + "." + dns_domain)400win_domain = Rex::Text.to_unicode(win_domain)401dns_domain = Rex::Text.to_unicode(dns_domain)402win_name = Rex::Text.to_unicode(win_name)403decode = Rex::Text.decode_base64(message.strip)404405type = decode[8,1].unpack("C").first406407if (type == 1)408# A type 1 message has been received, lets build a type 2 message response409410reqflags = decode[12,4]411reqflags = reqflags.unpack("V").first412413if (reqflags & CONST::REQUEST_TARGET) == CONST::REQUEST_TARGET414415if (downgrade)416# At this time NTLMv2 and signing requirements are not supported417if (reqflags & CONST::NEGOTIATE_NTLM2_KEY) == CONST::NEGOTIATE_NTLM2_KEY418reqflags = reqflags - CONST::NEGOTIATE_NTLM2_KEY419end420if (reqflags & CONST::NEGOTIATE_ALWAYS_SIGN) == CONST::NEGOTIATE_ALWAYS_SIGN421reqflags = reqflags - CONST::NEGOTIATE_ALWAYS_SIGN422end423end424425flags = reqflags + CONST::TARGET_TYPE_DOMAIN + CONST::TARGET_TYPE_SERVER426tid = true427428tidoffset = 48 + win_domain.length429tidbuff =430[2].pack('v') + # tid type, win domain431[win_domain.length].pack('v') +432win_domain +433[1].pack('v') + # tid type, server name434[win_name.length].pack('v') +435win_name +436[4].pack('v') + # tid type, domain name437[dns_domain.length].pack('v') +438dns_domain +439[3].pack('v') + # tid type, dns_name440[dns_name.length].pack('v') +441dns_name442else443flags = CONST::NEGOTIATE_UNICODE + CONST::NEGOTIATE_NTLM444tid = false445end446447type2msg = "NTLMSSP\0" + # protocol, 8 bytes448"\x02\x00\x00\x00" # type, 4 bytes449450if (tid)451type2msg += # Target security info, 8 bytes. Filled if REQUEST_TARGET452[win_domain.length].pack('v') + # Length, 2 bytes453[win_domain.length].pack('v') # Allocated space, 2 bytes454end455456type2msg +="\x30\x00\x00\x00" + # Offset, 4 bytes457[flags].pack('V') + # flags, 4 bytes458nonce + # the nonce, 8 bytes459"\x00" * 8 # Context (all 0s), 8 bytes460461if (tid)462type2msg += # Target information security buffer. Filled if REQUEST_TARGET463[tidbuff.length].pack('v') + # Length, 2 bytes464[tidbuff.length].pack('v') + # Allocated space, 2 bytes465[tidoffset].pack('V') + # Offset, 4 bytes (usually \x48 + length of win_domain)466win_domain + # Target name data (domain in unicode if REQUEST_UNICODE)467# Target information data468tidbuff + # Type, 2 bytes469# Length, 2 bytes470# Data (in unicode if REQUEST_UNICODE)471"\x00\x00\x00\x00" # Terminator, 4 bytes, all \x00472end473474type2msg = Rex::Text.encode_base64(type2msg).delete("\n") # base64 encode and remove the returns475else476# This is not a Type2 message477type2msg = ""478end479480return type2msg481end482483#484# Downgrading Type messages to LMv1/NTLMv1 and removing signing485#486def self.downgrade_type_message(message)487decode = Rex::Text.decode_base64(message.strip)488489type = decode[8,1].unpack("C").first490491if (type > 0 and type < 4)492reqflags = decode[12..15] if (type == 1 or type == 3)493reqflags = decode[20..23] if (type == 2)494reqflags = reqflags.unpack("V")495496# Remove NEGOTIATE_NTLMV2_KEY and NEGOTIATE_ALWAYS_SIGN, this lowers the negotiation497# down to LMv1/NTLMv1.498if (reqflags & CONST::NEGOTIATE_NTLM2_KEY) == CONST::NEGOTIATE_NTLM2_KEY499reqflags = reqflags - CONST::NEGOTIATE_NTLM2_KEY500end501if (reqflags & CONST::NEGOTIATE_ALWAYS_SIGN) == CONST::NEGOTIATE_ALWAYS_SIGN502reqflags = reqflags - CONST::NEGOTIATE_ALWAYS_SIGN503end504505# Return the flags back to the decode so we can base64 it again506flags = reqflags.to_s(16)5070.upto(8) do |idx|508if (idx > flags.length)509flags.insert(0, "0")510end511end512513idx = 05140.upto(3) do |cnt|515if (type == 2)516decode[23-cnt] = [flags[idx,1]].pack("C")517else518decode[15-cnt] = [flags[idx,1]].pack("C")519end520idx += 2521end522523end524return Rex::Text.encode_base64(decode).delete("\n") # base64 encode and remove the returns525end526527end528end529end530end531532533