pwn 0.5.618 → 0.5.620
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/Gemfile +2 -2
- data/README.md +14 -6
- data/bin/pwn_gqrx_scanner +25 -5
- data/documentation/Agent-Tool-Registry.md +3 -3
- data/documentation/Diagrams.md +6 -2
- data/documentation/Extrospection.md +30 -11
- data/documentation/Home.md +5 -4
- data/documentation/How-PWN-Works.md +7 -4
- data/documentation/Mistakes.md +96 -0
- data/documentation/Persistence.md +3 -1
- data/documentation/SDR.md +23 -5
- data/documentation/Skills-Memory-Learning.md +15 -5
- data/documentation/diagrams/agent-tool-registry.svg +94 -92
- data/documentation/diagrams/dot/agent-tool-registry.dot +2 -2
- data/documentation/diagrams/dot/extrospection-world-awareness.dot +9 -7
- data/documentation/diagrams/dot/memory-skills-detailed.dot +12 -5
- data/documentation/diagrams/dot/mistakes-negative-feedback.dot +93 -0
- data/documentation/diagrams/dot/overall-pwn-architecture.dot +7 -5
- data/documentation/diagrams/dot/persistence-filesystem.dot +3 -2
- data/documentation/diagrams/dot/pwn-ai-feedback-learning-loop.dot +19 -7
- data/documentation/diagrams/dot/sdr-radio-flow.dot +13 -11
- data/documentation/diagrams/extrospection-world-awareness.svg +111 -93
- data/documentation/diagrams/memory-skills-detailed.svg +146 -97
- data/documentation/diagrams/mistakes-negative-feedback.svg +266 -0
- data/documentation/diagrams/overall-pwn-architecture.svg +184 -173
- data/documentation/diagrams/persistence-filesystem.svg +77 -62
- data/documentation/diagrams/pwn-ai-feedback-learning-loop.svg +217 -149
- data/documentation/diagrams/sdr-radio-flow.svg +76 -56
- data/documentation/pwn-ai-Agent.md +28 -15
- data/lib/pwn/ai/agent/extrospection.rb +65 -6
- data/lib/pwn/ai/agent/learning.rb +67 -7
- data/lib/pwn/ai/agent/loop.rb +72 -6
- data/lib/pwn/ai/agent/mistakes.rb +369 -0
- data/lib/pwn/ai/agent/prompt_builder.rb +10 -1
- data/lib/pwn/ai/agent/tools/extrospection.rb +2 -2
- data/lib/pwn/ai/agent/tools/mistakes.rb +132 -0
- data/lib/pwn/ai/agent.rb +1 -0
- data/lib/pwn/plugins/jira_data_center.rb +2 -2
- data/lib/pwn/sdr/decoder/adsb.rb +101 -0
- data/lib/pwn/sdr/decoder/apt.rb +75 -0
- data/lib/pwn/sdr/decoder/bluetooth.rb +78 -0
- data/lib/pwn/sdr/decoder/dect.rb +84 -0
- data/lib/pwn/sdr/decoder/gps.rb +83 -0
- data/lib/pwn/sdr/decoder/gsm.rb +50 -29
- data/lib/pwn/sdr/decoder/iridium.rb +81 -0
- data/lib/pwn/sdr/decoder/lora.rb +87 -0
- data/lib/pwn/sdr/decoder/lte.rb +88 -0
- data/lib/pwn/sdr/decoder/morse.rb +64 -0
- data/lib/pwn/sdr/decoder/p25.rb +71 -0
- data/lib/pwn/sdr/decoder/pager.rb +77 -0
- data/lib/pwn/sdr/decoder/pocsag.rb +26 -11
- data/lib/pwn/sdr/decoder/rfid.rb +88 -0
- data/lib/pwn/sdr/decoder/rtl433.rb +95 -0
- data/lib/pwn/sdr/decoder/rtty.rb +66 -0
- data/lib/pwn/sdr/decoder/wifi.rb +98 -0
- data/lib/pwn/sdr/decoder/zigbee.rb +96 -0
- data/lib/pwn/sdr/decoder.rb +67 -7
- data/lib/pwn/sdr/frequency_allocation.rb +82 -41
- data/lib/pwn/sdr/gqrx.rb +186 -95
- data/lib/pwn/version.rb +1 -1
- data/spec/lib/pwn/ai/agent/mistakes_spec.rb +36 -0
- data/spec/lib/pwn/ai/agent/tools/mistakes_spec.rb +12 -0
- data/spec/lib/pwn/sdr/decoder/adsb_spec.rb +15 -0
- data/spec/lib/pwn/sdr/decoder/apt_spec.rb +15 -0
- data/spec/lib/pwn/sdr/decoder/bluetooth_spec.rb +15 -0
- data/spec/lib/pwn/sdr/decoder/dect_spec.rb +15 -0
- data/spec/lib/pwn/sdr/decoder/gps_spec.rb +15 -0
- data/spec/lib/pwn/sdr/decoder/iridium_spec.rb +15 -0
- data/spec/lib/pwn/sdr/decoder/lora_spec.rb +15 -0
- data/spec/lib/pwn/sdr/decoder/lte_spec.rb +15 -0
- data/spec/lib/pwn/sdr/decoder/morse_spec.rb +15 -0
- data/spec/lib/pwn/sdr/decoder/p25_spec.rb +15 -0
- data/spec/lib/pwn/sdr/decoder/pager_spec.rb +15 -0
- data/spec/lib/pwn/sdr/decoder/rfid_spec.rb +15 -0
- data/spec/lib/pwn/sdr/decoder/rtl433_spec.rb +15 -0
- data/spec/lib/pwn/sdr/decoder/rtty_spec.rb +15 -0
- data/spec/lib/pwn/sdr/decoder/wifi_spec.rb +15 -0
- data/spec/lib/pwn/sdr/decoder/zigbee_spec.rb +15 -0
- data/third_party/pwn_rdoc.jsonl +92 -3
- metadata +44 -6
- data/README.md.bak +0 -200
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'shellwords'
|
|
4
|
+
|
|
5
|
+
module PWN
|
|
6
|
+
module SDR
|
|
7
|
+
module Decoder
|
|
8
|
+
# RFID decoder covering LF (125/134 kHz), HF (13.56 MHz) and UHF
|
|
9
|
+
# (860–960 MHz EPC Gen2). Near-field ASK/load-modulation is not
|
|
10
|
+
# recoverable from GQRX audio, so this drives a Proxmark3 (LF/HF) via
|
|
11
|
+
# `pm3 -c '...'` or `nfc-list` (HF), and `rtl_433` in flex/analyzer mode
|
|
12
|
+
# for UHF backscatter — whichever tool is present.
|
|
13
|
+
module RFID
|
|
14
|
+
# Supported Method Parameters::
|
|
15
|
+
# PWN::SDR::Decoder::RFID.decode(
|
|
16
|
+
# freq_obj: 'required - freq_obj returned from PWN::SDR::GQRX.init_freq'
|
|
17
|
+
# )
|
|
18
|
+
|
|
19
|
+
public_class_method def self.decode(opts = {})
|
|
20
|
+
freq_obj = opts[:freq_obj]
|
|
21
|
+
raise 'ERROR: :freq_obj is required' unless freq_obj.is_a?(Hash)
|
|
22
|
+
|
|
23
|
+
hz = PWN::SDR.hz_to_i(freq: freq_obj[:freq])
|
|
24
|
+
|
|
25
|
+
direct_cmd, bins, proto =
|
|
26
|
+
if hz < 1_000_000
|
|
27
|
+
["pm3 -c 'lf search'", %w[pm3], 'RFID-LF']
|
|
28
|
+
elsif hz.between?(13_000_000, 14_000_000)
|
|
29
|
+
if PWN::SDR::Decoder::Base.bin_available?(bin: 'pm3')
|
|
30
|
+
["pm3 -c 'hf search'", %w[pm3], 'RFID-HF']
|
|
31
|
+
else
|
|
32
|
+
['nfc-list -v', %w[nfc-list], 'RFID-HF']
|
|
33
|
+
end
|
|
34
|
+
else
|
|
35
|
+
["rtl_433 -f #{hz} -A -F json", %w[rtl_433], 'RFID-UHF']
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
PWN::SDR::Decoder::Base.run_pipeline(
|
|
39
|
+
freq_obj: freq_obj,
|
|
40
|
+
protocol: proto,
|
|
41
|
+
required_bins: bins,
|
|
42
|
+
direct_cmd: direct_cmd,
|
|
43
|
+
line_match: /(UID|EPC|TAG|ATQA|SAK|EM4|HID|ISO|Chipset|"model")/i,
|
|
44
|
+
parser: proc { |line| parse_line(line: line) }
|
|
45
|
+
)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Supported Method Parameters::
|
|
49
|
+
# PWN::SDR::Decoder::RFID.parse_line(line: 'UID: 04 A1 B2 C3 D4 E5 F6')
|
|
50
|
+
|
|
51
|
+
public_class_method def self.parse_line(opts = {})
|
|
52
|
+
line = opts[:line].to_s
|
|
53
|
+
out = { protocol: 'RFID' }
|
|
54
|
+
out[:uid] = ::Regexp.last_match(1).delete(' ') if line =~ /UID[:=]?\s*((?:[0-9A-Fa-f]{2}\s*){4,10})/
|
|
55
|
+
out[:epc] = ::Regexp.last_match(1) if line =~ /EPC[:=]?\s*([0-9A-Fa-f]+)/
|
|
56
|
+
out[:atqa] = ::Regexp.last_match(1) if line =~ /ATQA[:=]?\s*([0-9A-Fa-f ]+)/
|
|
57
|
+
out[:sak] = ::Regexp.last_match(1) if line =~ /SAK[:=]?\s*([0-9A-Fa-f]+)/
|
|
58
|
+
out[:tag] = ::Regexp.last_match(1) if line =~ /(EM4\w+|HID\w*|Mifare\w*|NTAG\w*|ISO\s?\d+)/i
|
|
59
|
+
out[:summary] = "RFID #{out[:tag]} UID=#{out[:uid] || out[:epc]}".squeeze(' ')
|
|
60
|
+
out.compact
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Author(s):: 0day Inc. <support@0dayinc.com>
|
|
64
|
+
|
|
65
|
+
public_class_method def self.authors
|
|
66
|
+
"AUTHOR(S):\n 0day Inc. <support@0dayinc.com>\n"
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Display Usage for this Module
|
|
70
|
+
|
|
71
|
+
public_class_method def self.help
|
|
72
|
+
puts "USAGE:
|
|
73
|
+
#{self}.decode(
|
|
74
|
+
freq_obj: 'required - freq_obj returned from PWN::SDR::GQRX.init_freq'
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
NOTE: LF/HF need a Proxmark3 (`pm3`) or libnfc reader; UHF uses
|
|
78
|
+
`rtl_433 -A`. Band chosen automatically from freq_obj[:freq].
|
|
79
|
+
|
|
80
|
+
#{self}.parse_line(line: 'UID: 04 A1 B2 C3 D4 E5 F6 SAK: 08 Mifare Classic 1K')
|
|
81
|
+
|
|
82
|
+
#{self}.authors
|
|
83
|
+
"
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
require 'shellwords'
|
|
5
|
+
|
|
6
|
+
module PWN
|
|
7
|
+
module SDR
|
|
8
|
+
module Decoder
|
|
9
|
+
# Generic ISM/keyfob/sensor decoder backed by `rtl_433`.
|
|
10
|
+
#
|
|
11
|
+
# Covers the OOK/ASK/FSK device zoo on 300–315 / 390 / 433.92 / 868 /
|
|
12
|
+
# 902–928 MHz: car/garage keyfobs, TPMS, weather stations, utility
|
|
13
|
+
# meters, doorbells, alarm PIRs, etc. rtl_433 owns the SDR directly and
|
|
14
|
+
# emits one JSON object per decoded frame (`-F json`), which this module
|
|
15
|
+
# merges verbatim into the freq_obj log line.
|
|
16
|
+
module RTL433
|
|
17
|
+
# Supported Method Parameters::
|
|
18
|
+
# PWN::SDR::Decoder::RTL433.decode(
|
|
19
|
+
# freq_obj: 'required - freq_obj returned from PWN::SDR::GQRX.init_freq'
|
|
20
|
+
# )
|
|
21
|
+
|
|
22
|
+
public_class_method def self.decode(opts = {})
|
|
23
|
+
freq_obj = opts[:freq_obj]
|
|
24
|
+
raise 'ERROR: :freq_obj is required' unless freq_obj.is_a?(Hash)
|
|
25
|
+
|
|
26
|
+
hz = PWN::SDR.hz_to_i(freq: freq_obj[:freq])
|
|
27
|
+
gain = freq_obj[:rf_gain]
|
|
28
|
+
sdr_args = freq_obj[:sdr_args].to_s
|
|
29
|
+
|
|
30
|
+
cmd = ['rtl_433', '-f', hz.to_s, '-F', 'json', '-M', 'level', '-M', 'protocol']
|
|
31
|
+
cmd.push('-g', gain.to_s) if gain
|
|
32
|
+
cmd.push('-d', sdr_args) unless sdr_args.empty?
|
|
33
|
+
direct_cmd = Shellwords.join(cmd)
|
|
34
|
+
|
|
35
|
+
PWN::SDR::Decoder::Base.run_pipeline(
|
|
36
|
+
freq_obj: freq_obj,
|
|
37
|
+
protocol: 'RTL433',
|
|
38
|
+
required_bins: %w[rtl_433],
|
|
39
|
+
direct_cmd: direct_cmd,
|
|
40
|
+
line_match: /^\s*{/,
|
|
41
|
+
parser: proc { |line| parse_line(line: line) }
|
|
42
|
+
)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Supported Method Parameters::
|
|
46
|
+
# PWN::SDR::Decoder::RTL433.parse_line(line: '{"time":"...","model":"..."}')
|
|
47
|
+
|
|
48
|
+
public_class_method def self.parse_line(opts = {})
|
|
49
|
+
line = opts[:line].to_s
|
|
50
|
+
h = begin
|
|
51
|
+
JSON.parse(line, symbolize_names: true)
|
|
52
|
+
rescue StandardError
|
|
53
|
+
{ unparsed: line }
|
|
54
|
+
end
|
|
55
|
+
out = { protocol: 'RTL433' }.merge(h)
|
|
56
|
+
|
|
57
|
+
bits = []
|
|
58
|
+
bits << out[:model].to_s if out[:model]
|
|
59
|
+
bits << "id=#{out[:id]}" if out[:id]
|
|
60
|
+
bits << "ch=#{out[:channel]}" if out[:channel]
|
|
61
|
+
bits << "code=#{out[:code]}" if out[:code]
|
|
62
|
+
bits << "cmd=#{out[:cmd] || out[:button]}" if out[:cmd] || out[:button]
|
|
63
|
+
bits << "temp=#{out[:temperature_C]}C" if out[:temperature_C]
|
|
64
|
+
bits << "rssi=#{out[:rssi]}" if out[:rssi]
|
|
65
|
+
out[:summary] = bits.empty? ? line[0, 120] : bits.join(' ')
|
|
66
|
+
out
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Author(s):: 0day Inc. <support@0dayinc.com>
|
|
70
|
+
|
|
71
|
+
public_class_method def self.authors
|
|
72
|
+
"AUTHOR(S):\n 0day Inc. <support@0dayinc.com>\n"
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Display Usage for this Module
|
|
76
|
+
|
|
77
|
+
public_class_method def self.help
|
|
78
|
+
puts "USAGE:
|
|
79
|
+
#{self}.decode(
|
|
80
|
+
freq_obj: 'required - freq_obj returned from PWN::SDR::GQRX.init_freq'
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
NOTE: Requires `rtl_433`. Owns the SDR directly (pass
|
|
84
|
+
freq_obj[:sdr_args] like ':1' or 'driver=hackrf' to select
|
|
85
|
+
a device other than the one GQRX is holding).
|
|
86
|
+
|
|
87
|
+
#{self}.parse_line(line: '{\"model\":\"Acurite-Tower\",\"id\":1234,...}')
|
|
88
|
+
|
|
89
|
+
#{self}.authors
|
|
90
|
+
"
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module PWN
|
|
4
|
+
module SDR
|
|
5
|
+
module Decoder
|
|
6
|
+
# RTTY (Radioteletype, ITA2/Baudot) decoder for the amateur RTTY
|
|
7
|
+
# sub-bands (rtty20/rtty40/rtty80). GQRX supplies FM-demodulated audio
|
|
8
|
+
# containing the classic 170 Hz-shift 45.45-baud mark/space tones;
|
|
9
|
+
# `minimodem` recovers the 5-bit Baudot stream and prints ASCII.
|
|
10
|
+
module RTTY
|
|
11
|
+
# Supported Method Parameters::
|
|
12
|
+
# PWN::SDR::Decoder::RTTY.decode(
|
|
13
|
+
# freq_obj: 'required - freq_obj returned from PWN::SDR::GQRX.init_freq'
|
|
14
|
+
# )
|
|
15
|
+
|
|
16
|
+
public_class_method def self.decode(opts = {})
|
|
17
|
+
freq_obj = opts[:freq_obj]
|
|
18
|
+
|
|
19
|
+
# minimodem reads s16le mono from stdin at the rate we hand it; keep
|
|
20
|
+
# sox at 22 050 Hz (Base default) and tell minimodem the same.
|
|
21
|
+
PWN::SDR::Decoder::Base.run_pipeline(
|
|
22
|
+
freq_obj: freq_obj,
|
|
23
|
+
protocol: 'RTTY',
|
|
24
|
+
required_bins: %w[sox minimodem],
|
|
25
|
+
decode_cmd: 'minimodem --rx --quiet --rtty -R 22050 -f -',
|
|
26
|
+
line_match: /\S/,
|
|
27
|
+
parser: proc { |line| parse_line(line: line) }
|
|
28
|
+
)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Supported Method Parameters::
|
|
32
|
+
# PWN::SDR::Decoder::RTTY.parse_line(line: 'RYRYRY DE W1AW')
|
|
33
|
+
|
|
34
|
+
public_class_method def self.parse_line(opts = {})
|
|
35
|
+
line = opts[:line].to_s.strip
|
|
36
|
+
out = { protocol: 'RTTY', text: line }
|
|
37
|
+
if (m = line.match(/\bDE\s+([A-Z0-9]{1,3}\d[A-Z]{1,4})\b/))
|
|
38
|
+
out[:callsign] = m[1]
|
|
39
|
+
end
|
|
40
|
+
out[:summary] = "RTTY #{line}"[0, 120]
|
|
41
|
+
out
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Author(s):: 0day Inc. <support@0dayinc.com>
|
|
45
|
+
|
|
46
|
+
public_class_method def self.authors
|
|
47
|
+
"AUTHOR(S):\n 0day Inc. <support@0dayinc.com>\n"
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Display Usage for this Module
|
|
51
|
+
|
|
52
|
+
public_class_method def self.help
|
|
53
|
+
puts "USAGE:
|
|
54
|
+
#{self}.decode(
|
|
55
|
+
freq_obj: 'required - freq_obj returned from PWN::SDR::GQRX.init_freq'
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
#{self}.parse_line(line: 'RYRYRY CQ CQ DE W1AW')
|
|
59
|
+
|
|
60
|
+
#{self}.authors
|
|
61
|
+
"
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'shellwords'
|
|
4
|
+
|
|
5
|
+
module PWN
|
|
6
|
+
module SDR
|
|
7
|
+
module Decoder
|
|
8
|
+
# 802.11 (WiFi) beacon/probe decoder for the 2.4 / 5 / 6 GHz bands.
|
|
9
|
+
#
|
|
10
|
+
# 20+ MHz OFDM cannot be recovered from a general-purpose SDR at
|
|
11
|
+
# bit-level in real time, so this module drives an existing monitor-mode
|
|
12
|
+
# NIC via `tshark` (or `airodump-ng`) instead — freq_obj[:mon_iface]
|
|
13
|
+
# selects the interface, freq_obj[:freq] is mapped to a WLAN channel.
|
|
14
|
+
module WiFi
|
|
15
|
+
TSHARK_FIELDS = %w[
|
|
16
|
+
frame.time_relative
|
|
17
|
+
wlan.fc.type_subtype
|
|
18
|
+
wlan.bssid
|
|
19
|
+
wlan.sa
|
|
20
|
+
wlan.da
|
|
21
|
+
wlan_radio.channel
|
|
22
|
+
wlan_radio.signal_dbm
|
|
23
|
+
wlan.ssid
|
|
24
|
+
].freeze
|
|
25
|
+
|
|
26
|
+
# Supported Method Parameters::
|
|
27
|
+
# PWN::SDR::Decoder::WiFi.decode(
|
|
28
|
+
# freq_obj: 'required - freq_obj returned from PWN::SDR::GQRX.init_freq'
|
|
29
|
+
# )
|
|
30
|
+
|
|
31
|
+
public_class_method def self.decode(opts = {})
|
|
32
|
+
freq_obj = opts[:freq_obj]
|
|
33
|
+
raise 'ERROR: :freq_obj is required' unless freq_obj.is_a?(Hash)
|
|
34
|
+
|
|
35
|
+
iface = (freq_obj[:mon_iface] || 'wlan0mon').to_s
|
|
36
|
+
hz = PWN::SDR.hz_to_i(freq: freq_obj[:freq])
|
|
37
|
+
mhz = (hz / 1_000_000.0).round
|
|
38
|
+
|
|
39
|
+
tshark = ['tshark', '-i', iface, '-I', '-l', '-n',
|
|
40
|
+
'-Y', 'wlan.fc.type == 0',
|
|
41
|
+
'-T', 'fields', '-E', 'separator=|']
|
|
42
|
+
TSHARK_FIELDS.each { |f| tshark.push('-e', f) }
|
|
43
|
+
|
|
44
|
+
inner = "iw dev #{Shellwords.escape(iface)} set freq #{mhz} 2>/dev/null; " \
|
|
45
|
+
"exec #{Shellwords.join(tshark)}"
|
|
46
|
+
direct_cmd = "bash -c #{Shellwords.escape(inner)}"
|
|
47
|
+
|
|
48
|
+
PWN::SDR::Decoder::Base.run_pipeline(
|
|
49
|
+
freq_obj: freq_obj,
|
|
50
|
+
protocol: 'WiFi-802.11',
|
|
51
|
+
required_bins: %w[tshark iw],
|
|
52
|
+
direct_cmd: direct_cmd,
|
|
53
|
+
line_match: /\S/,
|
|
54
|
+
parser: proc { |line| parse_line(line: line) }
|
|
55
|
+
)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Supported Method Parameters::
|
|
59
|
+
# PWN::SDR::Decoder::WiFi.parse_line(line: 't|subtype|bssid|sa|da|ch|rssi|ssid')
|
|
60
|
+
|
|
61
|
+
public_class_method def self.parse_line(opts = {})
|
|
62
|
+
f = opts[:line].to_s.split('|', -1)
|
|
63
|
+
out = {
|
|
64
|
+
protocol: 'WiFi',
|
|
65
|
+
subtype: f[1], bssid: f[2], sa: f[3], da: f[4],
|
|
66
|
+
channel: f[5], rssi_dbm: f[6], ssid: f[7]
|
|
67
|
+
}.reject { |_, v| v.to_s.empty? }
|
|
68
|
+
out[:summary] = "WiFi ch#{out[:channel]} #{out[:bssid]} '#{out[:ssid]}' #{out[:rssi_dbm]}dBm"
|
|
69
|
+
out
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Author(s):: 0day Inc. <support@0dayinc.com>
|
|
73
|
+
|
|
74
|
+
public_class_method def self.authors
|
|
75
|
+
"AUTHOR(S):\n 0day Inc. <support@0dayinc.com>\n"
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Display Usage for this Module
|
|
79
|
+
|
|
80
|
+
public_class_method def self.help
|
|
81
|
+
puts "USAGE:
|
|
82
|
+
#{self}.decode(
|
|
83
|
+
freq_obj: 'required - freq_obj returned from PWN::SDR::GQRX.init_freq'
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
NOTE: Requires a monitor-mode WLAN interface (freq_obj[:mon_iface],
|
|
87
|
+
default 'wlan0mon'), `iw`, and `tshark`. General SDRs cannot
|
|
88
|
+
demodulate 802.11 OFDM in real time.
|
|
89
|
+
|
|
90
|
+
#{self}.parse_line(line: '0.1|8|aa:bb:..|aa:bb:..|ff:ff:..|6|-42|linksys')
|
|
91
|
+
|
|
92
|
+
#{self}.authors
|
|
93
|
+
"
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'shellwords'
|
|
4
|
+
|
|
5
|
+
module PWN
|
|
6
|
+
module SDR
|
|
7
|
+
module Decoder
|
|
8
|
+
# IEEE 802.15.4 / ZigBee decoder for the 2.405–2.480 GHz allocation.
|
|
9
|
+
# 250 kbit/s O-QPSK requires raw I/Q; this drives KillerBee's `zbdump`
|
|
10
|
+
# (with any supported adapter — CC2531, ApiMote, RZUSB, HackRF via
|
|
11
|
+
# gr-ieee802-15-4) or falls back to `whsniff` piped through `tshark`
|
|
12
|
+
# for structured PAN/src/dst/cmd extraction.
|
|
13
|
+
module ZigBee
|
|
14
|
+
TSHARK_FIELDS = %w[
|
|
15
|
+
frame.time_relative
|
|
16
|
+
wpan.src16
|
|
17
|
+
wpan.dst16
|
|
18
|
+
wpan.src64
|
|
19
|
+
wpan.dst64
|
|
20
|
+
wpan.dst_pan
|
|
21
|
+
wpan.frame_type
|
|
22
|
+
zbee_nwk.cmd.id
|
|
23
|
+
wpan.seq_no
|
|
24
|
+
].freeze
|
|
25
|
+
|
|
26
|
+
# Supported Method Parameters::
|
|
27
|
+
# PWN::SDR::Decoder::ZigBee.decode(
|
|
28
|
+
# freq_obj: 'required - freq_obj returned from PWN::SDR::GQRX.init_freq'
|
|
29
|
+
# )
|
|
30
|
+
|
|
31
|
+
public_class_method def self.decode(opts = {})
|
|
32
|
+
freq_obj = opts[:freq_obj]
|
|
33
|
+
raise 'ERROR: :freq_obj is required' unless freq_obj.is_a?(Hash)
|
|
34
|
+
|
|
35
|
+
hz = PWN::SDR.hz_to_i(freq: freq_obj[:freq])
|
|
36
|
+
# 802.15.4 ch11..26 → 2405 + 5*(ch-11) MHz
|
|
37
|
+
ch = (((hz - 2_405_000_000) / 5_000_000.0).round + 11).clamp(11, 26)
|
|
38
|
+
|
|
39
|
+
tshark = ['tshark', '-r', '-', '-l', '-T', 'fields', '-E', 'separator=|']
|
|
40
|
+
TSHARK_FIELDS.each { |f| tshark.push('-e', f) }
|
|
41
|
+
|
|
42
|
+
inner = "whsniff -c #{ch} 2>/dev/null | #{Shellwords.join(tshark)}"
|
|
43
|
+
direct_cmd = "bash -c #{Shellwords.escape(inner)}"
|
|
44
|
+
|
|
45
|
+
PWN::SDR::Decoder::Base.run_pipeline(
|
|
46
|
+
freq_obj: freq_obj,
|
|
47
|
+
protocol: 'ZigBee',
|
|
48
|
+
required_bins: %w[whsniff tshark],
|
|
49
|
+
direct_cmd: direct_cmd,
|
|
50
|
+
line_match: /\S/,
|
|
51
|
+
parser: proc { |line| parse_line(line: line) }
|
|
52
|
+
)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Supported Method Parameters::
|
|
56
|
+
# PWN::SDR::Decoder::ZigBee.parse_line(line: 't|src16|dst16|src64|dst64|pan|type|cmd|seq')
|
|
57
|
+
|
|
58
|
+
public_class_method def self.parse_line(opts = {})
|
|
59
|
+
f = opts[:line].to_s.split('|', -1)
|
|
60
|
+
out = {
|
|
61
|
+
protocol: 'ZigBee',
|
|
62
|
+
src16: f[1], dst16: f[2],
|
|
63
|
+
src64: f[3], dst64: f[4],
|
|
64
|
+
pan_id: f[5], frame_type: f[6],
|
|
65
|
+
nwk_cmd: f[7], seq: f[8]
|
|
66
|
+
}.reject { |_, v| v.to_s.empty? }
|
|
67
|
+
out[:summary] = "ZigBee PAN=#{out[:pan_id]} #{out[:src16] || out[:src64]}→#{out[:dst16] || out[:dst64]} type=#{out[:frame_type]}"
|
|
68
|
+
out
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Author(s):: 0day Inc. <support@0dayinc.com>
|
|
72
|
+
|
|
73
|
+
public_class_method def self.authors
|
|
74
|
+
"AUTHOR(S):\n 0day Inc. <support@0dayinc.com>\n"
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Display Usage for this Module
|
|
78
|
+
|
|
79
|
+
public_class_method def self.help
|
|
80
|
+
puts "USAGE:
|
|
81
|
+
#{self}.decode(
|
|
82
|
+
freq_obj: 'required - freq_obj returned from PWN::SDR::GQRX.init_freq'
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
NOTE: Requires `whsniff` (CC2531) or KillerBee, plus `tshark`.
|
|
86
|
+
Channel is derived from freq_obj[:freq] (11–26).
|
|
87
|
+
|
|
88
|
+
#{self}.parse_line(line: '0.1|0x0001|0xffff||||0x01||42')
|
|
89
|
+
|
|
90
|
+
#{self}.authors
|
|
91
|
+
"
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
data/lib/pwn/sdr/decoder.rb
CHANGED
|
@@ -5,15 +5,73 @@ module PWN
|
|
|
5
5
|
# into memory only when they're needed. For more information, see:
|
|
6
6
|
# http://www.rubyinside.com/ruby-techniques-revealed-autoload-1652.html
|
|
7
7
|
module SDR
|
|
8
|
-
#
|
|
8
|
+
# Decoder namespace for SDR signals. Every child module exposes a
|
|
9
|
+
# uniform `.decode(freq_obj:)` entry point (see PWN::SDR::Decoder::Base)
|
|
10
|
+
# so PWN::SDR::GQRX can dispatch on a `decoder:` key from
|
|
11
|
+
# PWN::SDR::FrequencyAllocation.band_plans.
|
|
9
12
|
module Decoder
|
|
10
|
-
autoload :
|
|
11
|
-
autoload :
|
|
12
|
-
autoload :
|
|
13
|
-
autoload :
|
|
14
|
-
autoload :
|
|
13
|
+
autoload :ADSB, 'pwn/sdr/decoder/adsb'
|
|
14
|
+
autoload :APT, 'pwn/sdr/decoder/apt'
|
|
15
|
+
autoload :Base, 'pwn/sdr/decoder/base'
|
|
16
|
+
autoload :Bluetooth, 'pwn/sdr/decoder/bluetooth'
|
|
17
|
+
autoload :DECT, 'pwn/sdr/decoder/dect'
|
|
18
|
+
autoload :Flex, 'pwn/sdr/decoder/flex'
|
|
19
|
+
autoload :GPS, 'pwn/sdr/decoder/gps'
|
|
20
|
+
autoload :GSM, 'pwn/sdr/decoder/gsm'
|
|
21
|
+
autoload :Iridium, 'pwn/sdr/decoder/iridium'
|
|
22
|
+
autoload :LTE, 'pwn/sdr/decoder/lte'
|
|
23
|
+
autoload :LoRa, 'pwn/sdr/decoder/lora'
|
|
24
|
+
autoload :Morse, 'pwn/sdr/decoder/morse'
|
|
25
|
+
autoload :P25, 'pwn/sdr/decoder/p25'
|
|
26
|
+
autoload :POCSAG, 'pwn/sdr/decoder/pocsag'
|
|
27
|
+
autoload :Pager, 'pwn/sdr/decoder/pager'
|
|
28
|
+
autoload :RDS, 'pwn/sdr/decoder/rds'
|
|
29
|
+
autoload :RFID, 'pwn/sdr/decoder/rfid'
|
|
30
|
+
autoload :RTL433, 'pwn/sdr/decoder/rtl433'
|
|
31
|
+
autoload :RTTY, 'pwn/sdr/decoder/rtty'
|
|
32
|
+
autoload :WiFi, 'pwn/sdr/decoder/wifi'
|
|
33
|
+
autoload :ZigBee, 'pwn/sdr/decoder/zigbee'
|
|
15
34
|
|
|
16
|
-
#
|
|
35
|
+
# symbol → module map. Keys are what a band_plan's :decoder value (or
|
|
36
|
+
# PWN::SDR::GQRX.init_freq's decoder: kwarg) may be set to.
|
|
37
|
+
REGISTRY = {
|
|
38
|
+
adsb: :ADSB,
|
|
39
|
+
apt: :APT,
|
|
40
|
+
bluetooth: :Bluetooth,
|
|
41
|
+
dect: :DECT,
|
|
42
|
+
flex: :Flex,
|
|
43
|
+
gprs: :GSM,
|
|
44
|
+
gps: :GPS,
|
|
45
|
+
gsm: :GSM,
|
|
46
|
+
iridium: :Iridium,
|
|
47
|
+
ism: :RTL433,
|
|
48
|
+
keyfob: :RTL433,
|
|
49
|
+
lora: :LoRa,
|
|
50
|
+
lte: :LTE,
|
|
51
|
+
morse: :Morse,
|
|
52
|
+
p25: :P25,
|
|
53
|
+
pager: :Pager,
|
|
54
|
+
pocsag: :POCSAG,
|
|
55
|
+
rds: :RDS,
|
|
56
|
+
rfid: :RFID,
|
|
57
|
+
rtl433: :RTL433,
|
|
58
|
+
rtty: :RTTY,
|
|
59
|
+
wifi: :WiFi,
|
|
60
|
+
zigbee: :ZigBee
|
|
61
|
+
}.freeze
|
|
62
|
+
|
|
63
|
+
# Supported Method Parameters::
|
|
64
|
+
# mod = PWN::SDR::Decoder.resolve(
|
|
65
|
+
# decoder: 'required - Symbol/String key from REGISTRY (e.g. :pocsag)'
|
|
66
|
+
# )
|
|
67
|
+
|
|
68
|
+
public_class_method def self.resolve(opts = {})
|
|
69
|
+
key = opts[:decoder].to_s.downcase.to_sym
|
|
70
|
+
const = REGISTRY[key]
|
|
71
|
+
raise "ERROR: Unknown decoder key #{key.inspect}. Supported: #{REGISTRY.keys.sort.join(', ')}" unless const
|
|
72
|
+
|
|
73
|
+
const_get(const)
|
|
74
|
+
end
|
|
17
75
|
|
|
18
76
|
# Author(s):: 0day Inc. <support@0dayinc.com>
|
|
19
77
|
|
|
@@ -21,6 +79,8 @@ module PWN
|
|
|
21
79
|
"AUTHOR(S):\n 0day Inc. <support@0dayinc.com>\n"
|
|
22
80
|
end
|
|
23
81
|
|
|
82
|
+
# Display a List of Every PWN::SDR::Decoder Module
|
|
83
|
+
|
|
24
84
|
public_class_method def self.help
|
|
25
85
|
constants.sort
|
|
26
86
|
end
|