pwn 0.5.618 → 0.5.621

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.
Files changed (107) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +2 -2
  3. data/README.md +14 -6
  4. data/bin/pwn_gqrx_scanner +25 -5
  5. data/documentation/Agent-Tool-Registry.md +4 -4
  6. data/documentation/Cron.md +13 -0
  7. data/documentation/Diagrams.md +6 -2
  8. data/documentation/Extrospection.md +96 -11
  9. data/documentation/Home.md +5 -4
  10. data/documentation/How-PWN-Works.md +7 -4
  11. data/documentation/Mistakes.md +97 -0
  12. data/documentation/Persistence.md +4 -1
  13. data/documentation/SDR.md +23 -5
  14. data/documentation/Skills-Memory-Learning.md +16 -5
  15. data/documentation/Transparent-Browser.md +18 -1
  16. data/documentation/diagrams/agent-tool-registry.svg +103 -100
  17. data/documentation/diagrams/ai-integration-tool-calling.svg +24 -24
  18. data/documentation/diagrams/aws-cloud-security.svg +25 -25
  19. data/documentation/diagrams/burp-vs-zap-preference.svg +12 -12
  20. data/documentation/diagrams/code-scanning-sast.svg +25 -25
  21. data/documentation/diagrams/cron-scheduling.svg +23 -23
  22. data/documentation/diagrams/dot/agent-tool-registry.dot +3 -3
  23. data/documentation/diagrams/dot/extrospection-world-awareness.dot +33 -10
  24. data/documentation/diagrams/dot/memory-skills-detailed.dot +16 -5
  25. data/documentation/diagrams/dot/mistakes-negative-feedback.dot +93 -0
  26. data/documentation/diagrams/dot/overall-pwn-architecture.dot +7 -5
  27. data/documentation/diagrams/dot/persistence-filesystem.dot +4 -2
  28. data/documentation/diagrams/dot/pwn-ai-feedback-learning-loop.dot +25 -7
  29. data/documentation/diagrams/dot/sdr-radio-flow.dot +13 -11
  30. data/documentation/diagrams/driver-framework.svg +13 -13
  31. data/documentation/diagrams/extrospection-world-awareness.svg +241 -93
  32. data/documentation/diagrams/fuzzing-workflow.svg +24 -24
  33. data/documentation/diagrams/hardware-hacking.svg +18 -18
  34. data/documentation/diagrams/history-to-drivers.svg +18 -18
  35. data/documentation/diagrams/memory-skills-detailed.svg +169 -97
  36. data/documentation/diagrams/mistakes-negative-feedback.svg +266 -0
  37. data/documentation/diagrams/network-infra-testing.svg +27 -27
  38. data/documentation/diagrams/overall-pwn-architecture.svg +184 -173
  39. data/documentation/diagrams/penetration-testing-workflow.svg +30 -30
  40. data/documentation/diagrams/persistence-filesystem.svg +111 -87
  41. data/documentation/diagrams/plugin-ecosystem.svg +35 -35
  42. data/documentation/diagrams/pwn-ai-feedback-learning-loop.svg +263 -149
  43. data/documentation/diagrams/pwn-repl-prototyping.svg +20 -20
  44. data/documentation/diagrams/reporting-pipeline.svg +18 -18
  45. data/documentation/diagrams/reverse-engineering-flow.svg +21 -21
  46. data/documentation/diagrams/sdr-radio-flow.svg +76 -56
  47. data/documentation/diagrams/sessions-cron-automation.svg +18 -18
  48. data/documentation/diagrams/swarm-multi-agent.svg +39 -39
  49. data/documentation/diagrams/web-application-testing.svg +26 -26
  50. data/documentation/diagrams/zero-day-research-flow.svg +25 -25
  51. data/documentation/pwn-ai-Agent.md +28 -15
  52. data/lib/pwn/ai/agent/extrospection.rb +494 -6
  53. data/lib/pwn/ai/agent/learning.rb +67 -7
  54. data/lib/pwn/ai/agent/loop.rb +72 -6
  55. data/lib/pwn/ai/agent/mistakes.rb +369 -0
  56. data/lib/pwn/ai/agent/prompt_builder.rb +10 -1
  57. data/lib/pwn/ai/agent/tools/extrospection.rb +88 -3
  58. data/lib/pwn/ai/agent/tools/mistakes.rb +132 -0
  59. data/lib/pwn/ai/agent.rb +1 -0
  60. data/lib/pwn/plugins/jira_data_center.rb +2 -2
  61. data/lib/pwn/sdr/decoder/adsb.rb +82 -0
  62. data/lib/pwn/sdr/decoder/apt.rb +136 -0
  63. data/lib/pwn/sdr/decoder/base.rb +258 -171
  64. data/lib/pwn/sdr/decoder/bluetooth.rb +71 -0
  65. data/lib/pwn/sdr/decoder/dect.rb +70 -0
  66. data/lib/pwn/sdr/decoder/dsp.rb +396 -0
  67. data/lib/pwn/sdr/decoder/flex.rb +177 -210
  68. data/lib/pwn/sdr/decoder/gps.rb +74 -0
  69. data/lib/pwn/sdr/decoder/gsm.rb +31 -62
  70. data/lib/pwn/sdr/decoder/iridium.rb +66 -0
  71. data/lib/pwn/sdr/decoder/lora.rb +80 -0
  72. data/lib/pwn/sdr/decoder/lte.rb +67 -0
  73. data/lib/pwn/sdr/decoder/morse.rb +138 -0
  74. data/lib/pwn/sdr/decoder/p25.rb +74 -0
  75. data/lib/pwn/sdr/decoder/pager.rb +61 -0
  76. data/lib/pwn/sdr/decoder/pocsag.rb +176 -54
  77. data/lib/pwn/sdr/decoder/rfid.rb +79 -0
  78. data/lib/pwn/sdr/decoder/rtl433.rb +86 -0
  79. data/lib/pwn/sdr/decoder/rtty.rb +139 -0
  80. data/lib/pwn/sdr/decoder/wifi.rb +77 -0
  81. data/lib/pwn/sdr/decoder/zigbee.rb +74 -0
  82. data/lib/pwn/sdr/decoder.rb +75 -7
  83. data/lib/pwn/sdr/frequency_allocation.rb +82 -41
  84. data/lib/pwn/sdr/gqrx.rb +186 -95
  85. data/lib/pwn/version.rb +1 -1
  86. data/spec/lib/pwn/ai/agent/mistakes_spec.rb +36 -0
  87. data/spec/lib/pwn/ai/agent/tools/mistakes_spec.rb +12 -0
  88. data/spec/lib/pwn/sdr/decoder/adsb_spec.rb +15 -0
  89. data/spec/lib/pwn/sdr/decoder/apt_spec.rb +15 -0
  90. data/spec/lib/pwn/sdr/decoder/bluetooth_spec.rb +15 -0
  91. data/spec/lib/pwn/sdr/decoder/dect_spec.rb +15 -0
  92. data/spec/lib/pwn/sdr/decoder/dsp_spec.rb +15 -0
  93. data/spec/lib/pwn/sdr/decoder/gps_spec.rb +15 -0
  94. data/spec/lib/pwn/sdr/decoder/iridium_spec.rb +15 -0
  95. data/spec/lib/pwn/sdr/decoder/lora_spec.rb +15 -0
  96. data/spec/lib/pwn/sdr/decoder/lte_spec.rb +15 -0
  97. data/spec/lib/pwn/sdr/decoder/morse_spec.rb +15 -0
  98. data/spec/lib/pwn/sdr/decoder/p25_spec.rb +15 -0
  99. data/spec/lib/pwn/sdr/decoder/pager_spec.rb +15 -0
  100. data/spec/lib/pwn/sdr/decoder/rfid_spec.rb +15 -0
  101. data/spec/lib/pwn/sdr/decoder/rtl433_spec.rb +15 -0
  102. data/spec/lib/pwn/sdr/decoder/rtty_spec.rb +15 -0
  103. data/spec/lib/pwn/sdr/decoder/wifi_spec.rb +15 -0
  104. data/spec/lib/pwn/sdr/decoder/zigbee_spec.rb +15 -0
  105. data/third_party/pwn_rdoc.jsonl +115 -5
  106. metadata +46 -6
  107. data/README.md.bak +0 -200
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PWN
4
+ module SDR
5
+ module Decoder
6
+ # Pure-Ruby Iridium L-band burst detector.
7
+ #
8
+ # Iridium simplex/duplex bursts are 25 kbit/s DE-QPSK across
9
+ # 1616–1626.5 MHz. Native mode reports burst count/duration/energy
10
+ # per channel. `parse_line` retained for offline iridium-toolkit
11
+ # text analysis.
12
+ module Iridium
13
+ # Supported Method Parameters::
14
+ # PWN::SDR::Decoder::Iridium.decode(
15
+ # freq_obj: 'required - freq_obj returned from PWN::SDR::GQRX.init_freq'
16
+ # )
17
+
18
+ public_class_method def self.decode(opts = {})
19
+ freq_obj = opts[:freq_obj]
20
+ PWN::SDR::Decoder::Base.run_detector(
21
+ freq_obj: freq_obj,
22
+ protocol: 'IRIDIUM',
23
+ note: '25 kbit/s DE-QPSK — native mode reports burst timing/energy only.',
24
+ threshold: 7.0,
25
+ describe: proc { |b| { modulation: 'DE-QPSK', symbol_rate: 25_000, classification: b[:duration_ms] < 10 ? 'simplex-burst' : 'duplex-frame' } }
26
+ )
27
+ end
28
+
29
+ # Supported Method Parameters::
30
+ # PWN::SDR::Decoder::Iridium.parse_line(line: 'IRA: sat:23 beam:14 ...')
31
+
32
+ public_class_method def self.parse_line(opts = {})
33
+ line = opts[:line].to_s
34
+ out = { protocol: 'IRIDIUM' }
35
+ out[:frame_type] = ::Regexp.last_match(1) if line =~ /^([A-Z]{3}):/
36
+ out[:sat] = ::Regexp.last_match(1) if line =~ /sat:(\d+)/
37
+ out[:beam] = ::Regexp.last_match(1) if line =~ /beam:(\d+)/
38
+ out[:pos] = ::Regexp.last_match(1) if line =~ /pos=\(([^)]+)\)/
39
+ out[:ra_id] = ::Regexp.last_match(1) if line =~ /ric:(\d+)/
40
+ out[:summary] = "IRIDIUM #{out[:frame_type]} sat=#{out[:sat]} beam=#{out[:beam]}"
41
+ out.compact
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 (ruby-native detector, no external binaries):
54
+ #{self}.decode(
55
+ freq_obj: 'required - freq_obj returned from PWN::SDR::GQRX.init_freq'
56
+ )
57
+
58
+ #{self}.parse_line(line: 'IRA: sat:23 beam:14 pos=(+32.1/-097.0) ...')
59
+
60
+ #{self}.authors
61
+ "
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+
5
+ module PWN
6
+ module SDR
7
+ module Decoder
8
+ # Pure-Ruby LoRa / LoRaWAN CSS activity detector.
9
+ #
10
+ # LoRa is chirp-spread-spectrum over 125/250/500 kHz — recoverable
11
+ # only from raw I/Q at ≥250 ksps, not from a 48 kHz audio tap.
12
+ # Native mode reports chirp-burst duration/energy (from which SF
13
+ # can be estimated). `parse_line` retained for offline JSON analysis.
14
+ module LoRa
15
+ # Supported Method Parameters::
16
+ # PWN::SDR::Decoder::LoRa.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
+ PWN::SDR::Decoder::Base.run_detector(
23
+ freq_obj: freq_obj,
24
+ protocol: 'LoRa',
25
+ note: 'CSS over 125–500 kHz — native mode reports chirp bursts and estimates SF from duration.',
26
+ threshold: 8.0,
27
+ describe: proc { |b|
28
+ # Preamble ≈ 8 symbols; T_sym = 2^SF / BW. Assume BW=125k.
29
+ t_sym_ms = b[:duration_ms] / 20.0
30
+ sf_est = t_sym_ms.positive? ? Math.log2(t_sym_ms * 125).round.clamp(6, 12) : nil
31
+ { modulation: 'CSS', bw_khz_assumed: 125, sf_estimate: sf_est }.compact
32
+ }
33
+ )
34
+ end
35
+
36
+ # Supported Method Parameters::
37
+ # PWN::SDR::Decoder::LoRa.parse_line(line: '{"model":"LoRa",...}')
38
+
39
+ public_class_method def self.parse_line(opts = {})
40
+ line = opts[:line].to_s
41
+ h = begin
42
+ JSON.parse(line, symbolize_names: true)
43
+ rescue StandardError
44
+ { unparsed: line }
45
+ end
46
+ out = { protocol: 'LoRa' }.merge(h)
47
+ bits = []
48
+ bits << "SF#{out[:sf]}" if out[:sf]
49
+ bits << "BW#{out[:bw]}" if out[:bw]
50
+ bits << "CR#{out[:cr]}" if out[:cr]
51
+ bits << "DevAddr=#{out[:devaddr] || out[:id]}" if out[:devaddr] || out[:id]
52
+ bits << "FCnt=#{out[:fcnt]}" if out[:fcnt]
53
+ bits << "len=#{out[:len] || (out[:data].to_s.length / 2)}" if out[:len] || out[:data]
54
+ out[:summary] = "LoRa #{bits.join(' ')}".strip
55
+ out
56
+ end
57
+
58
+ # Author(s):: 0day Inc. <support@0dayinc.com>
59
+
60
+ public_class_method def self.authors
61
+ "AUTHOR(S):\n 0day Inc. <support@0dayinc.com>\n"
62
+ end
63
+
64
+ # Display Usage for this Module
65
+
66
+ public_class_method def self.help
67
+ puts "USAGE (ruby-native detector, no external binaries):
68
+ #{self}.decode(
69
+ freq_obj: 'required - freq_obj returned from PWN::SDR::GQRX.init_freq'
70
+ )
71
+
72
+ #{self}.parse_line(line: '{\"model\":\"LoRa\",\"sf\":7,\"bw\":125,...}')
73
+
74
+ #{self}.authors
75
+ "
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PWN
4
+ module SDR
5
+ module Decoder
6
+ # Pure-Ruby LTE / UMTS / CDMA cellular activity detector.
7
+ #
8
+ # 1.4–20 MHz OFDMA/WCDMA is far beyond a 48 kHz audio tap and beyond
9
+ # interpreted Ruby's real-time I/Q throughput. Native mode reports
10
+ # channel occupancy / power only. `parse_line` retained for offline
11
+ # cell_search / CellSearch text analysis.
12
+ module LTE
13
+ # Supported Method Parameters::
14
+ # PWN::SDR::Decoder::LTE.decode(
15
+ # freq_obj: 'required - freq_obj returned from PWN::SDR::GQRX.init_freq'
16
+ # )
17
+
18
+ public_class_method def self.decode(opts = {})
19
+ freq_obj = opts[:freq_obj]
20
+ PWN::SDR::Decoder::Base.run_detector(
21
+ freq_obj: freq_obj,
22
+ protocol: 'LTE',
23
+ note: 'OFDMA (1.4–20 MHz, 15 kHz subcarriers) — native mode reports channel occupancy only.',
24
+ threshold: 4.0,
25
+ describe: proc { |_b| { modulation: 'OFDMA', subcarrier_khz: 15 } }
26
+ )
27
+ end
28
+
29
+ # Supported Method Parameters::
30
+ # PWN::SDR::Decoder::LTE.parse_line(line: 'Found CELL ... PCI: 123 ...')
31
+
32
+ public_class_method def self.parse_line(opts = {})
33
+ line = opts[:line].to_s
34
+ out = { protocol: 'LTE' }
35
+ out[:earfcn] = ::Regexp.last_match(1) if line =~ /EARFCN[:= ]+(\d+)/i
36
+ out[:pci] = ::Regexp.last_match(1) if line =~ /(?:PCI|N_id_cell|Id)[:= ]+(\d{1,3})/i
37
+ out[:prb] = (::Regexp.last_match(1) || ::Regexp.last_match(2)) if line =~ /(?:PRB[:= ]+(\d+)|(\d+)\s*PRB)/i
38
+ out[:cp] = ::Regexp.last_match(1) if line =~ /\b(Normal|Extended)\b\s*CP/i
39
+ out[:rsrp] = ::Regexp.last_match(1) if line =~ /(-?\d+(?:\.\d+)?)\s*dBm/
40
+ out[:freq_mhz] = ::Regexp.last_match(1) if line =~ /(\d{3,4}\.\d)\s*MHz/
41
+ out[:summary] = "LTE PCI=#{out[:pci]} EARFCN=#{out[:earfcn]} PRB=#{out[:prb]} RSRP=#{out[:rsrp]}dBm"
42
+ out.compact
43
+ end
44
+
45
+ # Author(s):: 0day Inc. <support@0dayinc.com>
46
+
47
+ public_class_method def self.authors
48
+ "AUTHOR(S):\n 0day Inc. <support@0dayinc.com>\n"
49
+ end
50
+
51
+ # Display Usage for this Module
52
+
53
+ public_class_method def self.help
54
+ puts "USAGE (ruby-native detector, no external binaries):
55
+ #{self}.decode(
56
+ freq_obj: 'required - freq_obj returned from PWN::SDR::GQRX.init_freq'
57
+ )
58
+
59
+ #{self}.parse_line(line: 'Found CELL 739.0 MHz, EARFCN=5110, PCI=123, 50 PRB, -85.2 dBm')
60
+
61
+ #{self}.authors
62
+ "
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,138 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PWN
4
+ module SDR
5
+ module Decoder
6
+ # Pure-Ruby CW / Morse decoder for the amateur CW sub-bands.
7
+ #
8
+ # GQRX's CW/USB demodulator produces a ~600–800 Hz sidetone in the
9
+ # 48 kHz UDP audio stream. This module envelope-detects the tone
10
+ # with a state-preserving single-pole low-pass, adaptively
11
+ # thresholds it into on/off runs, classifies each run as dit / dah
12
+ # / char-gap / word-gap by timing, and looks the resulting `.-`
13
+ # sequences up in DSP::MORSE_TABLE. No `multimon-ng`, no `sox`.
14
+ module Morse
15
+ # Stateful, streaming CW demodulator fed by Base.run_native.
16
+ class Demod
17
+ def initialize(rate: 48_000)
18
+ @rate = rate
19
+ # single-pole low-pass on |x|; τ ≈ 5 ms
20
+ @env_a = Math.exp(-1.0 / (rate * 0.005))
21
+ @env = 0.0
22
+ @floor = 0.0
23
+ @peak = 0.0
24
+ @state = :off
25
+ @run = 0
26
+ @dit = rate * 60 / 1000 # seed 60 ms (≈ 20 WPM)
27
+ @sym = +''
28
+ @word = +''
29
+ end
30
+
31
+ def feed(samples, &)
32
+ samples.each do |x|
33
+ @env = (@env_a * @env) + ((1.0 - @env_a) * x.abs)
34
+ # slow trackers for adaptive threshold (attack fast, decay slow)
35
+ @peak = @env > @peak ? ((@peak * 0.7) + (@env * 0.3)) : (@peak * 0.9999)
36
+ @floor = @env < @floor ? @env : ((@floor * 0.9999) + (@env * 0.0001))
37
+ thresh = @floor + ((@peak - @floor) * 0.4)
38
+ on = @env > thresh && (@peak - @floor) > 0.01
39
+ if on == (@state == :on)
40
+ @run += 1
41
+ else
42
+ classify_run(&)
43
+ @state = on ? :on : :off
44
+ @run = 1
45
+ end
46
+ end
47
+ return unless @state == :off && @run > 5 * @dit && !(@sym.empty? && @word.empty?)
48
+
49
+ flush_char
50
+ flush_word(&)
51
+ end
52
+
53
+ private
54
+
55
+ def classify_run(&)
56
+ return if @run < @rate / 500 # <2 ms glitch
57
+
58
+ if @state == :on
59
+ @dit = ((@dit * 3) + @run) / 4 if @run < @dit * 1.5 && @run > @rate / 200
60
+ @sym << (@run > 2 * @dit ? '-' : '.')
61
+ else
62
+ return if @sym.empty? && @word.empty?
63
+
64
+ if @run > 5 * @dit
65
+ flush_char
66
+ flush_word(&)
67
+ elsif @run > 2 * @dit
68
+ flush_char
69
+ end
70
+ end
71
+ end
72
+
73
+ def flush_char
74
+ return if @sym.empty?
75
+
76
+ @word << (PWN::SDR::Decoder::DSP::MORSE_TABLE[@sym] || '_')
77
+ @sym = +''
78
+ end
79
+
80
+ def flush_word
81
+ return if @word.empty?
82
+
83
+ out = { protocol: 'MORSE-CW', text: @word.dup }
84
+ if (m = @word.match(/\b([A-Z0-9]{1,3}\d[A-Z]{1,4})\b/))
85
+ out[:callsign] = m[1]
86
+ end
87
+ out[:summary] = "CW #{@word}"[0, 120]
88
+ yield out
89
+ @word = +''
90
+ end
91
+ end
92
+
93
+ # Supported Method Parameters::
94
+ # PWN::SDR::Decoder::Morse.decode(
95
+ # freq_obj: 'required - freq_obj returned from PWN::SDR::GQRX.init_freq'
96
+ # )
97
+
98
+ public_class_method def self.decode(opts = {})
99
+ freq_obj = opts[:freq_obj]
100
+ PWN::SDR::Decoder::Base.run_native(
101
+ freq_obj: freq_obj,
102
+ protocol: 'MORSE-CW',
103
+ demod: Demod.new
104
+ )
105
+ end
106
+
107
+ # Supported Method Parameters::
108
+ # h = PWN::SDR::Decoder::Morse.decode_string(pattern: '.- -...')
109
+
110
+ public_class_method def self.decode_string(opts = {})
111
+ pattern = opts[:pattern].to_s
112
+ txt = pattern.strip.split(/\s+/).map { |s| PWN::SDR::Decoder::DSP::MORSE_TABLE[s] || '_' }.join
113
+ { protocol: 'MORSE-CW', text: txt, summary: "CW #{txt}" }
114
+ end
115
+
116
+ # Author(s):: 0day Inc. <support@0dayinc.com>
117
+
118
+ public_class_method def self.authors
119
+ "AUTHOR(S):\n 0day Inc. <support@0dayinc.com>\n"
120
+ end
121
+
122
+ # Display Usage for this Module
123
+
124
+ public_class_method def self.help
125
+ puts "USAGE (ruby-native, no external binaries):
126
+ #{self}.decode(
127
+ freq_obj: 'required - freq_obj returned from PWN::SDR::GQRX.init_freq'
128
+ )
129
+
130
+ #{self}.decode_string(pattern: '.- -...')
131
+
132
+ #{self}.authors
133
+ "
134
+ end
135
+ end
136
+ end
137
+ end
138
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PWN
4
+ module SDR
5
+ module Decoder
6
+ # Pure-Ruby APCO Project 25 Phase-1 C4FM activity detector.
7
+ #
8
+ # P25 is 4800 sym/s 4-level FSK with 1/2-rate trellis + RS + IMBE
9
+ # vocoder. Full symbol recovery, deinterleave, FEC and voice decode
10
+ # is out of scope for a portable pure-Ruby implementation; this
11
+ # module instead characterises C4FM keying activity (talkgroup key-
12
+ # ups, control-channel duty cycle) natively via Base.run_detector.
13
+ # `parse_line` is retained for offline dsd-format log analysis.
14
+ module P25
15
+ # Supported Method Parameters::
16
+ # PWN::SDR::Decoder::P25.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
+ PWN::SDR::Decoder::Base.run_detector(
23
+ freq_obj: freq_obj,
24
+ protocol: 'P25',
25
+ note: 'C4FM 4-FSK + trellis/RS/IMBE — native mode reports key-up bursts (duration/peak/duty) only. Feed captured dsd text to .parse_line for NAC/TG/RID.',
26
+ threshold: 6.0,
27
+ describe: proc { |b|
28
+ kind = if b[:duration_ms] > 1500 then 'voice-superframe'
29
+ elsif b[:duration_ms] > 150 then 'LDU/HDU'
30
+ else 'TSBK/control'
31
+ end
32
+ { modulation: 'C4FM', symbol_rate: 4800, classification: kind }
33
+ }
34
+ )
35
+ end
36
+
37
+ # Supported Method Parameters::
38
+ # PWN::SDR::Decoder::P25.parse_line(line: 'Sync: +P25p1 NAC: 293 src: 1234 tg: 5678')
39
+
40
+ public_class_method def self.parse_line(opts = {})
41
+ line = opts[:line].to_s
42
+ out = { protocol: 'P25' }
43
+ out[:nac] = ::Regexp.last_match(1) if line =~ /NAC:\s*([0-9A-Fa-f]+)/
44
+ out[:talkgroup] = ::Regexp.last_match(1) if line =~ /(?:TGID|tg[: ])\s*(\d+)/i
45
+ out[:radio_id] = ::Regexp.last_match(1) if line =~ /(?:RID|src[: ])\s*(\d+)/i
46
+ out[:duid] = ::Regexp.last_match(1) if line =~ /DUID:\s*(\S+)/
47
+ out[:sync] = ::Regexp.last_match(1) if line =~ /Sync:\s*(\S+)/
48
+ out[:summary] = "P25 NAC=#{out[:nac]} TG=#{out[:talkgroup]} RID=#{out[:radio_id]}".squeeze(' ')
49
+ out.compact
50
+ end
51
+
52
+ # Author(s):: 0day Inc. <support@0dayinc.com>
53
+
54
+ public_class_method def self.authors
55
+ "AUTHOR(S):\n 0day Inc. <support@0dayinc.com>\n"
56
+ end
57
+
58
+ # Display Usage for this Module
59
+
60
+ public_class_method def self.help
61
+ puts "USAGE (ruby-native detector, no external binaries):
62
+ #{self}.decode(
63
+ freq_obj: 'required - freq_obj returned from PWN::SDR::GQRX.init_freq'
64
+ )
65
+
66
+ #{self}.parse_line(line: 'Sync: +P25p1 NAC: 293 src: 1234 tg: 5678')
67
+
68
+ #{self}.authors
69
+ "
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PWN
4
+ module SDR
5
+ module Decoder
6
+ # Pure-Ruby combined pager decoder for the mixed-protocol `pager_all`
7
+ # band plan. Feeds every incoming 48 kHz audio chunk to BOTH the
8
+ # native POCSAG and FLEX demodulators concurrently; whichever locks
9
+ # emits messages. No `multimon-ng`, no `sox`.
10
+ module Pager
11
+ # Composite demodulator wrapping POCSAG::Demod + Flex::Demod.
12
+ class Demod
13
+ def initialize(rate: 48_000)
14
+ @pocsag = PWN::SDR::Decoder::POCSAG::Demod.new(rate: rate)
15
+ @flex = PWN::SDR::Decoder::Flex::Demod.new(rate: rate)
16
+ end
17
+
18
+ def feed(samples, &)
19
+ @pocsag.feed(samples.dup, &)
20
+ @flex.feed(samples, &)
21
+ end
22
+ end
23
+
24
+ # Supported Method Parameters::
25
+ # PWN::SDR::Decoder::Pager.decode(
26
+ # freq_obj: 'required - freq_obj returned from PWN::SDR::GQRX.init_freq'
27
+ # )
28
+
29
+ public_class_method def self.decode(opts = {})
30
+ freq_obj = opts[:freq_obj]
31
+ PWN::SDR::Decoder::Base.run_native(
32
+ freq_obj: freq_obj,
33
+ protocol: 'PAGER',
34
+ demod: Demod.new
35
+ )
36
+ end
37
+
38
+ # Author(s):: 0day Inc. <support@0dayinc.com>
39
+
40
+ public_class_method def self.authors
41
+ "AUTHOR(S):\n 0day Inc. <support@0dayinc.com>\n"
42
+ end
43
+
44
+ # Display Usage for this Module
45
+
46
+ public_class_method def self.help
47
+ puts "USAGE (ruby-native, no external binaries):
48
+ #{self}.decode(
49
+ freq_obj: 'required - freq_obj returned from PWN::SDR::GQRX.init_freq'
50
+ )
51
+
52
+ NOTE: Runs the native POCSAG (512/1200/2400) and FLEX (1600)
53
+ demodulators in parallel on the same GQRX NBFM audio tap.
54
+
55
+ #{self}.authors
56
+ "
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end