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.
Files changed (82) 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 +3 -3
  6. data/documentation/Diagrams.md +6 -2
  7. data/documentation/Extrospection.md +30 -11
  8. data/documentation/Home.md +5 -4
  9. data/documentation/How-PWN-Works.md +7 -4
  10. data/documentation/Mistakes.md +96 -0
  11. data/documentation/Persistence.md +3 -1
  12. data/documentation/SDR.md +23 -5
  13. data/documentation/Skills-Memory-Learning.md +15 -5
  14. data/documentation/diagrams/agent-tool-registry.svg +94 -92
  15. data/documentation/diagrams/dot/agent-tool-registry.dot +2 -2
  16. data/documentation/diagrams/dot/extrospection-world-awareness.dot +9 -7
  17. data/documentation/diagrams/dot/memory-skills-detailed.dot +12 -5
  18. data/documentation/diagrams/dot/mistakes-negative-feedback.dot +93 -0
  19. data/documentation/diagrams/dot/overall-pwn-architecture.dot +7 -5
  20. data/documentation/diagrams/dot/persistence-filesystem.dot +3 -2
  21. data/documentation/diagrams/dot/pwn-ai-feedback-learning-loop.dot +19 -7
  22. data/documentation/diagrams/dot/sdr-radio-flow.dot +13 -11
  23. data/documentation/diagrams/extrospection-world-awareness.svg +111 -93
  24. data/documentation/diagrams/memory-skills-detailed.svg +146 -97
  25. data/documentation/diagrams/mistakes-negative-feedback.svg +266 -0
  26. data/documentation/diagrams/overall-pwn-architecture.svg +184 -173
  27. data/documentation/diagrams/persistence-filesystem.svg +77 -62
  28. data/documentation/diagrams/pwn-ai-feedback-learning-loop.svg +217 -149
  29. data/documentation/diagrams/sdr-radio-flow.svg +76 -56
  30. data/documentation/pwn-ai-Agent.md +28 -15
  31. data/lib/pwn/ai/agent/extrospection.rb +65 -6
  32. data/lib/pwn/ai/agent/learning.rb +67 -7
  33. data/lib/pwn/ai/agent/loop.rb +72 -6
  34. data/lib/pwn/ai/agent/mistakes.rb +369 -0
  35. data/lib/pwn/ai/agent/prompt_builder.rb +10 -1
  36. data/lib/pwn/ai/agent/tools/extrospection.rb +2 -2
  37. data/lib/pwn/ai/agent/tools/mistakes.rb +132 -0
  38. data/lib/pwn/ai/agent.rb +1 -0
  39. data/lib/pwn/plugins/jira_data_center.rb +2 -2
  40. data/lib/pwn/sdr/decoder/adsb.rb +101 -0
  41. data/lib/pwn/sdr/decoder/apt.rb +75 -0
  42. data/lib/pwn/sdr/decoder/bluetooth.rb +78 -0
  43. data/lib/pwn/sdr/decoder/dect.rb +84 -0
  44. data/lib/pwn/sdr/decoder/gps.rb +83 -0
  45. data/lib/pwn/sdr/decoder/gsm.rb +50 -29
  46. data/lib/pwn/sdr/decoder/iridium.rb +81 -0
  47. data/lib/pwn/sdr/decoder/lora.rb +87 -0
  48. data/lib/pwn/sdr/decoder/lte.rb +88 -0
  49. data/lib/pwn/sdr/decoder/morse.rb +64 -0
  50. data/lib/pwn/sdr/decoder/p25.rb +71 -0
  51. data/lib/pwn/sdr/decoder/pager.rb +77 -0
  52. data/lib/pwn/sdr/decoder/pocsag.rb +26 -11
  53. data/lib/pwn/sdr/decoder/rfid.rb +88 -0
  54. data/lib/pwn/sdr/decoder/rtl433.rb +95 -0
  55. data/lib/pwn/sdr/decoder/rtty.rb +66 -0
  56. data/lib/pwn/sdr/decoder/wifi.rb +98 -0
  57. data/lib/pwn/sdr/decoder/zigbee.rb +96 -0
  58. data/lib/pwn/sdr/decoder.rb +67 -7
  59. data/lib/pwn/sdr/frequency_allocation.rb +82 -41
  60. data/lib/pwn/sdr/gqrx.rb +186 -95
  61. data/lib/pwn/version.rb +1 -1
  62. data/spec/lib/pwn/ai/agent/mistakes_spec.rb +36 -0
  63. data/spec/lib/pwn/ai/agent/tools/mistakes_spec.rb +12 -0
  64. data/spec/lib/pwn/sdr/decoder/adsb_spec.rb +15 -0
  65. data/spec/lib/pwn/sdr/decoder/apt_spec.rb +15 -0
  66. data/spec/lib/pwn/sdr/decoder/bluetooth_spec.rb +15 -0
  67. data/spec/lib/pwn/sdr/decoder/dect_spec.rb +15 -0
  68. data/spec/lib/pwn/sdr/decoder/gps_spec.rb +15 -0
  69. data/spec/lib/pwn/sdr/decoder/iridium_spec.rb +15 -0
  70. data/spec/lib/pwn/sdr/decoder/lora_spec.rb +15 -0
  71. data/spec/lib/pwn/sdr/decoder/lte_spec.rb +15 -0
  72. data/spec/lib/pwn/sdr/decoder/morse_spec.rb +15 -0
  73. data/spec/lib/pwn/sdr/decoder/p25_spec.rb +15 -0
  74. data/spec/lib/pwn/sdr/decoder/pager_spec.rb +15 -0
  75. data/spec/lib/pwn/sdr/decoder/rfid_spec.rb +15 -0
  76. data/spec/lib/pwn/sdr/decoder/rtl433_spec.rb +15 -0
  77. data/spec/lib/pwn/sdr/decoder/rtty_spec.rb +15 -0
  78. data/spec/lib/pwn/sdr/decoder/wifi_spec.rb +15 -0
  79. data/spec/lib/pwn/sdr/decoder/zigbee_spec.rb +15 -0
  80. data/third_party/pwn_rdoc.jsonl +92 -3
  81. metadata +44 -6
  82. data/README.md.bak +0 -200
