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/crypt.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-03-08 improved through a code merge with Metasploit's SMB::Crypt15# -------------------------------------------------------------16#17# 2011-02-23 refactored and improved by Alexandre Maloteaux for Metasploit Project18# -------------------------------------------------------------19#20# 2006-02-11 refactored by Minero Aoki21# -------------------------------------------------------------22#23# All protocol information used to write this code stems from24# "The NTLM Authentication Protocol" by Eric Glass. The author25# would thank to him for this tremendous work and making it26# available on the net.27# http://davenport.sourceforge.net/ntlm.html28# -------------------------------------------------------------29# Copyright (c) 2003 Eric Glass30#31# Permission to use, copy, modify, and distribute this document32# for any purpose and without any fee is hereby granted,33# provided that the above copyright notice and this list of34# conditions appear in all copies.35# -------------------------------------------------------------36#37# The author also looked Mozilla-Firefox-1.0.7 source code,38# namely, security/manager/ssl/src/nsNTLMAuthModule.cpp and39# Jonathan Bastien-Filiatrault's libntlm-ruby.40# "http://x2a.org/websvn/filedetails.php?41# repname=libntlm-ruby&path=%2Ftrunk%2Fntlm.rb&sc=1"42# The latter has a minor bug in its separate_keys function.43# The third key has to begin from the 14th character of the44# input string instead of 13th:)454647module Rex48module Proto49module NTLM50class Crypt5152CONST = Rex::Proto::NTLM::Constants53BASE = Rex::Proto::NTLM::Base5455@@loaded_openssl = false5657begin58require 'openssl'59require 'openssl/digest'60@@loaded_openssl = true61rescue ::Exception62end6364def self.gen_keys(str)65str.scan(/.{7}/).map{ |key| des_56_to_64(key) }66end6768def self.des_56_to_64(ckey56s)69ckey64 = []70ckey56 = ckey56s.unpack('C*')71ckey64[0] = ckey56[0]72ckey64[1] = ((ckey56[0] << 7) & 0xFF) | (ckey56[1] >> 1)73ckey64[2] = ((ckey56[1] << 6) & 0xFF) | (ckey56[2] >> 2)74ckey64[3] = ((ckey56[2] << 5) & 0xFF) | (ckey56[3] >> 3)75ckey64[4] = ((ckey56[3] << 4) & 0xFF) | (ckey56[4] >> 4)76ckey64[5] = ((ckey56[4] << 3) & 0xFF) | (ckey56[5] >> 5)77ckey64[6] = ((ckey56[5] << 2) & 0xFF) | (ckey56[6] >> 6)78ckey64[7] = (ckey56[6] << 1) & 0xFF79ckey64.pack('C*')80end8182def self.apply_des(plain, keys)83raise RuntimeError, "No OpenSSL support" if not @@loaded_openssl84dec = OpenSSL::Cipher.new('DES')85keys.map do |k|86dec.encrypt87dec.key = k88dec.update(plain) + dec.final89end90end9192def self.lm_hash(password, half = false)93size = half ? 7 : 1494keys = gen_keys(password.upcase.ljust(size, "\0"))95apply_des(CONST::LM_MAGIC, keys).join96end9798def self.ntlm_hash(password, opt = {})99raise RuntimeError, "No OpenSSL support" if not @@loaded_openssl100pwd = password.dup101unless opt[:unicode]102pwd = Rex::Text.to_unicode(pwd)103end104OpenSSL::Digest::MD4.digest(pwd)105end106107# This hash is used for lmv2/ntlmv2 response calculation108def self.ntlmv2_hash(user, password, domain, opt={})109raise RuntimeError, "No OpenSSL support" if not @@loaded_openssl110111if opt[:pass_is_hash]112ntlmhash = password113else114ntlmhash = ntlm_hash(password, opt)115end116# With Win 7 and maybe other OSs we sometimes get the domain not uppercased117userdomain = user.upcase + domain118unless opt[:unicode]119userdomain = Rex::Text.to_unicode(userdomain)120end121OpenSSL::HMAC.digest(OpenSSL::Digest.new('MD5'), ntlmhash, userdomain)122end123124# Create the LANMAN response125def self.lm_response(arg, half = false)126begin127hash = arg[:lm_hash]128chal = arg[:challenge]129rescue130raise ArgumentError131end132chal = BASE::pack_int64le(chal) if chal.is_a?(Integer)133if half then size = 7 else size = 21 end134keys = gen_keys hash.ljust(size, "\0")135apply_des(chal, keys).join136end137138# Synonym of lm_response for old compatibility with lib/rex/proto/smb/crypt139def self.lanman_des(password, challenge)140lm_response({141:lm_hash => self.lm_hash(password),142:challenge => challenge143})144end145146def self.ntlm_response(arg)147hash = arg[:ntlm_hash]148chal = arg[:challenge]149chal = BASE::pack_int64le(chal) if chal.is_a?(::Integer)150keys = gen_keys(hash.ljust(21, "\0"))151apply_des(chal, keys).join152end153154#synonym of ntlm_response for old compatibility with lib/rex/proto/smb/crypt155def self.ntlm_md4(password, challenge)156ntlm_response({157:ntlm_hash => self.ntlm_hash(password),158:challenge => challenge159})160end161162def self.ntlmv2_response(arg, opt = {})163raise RuntimeError, "No OpenSSL support" if not @@loaded_openssl164165key, chal = arg[:ntlmv2_hash], arg[:challenge]166if not (key and chal)167raise ArgumentError , 'ntlmv2_hash and challenge are mandatory'168end169170chal = BASE::pack_int64le(chal) if chal.is_a?(::Integer)171bb = nil172173if opt[:nt_client_challenge]174if opt[:nt_client_challenge].to_s.length <= 8175raise ArgumentError,"nt_client_challenge is not in a correct format "176end177bb = opt[:nt_client_challenge]178else179if not arg[:target_info]180raise ArgumentError, "target_info is mandatory in this case"181end182183ti = arg[:target_info]184cc = opt[:client_challenge] || rand(CONST::MAX64)185cc = BASE::pack_int64le(cc) if cc.is_a?(::Integer)186187ts = opt[:timestamp] || ::Time.now.to_i188189# Convert the unix timestamp to windows format190# epoch -> milsec from Jan 1, 1601191ts = 10000000 * (ts + CONST::TIME_OFFSET)192193blob = BASE::Blob.new194blob.timestamp = ts195blob.challenge = cc196blob.target_info = ti197198bb = blob.serialize199end200201OpenSSL::HMAC.digest(OpenSSL::Digest.new('MD5'), key, chal + bb) + bb202end203204def self.lmv2_response(arg, opt = {})205raise RuntimeError, "No OpenSSL support" if not @@loaded_openssl206key = arg[:ntlmv2_hash]207chal = arg[:challenge]208209chal = BASE::pack_int64le(chal) if chal.is_a?(::Integer)210cc = opt[:client_challenge] || rand(CONST::MAX64)211cc = BASE::pack_int64le(cc) if cc.is_a?(::Integer)212213OpenSSL::HMAC.digest(OpenSSL::Digest.new('MD5'), key, chal + cc) + cc214end215216def self.ntlm2_session(arg, opt = {})217raise RuntimeError, "No OpenSSL support" if not @@loaded_openssl218passwd_hash,chal = arg[:ntlm_hash],arg[:challenge]219if not (passwd_hash and chal)220raise RuntimeError, "ntlm_hash and challenge are required"221end222223cc = opt[:client_challenge] || rand(CONST::MAX64)224cc = BASE::pack_int64le(cc) if cc.is_a?(Integer)225226keys = gen_keys(passwd_hash.ljust(21, "\0"))227session_hash = OpenSSL::Digest::MD5.digest(chal + cc)[0,8]228response = apply_des(session_hash, keys).join229[cc.ljust(24, "\0"), response]230end231232#this function will check if the net lm response provided correspond to en empty password233def self.is_hash_from_empty_pwd?(arg)234hash_type = arg[:type]235raise ArgumentError,"arg[:type] is mandatory" if not hash_type236raise ArgumentError,"arg[:type] must be lm or ntlm" if not hash_type =~ /^((lm)|(ntlm))$/237238ntlm_ver = arg[:ntlm_ver]239raise ArgumentError,"arg[:ntlm_ver] is mandatory" if not ntlm_ver240241hash = arg[:hash]242raise ArgumentError,"arg[:hash] is mandatory" if not hash243244srv_chall = arg[:srv_challenge]245raise ArgumentError,"arg[:srv_challenge] is mandatory" if not srv_chall246raise ArgumentError,"Server challenge length must be exactly 8 bytes" if srv_chall.length != 8247248#calculate responses for empty pwd249case ntlm_ver250when CONST::NTLM_V1_RESPONSE251if hash.length != 24252raise ArgumentError,"hash length must be exactly 24 bytes "253end254case hash_type255when 'lm'256arglm = { :lm_hash => self.lm_hash(''),257:challenge => srv_chall}258calculatedhash = self.lm_response(arglm)259when 'ntlm'260argntlm = { :ntlm_hash => self.ntlm_hash(''),261:challenge => srv_chall }262calculatedhash = self.ntlm_response(argntlm)263end264when CONST::NTLM_V2_RESPONSE265raise ArgumentError,"hash length must be exactly 16 bytes " if hash.length != 16266cli_chall = arg[:cli_challenge]267raise ArgumentError,"arg[:cli_challenge] is mandatory in this case" if not cli_chall268user = arg[:user]269raise ArgumentError,"arg[:user] is mandatory in this case" if not user270domain = arg[:domain]271raise ArgumentError,"arg[:domain] is mandatory in this case" if not domain272273case hash_type274when 'lm'275raise ArgumentError,"Client challenge length must be exactly 8 bytes " if cli_chall.length != 8276arglm = { :ntlmv2_hash => self.ntlmv2_hash(user,'', domain),277:challenge => srv_chall }278optlm = { :client_challenge => cli_chall}279calculatedhash = self.lmv2_response(arglm, optlm)[0,16]280when 'ntlm'281raise ArgumentError,"Client challenge length must be bigger then 8 bytes " if cli_chall.length <= 8282argntlm = { :ntlmv2_hash => self.ntlmv2_hash(user, '', domain),283:challenge => srv_chall }284optntlm = { :nt_client_challenge => cli_chall}285calculatedhash = self.ntlmv2_response(argntlm,optntlm)[0,16]286end287when CONST::NTLM_2_SESSION_RESPONSE288raise ArgumentError,"hash length must be exactly 16 bytes " if hash.length != 24289cli_chall = arg[:cli_challenge]290raise ArgumentError,"arg[:cli_challenge] is mandatory in this case" if not cli_chall291raise ArgumentError,"Client challenge length must be exactly 8 bytes " if cli_chall.length != 8292case hash_type293when 'lm'294raise ArgumentError, "ntlm2_session is incompatible with lm"295when 'ntlm'296argntlm = { :ntlm_hash => self.ntlm_hash(''),297:challenge => srv_chall }298optntlm = { :client_challenge => cli_chall}299end300calculatedhash = self.ntlm2_session(argntlm,optntlm).join[24,24]301else302raise ArgumentError,"ntlm_ver is of unknown type"303end304hash == calculatedhash305end306307308309#310# Signing method added for metasploit project311#312313# Used when only the LMv1 response is provided (i.e., with Win9x clients)314def self.lmv1_user_session_key(pass, opt = {})315if opt[:pass_is_hash]316usk = pass[0,8]317else318usk = self.lm_hash(pass.upcase[0,7],true)319end320usk.ljust(16,"\x00")321end322323# This variant is used when the client sends the NTLMv1 response324def self.ntlmv1_user_session_key(pass, opt = {})325raise RuntimeError, "No OpenSSL support" if not @@loaded_openssl326327if opt[:pass_is_hash]328usk = pass329else330usk = self.ntlm_hash(pass)331end332OpenSSL::Digest::MD4.digest(usk)333end334335# Used when NTLMv1 authentication is employed with NTLM2 session security336def self.ntlm2_session_user_session_key(pass, srv_chall, cli_chall, opt = {})337raise RuntimeError, "No OpenSSL support" if not @@loaded_openssl338339ntlm_key = self.ntlmv1_user_session_key(pass, opt )340session_chal = srv_chall + cli_chall341OpenSSL::HMAC.digest(OpenSSL::Digest.new('MD5'), ntlm_key, session_chal)342end343344# Used when the LMv2 response is sent345def self.lmv2_user_session_key(user, pass, domain, srv_chall, cli_chall, opt = {})346raise RuntimeError, "No OpenSSL support" if not @@loaded_openssl347348ntlmv2_key = self.ntlmv2_hash(user, pass, domain, opt)349hash1 = OpenSSL::HMAC.digest(OpenSSL::Digest.new('MD5'), ntlmv2_key, srv_chall + cli_chall)350OpenSSL::HMAC.digest(OpenSSL::Digest.new('MD5'), ntlmv2_key, hash1)351end352353# Used when the NTLMv2 response is sent354class << self; alias_method :ntlmv2_user_session_key, :lmv2_user_session_key; end355356# Used when LanMan Key flag is set357def self.lanman_session_key(pass, srvchall, opt = {})358if opt[:pass_is_hash]359halfhash = pass[0,8]360else361halfhash = lm_hash(pass.upcase[0,7],true)362end363plain = self.lm_response({364:lm_hash => halfhash[0,7],365:challenge => srvchall366}, true )367key = halfhash + ["bdbdbdbdbdbd"].pack("H*")368keys = self.gen_keys(key)369apply_des(plain, keys).join370end371372def self.encrypt_sessionkey(session_key, user_session_key)373raise RuntimeError, "No OpenSSL support" if not @@loaded_openssl374cipher = OpenSSL::Cipher.new('rc4')375cipher.encrypt376cipher.key = user_session_key377cipher.update(session_key)378end379380def self.decrypt_sessionkey(encrypted_session_key, user_session_key)381raise RuntimeError, "No OpenSSL support" if not @@loaded_openssl382cipher = OpenSSL::Cipher.new('rc4')383cipher.decrypt384cipher.key = user_session_key385cipher.update(encrypted_session_key)386end387388def self.make_weak_sessionkey(session_key,key_size,lanman_key = false)389case key_size390when 40391if lanman_key392return session_key[0,5] + "\xe5\x38\xb0"393else394return session_key[0,5]395end396when 56397if lanman_key398return session_key[0,7] + "\xa0"399else400return session_key[0,7]401end402else #128403return session_key[0,16]404end405end406407end408end409end410end411412413