packetgen-plugin-smb 0.3.0 → 0.6.2
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 +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
data/lib/packetgen-plugin-smb.rb
CHANGED
@@ -1,12 +1,15 @@
|
|
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 'packetgen'
|
9
9
|
require_relative 'packetgen/plugin/smb_version'
|
10
10
|
require_relative 'packetgen/plugin/gssapi'
|
11
|
+
require_relative 'packetgen/plugin/netbios'
|
11
12
|
require_relative 'packetgen/plugin/smb'
|
12
13
|
require_relative 'packetgen/plugin/smb2'
|
14
|
+
require_relative 'packetgen/plugin/llmnr'
|
15
|
+
require_relative 'packetgen/plugin/ntlm'
|
@@ -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
|
@@ -75,7 +75,7 @@ module PacketGen::Plugin
|
|
75
75
|
content: [sequence_of(:mech_types, RASN1::Types::ObjectId, explicit: 0, class: :context),
|
76
76
|
bit_string(:req_flags, explicit: 1, class: :context, constructed: true, optional: true),
|
77
77
|
octet_string(:mech_token, explicit: 2, class: :context, constructed: true, optional: true),
|
78
|
-
|
78
|
+
any(:mech_list_mic, explicit: 3, class: :context, constructed: true, optional: true)]
|
79
79
|
end
|
80
80
|
|
81
81
|
# GSS API Negotiation Token Response
|
@@ -86,7 +86,7 @@ module PacketGen::Plugin
|
|
86
86
|
'accept-incomplete' => 1,
|
87
87
|
'reject' => 2,
|
88
88
|
'request-mic' => 3
|
89
|
-
}
|
89
|
+
}.freeze
|
90
90
|
sequence :token, explicit: 1, class: :context, constructed: true,
|
91
91
|
content: [enumerated(:negstate, enum: NEG_STATES, explicit: 0, class: :context, constructed: true, optional: true),
|
92
92
|
objectid(:supported_mech, explicit: 1, class: :context, constructed: true, optional: true),
|
@@ -101,17 +101,20 @@ module PacketGen::Plugin
|
|
101
101
|
model(:token_resp, NegTokenResp)]
|
102
102
|
|
103
103
|
# @param [Hash] args
|
104
|
-
# @
|
104
|
+
# @option args [Symbol] :token +:init+ or +:response+ to force selection of
|
105
105
|
# token CHOICE.
|
106
106
|
def initialize(args={})
|
107
107
|
token = args.delete(:token)
|
108
108
|
super
|
109
|
-
self[:gssapi].chosen =
|
109
|
+
self[:gssapi].chosen = token == :init ? 0 : 1
|
110
110
|
end
|
111
|
+
|
111
112
|
# Populate Object from +str+
|
112
113
|
# @param [String] str
|
113
114
|
# @return [self]
|
114
115
|
def read(str)
|
116
|
+
return self if str.nil?
|
117
|
+
|
115
118
|
parse!(str, ber: true)
|
116
119
|
self
|
117
120
|
end
|
@@ -121,5 +124,7 @@ module PacketGen::Plugin
|
|
121
124
|
def sz
|
122
125
|
to_der.size
|
123
126
|
end
|
127
|
+
|
128
|
+
alias to_s to_der
|
124
129
|
end
|
125
130
|
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# This file is part of packetgen-plugin-smb.
|
4
|
+
# See https://github.com/sdaubert/packetgen-plugin-smb for more informations
|
5
|
+
# Copyright (C) 2018 Sylvain Daubert <sylvain.daubert@laposte.net>
|
6
|
+
# This program is published under MIT license.
|
7
|
+
|
8
|
+
module PacketGen::Plugin
|
9
|
+
# Link-Local Multicast Name Resolution (LLMNR) header ({https://tools.ietf.org/html/rfc4795 RFC 4795}).
|
10
|
+
# @author Sylvain Daubert
|
11
|
+
class LLMNR < PacketGen::Header::DNS
|
12
|
+
# UDP port number
|
13
|
+
UDP_PORT = 5355
|
14
|
+
# MAC address used with IPv4 multicast addresses
|
15
|
+
MAC_IPV4_MCAST = '01:00:5e:00:00:fc'
|
16
|
+
|
17
|
+
# @api private
|
18
|
+
# @note This method is used internally by PacketGen and should not be
|
19
|
+
# directly called
|
20
|
+
def added_to_packet(packet)
|
21
|
+
packet.instance_eval <<-END_OF_DEFINITION
|
22
|
+
def llmnrize(**kwargs)
|
23
|
+
llmnr = headers.find { |hdr| hdr.is_a? PacketGen::Plugin::LLMNR }
|
24
|
+
llmnr.llmnrize(**kwargs)
|
25
|
+
end
|
26
|
+
END_OF_DEFINITION
|
27
|
+
end
|
28
|
+
|
29
|
+
# Fixup IP header according to RFC 4795:
|
30
|
+
# * optionally set destination address,
|
31
|
+
# * set TTL to 1 if destination is a mcast address,
|
32
|
+
# * set MAC destination address to {MAC_IPV4_MCAST} if destination address is a mcast one.
|
33
|
+
# This method may be called as:
|
34
|
+
# # first way
|
35
|
+
# pkt.llmnr.llmnrize
|
36
|
+
# # second way
|
37
|
+
# pkt.llmnrize
|
38
|
+
# @param [String,nil] dst destination address. May be a dotted IP
|
39
|
+
# address (by example '224.0.0.252').
|
40
|
+
# @return [void]
|
41
|
+
def llmnrize(dst: nil)
|
42
|
+
ip = ip_header(self)
|
43
|
+
ip.dst = dst unless dst.nil?
|
44
|
+
ip.ttl = 1 if ip[:dst].mcast?
|
45
|
+
|
46
|
+
# rubocop:disable Lint/SuppressedException
|
47
|
+
begin
|
48
|
+
llh = ll_header(self)
|
49
|
+
llh.dst = MAC_IPV4_MCAST if ip[:dst].mcast?
|
50
|
+
rescue PacketGen::FormatError
|
51
|
+
end
|
52
|
+
# rubocop:enable Lint/SuppressedException
|
53
|
+
end
|
54
|
+
end
|
55
|
+
PacketGen::Header.add_class LLMNR
|
56
|
+
PacketGen::Header::UDP.bind LLMNR, sport: LLMNR::UDP_PORT
|
57
|
+
PacketGen::Header::UDP.bind LLMNR, dport: LLMNR::UDP_PORT
|
58
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# This file is part of PacketGen
|
4
|
+
# See https://github.com/sdaubert/packetgen-plugin-smb for more informations
|
5
|
+
# Copyright (C) 2016 Sylvain Daubert <sylvain.daubert@laposte.net>
|
6
|
+
# This program is published under MIT license.
|
7
|
+
|
8
|
+
# frozen_string_literal: true
|
9
|
+
|
10
|
+
module PacketGen::Plugin
|
11
|
+
# Module to group all NetBIOS headers
|
12
|
+
# @author Sylvain Daubert
|
13
|
+
module NetBIOS
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
require_relative 'netbios/name'
|
18
|
+
require_relative 'netbios/session'
|
19
|
+
require_relative 'netbios/datagram'
|
@@ -0,0 +1,108 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# This file is part of PacketGen
|
4
|
+
# See https://github.com/sdaubert/packetgen-plugin-smb for more informations
|
5
|
+
# Copyright (C) 2016 Sylvain Daubert <sylvain.daubert@laposte.net>
|
6
|
+
# This program is published under MIT license.
|
7
|
+
|
8
|
+
module PacketGen::Plugin
|
9
|
+
# Module to group all NetBIOS headers
|
10
|
+
# @author Sylvain Daubert
|
11
|
+
module NetBIOS
|
12
|
+
# NetBIOS Datagram Service messages.
|
13
|
+
# @author Sylvain Daubert
|
14
|
+
class Datagram < PacketGen::Header::Base
|
15
|
+
# Give protocol name
|
16
|
+
# @return [String]
|
17
|
+
def self.protocol_name
|
18
|
+
'NetBIOS::Datagram'
|
19
|
+
end
|
20
|
+
|
21
|
+
# Port number for NetBIOS Session Service over TCP
|
22
|
+
UDP_PORT = 138
|
23
|
+
|
24
|
+
# Datagram packet types
|
25
|
+
TYPES = {
|
26
|
+
'direct_unique' => 0x10,
|
27
|
+
'direct_group' => 0x11,
|
28
|
+
'broadcast' => 0x12,
|
29
|
+
'error' => 0x13,
|
30
|
+
'query_request' => 0x14,
|
31
|
+
'positive_query_resp' => 0x15,
|
32
|
+
'negative_query_resp' => 0x16,
|
33
|
+
}.freeze
|
34
|
+
|
35
|
+
# @!attribute type
|
36
|
+
# 8-bit session packet type
|
37
|
+
# @return [Integer]
|
38
|
+
define_field :type, PacketGen::Types::Int8Enum, enum: TYPES
|
39
|
+
# @!attribute flags
|
40
|
+
# 8-bit flags
|
41
|
+
# @return [Integer]
|
42
|
+
define_field :flags, PacketGen::Types::Int8
|
43
|
+
# @!attribute dgm_id
|
44
|
+
# 16-bit next transaction ID for datagrams
|
45
|
+
# @return [Integer]
|
46
|
+
define_field :dgm_id, PacketGen::Types::Int16
|
47
|
+
# @!attribute src_ip
|
48
|
+
# Source IP address
|
49
|
+
# @return [IP::Addr]
|
50
|
+
define_field :src_ip, PacketGen::Header::IP::Addr
|
51
|
+
# @!attribute src_port
|
52
|
+
# Source port
|
53
|
+
# @return [IP::Addr]
|
54
|
+
define_field :src_port, PacketGen::Types::Int16
|
55
|
+
# @!attribute dgm_length
|
56
|
+
# Length of data + second level of encoded names. Not present in error datagram.
|
57
|
+
# @return [Integer]
|
58
|
+
define_field :dgm_length, PacketGen::Types::Int16, optional: ->(h) { h.type != 0x13 }
|
59
|
+
# @!attribute packet_offset
|
60
|
+
# Not present in error datagram.
|
61
|
+
# @return [Integer]
|
62
|
+
define_field :packet_offset, PacketGen::Types::Int16, optional: ->(h) { h.type != 0x13 }
|
63
|
+
# @!attribute error_code
|
64
|
+
# Error code. Only present in error datagrams.
|
65
|
+
# @return [Integer]
|
66
|
+
define_field :error_code, PacketGen::Types::Int16, optional: ->(h) { h.type == 0x13 }
|
67
|
+
# @!attribute src_name
|
68
|
+
# NetBIOS source name. Only present in direct_unique, direct_group and broadcast datagrams.
|
69
|
+
# @return []
|
70
|
+
define_field :src_name, Name, default: '', optional: ->(h) { (h.type >= 0x10) && (h.type <= 0x12) }
|
71
|
+
# @!attribute dst_name
|
72
|
+
# NetBIOS destination name. Present in all but error datagrams.
|
73
|
+
# @return []
|
74
|
+
define_field :dst_name, Name, default: '', optional: ->(h) { h.type != 0x13 }
|
75
|
+
# @!attribute body
|
76
|
+
# User data. Ony present in direct_unique, direct_group and broadcast datagrams.
|
77
|
+
# @return [String]
|
78
|
+
define_field :body, PacketGen::Types::String, optional: ->(h) { (h.type >= 0x10) && (h.type <= 0x12) }
|
79
|
+
|
80
|
+
# @!attribute :rsv
|
81
|
+
# 4-bit rsv field. 4 upper bits of {#flags}
|
82
|
+
# @return [Integer]
|
83
|
+
# @!attribute :snt
|
84
|
+
# 2-bit SNT (Source end-Node Type) field from {#flags}.
|
85
|
+
# @return [Integer]
|
86
|
+
# @!attribute f
|
87
|
+
# First packet flag. If set then this is first
|
88
|
+
# (and possibly only) fragment of NetBIOS datagram.
|
89
|
+
# @return [Boolean]
|
90
|
+
# @!attribute m
|
91
|
+
# More flag. If set then more NetBIOS datagram
|
92
|
+
# fragments follow.
|
93
|
+
# @return [Boolean]
|
94
|
+
define_bit_fields_on :flags, :rsv, 4, :snt, 2, :f, :m
|
95
|
+
|
96
|
+
# Compute and set {#dgm_length} field
|
97
|
+
# @return [Integer] calculated length
|
98
|
+
def calc_length
|
99
|
+
length = self[:body].sz
|
100
|
+
length += self[:src_name].sz if present?(:src_name)
|
101
|
+
length += self[:dst_name].sz if present?(:dst_name)
|
102
|
+
self.dgm_length = length
|
103
|
+
end
|
104
|
+
end
|
105
|
+
PacketGen::Header.add_class Datagram
|
106
|
+
PacketGen::Header::UDP.bind Datagram, dport: Datagram::UDP_PORT, sport: Datagram::UDP_PORT
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# This file is part of PacketGen
|
4
|
+
# See https://github.com/sdaubert/packetgen-plugin-smb for more informations
|
5
|
+
# Copyright (C) 2016 Sylvain Daubert <sylvain.daubert@laposte.net>
|
6
|
+
# This program is published under MIT license.
|
7
|
+
|
8
|
+
module PacketGen::Plugin
|
9
|
+
# Module to group all NetBIOS headers
|
10
|
+
# @author Sylvain Daubert
|
11
|
+
module NetBIOS
|
12
|
+
# NetBIOS Name.
|
13
|
+
# @author Sylvain Daubert
|
14
|
+
class Name < PacketGen::Header::DNS::Name
|
15
|
+
# Size, in bytes, of an encoded NetBIOS name
|
16
|
+
ENCODED_NAME_SIZE = 32
|
17
|
+
|
18
|
+
# Read a NetBIOS name from a string
|
19
|
+
# @param [String] str
|
20
|
+
# @return [Name] self
|
21
|
+
def from_human(str)
|
22
|
+
clear
|
23
|
+
return self if str.nil?
|
24
|
+
|
25
|
+
encoded_name = encode_name(str)
|
26
|
+
super(encoded_name)
|
27
|
+
end
|
28
|
+
|
29
|
+
# Get a human readable string
|
30
|
+
# @return [String]
|
31
|
+
def to_human
|
32
|
+
encoded_name = super
|
33
|
+
decode_name(encoded_name)
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def encode_name(name)
|
39
|
+
basename, *scope_id = name.split('.')
|
40
|
+
basename = '' if basename.nil?
|
41
|
+
scope_id = scope_id.join('.')
|
42
|
+
encoded_name = +''
|
43
|
+
basename.each_byte do |byte|
|
44
|
+
a = (byte >> 4) + 0x41
|
45
|
+
b = (byte & 0xf) + 0x41
|
46
|
+
encoded_name << [a, b].pack('C2')
|
47
|
+
end
|
48
|
+
encoded_name << 'CA' * ((ENCODED_NAME_SIZE - encoded_name.size) / 2) if encoded_name.size < ENCODED_NAME_SIZE
|
49
|
+
encoded_name << ".#{scope_id}" if scope_id
|
50
|
+
encoded_name
|
51
|
+
end
|
52
|
+
|
53
|
+
def decode_name(encoded_name)
|
54
|
+
name = +''
|
55
|
+
encoded_name.partition('.').first.scan(/../).map do |duo|
|
56
|
+
a = (duo[0].ord - 0x41) & 0xf
|
57
|
+
b = (duo[1].ord - 0x41) & 0xf
|
58
|
+
name << (a << 4 | b).chr
|
59
|
+
end
|
60
|
+
name.strip
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# This file is part of PacketGen
|
4
|
+
# See https://github.com/sdaubert/packetgen-plugin-smb for more informations
|
5
|
+
# Copyright (C) 2016 Sylvain Daubert <sylvain.daubert@laposte.net>
|
6
|
+
# This program is published under MIT license.
|
7
|
+
|
8
|
+
module PacketGen::Plugin
|
9
|
+
# Module to group all NetBIOS headers
|
10
|
+
# @author Sylvain Daubert
|
11
|
+
module NetBIOS
|
12
|
+
# NetBIOS Session Service messages.
|
13
|
+
# @author Sylvain Daubert
|
14
|
+
class Session < PacketGen::Header::Base
|
15
|
+
# Give protocol name
|
16
|
+
# @return [String]
|
17
|
+
def self.protocol_name
|
18
|
+
'NetBIOS::Session'
|
19
|
+
end
|
20
|
+
|
21
|
+
# Port number for NetBIOS Session Service over TCP
|
22
|
+
TCP_PORT = 139
|
23
|
+
# Port number for NetBIOS Session Service over TCP (mainly used yb {SMB2})
|
24
|
+
TCP_PORT2 = 445
|
25
|
+
|
26
|
+
# Session packet types
|
27
|
+
TYPES = {
|
28
|
+
'message' => 0,
|
29
|
+
'request' => 0x81,
|
30
|
+
'positive_response' => 0x82,
|
31
|
+
'negative_response' => 0x83,
|
32
|
+
'retarget_response' => 0x84,
|
33
|
+
'keep_alive' => 0x85,
|
34
|
+
}.freeze
|
35
|
+
|
36
|
+
# @!attribute type
|
37
|
+
# 8-bit session packet type
|
38
|
+
# @return [Integer]
|
39
|
+
define_field :type, PacketGen::Types::Int8Enum, enum: TYPES
|
40
|
+
# @!attribute length
|
41
|
+
# 17-bit session packet length
|
42
|
+
# @return [Integer]
|
43
|
+
define_field :length, PacketGen::Types::Int24
|
44
|
+
# @!attribute body
|
45
|
+
# @return [String]
|
46
|
+
define_field :body, PacketGen::Types::String
|
47
|
+
|
48
|
+
# Compute and set {#length} field
|
49
|
+
# @return [Integer] calculated length
|
50
|
+
def calc_length
|
51
|
+
PacketGen::Header::Base.calculate_and_set_length(self, header_in_size: false)
|
52
|
+
end
|
53
|
+
|
54
|
+
# @api private
|
55
|
+
# @note This method is used internally by PacketGen and should not be
|
56
|
+
# directly called
|
57
|
+
# @since 2.7.0 Set TCP sport according to bindings, only if sport is 0.
|
58
|
+
# Needed by new bind API.
|
59
|
+
def added_to_packet(packet)
|
60
|
+
return unless packet.is? 'TCP'
|
61
|
+
return unless packet.tcp.sport.zero?
|
62
|
+
|
63
|
+
packet.tcp.sport = TCP_PORT
|
64
|
+
end
|
65
|
+
end
|
66
|
+
PacketGen::Header.add_class Session
|
67
|
+
PacketGen::Header::TCP.bind Session, dport: Session::TCP_PORT
|
68
|
+
PacketGen::Header::TCP.bind Session, sport: Session::TCP_PORT
|
69
|
+
PacketGen::Header::TCP.bind Session, dport: Session::TCP_PORT2
|
70
|
+
PacketGen::Header::TCP.bind Session, sport: Session::TCP_PORT2
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,211 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# This file is part of packetgen-plugin-smb.
|
4
|
+
# See https://github.com/sdaubert/packetgen-plugin-smb for more informations
|
5
|
+
# Copyright (C) 2018 Sylvain Daubert <sylvain.daubert@laposte.net>
|
6
|
+
# This program is published under MIT license.
|
7
|
+
|
8
|
+
module PacketGen::Plugin
|
9
|
+
# Base class for NTLM authentication protocol.
|
10
|
+
# @author Sylvain Daubert
|
11
|
+
class NTLM < PacketGen::Types::Fields
|
12
|
+
# NTLM message types
|
13
|
+
TYPES = {
|
14
|
+
'negotiate' => 1,
|
15
|
+
'challenge' => 2,
|
16
|
+
'authenticate' => 3
|
17
|
+
}.freeze
|
18
|
+
|
19
|
+
# NTLM signature
|
20
|
+
SIGNATURE = "NTLMSSP\0"
|
21
|
+
|
22
|
+
# void version
|
23
|
+
VOID_VERSION = [0].pack('q').freeze
|
24
|
+
VOID_CHALLENGE = VOID_VERSION
|
25
|
+
|
26
|
+
# @!attribute signature
|
27
|
+
# 8-byte NTLM signature
|
28
|
+
# @return [String]
|
29
|
+
define_field :signature, PacketGen::Types::String, static_length: 8, default: SIGNATURE
|
30
|
+
# @!attribute type
|
31
|
+
# 4-byte message type
|
32
|
+
# @return [Integer]
|
33
|
+
define_field :type, PacketGen::Types::Int32leEnum, enum: TYPES
|
34
|
+
# @!attribute payload
|
35
|
+
# @return [String]
|
36
|
+
define_field :payload, PacketGen::Types::String
|
37
|
+
|
38
|
+
class <<self
|
39
|
+
# @api private
|
40
|
+
# Return fields defined in payload one.
|
41
|
+
# @return [Hash]
|
42
|
+
attr_accessor :payload_fields
|
43
|
+
|
44
|
+
# Create a NTLM object from a binary string
|
45
|
+
# @param [String] str
|
46
|
+
# @return [NTLM]
|
47
|
+
def read(str)
|
48
|
+
ntlm = self.new.read(str)
|
49
|
+
type = TYPES.key(ntlm.type)
|
50
|
+
return ntlm if type.nil?
|
51
|
+
|
52
|
+
klass = NTLM.const_get(type.capitalize)
|
53
|
+
klass.new.read(str)
|
54
|
+
end
|
55
|
+
|
56
|
+
# Define a flags field.
|
57
|
+
# @return [void]
|
58
|
+
def define_negotiate_flags
|
59
|
+
define_field_before :payload, :flags, PacketGen::Types::Int32le
|
60
|
+
define_bit_fields_on :flags, :flags_w, :flags_v, :flags_u, :flags_r13, 3,
|
61
|
+
:flags_t, :flags_r4, :flags_s, :flags_r,
|
62
|
+
:flags_r5, :flags_q, :flags_p, :flags_r6,
|
63
|
+
:flags_o, :flags_n, :flags_m, :flags_r7,
|
64
|
+
:flags_l, :flags_k, :flags_j, :flags_r8,
|
65
|
+
:flags_h, :flags_r9, :flags_g, :flags_f,
|
66
|
+
:flags_e, :flags_d, :flags_r10, :flags_c,
|
67
|
+
:flags_b, :flags_a
|
68
|
+
alias_method :nego56?, :flags_w?
|
69
|
+
alias_method :key_exch?, :flags_v?
|
70
|
+
alias_method :nego128?, :flags_u?
|
71
|
+
alias_method :version?, :flags_t?
|
72
|
+
alias_method :target_info?, :flags_s?
|
73
|
+
alias_method :non_nt_session_key?, :flags_r?
|
74
|
+
alias_method :identify?, :flags_q?
|
75
|
+
alias_method :ext_session_security?, :flags_p?
|
76
|
+
alias_method :target_type_server?, :flags_o?
|
77
|
+
alias_method :target_type_domain?, :flags_n?
|
78
|
+
alias_method :always_sign?, :flags_m?
|
79
|
+
alias_method :oem_workstation_supplied?, :flags_l?
|
80
|
+
alias_method :oem_domain_supplied?, :flags_k?
|
81
|
+
alias_method :anonymous?, :flags_j?
|
82
|
+
alias_method :ntlm?, :flags_h?
|
83
|
+
alias_method :lm_key?, :flags_g?
|
84
|
+
alias_method :datagram?, :flags_f?
|
85
|
+
alias_method :seal?, :flags_e?
|
86
|
+
alias_method :sign?, :flags_d?
|
87
|
+
alias_method :request_target?, :flags_c?
|
88
|
+
alias_method :oem?, :flags_b?
|
89
|
+
alias_method :unicode?, :flags_a?
|
90
|
+
alias_method :old_flags_a=, :flags_a=
|
91
|
+
alias_method :old_flags=, :flags=
|
92
|
+
|
93
|
+
class_eval do
|
94
|
+
def flags_a=(value)
|
95
|
+
self.old_flags_a = value
|
96
|
+
self.class.payload_fields.each do |name, _|
|
97
|
+
attr = send(name)
|
98
|
+
attr.unicode = value if attr.respond_to?(:unicode=)
|
99
|
+
end
|
100
|
+
|
101
|
+
value
|
102
|
+
end
|
103
|
+
|
104
|
+
def flags=(value)
|
105
|
+
self.old_flags = value
|
106
|
+
self.flags_a = value & 1
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
# Define a field in payload. Also add +name_len+, +name_maxlen+ and
|
112
|
+
# +name_offset+ fields.
|
113
|
+
# @param [Symbol] name name of field.
|
114
|
+
# @param [Class,nil] type type of +name+ field.
|
115
|
+
# @param [Hash] options type's options needed at build time
|
116
|
+
# @return [void]
|
117
|
+
def define_in_payload(name, type=SMB::String, options={})
|
118
|
+
@payload_fields ||= {}
|
119
|
+
@payload_fields[name] = [type, options]
|
120
|
+
|
121
|
+
define_field_before :payload, :"#{name}_len", PacketGen::Types::Int16le
|
122
|
+
define_field_before :payload, :"#{name}_maxlen", PacketGen::Types::Int16le
|
123
|
+
define_field_before :payload, :"#{name}_offset", PacketGen::Types::Int32le
|
124
|
+
|
125
|
+
attr_accessor name
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
# @abstract This method is meaningful for {NTLM} subclasses only.
|
130
|
+
def initialize(options={})
|
131
|
+
super
|
132
|
+
return if self.class.payload_fields.nil?
|
133
|
+
|
134
|
+
self.class.payload_fields.each do |name, type_and_opt|
|
135
|
+
type, options = type_and_opt
|
136
|
+
content = if type.new.respond_to?(:unicode?)
|
137
|
+
type.new(options.merge(unicode: unicode?))
|
138
|
+
else
|
139
|
+
type.new(options)
|
140
|
+
end
|
141
|
+
send(:"#{name}=", content)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
# @abstract This class is meaningful for {NTLM} subclasses only.
|
146
|
+
# Populate object from a binary string
|
147
|
+
# @param [String] str
|
148
|
+
# @return [self]
|
149
|
+
def read(str)
|
150
|
+
super
|
151
|
+
return self if self.class.payload_fields.nil?
|
152
|
+
|
153
|
+
self.class.payload_fields.each do |name, type_and_opt|
|
154
|
+
type, options = type_and_opt
|
155
|
+
offset_in_payload = send(:"#{name}_offset") - offset_of(:payload)
|
156
|
+
length = send(:"#{name}_len")
|
157
|
+
content = if type.new.respond_to?(:unicode?)
|
158
|
+
type.new(options.merge(unicode: unicode?))
|
159
|
+
else
|
160
|
+
type.new(options)
|
161
|
+
end
|
162
|
+
content.read(payload[offset_in_payload, length]) if length.positive?
|
163
|
+
send(:"#{name}=", content)
|
164
|
+
end
|
165
|
+
|
166
|
+
self
|
167
|
+
end
|
168
|
+
|
169
|
+
# @abstract This class is meaningful for {NTLM} subclasses only.
|
170
|
+
# Calculate and set +len+, +maxlen+ and +offset+ fields defined for
|
171
|
+
# fields in {#payload}.
|
172
|
+
# @return [void]
|
173
|
+
def calc_length
|
174
|
+
return self if self.class.payload_fields.nil?
|
175
|
+
|
176
|
+
previous_len = 0
|
177
|
+
self.class.payload_fields.each do |name, _type_and_opt|
|
178
|
+
send(:"#{name}_len=", 0)
|
179
|
+
send(:"#{name}_offset=", offset_of(:payload) + previous_len)
|
180
|
+
|
181
|
+
field = send(name)
|
182
|
+
next unless field && !field.empty?
|
183
|
+
|
184
|
+
length = field.respond_to?(:sz) ? field.sz : field.size
|
185
|
+
send(:"#{name}_len=", length)
|
186
|
+
send(:"#{name}_maxlen=", length)
|
187
|
+
previous_len = length
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
# @abstract This class is meaningful for {NTLM} subclasses only.
|
192
|
+
# @return [String]
|
193
|
+
def to_s
|
194
|
+
s = super
|
195
|
+
return s if self.class.payload_fields.nil?
|
196
|
+
|
197
|
+
self.class.payload_fields.each do |name, _type_and_opt|
|
198
|
+
attr = send(name)
|
199
|
+
attr.unicode = unicode? if attr.respond_to?(:unicode=)
|
200
|
+
s << attr.to_s unless attr.nil? || send("#{name}_len").zero?
|
201
|
+
end
|
202
|
+
s
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
require_relative 'ntlm/av_pair'
|
208
|
+
require_relative 'ntlm/ntlmv2_response'
|
209
|
+
require_relative 'ntlm/negotiate'
|
210
|
+
require_relative 'ntlm/challenge'
|
211
|
+
require_relative 'ntlm/authenticate'
|