@@ -0,0 +1,132 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'pwn/ai/agent/registry'
4
+ require 'pwn/ai/agent/mistakes'
5
+
6
+ # Expose the negative-feedback ledger to the model so it can inspect its
7
+ # own recurring failure patterns, RECORD mistakes it recognises
8
+ # semantically (not just tool-dispatch errors), and — critically — RESOLVE
9
+ # them with an explicit fix, which is then re-injected into every future
10
+ # system prompt as an actionable "do THIS instead" lesson AND handed
11
+ # straight back inline on the next recurrence via
12
+ # Mistakes.correction_hint.
13
+
14
+ PWN::AI::Agent::Registry.register(
15
+ name: 'mistakes_list',
16
+ toolset: 'learning',
17
+ schema: {
18
+ name: 'mistakes_list',
19
+ description: 'List recorded failure fingerprints from ~/.pwn/mistakes.json ' \
20
+ '(signature, tool, normalised error, count, first/last seen, ' \
21
+ 'resolved, fix). Sorted by recurrence count. These are the ' \
22
+ 'specific mistakes the agent keeps making — inspect before ' \
23
+ 'attempting the same tool again, and call mistakes_resolve ' \
24
+ 'once a working alternative is found.',
25
+ parameters: {
26
+ type: 'object',
27
+ properties: {
28
+ limit: { type: 'integer', default: 20 },
29
+ unresolved_only: { type: 'boolean', default: true }
30
+ },
31
+ required: []
32
+ }
33
+ },
34
+ check: -> { defined?(PWN::AI::Agent::Mistakes) },
35
+ handler: lambda { |args|
36
+ PWN::AI::Agent::Mistakes.top(
37
+ limit: args[:limit] || 20,
38
+ unresolved_only: args.key?(:unresolved_only) ? args[:unresolved_only] : true
39
+ )
40
+ }
41
+ )
42
+
43
+ PWN::AI::Agent::Registry.register(
44
+ name: 'mistakes_record',
45
+ toolset: 'learning',
46
+ schema: {
47
+ name: 'mistakes_record',
48
+ description: 'Proactively fingerprint a mistake YOU just made (wrong ' \
49
+ 'assumption, wrong file edited, bad approach, hallucinated ' \
50
+ 'API) so the negative-feedback loop captures failures that ' \
51
+ 'are NOT tool-dispatch errors. The (tool, error) pair is ' \
52
+ 'normalised and its cross-session count incremented; once ' \
53
+ 'count ≥ 3 it is flagged [REPEATING] in every future system ' \
54
+ 'prompt. Follow up with mistakes_resolve when you find the fix.',
55
+ parameters: {
56
+ type: 'object',
57
+ properties: {
58
+ tool: {
59
+ type: 'string',
60
+ description: 'Component / area the mistake was in (e.g. "assumption", "shell", "pwn_eval", "plan", a module name).'
61
+ },
62
+ error: { type: 'string', description: 'What went wrong, in your own words.' },
63
+ args: { type: 'string', description: 'Optional sample of the input that triggered it.' }
64
+ },
65
+ required: %w[tool error]
66
+ }
67
+ },
68
+ check: -> { defined?(PWN::AI::Agent::Mistakes) },
69
+ handler: lambda { |args|
70
+ sid = (PWN::Env.dig(:ai, :session_id) if defined?(PWN::Env))
71
+ PWN::AI::Agent::Mistakes.record(
72
+ tool: args[:tool], error: args[:error], args: args[:args],
73
+ session_id: sid, source: :model
74
+ )
75
+ }
76
+ )
77
+
78
+ PWN::AI::Agent::Registry.register(
79
+ name: 'mistakes_resolve',
80
+ toolset: 'learning',
81
+ schema: {
82
+ name: 'mistakes_resolve',
83
+ description: 'Mark a recorded mistake as RESOLVED and attach the fix ' \
84
+ '(what to do INSTEAD). The fix is (a) promoted to a ' \
85
+ 'PWN::Memory :lesson so every future run sees "AVOID X — ' \
86
+ 'FIX: Y" in its system prompt, AND (b) handed straight back ' \
87
+ 'inline via correction_hint the next time that signature ' \
88
+ 'recurs. If the same signature recurs after resolution it is ' \
89
+ 'automatically re-opened and tagged [REGRESSED]. Call this ' \
90
+ 'the moment you find a working alternative to a ' \
91
+ 'previously-failing approach.',
92
+ parameters: {
93
+ type: 'object',
94
+ properties: {
95
+ signature: { type: 'string', description: 'Mistake signature from mistakes_list / KNOWN MISTAKES block / correction_hint.' },
96
+ fix: { type: 'string', description: 'What to do instead next time.' }
97
+ },
98
+ required: %w[signature fix]
99
+ }
100
+ },
101
+ check: -> { defined?(PWN::AI::Agent::Mistakes) },
102
+ handler: lambda { |args|
103
+ PWN::AI::Agent::Mistakes.resolve(signature: args[:signature], fix: args[:fix])
104
+ }
105
+ )
106
+
107
+ PWN::AI::Agent::Registry.register(
108
+ name: 'mistakes_reset',
109
+ toolset: 'learning',
110
+ schema: {
111
+ name: 'mistakes_reset',
112
+ description: 'Wipe ~/.pwn/mistakes.json (all recorded failure fingerprints ' \
113
+ 'and their fixes). Use only when moving to a new host/engagement ' \
114
+ 'where prior failure patterns no longer apply. IRREVERSIBLE — ' \
115
+ 'must pass confirm:true.',
116
+ parameters: {
117
+ type: 'object',
118
+ properties: {
119
+ confirm: { type: 'boolean', description: 'Must be true to actually reset.' }
120
+ },
121
+ required: %w[confirm]
122
+ }
123
+ },
124
+ check: -> { defined?(PWN::AI::Agent::Mistakes) },
125
+ handler: lambda { |args|
126
+ raise ArgumentError, 'refusing to reset mistakes without confirm:true' unless args[:confirm] == true
127
+
128
+ before = PWN::AI::Agent::Mistakes.load.keys.length
129
+ PWN::AI::Agent::Mistakes.reset
130
+ { reset: true, mistakes_cleared: before, file: PWN::AI::Agent::Mistakes::MISTAKES_FILE }
131
+ }
132
+ )
data/lib/pwn/ai/agent.rb CHANGED
@@ -25,6 +25,7 @@ module PWN
25
25
  autoload :Loop, 'pwn/ai/agent/loop'
