pwn 0.5.617 → 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 +101 -83
- data/bin/pwn_gqrx_scanner +25 -5
- data/documentation/AI-Integration.md +42 -26
- data/documentation/AWS.md +57 -0
- data/documentation/Agent-Tool-Registry.md +56 -0
- data/documentation/Banner.md +17 -0
- data/documentation/Blockchain.md +18 -0
- data/documentation/Bounty.md +21 -0
- data/documentation/BurpSuite.md +41 -16
- data/documentation/CLI-Drivers.md +58 -0
- data/documentation/Configuration.md +66 -0
- data/documentation/Contributing.md +33 -19
- data/documentation/Cron.md +47 -0
- data/documentation/Diagrams.md +6 -2
- data/documentation/Drivers.md +43 -16
- data/documentation/Extrospection.md +73 -0
- data/documentation/FFI.md +14 -0
- data/documentation/Fuzzing.md +36 -0
- data/documentation/Hardware.md +40 -0
- data/documentation/Home.md +5 -4
- data/documentation/How-PWN-Works.md +7 -4
- data/documentation/Metasploit.md +34 -0
- data/documentation/Mistakes.md +96 -0
- data/documentation/NmapIt.md +18 -12
- data/documentation/PWN.png +0 -0
- data/documentation/PWN_Contributors_and_Users.png +0 -0
- data/documentation/Persistence.md +40 -0
- data/documentation/Plugins.md +97 -47
- data/documentation/Reporting.md +25 -18
- data/documentation/SAST.md +39 -22
- data/documentation/SDR.md +58 -0
- data/documentation/Sessions.md +39 -0
- data/documentation/Skills-Memory-Learning.md +59 -29
- data/documentation/Swarm.md +71 -0
- data/documentation/Transparent-Browser.md +26 -22
- data/documentation/Troubleshooting.md +44 -25
- data/documentation/WWW.md +32 -0
- data/documentation/diagrams/agent-tool-registry.svg +286 -0
- data/documentation/diagrams/aws-cloud-security.svg +166 -0
- data/documentation/diagrams/cron-scheduling.svg +148 -0
- data/documentation/diagrams/dot/agent-tool-registry.dot +2 -2
- data/documentation/diagrams/dot/extrospection-world-awareness.dot +9 -7
- data/documentation/diagrams/dot/hardware-hacking.dot +48 -0
- 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 +30 -0
- 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 +203 -0
- data/documentation/diagrams/hardware-hacking.svg +163 -0
- 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 +189 -0
- data/documentation/diagrams/pwn-ai-feedback-learning-loop.svg +217 -149
- data/documentation/diagrams/sdr-radio-flow.svg +166 -0
- data/documentation/diagrams/swarm-multi-agent.svg +225 -0
- data/documentation/fax-spectrogram.png +0 -0
- data/documentation/fax-waveform.png +0 -0
- data/documentation/pwn-REPL.md +40 -24
- data/documentation/pwn-ai-Agent.md +72 -30
- data/documentation/pwn_android_war_dialer_session.png +0 -0
- data/documentation/pwn_install.png +0 -0
- data/documentation/pwn_wallpaper.jpg +0 -0
- data/documentation/ringing-spectrogram.png +0 -0
- data/documentation/ringing-waveform.png +0 -0
- 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 +114 -8
- metadata +81 -5
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'shellwords'
|
|
4
|
+
|
|
5
|
+
module PWN
|
|
6
|
+
module SDR
|
|
7
|
+
module Decoder
|
|
8
|
+
# LTE / UMTS / CDMA / PCS / AWS wideband cellular cell-search decoder.
|
|
9
|
+
#
|
|
10
|
+
# 1.4–20 MHz OFDMA/WCDMA cannot be recovered from GQRX audio; this
|
|
11
|
+
# module drives srsRAN's `cell_search` (or `LTE-Cell-Scanner`'s
|
|
12
|
+
# `CellSearch`) directly against the SDR to enumerate physical cells:
|
|
13
|
+
# EARFCN, PCI, CP type, RSRP/RSRQ, PSS/SSS correlation.
|
|
14
|
+
module LTE
|
|
15
|
+
# Supported Method Parameters::
|
|
16
|
+
# PWN::SDR::Decoder::LTE.decode(
|
|
17
|
+
# freq_obj: 'required - freq_obj returned from PWN::SDR::GQRX.init_freq'
|
|
18
|
+
# )
|
|
19
|
+
|
|
20
|
+
public_class_method def self.decode(opts = {})
|
|
21
|
+
freq_obj = opts[:freq_obj]
|
|
22
|
+
raise 'ERROR: :freq_obj is required' unless freq_obj.is_a?(Hash)
|
|
23
|
+
|
|
24
|
+
hz = PWN::SDR.hz_to_i(freq: freq_obj[:freq])
|
|
25
|
+
gain = (freq_obj[:rf_gain] || 40).to_s.to_f
|
|
26
|
+
sdr_args = (freq_obj[:sdr_args] || 'driver=rtlsdr').to_s
|
|
27
|
+
|
|
28
|
+
direct_cmd =
|
|
29
|
+
if PWN::SDR::Decoder::Base.bin_available?(bin: 'cell_search')
|
|
30
|
+
# srsRAN cell_search takes a band number; fall back to explicit
|
|
31
|
+
# DL frequency via -s/-e range when unavailable.
|
|
32
|
+
"cell_search -a #{Shellwords.escape(sdr_args)} -g #{gain} -s #{hz} -e #{hz}"
|
|
33
|
+
else
|
|
34
|
+
"CellSearch -s #{hz / 1_000_000.0} -e #{hz / 1_000_000.0} -g #{gain}"
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
PWN::SDR::Decoder::Base.run_pipeline(
|
|
38
|
+
freq_obj: freq_obj,
|
|
39
|
+
protocol: 'LTE',
|
|
40
|
+
required_bins: %w[cell_search],
|
|
41
|
+
direct_cmd: direct_cmd,
|
|
42
|
+
line_match: /(CELL|PCI|EARFCN|Found|RSRP|CID)/i,
|
|
43
|
+
parser: proc { |line| parse_line(line: line) }
|
|
44
|
+
)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Supported Method Parameters::
|
|
48
|
+
# PWN::SDR::Decoder::LTE.parse_line(line: 'Found CELL ... PCI: 123 ... PRB: 50 ... -85.0 dBm')
|
|
49
|
+
|
|
50
|
+
public_class_method def self.parse_line(opts = {})
|
|
51
|
+
line = opts[:line].to_s
|
|
52
|
+
out = { protocol: 'LTE' }
|
|
53
|
+
out[:earfcn] = ::Regexp.last_match(1) if line =~ /EARFCN[:= ]+(\d+)/i
|
|
54
|
+
out[:pci] = ::Regexp.last_match(1) if line =~ /(?:PCI|N_id_cell|Id)[:= ]+(\d{1,3})/i
|
|
55
|
+
out[:prb] = (::Regexp.last_match(1) || ::Regexp.last_match(2)) if line =~ /(?:PRB[:= ]+(\d+)|(\d+)\s*PRB)/i
|
|
56
|
+
out[:cp] = ::Regexp.last_match(1) if line =~ /\b(Normal|Extended)\b\s*CP/i
|
|
57
|
+
out[:rsrp] = ::Regexp.last_match(1) if line =~ /(-?\d+(?:\.\d+)?)\s*dBm/
|
|
58
|
+
out[:freq_mhz] = ::Regexp.last_match(1) if line =~ /(\d{3,4}\.\d)\s*MHz/
|
|
59
|
+
out[:summary] = "LTE PCI=#{out[:pci]} EARFCN=#{out[:earfcn]} PRB=#{out[:prb]} RSRP=#{out[:rsrp]}dBm"
|
|
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: Requires srsRAN `cell_search` (or LTE-Cell-Scanner
|
|
78
|
+
`CellSearch`). Owns the SDR — set freq_obj[:sdr_args].
|
|
79
|
+
|
|
80
|
+
#{self}.parse_line(line: 'Found CELL 739.0 MHz, EARFCN=5110, PCI=123, 50 PRB, -85.2 dBm')
|
|
81
|
+
|
|
82
|
+
#{self}.authors
|
|
83
|
+
"
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module PWN
|
|
4
|
+
module SDR
|
|
5
|
+
module Decoder
|
|
6
|
+
# CW / Morse decoder for the amateur CW sub-bands (cw20/cw40/cw80,
|
|
7
|
+
# amateur_30m). GQRX's CW demodulator produces a ~700 Hz sidetone in the
|
|
8
|
+
# 48 kHz UDP audio stream; multimon-ng's MORSE_CW demod recovers the
|
|
9
|
+
# dit/dah timing and prints decoded characters one line at a time.
|
|
10
|
+
module Morse
|
|
11
|
+
# Supported Method Parameters::
|
|
12
|
+
# PWN::SDR::Decoder::Morse.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
|
+
PWN::SDR::Decoder::Base.run_pipeline(
|
|
20
|
+
freq_obj: freq_obj,
|
|
21
|
+
protocol: 'MORSE-CW',
|
|
22
|
+
required_bins: %w[sox multimon-ng],
|
|
23
|
+
decode_cmd: 'multimon-ng -q -t raw -a MORSE_CW -',
|
|
24
|
+
line_match: /\S/,
|
|
25
|
+
parser: proc { |line| parse_line(line: line) }
|
|
26
|
+
)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Supported Method Parameters::
|
|
30
|
+
# PWN::SDR::Decoder::Morse.parse_line(line: 'CQ CQ DE W1AW')
|
|
31
|
+
|
|
32
|
+
public_class_method def self.parse_line(opts = {})
|
|
33
|
+
line = opts[:line].to_s.gsub(/^MORSE(?:_CW)?:\s*/i, '').strip
|
|
34
|
+
out = { protocol: 'MORSE-CW', text: line }
|
|
35
|
+
if (m = line.match(/\b([A-Z0-9]{1,3}\d[A-Z]{1,4})\b/))
|
|
36
|
+
out[:callsign] = m[1]
|
|
37
|
+
end
|
|
38
|
+
out[:summary] = "CW #{line}"[0, 120]
|
|
39
|
+
out
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Author(s):: 0day Inc. <support@0dayinc.com>
|
|
43
|
+
|
|
44
|
+
public_class_method def self.authors
|
|
45
|
+
"AUTHOR(S):\n 0day Inc. <support@0dayinc.com>\n"
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Display Usage for this Module
|
|
49
|
+
|
|
50
|
+
public_class_method def self.help
|
|
51
|
+
puts "USAGE:
|
|
52
|
+
#{self}.decode(
|
|
53
|
+
freq_obj: 'required - freq_obj returned from PWN::SDR::GQRX.init_freq'
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
#{self}.parse_line(line: 'CQ CQ DE W1AW W1AW K')
|
|
57
|
+
|
|
58
|
+
#{self}.authors
|
|
59
|
+
"
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module PWN
|
|
4
|
+
module SDR
|
|
5
|
+
module Decoder
|
|
6
|
+
# APCO Project 25 (P25) Phase-1 C4FM decoder for the 700/800 MHz
|
|
7
|
+
# public-safety allocations. GQRX supplies NBFM-discriminator audio via
|
|
8
|
+
# the UDP tap; `dsd` (Digital Speech Decoder) recovers the C4FM symbol
|
|
9
|
+
# stream and prints trunking-control frames (NAC, TGID, RID, DUID, ...).
|
|
10
|
+
module P25
|
|
11
|
+
# Supported Method Parameters::
|
|
12
|
+
# PWN::SDR::Decoder::P25.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
|
+
# dsd expects 48 kHz s16le on stdin — bypass Base's 22 050 Hz resample
|
|
20
|
+
# by asking for 48 000 (sox becomes a passthrough / format guard).
|
|
21
|
+
PWN::SDR::Decoder::Base.run_pipeline(
|
|
22
|
+
freq_obj: freq_obj,
|
|
23
|
+
protocol: 'P25',
|
|
24
|
+
required_bins: %w[sox dsd],
|
|
25
|
+
resample_hz: 48_000,
|
|
26
|
+
decode_cmd: 'dsd -q -i - -o /dev/null -f1',
|
|
27
|
+
line_match: /(NAC|TGID|TG:|RID|src:|P25|Sync:)/i,
|
|
28
|
+
parser: proc { |line| parse_line(line: line) }
|
|
29
|
+
)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Supported Method Parameters::
|
|
33
|
+
# PWN::SDR::Decoder::P25.parse_line(line: 'Sync: +P25p1 NAC: 293 src: 1234 tg: 5678')
|
|
34
|
+
|
|
35
|
+
public_class_method def self.parse_line(opts = {})
|
|
36
|
+
line = opts[:line].to_s
|
|
37
|
+
out = { protocol: 'P25' }
|
|
38
|
+
out[:nac] = ::Regexp.last_match(1) if line =~ /NAC:\s*([0-9A-Fa-f]+)/
|
|
39
|
+
out[:talkgroup] = ::Regexp.last_match(1) if line =~ /(?:TGID|tg[: ])\s*(\d+)/i
|
|
40
|
+
out[:radio_id] = ::Regexp.last_match(1) if line =~ /(?:RID|src[: ])\s*(\d+)/i
|
|
41
|
+
out[:duid] = ::Regexp.last_match(1) if line =~ /DUID:\s*(\S+)/
|
|
42
|
+
out[:sync] = ::Regexp.last_match(1) if line =~ /Sync:\s*(\S+)/
|
|
43
|
+
out[:summary] = "P25 NAC=#{out[:nac]} TG=#{out[:talkgroup]} RID=#{out[:radio_id]}".squeeze(' ')
|
|
44
|
+
out.compact
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Author(s):: 0day Inc. <support@0dayinc.com>
|
|
48
|
+
|
|
49
|
+
public_class_method def self.authors
|
|
50
|
+
"AUTHOR(S):\n 0day Inc. <support@0dayinc.com>\n"
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Display Usage for this Module
|
|
54
|
+
|
|
55
|
+
public_class_method def self.help
|
|
56
|
+
puts "USAGE:
|
|
57
|
+
#{self}.decode(
|
|
58
|
+
freq_obj: 'required - freq_obj returned from PWN::SDR::GQRX.init_freq'
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
NOTE: Requires `dsd`. Set GQRX demod to Narrow FM, ~12.5 kHz.
|
|
62
|
+
|
|
63
|
+
#{self}.parse_line(line: 'Sync: +P25p1 NAC: 293 src: 1234 tg: 5678')
|
|
64
|
+
|
|
65
|
+
#{self}.authors
|
|
66
|
+
"
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module PWN
|
|
4
|
+
module SDR
|
|
5
|
+
module Decoder
|
|
6
|
+
# Combined pager decoder for the mixed-protocol `pager_all` band plan.
|
|
7
|
+
#
|
|
8
|
+
# Runs multimon-ng with FLEX + FLEX_NEXT + POCSAG512/1200/2400 enabled
|
|
9
|
+
# simultaneously and dispatches each output line to the appropriate
|
|
10
|
+
# per-protocol parser (::Flex-style or ::POCSAG.parse_line).
|
|
11
|
+
module Pager
|
|
12
|
+
# Supported Method Parameters::
|
|
13
|
+
# PWN::SDR::Decoder::Pager.decode(
|
|
14
|
+
# freq_obj: 'required - freq_obj returned from PWN::SDR::GQRX.init_freq'
|
|
15
|
+
# )
|
|
16
|
+
|
|
17
|
+
public_class_method def self.decode(opts = {})
|
|
18
|
+
freq_obj = opts[:freq_obj]
|
|
19
|
+
|
|
20
|
+
decode_cmd = 'multimon-ng -q -t raw -e -u -p ' \
|
|
21
|
+
'-a FLEX -a FLEX_NEXT ' \
|
|
22
|
+
'-a POCSAG512 -a POCSAG1200 -a POCSAG2400 -'
|
|
23
|
+
|
|
24
|
+
PWN::SDR::Decoder::Base.run_pipeline(
|
|
25
|
+
freq_obj: freq_obj,
|
|
26
|
+
protocol: 'PAGER',
|
|
27
|
+
required_bins: %w[sox multimon-ng],
|
|
28
|
+
decode_cmd: decode_cmd,
|
|
29
|
+
line_match: /^(FLEX|POCSAG)/,
|
|
30
|
+
parser: proc { |line| parse_line(line: line) }
|
|
31
|
+
)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Supported Method Parameters::
|
|
35
|
+
# PWN::SDR::Decoder::Pager.parse_line(line: 'FLEX|... or POCSAG1200: ...')
|
|
36
|
+
|
|
37
|
+
public_class_method def self.parse_line(opts = {})
|
|
38
|
+
line = opts[:line].to_s
|
|
39
|
+
return PWN::SDR::Decoder::POCSAG.parse_line(line: line) if line.start_with?('POCSAG')
|
|
40
|
+
|
|
41
|
+
# FLEX / FLEX_NEXT — pipe- or space-delimited (see ::Flex for format).
|
|
42
|
+
delim = line.start_with?('FLEX: ') ? ' ' : '|'
|
|
43
|
+
parts = line.split(delim)
|
|
44
|
+
proto = line.start_with?('FLEX_NEXT') ? 'FLEX_NEXT' : 'FLEX'
|
|
45
|
+
types = %w[ALN BIN HEX NUM TON TONE UNK]
|
|
46
|
+
t_idx = parts.index { |p| types.include?(p) }
|
|
47
|
+
out = { protocol: proto, raw_inspected: line.inspect }
|
|
48
|
+
out[:capcode] = parts.find { |p| p.match?(/^\[?\d{7,10}\]?$/) }
|
|
49
|
+
out[:type] = t_idx ? parts[t_idx] : nil
|
|
50
|
+
out[:type_payload] = t_idx ? parts[(t_idx + 1)..].join(delim) : nil
|
|
51
|
+
out[:summary] = "#{proto} RIC=#{out[:capcode]} #{out[:type]}: #{out[:type_payload]}"
|
|
52
|
+
out.compact
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Author(s):: 0day Inc. <support@0dayinc.com>
|
|
56
|
+
|
|
57
|
+
public_class_method def self.authors
|
|
58
|
+
"AUTHOR(S):\n 0day Inc. <support@0dayinc.com>\n"
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Display Usage for this Module
|
|
62
|
+
|
|
63
|
+
public_class_method def self.help
|
|
64
|
+
puts "USAGE:
|
|
65
|
+
#{self}.decode(
|
|
66
|
+
freq_obj: 'required - freq_obj returned from PWN::SDR::GQRX.init_freq'
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
#{self}.parse_line(line: 'FLEX|3200|...|ALN|MESSAGE')
|
|
70
|
+
|
|
71
|
+
#{self}.authors
|
|
72
|
+
"
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
@@ -5,7 +5,8 @@ module PWN
|
|
|
5
5
|
module Decoder
|
|
6
6
|
# POCSAG (CCIR Radiopaging Code No. 1) decoder for pager networks.
|
|
7
7
|
#
|
|
8
|
-
# Pipeline
|
|
8
|
+
# Pipeline (identical shape to PWN::SDR::Decoder::Flex):
|
|
9
|
+
# GQRX 48 kHz s16le UDP audio → sox → 22 050 Hz → multimon-ng.
|
|
9
10
|
#
|
|
10
11
|
# multimon-ng emits lines of the form:
|
|
11
12
|
# POCSAG1200: Address: 123456 Function: 3 Alpha: FIRE ALARM ZONE 4
|
|
@@ -13,8 +14,8 @@ module PWN
|
|
|
13
14
|
# POCSAG2400: Address: 987654 Function: 1 Skyper: ...
|
|
14
15
|
# POCSAG1200: Address: 123456 Function: 2 Alpha: <partial><EOT>
|
|
15
16
|
#
|
|
16
|
-
#
|
|
17
|
-
# function_desc, type, type_desc, message } and JSON-
|
|
17
|
+
# Each line is parsed into { protocol, baud, address/capcode, function,
|
|
18
|
+
# function_desc, type, type_desc, message } and JSON-logged.
|
|
18
19
|
module POCSAG
|
|
19
20
|
FUNCTION_DESC = {
|
|
20
21
|
0 => 'Numeric (Tone/A)',
|
|
@@ -38,12 +39,19 @@ module PWN
|
|
|
38
39
|
public_class_method def self.decode(opts = {})
|
|
39
40
|
freq_obj = opts[:freq_obj]
|
|
40
41
|
|
|
42
|
+
# -e hide empty messages
|
|
43
|
+
# -u heuristically prune unlikely decodes (cuts BCH false-positives)
|
|
44
|
+
# -p emit partially received messages (still useful intel)
|
|
45
|
+
# -f alpha force Alpha framing when function bits are ambiguous —
|
|
46
|
+
# mirrors the behaviour operators expect from Flex ALN.
|
|
47
|
+
decode_cmd = 'multimon-ng -q -t raw -e -u -p -f alpha ' \
|
|
48
|
+
'-a POCSAG512 -a POCSAG1200 -a POCSAG2400 -'
|
|
49
|
+
|
|
41
50
|
PWN::SDR::Decoder::Base.run_pipeline(
|
|
42
51
|
freq_obj: freq_obj,
|
|
43
52
|
protocol: 'POCSAG',
|
|
44
53
|
required_bins: %w[sox multimon-ng],
|
|
45
|
-
decode_cmd:
|
|
46
|
-
'-a POCSAG512 -a POCSAG1200 -a POCSAG2400 -f auto -',
|
|
54
|
+
decode_cmd: decode_cmd,
|
|
47
55
|
line_match: /^POCSAG(?:512|1200|2400):/,
|
|
48
56
|
parser: proc { |line| parse_line(line: line) }
|
|
49
57
|
)
|
|
@@ -54,14 +62,14 @@ module PWN
|
|
|
54
62
|
|
|
55
63
|
public_class_method def self.parse_line(opts = {})
|
|
56
64
|
line = opts[:line].to_s
|
|
57
|
-
out = { protocol: 'POCSAG' }
|
|
65
|
+
out = { protocol: 'POCSAG', raw_inspected: line.inspect }
|
|
58
66
|
|
|
59
67
|
if (m = line.match(/^POCSAG(\d+):/))
|
|
60
68
|
out[:baud] = m[1].to_i
|
|
61
69
|
end
|
|
62
70
|
if (m = line.match(/Address:\s*(\d+)/i))
|
|
63
71
|
out[:address] = m[1].to_i
|
|
64
|
-
out[:capcode] = m[1]
|
|
72
|
+
out[:capcode] = m[1].rjust(7, '0')
|
|
65
73
|
end
|
|
66
74
|
if (m = line.match(/Function:\s*(\d+)/i))
|
|
67
75
|
fn = m[1].to_i
|
|
@@ -70,11 +78,18 @@ module PWN
|
|
|
70
78
|
end
|
|
71
79
|
if (m = line.match(/\b(Alpha|Numeric|Skyper|Tone):\s*(.*)$/i))
|
|
72
80
|
type = m[1].capitalize
|
|
73
|
-
out[:type]
|
|
74
|
-
out[:type_desc]
|
|
75
|
-
out[:
|
|
81
|
+
out[:type] = type
|
|
82
|
+
out[:type_desc] = TYPE_DESC[type] || 'Unknown'
|
|
83
|
+
out[:type_payload] = m[2].to_s.gsub(/<[A-Z]{2,4}>/, '').strip
|
|
84
|
+
out[:message] = out[:type_payload]
|
|
76
85
|
end
|
|
77
|
-
|
|
86
|
+
|
|
87
|
+
bits = []
|
|
88
|
+
bits << "POCSAG#{out[:baud]}" if out[:baud]
|
|
89
|
+
bits << "RIC=#{out[:capcode]}" if out[:capcode]
|
|
90
|
+
bits << "F#{out[:function]}(#{out[:function_desc]})" if out[:function]
|
|
91
|
+
bits << "#{out[:type]}: #{out[:message]}" if out[:type]
|
|
92
|
+
out[:summary] = bits.join(' ')
|
|
78
93
|
out
|
|
79
94
|
end
|
|
80
95
|
|
|
@@ -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
|