pwn 0.5.621 → 0.5.626
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/.rubocop.yml +2 -2
- data/Gemfile +1 -1
- data/README.md +7 -1
- data/bin/pwn_gqrx_scanner +78 -9
- data/documentation/Agent-Tool-Registry.md +3 -3
- data/documentation/Configuration.md +301 -47
- data/documentation/Cron.md +5 -3
- data/documentation/Diagrams.md +1 -1
- data/documentation/Extrospection.md +472 -26
- data/documentation/FFI.md +72 -8
- data/documentation/Home.md +4 -4
- data/documentation/How-PWN-Works.md +7 -3
- data/documentation/Mistakes.md +9 -0
- data/documentation/SDR.md +83 -10
- data/documentation/Skills-Memory-Learning.md +101 -11
- data/documentation/diagrams/agent-tool-registry.svg +56 -55
- data/documentation/diagrams/ai-integration-tool-calling.svg +24 -24
- data/documentation/diagrams/aws-cloud-security.svg +25 -25
- data/documentation/diagrams/burp-vs-zap-preference.svg +12 -12
- data/documentation/diagrams/code-scanning-sast.svg +25 -25
- data/documentation/diagrams/cron-scheduling.svg +23 -23
- data/documentation/diagrams/dot/agent-tool-registry.dot +2 -2
- data/documentation/diagrams/dot/extrospection-world-awareness.dot +33 -6
- data/documentation/diagrams/dot/hardware-hacking.dot +3 -1
- data/documentation/diagrams/dot/memory-skills-detailed.dot +4 -2
- data/documentation/diagrams/dot/mistakes-negative-feedback.dot +4 -2
- data/documentation/diagrams/dot/overall-pwn-architecture.dot +3 -3
- data/documentation/diagrams/dot/persistence-filesystem.dot +3 -3
- data/documentation/diagrams/dot/pwn-ai-feedback-learning-loop.dot +10 -4
- data/documentation/diagrams/dot/sdr-radio-flow.dot +39 -16
- data/documentation/diagrams/driver-framework.svg +13 -13
- data/documentation/diagrams/extrospection-world-awareness.svg +383 -177
- data/documentation/diagrams/fuzzing-workflow.svg +24 -24
- data/documentation/diagrams/hardware-hacking.svg +84 -69
- data/documentation/diagrams/history-to-drivers.svg +18 -18
- data/documentation/diagrams/memory-skills-detailed.svg +137 -120
- data/documentation/diagrams/mistakes-negative-feedback.svg +152 -137
- data/documentation/diagrams/network-infra-testing.svg +27 -27
- data/documentation/diagrams/overall-pwn-architecture.svg +97 -97
- data/documentation/diagrams/penetration-testing-workflow.svg +30 -30
- data/documentation/diagrams/persistence-filesystem.svg +100 -94
- data/documentation/diagrams/plugin-ecosystem.svg +35 -35
- data/documentation/diagrams/pwn-ai-feedback-learning-loop.svg +254 -207
- data/documentation/diagrams/pwn-repl-prototyping.svg +20 -20
- data/documentation/diagrams/reporting-pipeline.svg +18 -18
- data/documentation/diagrams/reverse-engineering-flow.svg +21 -21
- data/documentation/diagrams/sdr-radio-flow.svg +223 -91
- data/documentation/diagrams/sessions-cron-automation.svg +18 -18
- data/documentation/diagrams/swarm-multi-agent.svg +39 -39
- data/documentation/diagrams/web-application-testing.svg +26 -26
- data/documentation/diagrams/zero-day-research-flow.svg +25 -25
- data/documentation/pwn-ai-Agent.md +16 -10
- data/documentation/pwn_silent_help_learn_demo.gif +0 -0
- data/lib/pwn/ai/agent/assembly.rb +1 -1
- data/lib/pwn/ai/agent/btc.rb +1 -1
- data/lib/pwn/ai/agent/burp_suite.rb +1 -1
- data/lib/pwn/ai/agent/extrospection.rb +2562 -57
- data/lib/pwn/ai/agent/gqrx.rb +2 -2
- data/lib/pwn/ai/agent/hacker_one.rb +1 -1
- data/lib/pwn/ai/agent/learning.rb +16 -16
- data/lib/pwn/ai/agent/loop.rb +1 -1
- data/lib/pwn/ai/agent/{introspection.rb → reflect.rb} +12 -12
- data/lib/pwn/ai/agent/sast.rb +1 -1
- data/lib/pwn/ai/agent/tools/extrospection.rb +311 -8
- data/lib/pwn/ai/agent/tools/learning.rb +7 -7
- data/lib/pwn/ai/agent/transparent_browser.rb +1 -1
- data/lib/pwn/ai/agent/vuln_gen.rb +2 -2
- data/lib/pwn/ai/agent.rb +1 -1
- data/lib/pwn/ai/open_ai.rb +10 -6
- data/lib/pwn/config.rb +5 -4
- data/lib/pwn/ffi/adalm_pluto.rb +498 -0
- data/lib/pwn/ffi/fftw.rb +192 -0
- data/lib/pwn/ffi/hack_rf.rb +193 -0
- data/lib/pwn/ffi/liquid.rb +244 -0
- data/lib/pwn/ffi/rtl_sdr.rb +210 -0
- data/lib/pwn/ffi/soapy_sdr.rb +177 -0
- data/lib/pwn/ffi/stdio.rb +9 -1
- data/lib/pwn/ffi/volk.rb +241 -0
- data/lib/pwn/ffi.rb +45 -2
- data/lib/pwn/plugins/burp_suite.rb +2 -2
- data/lib/pwn/sast/pom_version.rb +2 -2
- data/lib/pwn/sast/test_case_engine.rb +1 -1
- data/lib/pwn/sdr/decoder/adsb.rb +201 -12
- data/lib/pwn/sdr/decoder/apt.rb +19 -5
- data/lib/pwn/sdr/decoder/base.rb +314 -1
- data/lib/pwn/sdr/decoder/bluetooth.rb +91 -18
- data/lib/pwn/sdr/decoder/dect.rb +81 -23
- data/lib/pwn/sdr/decoder/dsp.rb +229 -15
- data/lib/pwn/sdr/decoder/flex.rb +23 -6
- data/lib/pwn/sdr/decoder/gps.rb +80 -18
- data/lib/pwn/sdr/decoder/gsm.rb +83 -28
- data/lib/pwn/sdr/decoder/iridium.rb +80 -18
- data/lib/pwn/sdr/decoder/lora.rb +84 -28
- data/lib/pwn/sdr/decoder/lte.rb +80 -18
- data/lib/pwn/sdr/decoder/morse.rb +23 -6
- data/lib/pwn/sdr/decoder/p25.rb +90 -31
- data/lib/pwn/sdr/decoder/pager.rb +23 -6
- data/lib/pwn/sdr/decoder/pocsag.rb +23 -6
- data/lib/pwn/sdr/decoder/rds.rb +219 -52
- data/lib/pwn/sdr/decoder/rfid.rb +81 -27
- data/lib/pwn/sdr/decoder/rtl433.rb +85 -27
- data/lib/pwn/sdr/decoder/rtty.rb +23 -6
- data/lib/pwn/sdr/decoder/wifi.rb +86 -32
- data/lib/pwn/sdr/decoder/zigbee.rb +88 -35
- data/lib/pwn/sdr/decoder.rb +6 -5
- data/lib/pwn/sdr/frequency_allocation.rb +124 -81
- data/lib/pwn/sdr/gqrx.rb +1513 -165
- data/lib/pwn/version.rb +1 -1
- data/spec/lib/pwn/ai/agent/extrospection_spec.rb +96 -0
- data/spec/lib/pwn/ai/agent/{introspection_spec.rb → reflect_spec.rb} +3 -3
- data/spec/lib/pwn/ai/agent/tools/extrospection_spec.rb +8 -24
- data/spec/lib/pwn/ffi/adalm_pluto_spec.rb +37 -0
- data/spec/lib/pwn/ffi/fftw_spec.rb +26 -0
- data/spec/lib/pwn/ffi/hack_rf_spec.rb +25 -0
- data/spec/lib/pwn/ffi/liquid_spec.rb +42 -0
- data/spec/lib/pwn/ffi/rtl_sdr_spec.rb +24 -0
- data/spec/lib/pwn/ffi/soapy_sdr_spec.rb +25 -0
- data/spec/lib/pwn/ffi/volk_spec.rb +36 -0
- data/spec/lib/pwn/sdr/decoder/base_iq_spec.rb +65 -0
- data/spec/lib/pwn/sdr/decoder/dsp_spec.rb +50 -4
- data/spec/lib/pwn/sdr/decoder/rds_spec.rb +45 -4
- data/third_party/pwn_rdoc.jsonl +279 -30
- metadata +21 -5
data/lib/pwn/ffi.rb
CHANGED
|
@@ -1,13 +1,54 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'ffi'
|
|
4
|
+
|
|
5
|
+
# Top-level alias so PWN::FFI::* modules can `extend PubFFI::Library`
|
|
6
|
+
# without the PWN::FFI ↔ ::FFI (gem) namespace collision. Every binding
|
|
7
|
+
# under lib/pwn/ffi/*.rb reuses this — define exactly once.
|
|
8
|
+
PubFFI = ::FFI unless defined?(PubFFI) # rubocop:disable Style/RedundantConstantBase
|
|
9
|
+
|
|
3
10
|
module PWN
|
|
4
11
|
# This file, using the autoload directive loads FFI modules
|
|
5
12
|
# into memory only when they're needed. For more information, see:
|
|
6
13
|
# http://www.rubyinside.com/ruby-techniques-revealed-autoload-1652.html
|
|
14
|
+
#
|
|
15
|
+
# Bindings under this namespace are *thin* — they attach functions from
|
|
16
|
+
# already-installed system shared objects (libliquid, libvolk, libfftw3f,
|
|
17
|
+
# librtlsdr, libhackrf, libSoapySDR) so PWN::SDR::Decoder::* can run
|
|
18
|
+
# MHz-rate DSP inner loops in native/SIMD code while Ruby stays in charge
|
|
19
|
+
# of orchestration. Nothing here shells out; nothing here compiles at
|
|
20
|
+
# `gem install` time. If a .so is missing the module still loads and
|
|
21
|
+
# `.available?` returns false so callers can fall back to pure Ruby.
|
|
7
22
|
module FFI
|
|
8
|
-
autoload :
|
|
23
|
+
autoload :AdalmPluto, 'pwn/ffi/adalm_pluto'
|
|
24
|
+
autoload :FFTW, 'pwn/ffi/fftw'
|
|
25
|
+
autoload :HackRF, 'pwn/ffi/hack_rf'
|
|
26
|
+
autoload :Liquid, 'pwn/ffi/liquid'
|
|
27
|
+
autoload :RTLSdr, 'pwn/ffi/rtl_sdr'
|
|
28
|
+
autoload :SoapySDR, 'pwn/ffi/soapy_sdr'
|
|
29
|
+
autoload :Stdio, 'pwn/ffi/stdio'
|
|
30
|
+
autoload :Volk, 'pwn/ffi/volk'
|
|
9
31
|
|
|
10
|
-
#
|
|
32
|
+
# Supported Method Parameters::
|
|
33
|
+
# ok = PWN::FFI.available?(
|
|
34
|
+
# mod: 'required - Symbol or Module (e.g. :Liquid or PWN::FFI::Liquid)'
|
|
35
|
+
# )
|
|
36
|
+
|
|
37
|
+
public_class_method def self.available?(opts = {})
|
|
38
|
+
mod = opts[:mod]
|
|
39
|
+
mod = const_get(mod) if mod.is_a?(Symbol) || mod.is_a?(String)
|
|
40
|
+
mod.respond_to?(:available?) && mod.available?
|
|
41
|
+
rescue NameError, LoadError
|
|
42
|
+
false
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Supported Method Parameters::
|
|
46
|
+
# PWN::FFI.backends
|
|
47
|
+
# Returns { ModuleName => true|false } for every registered binding.
|
|
48
|
+
|
|
49
|
+
public_class_method def self.backends
|
|
50
|
+
(constants - [:Stdio]).sort.to_h { |c| [c, available?(mod: c)] }
|
|
51
|
+
end
|
|
11
52
|
|
|
12
53
|
# Author(s):: 0day Inc. <support@0dayinc.com>
|
|
13
54
|
|
|
@@ -15,6 +56,8 @@ module PWN
|
|
|
15
56
|
"AUTHOR(S):\n 0day Inc. <support@0dayinc.com>\n"
|
|
16
57
|
end
|
|
17
58
|
|
|
59
|
+
# Display a List of Every PWN::FFI Module
|
|
60
|
+
|
|
18
61
|
public_class_method def self.help
|
|
19
62
|
constants.sort
|
|
20
63
|
end
|
|
@@ -50,7 +50,7 @@ module PWN
|
|
|
50
50
|
# type: 'required - type of history to introspect (:sitemap, :proxy_history, :websocket_history)'
|
|
51
51
|
# )
|
|
52
52
|
private_class_method def self.init_introspection_thread(opts = {})
|
|
53
|
-
# if PWN::Env[:ai][:
|
|
53
|
+
# if PWN::Env[:ai][:module_reflection] is true,
|
|
54
54
|
# spin up Thread to:
|
|
55
55
|
# 1. Periodically call get_proxy_history(burp_obj: burp_obj) method
|
|
56
56
|
# 2. For each entry w/ empty comment,
|
|
@@ -65,7 +65,7 @@ module PWN
|
|
|
65
65
|
type = opts[:type]
|
|
66
66
|
raise "ERROR: type parameter is required and must be one of: #{valid_types.join(', ')}" unless valid_types.include?(type)
|
|
67
67
|
|
|
68
|
-
if PWN::Env[:ai][:
|
|
68
|
+
if PWN::Env[:ai][:module_reflection]
|
|
69
69
|
introspection_thread_arr = burp_obj[:introspection_threads] ||= []
|
|
70
70
|
introspection_thread = Thread.new do
|
|
71
71
|
get_highlight_color = lambda do |opts = {}|
|
data/lib/pwn/sast/pom_version.rb
CHANGED
|
@@ -19,8 +19,8 @@ module PWN
|
|
|
19
19
|
dir_path = opts[:dir_path]
|
|
20
20
|
git_repo_root_uri = opts[:git_repo_root_uri].to_s.scrub
|
|
21
21
|
result_arr = []
|
|
22
|
-
|
|
23
|
-
logger_results = "AI
|
|
22
|
+
ai_module_reflection = PWN::Env[:ai][:module_reflection]
|
|
23
|
+
logger_results = "AI Module Reflection => #{PWN::Env[:ai][:module_reflection]} => "
|
|
24
24
|
|
|
25
25
|
PWN::Plugins::FileFu.recurse_in_dir(dir_path: dir_path) do |entry|
|
|
26
26
|
if File.file?(entry) && File.basename(entry) == 'pom.xml' && entry !~ /test/i
|
|
@@ -63,7 +63,7 @@ module PWN
|
|
|
63
63
|
git_repo_root_uri = opts[:git_repo_root_uri].to_s.scrub
|
|
64
64
|
|
|
65
65
|
result_arr = []
|
|
66
|
-
logger_results = "AI
|
|
66
|
+
logger_results = "AI Module Reflection => #{PWN::Env[:ai][:module_reflection]} => "
|
|
67
67
|
|
|
68
68
|
PWN::Plugins::FileFu.recurse_in_dir(
|
|
69
69
|
dir_path: dir_path,
|
data/lib/pwn/sdr/decoder/adsb.rb
CHANGED
|
@@ -3,14 +3,12 @@
|
|
|
3
3
|
module PWN
|
|
4
4
|
module SDR
|
|
5
5
|
module Decoder
|
|
6
|
-
#
|
|
6
|
+
# ADS-B (1090 MHz Mode-S / 978 MHz UAT) true-air decoder.
|
|
7
7
|
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
# (bursts/sec, peak dBFS, floor) natively in Ruby. `parse_line` is
|
|
13
|
-
# retained for offline SBS-1 CSV analysis.
|
|
8
|
+
# Prefer PWN::FFI::{RTLSdr,AdalmPluto,HackRF} at ≥2 Msps and run a
|
|
9
|
+
# pure-Ruby Mode-S preamble correlator + 112-bit PPM slicer over
|
|
10
|
+
# magnitude samples. Falls back to Base.run_detector energy mode
|
|
11
|
+
# when no I/Q source is available. Offline SBS-1 CSV → .parse_line.
|
|
14
12
|
module ADSB
|
|
15
13
|
SBS_FIELDS = %i[
|
|
16
14
|
msg_type tx_type session_id aircraft_id icao24 flight_id
|
|
@@ -19,6 +17,188 @@ module PWN
|
|
|
19
17
|
alert emergency spi on_ground
|
|
20
18
|
].freeze
|
|
21
19
|
|
|
20
|
+
# 8 μs Mode-S preamble at 2 Msps → 16 samples: 1 0 1 0 0 0 0 1 0 1 0 0 0 0 0 0
|
|
21
|
+
PREAMBLE = [1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0].map(&:to_f).freeze
|
|
22
|
+
SAMPLES_PER_US = 2 # @ 2 Msps
|
|
23
|
+
# CRC-24 (Mode-S) generator 0x1FFF409 (poly over GF(2), 24-bit)
|
|
24
|
+
MODE_S_CRC_POLY = 0x1FFF409
|
|
25
|
+
|
|
26
|
+
# Streaming I/Q demod for Base.run_iq.
|
|
27
|
+
class DemodIQ
|
|
28
|
+
def initialize(rate: 2_000_000)
|
|
29
|
+
@rate = rate.to_f
|
|
30
|
+
@spb = @rate / 1_000_000.0 # samples per µs (expect ~2)
|
|
31
|
+
@mag = []
|
|
32
|
+
@seen = {}
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def feed_iq(samples, rate: nil, &emit)
|
|
36
|
+
@rate = rate.to_f if rate
|
|
37
|
+
@spb = @rate / 1_000_000.0
|
|
38
|
+
m2 = PWN::SDR::Decoder::DSP.mag_sq(iq: samples)
|
|
39
|
+
@mag.concat(m2)
|
|
40
|
+
# Scan the entire buffer FIRST so frames that land earlier in a
|
|
41
|
+
# multi-ms chunk are not discarded by the ring-buffer clamp below.
|
|
42
|
+
# Only AFTER scan() has consumed what it can do we cap residual
|
|
43
|
+
# history (frame + preamble headroom ≈ 0.5 ms, keep 50 ms).
|
|
44
|
+
scan(&emit)
|
|
45
|
+
max = (@rate * 0.05).to_i
|
|
46
|
+
@mag.shift(@mag.length - max) if @mag.length > max
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
private
|
|
50
|
+
|
|
51
|
+
def scan
|
|
52
|
+
plen = PREAMBLE.length
|
|
53
|
+
return if @mag.length < plen + (112 * 2)
|
|
54
|
+
|
|
55
|
+
# Sliding correlator for the 4-pulse Mode-S preamble. Require the
|
|
56
|
+
# on-pulse peaks to dominate the off-slots by a clear margin and
|
|
57
|
+
# require DF ∈ known set before emitting (cuts noise catastrophically).
|
|
58
|
+
i = 0
|
|
59
|
+
accepted = 0
|
|
60
|
+
while i < @mag.length - (plen + 224)
|
|
61
|
+
# Pulse peaks at samples 0, 2, 7, 9; valleys at 1,3,4,5,6,8,10..
|
|
62
|
+
p0 = @mag[i]
|
|
63
|
+
p2 = @mag[i + 2]
|
|
64
|
+
p7 = @mag[i + 7]
|
|
65
|
+
p9 = @mag[i + 9]
|
|
66
|
+
peak = (p0 + p2 + p7 + p9) / 4.0
|
|
67
|
+
valley = (
|
|
68
|
+
@mag[i + 1] + @mag[i + 3] + @mag[i + 4] + @mag[i + 5] +
|
|
69
|
+
@mag[i + 6] + @mag[i + 8] + @mag[i + 10] + @mag[i + 12]
|
|
70
|
+
) / 8.0
|
|
71
|
+
if peak > valley * 2.5 && peak.positive?
|
|
72
|
+
bits = slice_ppm(@mag, i + plen, 112)
|
|
73
|
+
if bits && bits.length == 112
|
|
74
|
+
df = PWN::SDR::Decoder::DSP.bits_to_int(bits: bits[0, 5])
|
|
75
|
+
# DF 0/4/5/11/16/17/18/20/21 are the Mode-S set we care about
|
|
76
|
+
if [0, 4, 5, 11, 16, 17, 18, 20, 21].include?(df)
|
|
77
|
+
# ICAO all-zeros / all-ones is almost always garbage
|
|
78
|
+
icao_bits = bits[8, 24]
|
|
79
|
+
icao_int = PWN::SDR::Decoder::DSP.bits_to_int(bits: icao_bits)
|
|
80
|
+
if icao_int.positive? && icao_int != 0xFFFFFF && ADSB.crc_ok?(bits: bits)
|
|
81
|
+
msg = ADSB.decode_modes(bits: bits)
|
|
82
|
+
key = "#{msg[:icao24]}:#{msg[:df]}:#{msg[:raw_hex].to_s[0, 8]}"
|
|
83
|
+
unless @seen[key]
|
|
84
|
+
@seen[key] = true
|
|
85
|
+
@seen.shift if @seen.length > 512
|
|
86
|
+
yield msg
|
|
87
|
+
accepted += 1
|
|
88
|
+
end
|
|
89
|
+
i += plen + 224
|
|
90
|
+
next
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
i += 1
|
|
96
|
+
end
|
|
97
|
+
drop = [i - plen, 0].max
|
|
98
|
+
@mag.shift(drop) if drop.positive?
|
|
99
|
+
accepted
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Mode-S Pulse-Position Modulation: 1 µs = 2 samples; high-first → 1.
|
|
103
|
+
def slice_ppm(mag, start, nbits)
|
|
104
|
+
bits = Array.new(nbits)
|
|
105
|
+
nbits.times do |b|
|
|
106
|
+
a = start + (b * 2)
|
|
107
|
+
return nil if a + 1 >= mag.length
|
|
108
|
+
|
|
109
|
+
bits[b] = mag[a] >= mag[a + 1] ? 1 : 0
|
|
110
|
+
end
|
|
111
|
+
bits
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Supported Method Parameters::
|
|
116
|
+
# crc = PWN::SDR::Decoder::ADSB.crc24(bits: Array<0|1>)
|
|
117
|
+
# CRC over all bits except the final 24 (which hold the parity).
|
|
118
|
+
|
|
119
|
+
public_class_method def self.crc24(opts = {})
|
|
120
|
+
bits = opts[:bits] || []
|
|
121
|
+
return nil if bits.length < 32
|
|
122
|
+
|
|
123
|
+
# Mode-S CRC-24: left-shift register fed by every message bit
|
|
124
|
+
# (including the 24 parity bits). A valid frame leaves residual 0.
|
|
125
|
+
reg = 0
|
|
126
|
+
bits.each do |b|
|
|
127
|
+
reg <<= 1
|
|
128
|
+
reg |= (b & 1)
|
|
129
|
+
reg ^= MODE_S_CRC_POLY if reg.anybits?(0x1000000)
|
|
130
|
+
end
|
|
131
|
+
reg & 0xFFFFFF
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# Supported Method Parameters::
|
|
135
|
+
# ok = PWN::SDR::Decoder::ADSB.crc_ok?(bits: Array<0|1>)
|
|
136
|
+
|
|
137
|
+
public_class_method def self.crc_ok?(opts = {})
|
|
138
|
+
bits = opts[:bits] || []
|
|
139
|
+
return false unless [56, 112].include?(bits.length)
|
|
140
|
+
|
|
141
|
+
crc24(bits: bits).zero?
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# Supported Method Parameters::
|
|
145
|
+
# h = PWN::SDR::Decoder::ADSB.decode_modes(bits: Array<0|1> of length 56 or 112)
|
|
146
|
+
|
|
147
|
+
public_class_method def self.decode_modes(opts = {})
|
|
148
|
+
bits = opts[:bits] || []
|
|
149
|
+
return nil unless [56, 112].include?(bits.length)
|
|
150
|
+
|
|
151
|
+
# DF (5) + CA (3) + ICAO (24) + ...
|
|
152
|
+
df = PWN::SDR::Decoder::DSP.bits_to_int(bits: bits[0, 5])
|
|
153
|
+
icao = format('%06X', PWN::SDR::Decoder::DSP.bits_to_int(bits: bits[8, 24]))
|
|
154
|
+
out = {
|
|
155
|
+
protocol: 'ADSB',
|
|
156
|
+
df: df,
|
|
157
|
+
icao24: icao,
|
|
158
|
+
bits: bits.length,
|
|
159
|
+
raw_hex: bits.each_slice(4).map { |n| PWN::SDR::Decoder::DSP.bits_to_int(bits: n).to_s(16) }.join.upcase
|
|
160
|
+
}
|
|
161
|
+
# DF17/18 ME field (56 bits starting at bit 32)
|
|
162
|
+
if [17, 18].include?(df) && bits.length >= 88
|
|
163
|
+
tc = PWN::SDR::Decoder::DSP.bits_to_int(bits: bits[32, 5])
|
|
164
|
+
out[:type_code] = tc
|
|
165
|
+
if tc.between?(1, 4)
|
|
166
|
+
# aircraft identification — 8× 6-bit AIS chars
|
|
167
|
+
cs = bits[40, 48].each_slice(6).map { |ch| ais_char(code: PWN::SDR::Decoder::DSP.bits_to_int(bits: ch)) }.join.strip
|
|
168
|
+
out[:callsign] = cs
|
|
169
|
+
elsif tc.between?(9, 18) || tc.between?(20, 22)
|
|
170
|
+
out[:altitude_ft] = modes_altitude(bits12: bits[40, 12])
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
bits_s = []
|
|
174
|
+
bits_s << "ICAO=#{out[:icao24]}"
|
|
175
|
+
bits_s << "DF=#{df}"
|
|
176
|
+
bits_s << "CS=#{out[:callsign]}" if out[:callsign]
|
|
177
|
+
bits_s << "ALT=#{out[:altitude_ft]}ft" if out[:altitude_ft]
|
|
178
|
+
bits_s << "TC=#{out[:type_code]}" if out[:type_code]
|
|
179
|
+
out[:summary] = "ADSB #{bits_s.join(' ')}"
|
|
180
|
+
out
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
public_class_method def self.ais_char(opts = {})
|
|
184
|
+
code = opts[:code]
|
|
185
|
+
table = '#ABCDEFGHIJKLMNOPQRSTUVWXYZ##### ###############0123456789######'
|
|
186
|
+
table[code] || ' '
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
public_class_method def self.modes_altitude(opts = {})
|
|
190
|
+
bits12 = opts[:bits12]
|
|
191
|
+
return nil unless bits12.is_a?(Array) && bits12.length == 12
|
|
192
|
+
|
|
193
|
+
# Gillham / 25 ft encoding — simplified (bit 7 is Q)
|
|
194
|
+
q = bits12[7]
|
|
195
|
+
if q == 1
|
|
196
|
+
n = PWN::SDR::Decoder::DSP.bits_to_int(bits: bits12[0, 7] + bits12[8, 4])
|
|
197
|
+
return (n * 25) - 1000
|
|
198
|
+
end
|
|
199
|
+
nil
|
|
200
|
+
end
|
|
201
|
+
|
|
22
202
|
# Supported Method Parameters::
|
|
23
203
|
# PWN::SDR::Decoder::ADSB.decode(
|
|
24
204
|
# freq_obj: 'required - freq_obj returned from PWN::SDR::GQRX.init_freq'
|
|
@@ -28,10 +208,16 @@ module PWN
|
|
|
28
208
|
freq_obj = opts[:freq_obj]
|
|
29
209
|
hz = PWN::SDR.hz_to_i(freq: freq_obj[:freq])
|
|
30
210
|
uat = hz.between?(977_000_000, 979_000_000)
|
|
31
|
-
|
|
211
|
+
proto = uat ? 'ADSB-UAT978' : 'ADSB-1090ES'
|
|
212
|
+
rate = (opts[:sample_rate] || freq_obj[:iq_rate] || 2_000_000).to_i
|
|
213
|
+
PWN::SDR::Decoder::Base.run_iq(
|
|
32
214
|
freq_obj: freq_obj,
|
|
33
|
-
protocol:
|
|
34
|
-
|
|
215
|
+
protocol: proto,
|
|
216
|
+
sample_rate: rate,
|
|
217
|
+
source: opts[:source],
|
|
218
|
+
file: opts[:file],
|
|
219
|
+
demod: DemodIQ.new(rate: rate),
|
|
220
|
+
note: 'Mode-S 2 Mbit/s PPM — true-air path uses RTL-SDR/Pluto I/Q; detector fallback characterises squitter density only.',
|
|
35
221
|
describe: proc { |b| { modulation: 'PPM', frame_len_us: 120, classification: b[:duration_ms] < 5 ? 'squitter' : 'interrogation-train' } }
|
|
36
222
|
)
|
|
37
223
|
end
|
|
@@ -66,11 +252,14 @@ module PWN
|
|
|
66
252
|
# Display Usage for this Module
|
|
67
253
|
|
|
68
254
|
public_class_method def self.help
|
|
69
|
-
puts "USAGE (
|
|
255
|
+
puts "USAGE (true-air I/Q via PWN::FFI + detector fallback):
|
|
70
256
|
#{self}.decode(
|
|
71
|
-
freq_obj: 'required - freq_obj returned from PWN::SDR::GQRX.init_freq'
|
|
257
|
+
freq_obj: 'required - freq_obj returned from PWN::SDR::GQRX.init_freq',
|
|
258
|
+
source: 'optional - :auto|:rtlsdr|:adalm_pluto|:file',
|
|
259
|
+
file: 'optional - .cu8/.cs16 capture at ≥2 Msps'
|
|
72
260
|
)
|
|
73
261
|
|
|
262
|
+
#{self}.decode_modes(bits: [0,1,...]) # 56/112 Mode-S bits
|
|
74
263
|
#{self}.parse_line(line: 'MSG,3,1,1,ABCDEF,1,...')
|
|
75
264
|
|
|
76
265
|
#{self}.authors
|
data/lib/pwn/sdr/decoder/apt.rb
CHANGED
|
@@ -102,11 +102,25 @@ module PWN
|
|
|
102
102
|
|
|
103
103
|
public_class_method def self.decode(opts = {})
|
|
104
104
|
freq_obj = opts[:freq_obj]
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
105
|
+
want_iq = opts[:source] || opts[:file] || freq_obj[:iq_source] || freq_obj[:iq_file]
|
|
106
|
+
if want_iq
|
|
107
|
+
PWN::SDR::Decoder::Base.run_iq(
|
|
108
|
+
freq_obj: freq_obj,
|
|
109
|
+
protocol: 'NOAA-APT',
|
|
110
|
+
demod: Demod.new(out_path: opts[:out_path]),
|
|
111
|
+
sample_rate: (opts[:sample_rate] || freq_obj[:iq_rate] || 48_000).to_i,
|
|
112
|
+
source: opts[:source],
|
|
113
|
+
file: opts[:file],
|
|
114
|
+
fm_demod: true,
|
|
115
|
+
note: 'NOAA APT true-air: FM-demod I/Q then 2400 Hz AM envelope → PGM.'
|
|
116
|
+
)
|
|
117
|
+
else
|
|
118
|
+
PWN::SDR::Decoder::Base.run_native(
|
|
119
|
+
freq_obj: freq_obj,
|
|
120
|
+
protocol: 'NOAA-APT',
|
|
121
|
+
demod: Demod.new(out_path: opts[:out_path])
|
|
122
|
+
)
|
|
123
|
+
end
|
|
110
124
|
end
|
|
111
125
|
|
|
112
126
|
# Author(s):: 0day Inc. <support@0dayinc.com>
|