26
26
  autoload :Metrics, 'pwn/ai/agent/metrics'
27
27
  autoload :Learning, 'pwn/ai/agent/learning'
28
+ autoload :Mistakes, 'pwn/ai/agent/mistakes'
28
29
  autoload :Extrospection, 'pwn/ai/agent/extrospection'
29
30
  autoload :Introspection, 'pwn/ai/agent/introspection'
30
31
  autoload :Swarm, 'pwn/ai/agent/swarm'
@@ -522,7 +522,7 @@ module PWN
522
522
  if elem.is_a?(Hash)
523
523
  # Retain common identifier keys; if none present keep full hash
524
524
  keys_to_keep = %i[id key name value]
525
- elem.keys.any? { |kk| keys_to_keep.include?(kk) } ? elem.slice(*keys_to_keep) : elem
525
+ elem.keys.intersect?(keys_to_keep) ? elem.slice(*keys_to_keep) : elem
526
526
  else
527
527
  elem
528
528
  end
@@ -530,7 +530,7 @@ module PWN
530
530
  when Hash
531
531
  keys_to_keep = %i[id key name value]
532
532
  filtered_fields[k] =
533
- if v.keys.any? { |kk| keys_to_keep.include?(kk) }
533
+ if v.keys.intersect?(keys_to_keep)
534
534
  v.slice(*keys_to_keep)
