meshtastic 0.0.2 → 0.0.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/main.yml +27 -0
- data/.gitignore +29 -0
- data/.rspec +3 -0
- data/.rubocop.yml +10 -0
- data/.rubocop_todo.yml +107 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/AUTOGEN_meshtastic_protobufs.sh +25 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/Gemfile +23 -0
- data/LICENSE +21 -0
- data/README.md +31 -0
- data/Rakefile +17 -0
- data/bin/console +11 -0
- data/bin/meshtastic_autoinc_version +38 -0
- data/bin/setup +8 -0
- data/build_meshtastic_gem.sh +58 -0
- data/git_commit.sh +23 -0
- data/lib/meshtastic/admin_pb.rb +100 -0
- data/lib/meshtastic/apponly_pb.rb +20 -0
- data/lib/meshtastic/atak_pb.rb +80 -0
- data/lib/meshtastic/cannedmessages_pb.rb +16 -0
- data/lib/meshtastic/channel_pb.rb +38 -0
- data/lib/meshtastic/clientonly_pb.rb +22 -0
- data/lib/meshtastic/config_pb.rb +235 -0
- data/lib/meshtastic/connection_status_pb.rb +47 -0
- data/lib/meshtastic/deviceonly_pb.rb +75 -0
- data/lib/meshtastic/localonly_pb.rb +43 -0
- data/lib/meshtastic/mesh_pb.rb +359 -0
- data/lib/meshtastic/module_config_pb.rb +227 -0
- data/lib/meshtastic/mqtt.rb +271 -0
- data/lib/meshtastic/mqtt.rb.BAKq! +214 -0
- data/lib/meshtastic/mqtt_pb.rb +37 -0
- data/lib/meshtastic/paxcount_pb.rb +18 -0
- data/lib/meshtastic/portnums_pb.rb +42 -0
- data/lib/meshtastic/remote_hardware_pb.rb +27 -0
- data/lib/meshtastic/rtttl_pb.rb +16 -0
- data/lib/meshtastic/storeforward_pb.rb +64 -0
- data/lib/meshtastic/telemetry_pb.rb +85 -0
- data/lib/meshtastic/version.rb +5 -0
- data/lib/meshtastic/xmodem_pb.rb +30 -0
- data/lib/meshtastic.rb +34 -0
- data/meshtastic.gemspec +73 -0
- data/sig/meshtastic.rbs +4 -0
- data/spec/lib/meshtastic/admin_pb_spec.rb +6 -0
- data/spec/lib/meshtastic/apponly_pb_spec.rb +6 -0
- data/spec/lib/meshtastic/atak_pb_spec.rb +6 -0
- data/spec/lib/meshtastic/cannedmessages_pb_spec.rb +6 -0
- data/spec/lib/meshtastic/channel_pb_spec.rb +6 -0
- data/spec/lib/meshtastic/clientonly_pb_spec.rb +6 -0
- data/spec/lib/meshtastic/config_pb_spec.rb +6 -0
- data/spec/lib/meshtastic/connection_status_pb_spec.rb +6 -0
- data/spec/lib/meshtastic/deviceonly_pb_spec.rb +6 -0
- data/spec/lib/meshtastic/localonly_pb_spec.rb +6 -0
- data/spec/lib/meshtastic/mesh_pb_spec.rb +6 -0
- data/spec/lib/meshtastic/module_config_pb_spec.rb +6 -0
- data/spec/lib/meshtastic/mqtt_pb_spec.rb +6 -0
- data/spec/lib/meshtastic/mqtt_spec.rb +6 -0
- data/spec/lib/meshtastic/paxcount_pb_spec.rb +6 -0
- data/spec/lib/meshtastic/portnums_pb_spec.rb +6 -0
- data/spec/lib/meshtastic/remote_hardware_pb_spec.rb +6 -0
- data/spec/lib/meshtastic/rtttl_pb_spec.rb +6 -0
- data/spec/lib/meshtastic/storeforward_pb_spec.rb +6 -0
- data/spec/lib/meshtastic/telemetry_pb_spec.rb +6 -0
- data/spec/lib/meshtastic/version_spec.rb +17 -0
- data/spec/lib/meshtastic/xmodem_pb_spec.rb +6 -0
- data/spec/lib/meshtastic_spec.rb +6 -0
- data/spec/spec_helper.rb +15 -0
- data/upgrade_Gemfile_gems.sh +20 -0
- data/upgrade_gem.sh +4 -0
- data/upgrade_meshtastic.sh +13 -0
- data/upgrade_ruby.sh +45 -0
- metadata +106 -3
@@ -0,0 +1,271 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'base64'
|
4
|
+
require 'geocoder'
|
5
|
+
require 'json'
|
6
|
+
require 'mqtt'
|
7
|
+
require 'openssl'
|
8
|
+
require 'securerandom'
|
9
|
+
require 'meshtastic/mqtt_pb'
|
10
|
+
|
11
|
+
# Avoiding Namespace Collisions
|
12
|
+
MQTTClient = MQTT::Client
|
13
|
+
|
14
|
+
# Plugin used to interact with Meshtastic nodes
|
15
|
+
module Meshtastic
|
16
|
+
module MQTT
|
17
|
+
# Supported Method Parameters::
|
18
|
+
# mqtt_obj = Meshtastic::MQQT.connect(
|
19
|
+
# host: 'optional - mqtt host (default: mqtt.meshtastic.org)',
|
20
|
+
# port: 'optional - mqtt port (defaults: 1883)',
|
21
|
+
# username: 'optional - mqtt username (default: meshdev)',
|
22
|
+
# password: 'optional - (default: large4cats)'
|
23
|
+
# )
|
24
|
+
|
25
|
+
public_class_method def self.connect(opts = {})
|
26
|
+
# Publicly available MQTT server / credentials by default
|
27
|
+
host = opts[:host] ||= 'mqtt.meshtastic.org'
|
28
|
+
port = opts[:port] ||= 1883
|
29
|
+
username = opts[:username] ||= 'meshdev'
|
30
|
+
password = opts[:password] ||= 'large4cats'
|
31
|
+
|
32
|
+
mqtt_obj = MQTTClient.connect(
|
33
|
+
host: host,
|
34
|
+
port: port,
|
35
|
+
username: username,
|
36
|
+
password: password
|
37
|
+
)
|
38
|
+
|
39
|
+
mqtt_obj.client_id = SecureRandom.random_bytes(8).unpack1('H*')
|
40
|
+
|
41
|
+
mqtt_obj
|
42
|
+
rescue StandardError => e
|
43
|
+
raise e
|
44
|
+
end
|
45
|
+
|
46
|
+
# Supported Method Parameters::
|
47
|
+
# Meshtastic::MQQT.subscribe(
|
48
|
+
# mqtt_obj: 'required - mqtt_obj returned from #connect method'
|
49
|
+
# region: 'optional - region (default: US)',
|
50
|
+
# channel: 'optional - channel name (default: LongFast)',
|
51
|
+
# psk: 'optional - channel pre-shared key (default: AQ==)',
|
52
|
+
# qos: 'optional - quality of service (default: 0)',
|
53
|
+
# json: 'optional - JSON output (default: false)'
|
54
|
+
# )
|
55
|
+
|
56
|
+
public_class_method def self.subscribe(opts = {})
|
57
|
+
mqtt_obj = opts[:mqtt_obj]
|
58
|
+
region = opts[:region] ||= 'US'
|
59
|
+
channel = opts[:channel] ||= 'LongFast'
|
60
|
+
psk = opts[:psk] ||= 'AQ=='
|
61
|
+
qos = opts[:qos] ||= 0
|
62
|
+
json = opts[:json] ||= false
|
63
|
+
|
64
|
+
# TODO: Find JSON URI for this
|
65
|
+
root_topic = "msh/#{region}/2/json" if json
|
66
|
+
# root_topic = "msh/#{region}/2/e" unless json
|
67
|
+
root_topic = "msh/#{region}/2/c" unless json
|
68
|
+
mqtt_obj.subscribe("#{root_topic}/#{channel}/#", qos)
|
69
|
+
|
70
|
+
# Decrypt the message
|
71
|
+
# Our AES key is 128 or 256 bits, shared as part of the 'Channel' specification.
|
72
|
+
|
73
|
+
# Actual pre-shared key for LongFast channel
|
74
|
+
psk = '1PG7OiApB1nwvP+rz05pAQ==' if channel == 'LongFast'
|
75
|
+
padded_psk = psk.ljust(psk.length + ((4 - (psk.length % 4)) % 4), '=')
|
76
|
+
replaced_psk = padded_psk.gsub('-', '+').gsub('_', '/')
|
77
|
+
psk = replaced_psk
|
78
|
+
dec_psk = Base64.strict_decode64(psk)
|
79
|
+
|
80
|
+
# cipher = OpenSSL::Cipher.new('AES-256-CTR')
|
81
|
+
cipher = OpenSSL::Cipher.new('AES-128-CTR')
|
82
|
+
mqtt_obj.get_packet do |packet|
|
83
|
+
raw_packet = packet.to_s.b
|
84
|
+
raw_packet_len = raw_packet.length
|
85
|
+
begin
|
86
|
+
puts '-' * 80
|
87
|
+
|
88
|
+
if json
|
89
|
+
raw_payload = JSON.parse(packet.payload, symbolize_names: true)
|
90
|
+
payload = Meshtastic::ServiceEnvelope.json_decode(raw_payload)
|
91
|
+
map_report = Meshtastic::MapReport.json_decode(raw_payload)
|
92
|
+
else
|
93
|
+
raw_payload = packet.payload.to_s
|
94
|
+
payload = Meshtastic::ServiceEnvelope.decode(raw_payload).to_hash[:packet]
|
95
|
+
map_report = Meshtastic::MapReport.decode(raw_payload).to_hash
|
96
|
+
end
|
97
|
+
|
98
|
+
puts "*** MESSAGE ***"
|
99
|
+
packet_from = payload[:from]
|
100
|
+
puts "Packet From: #{packet_from}"
|
101
|
+
packet_to = payload[:to]
|
102
|
+
puts "Packet To: #{packet_to}"
|
103
|
+
channel = payload[:channel]
|
104
|
+
puts "Channel: #{channel}"
|
105
|
+
packet_id = payload[:id]
|
106
|
+
puts "Packet ID: #{packet_id}"
|
107
|
+
topic = packet.topic
|
108
|
+
puts "\nTopic: #{topic}"
|
109
|
+
|
110
|
+
decoded_payload = payload[:decoded]
|
111
|
+
if decoded_payload
|
112
|
+
port_num = decoded_payload[:portnum]
|
113
|
+
puts "Port Number: #{port_num}"
|
114
|
+
decp = decoded_payload[:payload].b
|
115
|
+
puts "Decoded Payload: #{decp.inspect}"
|
116
|
+
want_response = decoded_payload[:want_response]
|
117
|
+
puts "Want Response: #{want_response}"
|
118
|
+
dest = decoded_payload[:dest]
|
119
|
+
puts "Destination: #{dest}"
|
120
|
+
source = decoded_payload[:source]
|
121
|
+
puts "Source: #{source}"
|
122
|
+
request_id = decoded_payload[:request_id]
|
123
|
+
puts "Request ID: #{request_id}"
|
124
|
+
reply_id = decoded_payload[:reply_id]
|
125
|
+
puts "Reply ID: #{reply_id}"
|
126
|
+
emoji = decoded_payload[:emoji]
|
127
|
+
puts "Emoji: #{emoji}"
|
128
|
+
end
|
129
|
+
|
130
|
+
encrypted_payload = payload[:encrypted]
|
131
|
+
# If encrypted_payload is not nil, then decrypt the message
|
132
|
+
if encrypted_payload.length.positive?
|
133
|
+
nonce_packet_id = [packet_id].pack('V').ljust(8, "\x00")
|
134
|
+
nonce_from_node = [packet_from].pack('V').ljust(8, "\x00")
|
135
|
+
nonce = "#{nonce_packet_id}#{nonce_from_node}".b
|
136
|
+
puts "Nonce: #{nonce.inspect} | Length: #{nonce.length}"
|
137
|
+
|
138
|
+
# Decrypt the message
|
139
|
+
# Key must be 32 bytes
|
140
|
+
# IV mustr be 16 bytes
|
141
|
+
cipher.decrypt
|
142
|
+
cipher.key = dec_psk
|
143
|
+
cipher.iv = nonce
|
144
|
+
puts "\nEncrypted Payload:\n#{encrypted_payload.inspect}"
|
145
|
+
puts "Length: #{encrypted_payload.length}" if encrypted_payload
|
146
|
+
|
147
|
+
decrypted = cipher.update(encrypted_payload) + cipher.final
|
148
|
+
puts "\nDecrypted Payload:\n#{decrypted.inspect}"
|
149
|
+
puts "Length: #{decrypted.length}" if decrypted
|
150
|
+
end
|
151
|
+
puts '*' * 20
|
152
|
+
|
153
|
+
map_long_name = map_report[:long_name].b
|
154
|
+
puts "\n*** MAP STATS ***"
|
155
|
+
puts "Map Long Name: #{map_long_name.inspect}"
|
156
|
+
map_short_name = map_report[:short_name]
|
157
|
+
puts "Map Short Name: #{map_short_name}"
|
158
|
+
role = map_report[:role]
|
159
|
+
puts "Role: #{role}"
|
160
|
+
hw_model = map_report[:hw_model]
|
161
|
+
puts "Hardware Model: #{hw_model}"
|
162
|
+
firmware_version = map_report[:firmware_version]
|
163
|
+
puts "Firmware Version: #{firmware_version}"
|
164
|
+
region = map_report[:region]
|
165
|
+
puts "Region: #{region}"
|
166
|
+
modem_preset = map_report[:modem_preset]
|
167
|
+
puts "Modem Preset: #{modem_preset}"
|
168
|
+
has_default_channel = map_report[:has_default_channel]
|
169
|
+
puts "Has Default Channel: #{has_default_channel}"
|
170
|
+
latitiude_i = map_report[:latitude_i]
|
171
|
+
puts "Latitude: #{latitiude_i}"
|
172
|
+
longitude_i = map_report[:longitude_i]
|
173
|
+
puts "Longitude: #{longitude_i}"
|
174
|
+
altitude = map_report[:altitude]
|
175
|
+
puts "Altitude: #{altitude}"
|
176
|
+
position_precision = map_report[:position_precision]
|
177
|
+
puts "Position Precision: #{position_precision}"
|
178
|
+
num_online_local_nodes = map_report[:num_online_local_nodes]
|
179
|
+
puts "Number of Online Local Nodes: #{num_online_local_nodes}"
|
180
|
+
puts '*' * 20
|
181
|
+
|
182
|
+
puts "\n*** PACKET DEBUGGING ***"
|
183
|
+
puts "Payload: #{payload.inspect}"
|
184
|
+
puts "\nMap Report: #{map_report.inspect}"
|
185
|
+
puts "\nRaw Packet: #{raw_packet.inspect}"
|
186
|
+
puts "Length: #{raw_packet_len}"
|
187
|
+
puts '*' * 20
|
188
|
+
rescue Google::Protobuf::ParseError
|
189
|
+
puts "\nRaw Packet: #{raw_packet.inspect}"
|
190
|
+
puts "Length: #{raw_packet_len}"
|
191
|
+
next
|
192
|
+
ensure
|
193
|
+
puts '-' * 80
|
194
|
+
puts "\n\n\n"
|
195
|
+
end
|
196
|
+
end
|
197
|
+
rescue Interrupt
|
198
|
+
puts "\nCTRL+C detected. Exiting..."
|
199
|
+
rescue StandardError => e
|
200
|
+
raise e
|
201
|
+
ensure
|
202
|
+
mqtt_obj.disconnect if mqtt_obj
|
203
|
+
end
|
204
|
+
|
205
|
+
# Supported Method Parameters::
|
206
|
+
# mqtt_obj = Meshtastic.gps_search(
|
207
|
+
# lat: 'required - latitude float (e.g. 37.7749)',
|
208
|
+
# lon: 'required - longitude float (e.g. -122.4194)',
|
209
|
+
# )
|
210
|
+
public_class_method def self.gps_search(opts = {})
|
211
|
+
lat = opts[:lat]
|
212
|
+
lon = opts[:lon]
|
213
|
+
|
214
|
+
raise 'ERROR: Latitude and Longitude are required' unless lat && lon
|
215
|
+
|
216
|
+
gps_arr = [lat.to_f, lon.to_f]
|
217
|
+
|
218
|
+
Geocoder.search(gps_arr)
|
219
|
+
rescue StandardError => e
|
220
|
+
raise e
|
221
|
+
end
|
222
|
+
|
223
|
+
# Supported Method Parameters::
|
224
|
+
# mqtt_obj = Meshtastic.disconnect(
|
225
|
+
# mqtt_obj: 'required - mqtt_obj returned from #connect method'
|
226
|
+
# )
|
227
|
+
public_class_method def self.disconnect(opts = {})
|
228
|
+
mqtt_obj = opts[:mqtt_obj]
|
229
|
+
|
230
|
+
mqtt_obj.disconnect if mqtt_obj
|
231
|
+
nil
|
232
|
+
rescue StandardError => e
|
233
|
+
raise e
|
234
|
+
end
|
235
|
+
|
236
|
+
# Author(s):: 0day Inc. <request.pentest@0dayinc.com>
|
237
|
+
|
238
|
+
public_class_method def self.authors
|
239
|
+
"AUTHOR(S):
|
240
|
+
0day Inc. <request.pentest@0dayinc.com>
|
241
|
+
"
|
242
|
+
end
|
243
|
+
|
244
|
+
# Display Usage for this Module
|
245
|
+
|
246
|
+
public_class_method def self.help
|
247
|
+
puts "USAGE:
|
248
|
+
mqtt_obj = #{self}.connect(
|
249
|
+
host: 'optional - mqtt host (default: mqtt.meshtastic.org)',
|
250
|
+
port: 'optional - mqtt port (defaults: 1883)',
|
251
|
+
username: 'optional - mqtt username (default: meshdev)',
|
252
|
+
password: 'optional - (default: large4cats)'
|
253
|
+
)
|
254
|
+
|
255
|
+
#{self}.subscribe(
|
256
|
+
mqtt_obj: 'required - mqtt_obj object returned from #connect method',
|
257
|
+
region: 'optional - region (default: US)',
|
258
|
+
channel: 'optional - channel name (default: LongFast)',
|
259
|
+
psk: 'optional - channel pre-shared key (default: AQ==)',
|
260
|
+
qos: 'optional - quality of service (default: 0)'
|
261
|
+
)
|
262
|
+
|
263
|
+
mqtt_obj = #{self}.disconnect(
|
264
|
+
mqtt_obj: 'required - mqtt_obj object returned from #connect method'
|
265
|
+
)
|
266
|
+
|
267
|
+
#{self}.authors
|
268
|
+
"
|
269
|
+
end
|
270
|
+
end
|
271
|
+
end
|
@@ -0,0 +1,214 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'base64'
|
4
|
+
require 'geocoder'
|
5
|
+
require 'json'
|
6
|
+
require 'mqtt'
|
7
|
+
require 'openssl'
|
8
|
+
require 'securerandom'
|
9
|
+
require 'meshtastic/mqtt_pb'
|
10
|
+
|
11
|
+
# Avoiding Namespace Collisions
|
12
|
+
MQTTClient = MQTT::Client
|
13
|
+
|
14
|
+
# Plugin used to interact with Meshtastic nodes
|
15
|
+
module Meshtastic
|
16
|
+
module MQTT
|
17
|
+
# Supported Method Parameters::
|
18
|
+
# mqtt_obj = Meshtastic::MQQT.connect(
|
19
|
+
# host: 'optional - mqtt host (default: mqtt.meshtastic.org)',
|
20
|
+
# port: 'optional - mqtt port (defaults: 1883)',
|
21
|
+
# username: 'optional - mqtt username (default: meshdev)',
|
22
|
+
# password: 'optional - (default: large4cats)'
|
23
|
+
# )
|
24
|
+
|
25
|
+
public_class_method def self.connect(opts = {})
|
26
|
+
# Publicly available MQTT server / credentials by default
|
27
|
+
host = opts[:host] ||= 'mqtt.meshtastic.org'
|
28
|
+
port = opts[:port] ||= 1883
|
29
|
+
username = opts[:username] ||= 'meshdev'
|
30
|
+
password = opts[:password] ||= 'large4cats'
|
31
|
+
|
32
|
+
mqtt_obj = MQTTClient.connect(
|
33
|
+
host: host,
|
34
|
+
port: port,
|
35
|
+
username: username,
|
36
|
+
password: password
|
37
|
+
)
|
38
|
+
|
39
|
+
mqtt_obj.client_id = SecureRandom.random_bytes(8).unpack1('H*')
|
40
|
+
|
41
|
+
mqtt_obj
|
42
|
+
rescue StandardError => e
|
43
|
+
raise e
|
44
|
+
end
|
45
|
+
|
46
|
+
# Supported Method Parameters::
|
47
|
+
# Meshtastic::MQQT.subscribe(
|
48
|
+
# mqtt_obj: 'required - mqtt_obj returned from #connect method'
|
49
|
+
# region: 'optional - region (default: US)',
|
50
|
+
# channel: 'optional - channel name (default: LongFast)',
|
51
|
+
# psk: 'optional - channel pre-shared key (default: AQ==)',
|
52
|
+
# qos: 'optional - quality of service (default: 0)',
|
53
|
+
# json: 'optional - JSON output (default: false)'
|
54
|
+
# )
|
55
|
+
|
56
|
+
public_class_method def self.subscribe(opts = {})
|
57
|
+
mqtt_obj = opts[:mqtt_obj]
|
58
|
+
region = opts[:region] ||= 'US'
|
59
|
+
channel = opts[:channel] ||= 'LongFast'
|
60
|
+
psk = opts[:psk] ||= 'AQ=='
|
61
|
+
qos = opts[:qos] ||= 0
|
62
|
+
json = opts[:json] ||= false
|
63
|
+
|
64
|
+
# TODO: Find JSON URI for this
|
65
|
+
root_topic = "msh/#{region}/2/json" if json
|
66
|
+
# root_topic = "msh/#{region}/2/e" unless json
|
67
|
+
root_topic = "msh/#{region}/2/c" unless json
|
68
|
+
mqtt_obj.subscribe("#{root_topic}/#{channel}/#", qos)
|
69
|
+
|
70
|
+
# Decrypt the message
|
71
|
+
# Our AES key is 128 or 256 bits, shared as part of the 'Channel' specification.
|
72
|
+
|
73
|
+
# Actual pre-shared key for LongFast channel
|
74
|
+
psk = '1PG7OiApB1nwvP+rz05pAQ==' if channel == 'LongFast'
|
75
|
+
padded_psk = psk.ljust(psk.length + ((4 - (psk.length % 4)) % 4), '=')
|
76
|
+
replaced_psk = padded_psk.gsub('-', '+').gsub('_', '/')
|
77
|
+
psk = replaced_psk
|
78
|
+
dec_psk = Base64.strict_decode64(psk)
|
79
|
+
|
80
|
+
# cipher = OpenSSL::Cipher.new('AES-256-CTR')
|
81
|
+
cipher = OpenSSL::Cipher.new('AES-128-CTR')
|
82
|
+
mqtt_obj.get_packet do |packet|
|
83
|
+
begin
|
84
|
+
puts '-' * 80
|
85
|
+
|
86
|
+
if json
|
87
|
+
raw_payload = JSON.parse(packet.payload, symbolize_names: true)
|
88
|
+
payload = Meshtastic::ServiceEnvelope.json_decode(raw_payload)
|
89
|
+
map_report = Meshtastic::MapReport.json_decode(raw_payload)
|
90
|
+
else
|
91
|
+
raw_payload = packet.payload.to_s
|
92
|
+
payload = Meshtastic::ServiceEnvelope.decode(raw_payload)
|
93
|
+
map_report = Meshtastic::MapReport.decode(raw_payload)
|
94
|
+
end
|
95
|
+
|
96
|
+
puts "Payload: #{payload.inspect}"
|
97
|
+
puts "Payload Length: #{payload.length}"
|
98
|
+
puts "Map Report: #{map_report.inspect}"
|
99
|
+
puts "Map Report Length: #{map_report.length}"
|
100
|
+
|
101
|
+
packet_from = packet_from_hex.to_i(16)
|
102
|
+
packet_to = packet_to_hex.to_i(16)
|
103
|
+
msg_len = msg_len_hex.to_i(16)
|
104
|
+
channel = channel_hex.to_i(16)
|
105
|
+
packet_id = pid_hex.to_i(16)
|
106
|
+
topic = packet.topic
|
107
|
+
puts "\nTopic: #{topic}"
|
108
|
+
|
109
|
+
|
110
|
+
nonce_packet_id = [packet_id].pack('V').ljust(8, "\x00")
|
111
|
+
nonce_from_node = [packet_from].pack('V').ljust(8, "\x00")
|
112
|
+
nonce = "#{nonce_packet_id}#{nonce_from_node}".b
|
113
|
+
puts "Nonce: #{nonce.inspect} | Length: #{nonce.length}"
|
114
|
+
|
115
|
+
# Decrypt the message
|
116
|
+
# Key must be 32 bytes
|
117
|
+
# IV mustr be 16 bytes
|
118
|
+
cipher.decrypt
|
119
|
+
cipher.key = dec_psk
|
120
|
+
cipher.iv = nonce
|
121
|
+
first_byte = 16
|
122
|
+
last_byte = first_byte + msg_len - 1
|
123
|
+
encrypted_payload = payload[first_byte..last_byte]
|
124
|
+
puts "\nEncrypted Payload:\n#{encrypted_payload.inspect}"
|
125
|
+
puts "Length: #{encrypted_payload.length}" if encrypted_payload
|
126
|
+
|
127
|
+
decrypted = cipher.update(encrypted_payload) + cipher.final
|
128
|
+
puts "\nDecrypted Payload:\n#{decrypted.inspect}"
|
129
|
+
puts "Length: #{decrypted.length}" if decrypted
|
130
|
+
|
131
|
+
raw_packet = packet.to_s.b
|
132
|
+
raw_packet_len = raw_packet.length
|
133
|
+
puts "\nRaw Packet: #{raw_packet.inspect}"
|
134
|
+
puts "Length: #{raw_packet_len}"
|
135
|
+
rescue Google::Protobuf::ParseError => e
|
136
|
+
puts "Error parsing message: #{e}"
|
137
|
+
next
|
138
|
+
end
|
139
|
+
end
|
140
|
+
rescue Interrupt
|
141
|
+
puts "\nCTRL+C detected. Exiting..."
|
142
|
+
rescue StandardError => e
|
143
|
+
raise e
|
144
|
+
ensure
|
145
|
+
mqtt_obj.disconnect if mqtt_obj
|
146
|
+
end
|
147
|
+
|
148
|
+
# Supported Method Parameters::
|
149
|
+
# mqtt_obj = Meshtastic.gps_search(
|
150
|
+
# lat: 'required - latitude float (e.g. 37.7749)',
|
151
|
+
# lon: 'required - longitude float (e.g. -122.4194)',
|
152
|
+
# )
|
153
|
+
public_class_method def self.gps_search(opts = {})
|
154
|
+
lat = opts[:lat]
|
155
|
+
lon = opts[:lon]
|
156
|
+
|
157
|
+
raise 'ERROR: Latitude and Longitude are required' unless lat && lon
|
158
|
+
|
159
|
+
gps_arr = [lat.to_f, lon.to_f]
|
160
|
+
|
161
|
+
Geocoder.search(gps_arr)
|
162
|
+
rescue StandardError => e
|
163
|
+
raise e
|
164
|
+
end
|
165
|
+
|
166
|
+
# Supported Method Parameters::
|
167
|
+
# mqtt_obj = Meshtastic.disconnect(
|
168
|
+
# mqtt_obj: 'required - mqtt_obj returned from #connect method'
|
169
|
+
# )
|
170
|
+
public_class_method def self.disconnect(opts = {})
|
171
|
+
mqtt_obj = opts[:mqtt_obj]
|
172
|
+
|
173
|
+
mqtt_obj.disconnect if mqtt_obj
|
174
|
+
nil
|
175
|
+
rescue StandardError => e
|
176
|
+
raise e
|
177
|
+
end
|
178
|
+
|
179
|
+
# Author(s):: 0day Inc. <request.pentest@0dayinc.com>
|
180
|
+
|
181
|
+
public_class_method def self.authors
|
182
|
+
"AUTHOR(S):
|
183
|
+
0day Inc. <request.pentest@0dayinc.com>
|
184
|
+
"
|
185
|
+
end
|
186
|
+
|
187
|
+
# Display Usage for this Module
|
188
|
+
|
189
|
+
public_class_method def self.help
|
190
|
+
puts "USAGE:
|
191
|
+
mqtt_obj = #{self}.connect(
|
192
|
+
host: 'optional - mqtt host (default: mqtt.meshtastic.org)',
|
193
|
+
port: 'optional - mqtt port (defaults: 1883)',
|
194
|
+
username: 'optional - mqtt username (default: meshdev)',
|
195
|
+
password: 'optional - (default: large4cats)'
|
196
|
+
)
|
197
|
+
|
198
|
+
#{self}.subscribe(
|
199
|
+
mqtt_obj: 'required - mqtt_obj object returned from #connect method',
|
200
|
+
region: 'optional - region (default: US)',
|
201
|
+
channel: 'optional - channel name (default: LongFast)',
|
202
|
+
psk: 'optional - channel pre-shared key (default: AQ==)',
|
203
|
+
qos: 'optional - quality of service (default: 0)'
|
204
|
+
)
|
205
|
+
|
206
|
+
mqtt_obj = #{self}.disconnect(
|
207
|
+
mqtt_obj: 'required - mqtt_obj object returned from #connect method'
|
208
|
+
)
|
209
|
+
|
210
|
+
#{self}.authors
|
211
|
+
"
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
2
|
+
# source: meshtastic/mqtt.proto
|
3
|
+
|
4
|
+
require 'google/protobuf'
|
5
|
+
|
6
|
+
require 'meshtastic/config_pb'
|
7
|
+
require 'meshtastic/mesh_pb'
|
8
|
+
|
9
|
+
Google::Protobuf::DescriptorPool.generated_pool.build do
|
10
|
+
add_file("meshtastic/mqtt.proto", :syntax => :proto3) do
|
11
|
+
add_message "meshtastic.ServiceEnvelope" do
|
12
|
+
optional :packet, :message, 1, "meshtastic.MeshPacket"
|
13
|
+
optional :channel_id, :string, 2
|
14
|
+
optional :gateway_id, :string, 3
|
15
|
+
end
|
16
|
+
add_message "meshtastic.MapReport" do
|
17
|
+
optional :long_name, :string, 1
|
18
|
+
optional :short_name, :string, 2
|
19
|
+
optional :role, :enum, 3, "meshtastic.Config.DeviceConfig.Role"
|
20
|
+
optional :hw_model, :enum, 4, "meshtastic.HardwareModel"
|
21
|
+
optional :firmware_version, :string, 5
|
22
|
+
optional :region, :enum, 6, "meshtastic.Config.LoRaConfig.RegionCode"
|
23
|
+
optional :modem_preset, :enum, 7, "meshtastic.Config.LoRaConfig.ModemPreset"
|
24
|
+
optional :has_default_channel, :bool, 8
|
25
|
+
optional :latitude_i, :sfixed32, 9
|
26
|
+
optional :longitude_i, :sfixed32, 10
|
27
|
+
optional :altitude, :int32, 11
|
28
|
+
optional :position_precision, :uint32, 12
|
29
|
+
optional :num_online_local_nodes, :uint32, 13
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
module Meshtastic
|
35
|
+
ServiceEnvelope = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("meshtastic.ServiceEnvelope").msgclass
|
36
|
+
MapReport = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("meshtastic.MapReport").msgclass
|
37
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
2
|
+
# source: meshtastic/paxcount.proto
|
3
|
+
|
4
|
+
require 'google/protobuf'
|
5
|
+
|
6
|
+
Google::Protobuf::DescriptorPool.generated_pool.build do
|
7
|
+
add_file("meshtastic/paxcount.proto", :syntax => :proto3) do
|
8
|
+
add_message "meshtastic.Paxcount" do
|
9
|
+
optional :wifi, :uint32, 1
|
10
|
+
optional :ble, :uint32, 2
|
11
|
+
optional :uptime, :uint32, 3
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
module Meshtastic
|
17
|
+
Paxcount = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("meshtastic.Paxcount").msgclass
|
18
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
2
|
+
# source: meshtastic/portnums.proto
|
3
|
+
|
4
|
+
require 'google/protobuf'
|
5
|
+
|
6
|
+
Google::Protobuf::DescriptorPool.generated_pool.build do
|
7
|
+
add_file("meshtastic/portnums.proto", :syntax => :proto3) do
|
8
|
+
add_enum "meshtastic.PortNum" do
|
9
|
+
value :UNKNOWN_APP, 0
|
10
|
+
value :TEXT_MESSAGE_APP, 1
|
11
|
+
value :REMOTE_HARDWARE_APP, 2
|
12
|
+
value :POSITION_APP, 3
|
13
|
+
value :NODEINFO_APP, 4
|
14
|
+
value :ROUTING_APP, 5
|
15
|
+
value :ADMIN_APP, 6
|
16
|
+
value :TEXT_MESSAGE_COMPRESSED_APP, 7
|
17
|
+
value :WAYPOINT_APP, 8
|
18
|
+
value :AUDIO_APP, 9
|
19
|
+
value :DETECTION_SENSOR_APP, 10
|
20
|
+
value :REPLY_APP, 32
|
21
|
+
value :IP_TUNNEL_APP, 33
|
22
|
+
value :PAXCOUNTER_APP, 34
|
23
|
+
value :SERIAL_APP, 64
|
24
|
+
value :STORE_FORWARD_APP, 65
|
25
|
+
value :RANGE_TEST_APP, 66
|
26
|
+
value :TELEMETRY_APP, 67
|
27
|
+
value :ZPS_APP, 68
|
28
|
+
value :SIMULATOR_APP, 69
|
29
|
+
value :TRACEROUTE_APP, 70
|
30
|
+
value :NEIGHBORINFO_APP, 71
|
31
|
+
value :ATAK_PLUGIN, 72
|
32
|
+
value :MAP_REPORT_APP, 73
|
33
|
+
value :PRIVATE_APP, 256
|
34
|
+
value :ATAK_FORWARDER, 257
|
35
|
+
value :MAX, 511
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
module Meshtastic
|
41
|
+
PortNum = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("meshtastic.PortNum").enummodule
|
42
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
2
|
+
# source: meshtastic/remote_hardware.proto
|
3
|
+
|
4
|
+
require 'google/protobuf'
|
5
|
+
|
6
|
+
Google::Protobuf::DescriptorPool.generated_pool.build do
|
7
|
+
add_file("meshtastic/remote_hardware.proto", :syntax => :proto3) do
|
8
|
+
add_message "meshtastic.HardwareMessage" do
|
9
|
+
optional :type, :enum, 1, "meshtastic.HardwareMessage.Type"
|
10
|
+
optional :gpio_mask, :uint64, 2
|
11
|
+
optional :gpio_value, :uint64, 3
|
12
|
+
end
|
13
|
+
add_enum "meshtastic.HardwareMessage.Type" do
|
14
|
+
value :UNSET, 0
|
15
|
+
value :WRITE_GPIOS, 1
|
16
|
+
value :WATCH_GPIOS, 2
|
17
|
+
value :GPIOS_CHANGED, 3
|
18
|
+
value :READ_GPIOS, 4
|
19
|
+
value :READ_GPIOS_REPLY, 5
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
module Meshtastic
|
25
|
+
HardwareMessage = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("meshtastic.HardwareMessage").msgclass
|
26
|
+
HardwareMessage::Type = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("meshtastic.HardwareMessage.Type").enummodule
|
27
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
2
|
+
# source: meshtastic/rtttl.proto
|
3
|
+
|
4
|
+
require 'google/protobuf'
|
5
|
+
|
6
|
+
Google::Protobuf::DescriptorPool.generated_pool.build do
|
7
|
+
add_file("meshtastic/rtttl.proto", :syntax => :proto3) do
|
8
|
+
add_message "meshtastic.RTTTLConfig" do
|
9
|
+
optional :ringtone, :string, 1
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
module Meshtastic
|
15
|
+
RTTTLConfig = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("meshtastic.RTTTLConfig").msgclass
|
16
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
2
|
+
# source: meshtastic/storeforward.proto
|
3
|
+
|
4
|
+
require 'google/protobuf'
|
5
|
+
|
6
|
+
Google::Protobuf::DescriptorPool.generated_pool.build do
|
7
|
+
add_file("meshtastic/storeforward.proto", :syntax => :proto3) do
|
8
|
+
add_message "meshtastic.StoreAndForward" do
|
9
|
+
optional :rr, :enum, 1, "meshtastic.StoreAndForward.RequestResponse"
|
10
|
+
oneof :variant do
|
11
|
+
optional :stats, :message, 2, "meshtastic.StoreAndForward.Statistics"
|
12
|
+
optional :history, :message, 3, "meshtastic.StoreAndForward.History"
|
13
|
+
optional :heartbeat, :message, 4, "meshtastic.StoreAndForward.Heartbeat"
|
14
|
+
optional :text, :bytes, 5
|
15
|
+
end
|
16
|
+
end
|
17
|
+
add_message "meshtastic.StoreAndForward.Statistics" do
|
18
|
+
optional :messages_total, :uint32, 1
|
19
|
+
optional :messages_saved, :uint32, 2
|
20
|
+
optional :messages_max, :uint32, 3
|
21
|
+
optional :up_time, :uint32, 4
|
22
|
+
optional :requests, :uint32, 5
|
23
|
+
optional :requests_history, :uint32, 6
|
24
|
+
optional :heartbeat, :bool, 7
|
25
|
+
optional :return_max, :uint32, 8
|
26
|
+
optional :return_window, :uint32, 9
|
27
|
+
end
|
28
|
+
add_message "meshtastic.StoreAndForward.History" do
|
29
|
+
optional :history_messages, :uint32, 1
|
30
|
+
optional :window, :uint32, 2
|
31
|
+
optional :last_request, :uint32, 3
|
32
|
+
end
|
33
|
+
add_message "meshtastic.StoreAndForward.Heartbeat" do
|
34
|
+
optional :period, :uint32, 1
|
35
|
+
optional :secondary, :uint32, 2
|
36
|
+
end
|
37
|
+
add_enum "meshtastic.StoreAndForward.RequestResponse" do
|
38
|
+
value :UNSET, 0
|
39
|
+
value :ROUTER_ERROR, 1
|
40
|
+
value :ROUTER_HEARTBEAT, 2
|
41
|
+
value :ROUTER_PING, 3
|
42
|
+
value :ROUTER_PONG, 4
|
43
|
+
value :ROUTER_BUSY, 5
|
44
|
+
value :ROUTER_HISTORY, 6
|
45
|
+
value :ROUTER_STATS, 7
|
46
|
+
value :ROUTER_TEXT_DIRECT, 8
|
47
|
+
value :ROUTER_TEXT_BROADCAST, 9
|
48
|
+
value :CLIENT_ERROR, 64
|
49
|
+
value :CLIENT_HISTORY, 65
|
50
|
+
value :CLIENT_STATS, 66
|
51
|
+
value :CLIENT_PING, 67
|
52
|
+
value :CLIENT_PONG, 68
|
53
|
+
value :CLIENT_ABORT, 106
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
module Meshtastic
|
59
|
+
StoreAndForward = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("meshtastic.StoreAndForward").msgclass
|
60
|
+
StoreAndForward::Statistics = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("meshtastic.StoreAndForward.Statistics").msgclass
|
61
|
+
StoreAndForward::History = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("meshtastic.StoreAndForward.History").msgclass
|
62
|
+
StoreAndForward::Heartbeat = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("meshtastic.StoreAndForward.Heartbeat").msgclass
|
63
|
+
StoreAndForward::RequestResponse = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("meshtastic.StoreAndForward.RequestResponse").enummodule
|
64
|
+
end
|