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,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.
|
|
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.
|
|
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
|