535
535
  else
536
536
  v
@@ -0,0 +1,101 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'shellwords'
4
+
5
+ module PWN
6
+ module SDR
7
+ module Decoder
8
+ # ADS-B (Automatic Dependent Surveillance – Broadcast) decoder for
9
+ # aircraft transponder squitters on 1090 MHz (Mode-S / ES) and 978 MHz
10
+ # (UAT). 2 Mbit/s PPM cannot be recovered from GQRX audio, so this
11
+ # module drives `dump1090` (or `dump978-fa`) directly against the SDR
12
+ # and structures each SBS-1/BaseStation CSV line it emits on stdout.
13
+ module ADSB
14
+ SBS_FIELDS = %i[
15
+ msg_type tx_type session_id aircraft_id icao24 flight_id
16
+ date_gen time_gen date_log time_log callsign altitude_ft
17
+ ground_speed_kt track_deg lat lon vertical_rate_fpm squawk
18
+ alert emergency spi on_ground
19
+ ].freeze
20
+
21
+ # Supported Method Parameters::
22
+ # PWN::SDR::Decoder::ADSB.decode(
23
+ # freq_obj: 'required - freq_obj returned from PWN::SDR::GQRX.init_freq'
24
+ # )
25
+
26
+ public_class_method def self.decode(opts = {})
27
+ freq_obj = opts[:freq_obj]
28
+ raise 'ERROR: :freq_obj is required' unless freq_obj.is_a?(Hash)
29
+
30
+ hz = PWN::SDR.hz_to_i(freq: freq_obj[:freq])
31
+ gain = (freq_obj[:rf_gain] || 40).to_s.to_f
32
+ uat = hz.between?(977_000_000, 979_000_000)
33
+
34
+ direct_cmd =
35
+ if uat && PWN::SDR::Decoder::Base.bin_available?(bin: 'dump978-fa')
36
+ "bash -c #{Shellwords.escape("dump978-fa --sdr driver=rtlsdr --sdr-gain #{gain} --raw-stdout 2>/dev/null | uat2text")}"
37
+ elsif PWN::SDR::Decoder::Base.bin_available?(bin: 'dump1090-fa')
38
+ "dump1090-fa --device-type rtlsdr --gain #{gain} --net-sbs-stdout --quiet"
39
+ else
40
+ "dump1090 --gain #{gain} --net-sbs-stdout --quiet"
41
+ end
42
+
43
+ PWN::SDR::Decoder::Base.run_pipeline(
44
+ freq_obj: freq_obj,
45
+ protocol: uat ? 'ADSB-UAT978' : 'ADSB-1090ES',
46
+ required_bins: uat ? %w[dump978-fa uat2text] : %w[dump1090],
47
+ direct_cmd: direct_cmd,
48
+ line_match: /^(MSG,|-|\+)/,
49
+ parser: proc { |line| parse_line(line: line) }
50
+ )
51
+ end
52
+
53
+ # Supported Method Parameters::
54
+ # PWN::SDR::Decoder::ADSB.parse_line(line: 'MSG,3,1,1,ABCDEF,...')
55
+
56
+ public_class_method def self.parse_line(opts = {})
57
+ line = opts[:line].to_s
58
+ return { protocol: 'ADSB-UAT', summary: line.strip } unless line.start_with?('MSG,')
59
+
60
+ f = line.split(',', -1)
61
+ out = { protocol: 'ADSB' }
62
+ SBS_FIELDS.each_with_index { |k, i| out[k] = f[i] unless f[i].to_s.empty? }
63
+
64
+ bits = []
65
+ bits << "ICAO=#{out[:icao24]}" if out[:icao24]
66
+ bits << "CS=#{out[:callsign].to_s.strip}" if out[:callsign]
67
+ bits << "ALT=#{out[:altitude_ft]}ft" if out[:altitude_ft]
68
+ bits << "POS=#{out[:lat]},#{out[:lon]}" if out[:lat] && out[:lon]
69
+ bits << "GS=#{out[:ground_speed_kt]}kt" if out[:ground_speed_kt]
70
+ bits << "SQK=#{out[:squawk]}" if out[:squawk]
71
+ out[:summary] = "ADSB #{bits.join(' ')}".strip
72
+ out
73
+ end
74
+
75
+ # Author(s):: 0day Inc. <support@0dayinc.com>
76
+
77
+ public_class_method def self.authors
78
+ "AUTHOR(S):\n 0day Inc. <support@0dayinc.com>\n"
79
+ end
80
+
81
+ # Display Usage for this Module
82
+
83
+ public_class_method def self.help
84
+ puts "USAGE:
85
+ #{self}.decode(
86
+ freq_obj: 'required - freq_obj returned from PWN::SDR::GQRX.init_freq'
87
+ )
88
+
89
+ NOTE: Requires `dump1090` (1090 MHz Mode-S/ES) or
90
+ `dump978-fa` + `uat2text` (978 MHz UAT). Owns the SDR
91
+ directly — set freq_obj[:sdr_args] if GQRX has device 0.
92
+
93
+ #{self}.parse_line(line: 'MSG,3,1,1,ABCDEF,1,...')
94
+
95
+ #{self}.authors
96
+ "
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'shellwords'
4
+
5
+ module PWN
6
+ module SDR
7
+ module Decoder
8
+ # NOAA APT (Automatic Picture Transmission) decoder for the 137 MHz
9
+ # polar-orbiting weather satellites (NOAA-15/18/19). APT is a 2400 Hz
10
+ # AM subcarrier inside a 34 kHz-wide FM downlink; GQRX's UDP audio is
11
+ # recorded to a WAV, then handed to `noaa-apt` (or `satdump`) to render
12
+ # the two-channel visible/IR image strip.
13
+ module APT
14
+ # Supported Method Parameters::
15
+ # PWN::SDR::Decoder::APT.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
+ stamp = Time.now.strftime('%Y%m%d_%H%M%S')
24
+ wav_out = "/tmp/apt_#{stamp}.wav"
25
+ png_out = "/tmp/apt_#{stamp}.png"
26
+
27
+ # Record 11 025 Hz WAV (noaa-apt's native rate) until [ENTER],
28
+ # emitting a heartbeat line per second so the spinner stays live,
29
+ # then decode to PNG on exit.
30
+ inner = 'sox -t raw -e signed-integer -b 16 -r 22050 -c 1 - ' \
31
+ "-t wav -r 11025 #{Shellwords.escape(wav_out)} & SOXPID=$!; " \
32
+ 'trap "kill $SOXPID 2>/dev/null" EXIT INT TERM; ' \
33
+ 'while kill -0 $SOXPID 2>/dev/null; do ' \
34
+ "echo APT_REC seconds=$SECONDS file=#{wav_out}; sleep 1; done"
35
+
36
+ PWN::SDR::Decoder::Base.run_pipeline(
37
+ freq_obj: freq_obj,
38
+ protocol: 'NOAA-APT',
39
+ required_bins: %w[sox noaa-apt],
40
+ decode_cmd: "bash -c #{Shellwords.escape(inner)}",
41
+ line_match: /^APT_REC/,
42
+ parser: proc { |line| { protocol: 'NOAA-APT', wav: wav_out, png: png_out, summary: line.strip } }
43
+ )
44
+
45
+ return unless File.exist?(wav_out) && File.size(wav_out).positive?
46
+
47
+ system('noaa-apt', wav_out, '-o', png_out)
48
+
49
+ puts "[*] APT image written: #{png_out}" if File.exist?(png_out)
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:
62
+ #{self}.decode(
63
+ freq_obj: 'required - freq_obj returned from PWN::SDR::GQRX.init_freq'
64
+ )
65
+
66
+ NOTE: Requires `sox` and `noaa-apt`. Set GQRX to FM, ~34 kHz BW.
67
+ Records the pass to /tmp/apt_<ts>.wav, decodes to PNG on exit.
68
+
69
+ #{self}.authors
70
+ "
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'shellwords'
4
+
5
+ module PWN
6
+ module SDR
7
+ module Decoder
8
+ # Bluetooth Classic (BR/EDR) & BLE advertising decoder for 2.402–2.480 GHz.
9
+ #
10
+ # 1 Mbit/s GFSK with 79 (BR/EDR) or 40 (BLE) FHSS channels cannot be
11
+ # recovered from GQRX audio. This module drives an Ubertooth One via
12
+ # `ubertooth-rx` (LAP/UAP discovery) or `ubertooth-btle -f -p` (BLE
13
+ # advertising follow) directly and structures each output line.
14
+ module Bluetooth
15
+ # Supported Method Parameters::
16
+ # PWN::SDR::Decoder::Bluetooth.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
+ ble = freq_obj[:ble] || freq_obj[:mode].to_s.casecmp('ble').zero?
25
+ direct_cmd = ble ? 'ubertooth-btle -f -p' : 'ubertooth-rx -z'
26
+
27
+ PWN::SDR::Decoder::Base.run_pipeline(
28
+ freq_obj: freq_obj,
29
+ protocol: ble ? 'BLE' : 'BT-BR/EDR',
30
+ required_bins: [ble ? 'ubertooth-btle' : 'ubertooth-rx'],
31
+ direct_cmd: direct_cmd,
32
+ line_match: /(LAP|AdvA|ADV_|SCAN_|BD_ADDR|systime)/i,
33
+ parser: proc { |line| parse_line(line: line) }
34
+ )
35
+ end
36
+
37
+ # Supported Method Parameters::
38
+ # PWN::SDR::Decoder::Bluetooth.parse_line(line: 'systime=... LAP=9e8b33 ...')
39
+
40
+ public_class_method def self.parse_line(opts = {})
41
+ line = opts[:line].to_s
42
+ out = { protocol: 'Bluetooth' }
43
+ out[:lap] = ::Regexp.last_match(1) if line =~ /LAP[=: ]([0-9a-fA-F]{6})/
44
+ out[:uap] = ::Regexp.last_match(1) if line =~ /UAP[=: ]([0-9a-fA-F]{2})/
45
+ out[:bd_addr] = ::Regexp.last_match(1) if line =~ /(?:AdvA|BD_ADDR)[=: ]([0-9a-fA-F:]{12,17})/
46
+ out[:pdu_type] = ::Regexp.last_match(1) if line =~ /\b(ADV_\w+|SCAN_\w+|CONNECT_REQ)\b/
47
+ out[:channel] = ::Regexp.last_match(1) if line =~ /ch[=: ]?(\d{1,2})\b/i
48
+ out[:rssi] = ::Regexp.last_match(1) if line =~ /rssi[=: ]?(-?\d+)/i
49
+ out[:summary] = "BT #{out.values_at(:pdu_type, :bd_addr, :lap).compact.join(' ')}".strip
50
+ out.compact
51
+ end
52
+
53
+ # Author(s):: 0day Inc. <support@0dayinc.com>
54
+
55
+ public_class_method def self.authors
56
+ "AUTHOR(S):\n 0day Inc. <support@0dayinc.com>\n"
57
+ end
58
+
59
+ # Display Usage for this Module
60
+
61
+ public_class_method def self.help
62
+ puts "USAGE:
63
+ #{self}.decode(
64
+ freq_obj: 'required - freq_obj returned from PWN::SDR::GQRX.init_freq'
65
+ )
66
+
67
+ NOTE: Requires an Ubertooth One and `ubertooth-rx` /
68
+ `ubertooth-btle`. Set freq_obj[:ble] = true for BLE mode.
69
+
70
+ #{self}.parse_line(line: 'systime=... ch=37 LAP=9e8b33 ...')
71
+
72
+ #{self}.authors
73
+ "
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'shellwords'
4
+
5
+ module PWN
6
+ module SDR
7
+ module Decoder
8
+ # DECT (Digital Enhanced Cordless Telecommunications) decoder for the
9
+ # 1.880–1.900 GHz (EU) / 1.920–1.930 GHz (US DECT-6.0) allocation.
10
+ # 1.152 Mbit/s GFSK across 10 TDMA carriers requires raw I/Q; this
11
+ # module drives gr-dect2's `dect_cli` (or the `re-DECTed` toolkit)
12
+ # directly and structures each RFPI/PP scan line.
13
+ module DECT
14
+ # Supported Method Parameters::
15
+ # PWN::SDR::Decoder::DECT.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
+ # DECT carriers are 1.728 MHz apart; ch0 = 1 897.344 MHz (EU).
25
+ ch = ((1_897_344_000 - hz) / 1_728_000.0).round.clamp(0, 9)
26
+
27
+ direct_cmd =
28
+ if PWN::SDR::Decoder::Base.bin_available?(bin: 'dect_cli')
29
+ "dect_cli -s #{ch}"
30
+ else
31
+ "dectrx -c #{ch}"
32
+ end
33
+
34
+ PWN::SDR::Decoder::Base.run_pipeline(
35
+ freq_obj: freq_obj,
36
+ protocol: 'DECT',
37
+ required_bins: %w[dect_cli],
38
+ direct_cmd: direct_cmd,
39
+ line_match: /(RFPI|FP|PP|slot|carrier|RSSI)/i,
40
+ parser: proc { |line| parse_line(line: line) }
41
+ )
42
+ end
43
+
44
+ # Supported Method Parameters::
45
+ # PWN::SDR::Decoder::DECT.parse_line(line: 'RFPI: 01 23 45 67 89 slot 4 carrier 3 RSSI -55')
46
+
47
+ public_class_method def self.parse_line(opts = {})
48
+ line = opts[:line].to_s
49
+ out = { protocol: 'DECT' }
50
+ out[:rfpi] = ::Regexp.last_match(1).delete(' ') if line =~ /RFPI[:=]?\s*((?:[0-9A-Fa-f]{2}\s*){5})/
51
+ out[:slot] = ::Regexp.last_match(1) if line =~ /slot\s*(\d+)/i
52
+ out[:carrier] = ::Regexp.last_match(1) if line =~ /carrier\s*(\d+)/i
53
+ out[:rssi] = ::Regexp.last_match(1) if line =~ /RSSI[:=]?\s*(-?\d+)/i
54
+ out[:role] = ::Regexp.last_match(1) if line =~ /\b(FP|PP)\b/
55
+ out[:summary] = "DECT RFPI=#{out[:rfpi]} slot=#{out[:slot]} carrier=#{out[:carrier]}"
56
+ out.compact
57
+ end
58
+
59
+ # Author(s):: 0day Inc. <support@0dayinc.com>
60
+
61
+ public_class_method def self.authors
62
+ "AUTHOR(S):\n 0day Inc. <support@0dayinc.com>\n"
63
+ end
64
+
65
+ # Display Usage for this Module
66
+
67
+ public_class_method def self.help
68
+ puts "USAGE:
69
+ #{self}.decode(
70
+ freq_obj: 'required - freq_obj returned from PWN::SDR::GQRX.init_freq'
71
+ )
72
+
73
+ NOTE: Requires gr-dect2's `dect_cli` (or re-DECTed `dectrx`).
74
+ Owns the SDR directly.
75
+
76
+ #{self}.parse_line(line: 'RFPI: 01 23 45 67 89 slot 4 carrier 3')
77
+
78
+ #{self}.authors
79
+ "
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'shellwords'
4
+
5
+ module PWN
6
+ module SDR
7
+ module Decoder
8
+ # GNSS / GPS L1-C/A (1575.42 MHz) & L2C (1227.60 MHz) decoder. BPSK/DSSS
9
+ # at 1.023 / 10.23 Mcps needs raw I/Q, so this drives `gnss-sdr` (or
10
+ # `rtl_gps`) directly and structures the PVT / NMEA lines it emits
11
+ # (satellite PRNs acquired, C/N0, computed position fix, UTC time).
12
+ module GPS
13
+ # Supported Method Parameters::
14
+ # PWN::SDR::Decoder::GPS.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
+ raise 'ERROR: :freq_obj is required' unless freq_obj.is_a?(Hash)
21
+
22
+ conf = (freq_obj[:gnss_conf] || '/usr/share/gnss-sdr/conf/gnss-sdr_GPS_L1_rtlsdr_realtime.conf').to_s
23
+ direct_cmd = "gnss-sdr --config_file=#{Shellwords.escape(conf)}"
24
+
25
+ PWN::SDR::Decoder::Base.run_pipeline(
26
+ freq_obj: freq_obj,
27
+ protocol: 'GPS',
28
+ required_bins: %w[gnss-sdr],
29
+ direct_cmd: direct_cmd,
30
+ line_match: /(PVT|Position|PRN|Tracking|\$G[PN])/,
31
+ parser: proc { |line| parse_line(line: line) }
32
+ )
33
+ end
34
+
35
+ # Supported Method Parameters::
36
+ # PWN::SDR::Decoder::GPS.parse_line(line: 'Position at ... Lat = 32.7 [deg], Long = -97.1 ...')
37
+
38
+ public_class_method def self.parse_line(opts = {})
39
+ line = opts[:line].to_s
40
+ out = { protocol: 'GPS' }
41
+ out[:prn] = ::Regexp.last_match(1) if line =~ /PRN[ =]?(\d{1,2})/
42
+ out[:cn0] = ::Regexp.last_match(1) if line =~ /CN0[ =]?([\d.]+)/i
43
+ out[:lat] = ::Regexp.last_match(1) if line =~ /Lat(?:itude)?\s*=\s*(-?[\d.]+)/i
44
+ out[:lon] = ::Regexp.last_match(1) if line =~ /Long(?:itude)?\s*=\s*(-?[\d.]+)/i
45
+ out[:alt] = ::Regexp.last_match(1) if line =~ /Height\s*=\s*(-?[\d.]+)/i
46
+ out[:utc] = ::Regexp.last_match(1) if line =~ /UTC\s*=?\s*([\d:.-]+T?[\d:.]*)/
47
+ out[:nmea] = line if line.start_with?('$G')
48
+ out[:summary] = if out[:lat]
49
+ "GPS FIX #{out[:lat]},#{out[:lon]} alt=#{out[:alt]}m"
50
+ elsif out[:prn]
51
+ "GPS PRN#{out[:prn]} CN0=#{out[:cn0]}"
52
+ else
53
+ "GPS #{line[0, 100]}"
54
+ end
55
+ out.compact
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:
68
+ #{self}.decode(
69
+ freq_obj: 'required - freq_obj returned from PWN::SDR::GQRX.init_freq'
70
+ )
71
+
72
+ NOTE: Requires `gnss-sdr` and an active-antenna-biased front end.
73
+ Override the receiver config via freq_obj[:gnss_conf].
74
+
75
+ #{self}.parse_line(line: 'Position ... Lat = 32.7 Long = -97.1 Height = 210')
76
+
77
+ #{self}.authors
78
+ "
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end