pwn 0.5.617 → 0.5.620
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Gemfile +2 -2
- data/README.md +101 -83
- data/bin/pwn_gqrx_scanner +25 -5
- data/documentation/AI-Integration.md +42 -26
- data/documentation/AWS.md +57 -0
- data/documentation/Agent-Tool-Registry.md +56 -0
- data/documentation/Banner.md +17 -0
- data/documentation/Blockchain.md +18 -0
- data/documentation/Bounty.md +21 -0
- data/documentation/BurpSuite.md +41 -16
- data/documentation/CLI-Drivers.md +58 -0
- data/documentation/Configuration.md +66 -0
- data/documentation/Contributing.md +33 -19
- data/documentation/Cron.md +47 -0
- data/documentation/Diagrams.md +6 -2
- data/documentation/Drivers.md +43 -16
- data/documentation/Extrospection.md +73 -0
- data/documentation/FFI.md +14 -0
- data/documentation/Fuzzing.md +36 -0
- data/documentation/Hardware.md +40 -0
- data/documentation/Home.md +5 -4
- data/documentation/How-PWN-Works.md +7 -4
- data/documentation/Metasploit.md +34 -0
- data/documentation/Mistakes.md +96 -0
- data/documentation/NmapIt.md +18 -12
- data/documentation/PWN.png +0 -0
- data/documentation/PWN_Contributors_and_Users.png +0 -0
- data/documentation/Persistence.md +40 -0
- data/documentation/Plugins.md +97 -47
- data/documentation/Reporting.md +25 -18
- data/documentation/SAST.md +39 -22
- data/documentation/SDR.md +58 -0
- data/documentation/Sessions.md +39 -0
- data/documentation/Skills-Memory-Learning.md +59 -29
- data/documentation/Swarm.md +71 -0
- data/documentation/Transparent-Browser.md +26 -22
- data/documentation/Troubleshooting.md +44 -25
- data/documentation/WWW.md +32 -0
- data/documentation/diagrams/agent-tool-registry.svg +286 -0
- data/documentation/diagrams/aws-cloud-security.svg +166 -0
- data/documentation/diagrams/cron-scheduling.svg +148 -0
- data/documentation/diagrams/dot/agent-tool-registry.dot +2 -2
- data/documentation/diagrams/dot/extrospection-world-awareness.dot +9 -7
- data/documentation/diagrams/dot/hardware-hacking.dot +48 -0
- data/documentation/diagrams/dot/memory-skills-detailed.dot +12 -5
- data/documentation/diagrams/dot/mistakes-negative-feedback.dot +93 -0
- data/documentation/diagrams/dot/overall-pwn-architecture.dot +7 -5
- data/documentation/diagrams/dot/persistence-filesystem.dot +30 -0
- data/documentation/diagrams/dot/pwn-ai-feedback-learning-loop.dot +19 -7
- data/documentation/diagrams/dot/sdr-radio-flow.dot +13 -11
- data/documentation/diagrams/extrospection-world-awareness.svg +203 -0
- data/documentation/diagrams/hardware-hacking.svg +163 -0
- data/documentation/diagrams/memory-skills-detailed.svg +146 -97
- data/documentation/diagrams/mistakes-negative-feedback.svg +266 -0
- data/documentation/diagrams/overall-pwn-architecture.svg +184 -173
- data/documentation/diagrams/persistence-filesystem.svg +189 -0
- data/documentation/diagrams/pwn-ai-feedback-learning-loop.svg +217 -149
- data/documentation/diagrams/sdr-radio-flow.svg +166 -0
- data/documentation/diagrams/swarm-multi-agent.svg +225 -0
- data/documentation/fax-spectrogram.png +0 -0
- data/documentation/fax-waveform.png +0 -0
- data/documentation/pwn-REPL.md +40 -24
- data/documentation/pwn-ai-Agent.md +72 -30
- data/documentation/pwn_android_war_dialer_session.png +0 -0
- data/documentation/pwn_install.png +0 -0
- data/documentation/pwn_wallpaper.jpg +0 -0
- data/documentation/ringing-spectrogram.png +0 -0
- data/documentation/ringing-waveform.png +0 -0
- data/lib/pwn/ai/agent/extrospection.rb +65 -6
- data/lib/pwn/ai/agent/learning.rb +67 -7
- data/lib/pwn/ai/agent/loop.rb +72 -6
- data/lib/pwn/ai/agent/mistakes.rb +369 -0
- data/lib/pwn/ai/agent/prompt_builder.rb +10 -1
- data/lib/pwn/ai/agent/tools/extrospection.rb +2 -2
- data/lib/pwn/ai/agent/tools/mistakes.rb +132 -0
- data/lib/pwn/ai/agent.rb +1 -0
- data/lib/pwn/plugins/jira_data_center.rb +2 -2
- data/lib/pwn/sdr/decoder/adsb.rb +101 -0
- data/lib/pwn/sdr/decoder/apt.rb +75 -0
- data/lib/pwn/sdr/decoder/bluetooth.rb +78 -0
- data/lib/pwn/sdr/decoder/dect.rb +84 -0
- data/lib/pwn/sdr/decoder/gps.rb +83 -0
- data/lib/pwn/sdr/decoder/gsm.rb +50 -29
- data/lib/pwn/sdr/decoder/iridium.rb +81 -0
- data/lib/pwn/sdr/decoder/lora.rb +87 -0
- data/lib/pwn/sdr/decoder/lte.rb +88 -0
- data/lib/pwn/sdr/decoder/morse.rb +64 -0
- data/lib/pwn/sdr/decoder/p25.rb +71 -0
- data/lib/pwn/sdr/decoder/pager.rb +77 -0
- data/lib/pwn/sdr/decoder/pocsag.rb +26 -11
- data/lib/pwn/sdr/decoder/rfid.rb +88 -0
- data/lib/pwn/sdr/decoder/rtl433.rb +95 -0
- data/lib/pwn/sdr/decoder/rtty.rb +66 -0
- data/lib/pwn/sdr/decoder/wifi.rb +98 -0
- data/lib/pwn/sdr/decoder/zigbee.rb +96 -0
- data/lib/pwn/sdr/decoder.rb +67 -7
- data/lib/pwn/sdr/frequency_allocation.rb +82 -41
- data/lib/pwn/sdr/gqrx.rb +186 -95
- data/lib/pwn/version.rb +1 -1
- data/spec/lib/pwn/ai/agent/mistakes_spec.rb +36 -0
- data/spec/lib/pwn/ai/agent/tools/mistakes_spec.rb +12 -0
- data/spec/lib/pwn/sdr/decoder/adsb_spec.rb +15 -0
- data/spec/lib/pwn/sdr/decoder/apt_spec.rb +15 -0
- data/spec/lib/pwn/sdr/decoder/bluetooth_spec.rb +15 -0
- data/spec/lib/pwn/sdr/decoder/dect_spec.rb +15 -0
- data/spec/lib/pwn/sdr/decoder/gps_spec.rb +15 -0
- data/spec/lib/pwn/sdr/decoder/iridium_spec.rb +15 -0
- data/spec/lib/pwn/sdr/decoder/lora_spec.rb +15 -0
- data/spec/lib/pwn/sdr/decoder/lte_spec.rb +15 -0
- data/spec/lib/pwn/sdr/decoder/morse_spec.rb +15 -0
- data/spec/lib/pwn/sdr/decoder/p25_spec.rb +15 -0
- data/spec/lib/pwn/sdr/decoder/pager_spec.rb +15 -0
- data/spec/lib/pwn/sdr/decoder/rfid_spec.rb +15 -0
- data/spec/lib/pwn/sdr/decoder/rtl433_spec.rb +15 -0
- data/spec/lib/pwn/sdr/decoder/rtty_spec.rb +15 -0
- data/spec/lib/pwn/sdr/decoder/wifi_spec.rb +15 -0
- data/spec/lib/pwn/sdr/decoder/zigbee_spec.rb +15 -0
- data/third_party/pwn_rdoc.jsonl +114 -8
- metadata +81 -5
|
@@ -0,0 +1,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
|
data/lib/pwn/sdr/decoder/gsm.rb
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'shellwords'
|
|
4
|
+
|
|
3
5
|
module PWN
|
|
4
6
|
module SDR
|
|
5
7
|
module Decoder
|
|
@@ -7,14 +9,32 @@ module PWN
|
|
|
7
9
|
#
|
|
8
10
|
# GSM is 270.833 kbit/s GMSK — it CANNOT be recovered from GQRX's 48 kHz
|
|
9
11
|
# demodulated-audio UDP tap. This module therefore drives the SDR
|
|
10
|
-
# directly via `grgsm_livemon_headless` (from gr-gsm) which publishes
|
|
11
|
-
# decoded Um bursts as GSMTAP on udp/4729,
|
|
12
|
+
# directly via `grgsm_livemon_headless` (from gr-gsm), which publishes
|
|
13
|
+
# decoded Um bursts as GSMTAP on udp/4729, and reads them back with
|
|
12
14
|
# `tshark` for structured field extraction (MCC/MNC/LAC/CI/ARFCN, paging
|
|
13
15
|
# IMSIs/TMSIs, System Information messages, etc.).
|
|
14
16
|
#
|
|
15
17
|
# Interface matches PWN::SDR::Decoder::Flex / ::RDS so the GQRX
|
|
16
18
|
# dispatcher (`decoder: :gsm`) works uniformly.
|
|
19
|
+
#
|
|
20
|
+
# NOTE: grgsm_livemon_headless opens the SDR hardware itself. If GQRX
|
|
21
|
+
# already owns the device, pass a distinct `--args` string via
|
|
22
|
+
# freq_obj[:sdr_args] (e.g. 'rtl=1' or 'hackrf=0') or stop GQRX's DSP
|
|
23
|
+
# first (`U DSP 0`).
|
|
17
24
|
module GSM
|
|
25
|
+
TSHARK_FIELDS = %w[
|
|
26
|
+
frame.time
|
|
27
|
+
gsmtap.arfcn
|
|
28
|
+
gsmtap.chan_type
|
|
29
|
+
gsm_a.imsi
|
|
30
|
+
gsm_a.tmsi
|
|
31
|
+
e212.mcc
|
|
32
|
+
e212.mnc
|
|
33
|
+
gsm_a.lac
|
|
34
|
+
gsm_a.bssmap.cell_ci
|
|
35
|
+
gsm_a.dtap.msg_rr_type
|
|
36
|
+
].freeze
|
|
37
|
+
|
|
18
38
|
# Supported Method Parameters::
|
|
19
39
|
# PWN::SDR::Decoder::GSM.decode(
|
|
20
40
|
# freq_obj: 'required - freq_obj returned from PWN::SDR::GQRX.init_freq'
|
|
@@ -24,26 +44,25 @@ module PWN
|
|
|
24
44
|
freq_obj = opts[:freq_obj]
|
|
25
45
|
raise 'ERROR: :freq_obj is required' unless freq_obj.is_a?(Hash)
|
|
26
46
|
|
|
27
|
-
hz
|
|
28
|
-
gain
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
"#{tshark_fields.map { |f| "-e #{f}" }.join(' ')}'"
|
|
47
|
+
hz = PWN::SDR.hz_to_i(freq: freq_obj[:freq])
|
|
48
|
+
gain = (freq_obj[:rf_gain] || 40).to_s.to_f
|
|
49
|
+
sdr_args = freq_obj[:sdr_args].to_s
|
|
50
|
+
gsmtap = (freq_obj[:gsmtap_port] || 4729).to_i
|
|
51
|
+
|
|
52
|
+
grgsm = ['grgsm_livemon_headless', '-f', hz.to_s, '-g', gain.to_s]
|
|
53
|
+
grgsm.push('--args', sdr_args) unless sdr_args.empty?
|
|
54
|
+
|
|
55
|
+
tshark = ['tshark', '-i', 'lo', '-l', '-n',
|
|
56
|
+
'-f', "udp port #{gsmtap}",
|
|
57
|
+
'-Y', 'gsmtap',
|
|
58
|
+
'-T', 'fields', '-E', 'separator=|']
|
|
59
|
+
TSHARK_FIELDS.each { |f| tshark.push('-e', f) }
|
|
60
|
+
|
|
61
|
+
# bash -c '<grgsm> >/dev/null 2>&1 & pid=$!; trap ... ; <tshark>'
|
|
62
|
+
inner = "#{Shellwords.join(grgsm)} >/dev/null 2>&1 & " \
|
|
63
|
+
'LMPID=$!; trap "kill $LMPID 2>/dev/null" EXIT INT TERM; ' \
|
|
64
|
+
"sleep 2; exec #{Shellwords.join(tshark)}"
|
|
65
|
+
direct_cmd = "bash -c #{Shellwords.escape(inner)}"
|
|
47
66
|
|
|
48
67
|
PWN::SDR::Decoder::Base.run_pipeline(
|
|
49
68
|
freq_obj: freq_obj,
|
|
@@ -63,6 +82,7 @@ module PWN
|
|
|
63
82
|
f = line.split('|', -1)
|
|
64
83
|
out = {
|
|
65
84
|
protocol: 'GSM',
|
|
85
|
+
frame_time: f[0],
|
|
66
86
|
arfcn: f[1],
|
|
67
87
|
chan_type: f[2],
|
|
68
88
|
imsi: f[3],
|
|
@@ -80,13 +100,13 @@ module PWN
|
|
|
80
100
|
out[:imsi_msin] = out[:imsi][6..]
|
|
81
101
|
end
|
|
82
102
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
out[:summary] = "GSM #{
|
|
103
|
+
bits = []
|
|
104
|
+
bits << "ARFCN=#{out[:arfcn]}" if out[:arfcn]
|
|
105
|
+
bits << "MCC/MNC=#{out[:mcc]}/#{out[:mnc]}" if out[:mcc]
|
|
106
|
+
bits << "LAC=#{out[:lac]} CI=#{out[:cell_id]}" if out[:lac]
|
|
107
|
+
bits << "IMSI=#{out[:imsi]}" if out[:imsi]
|
|
108
|
+
bits << "TMSI=#{out[:tmsi]}" if out[:tmsi]
|
|
109
|
+
out[:summary] = "GSM #{bits.join(' ')}".strip
|
|
90
110
|
out
|
|
91
111
|
end
|
|
92
112
|
|
|
@@ -107,6 +127,7 @@ module PWN
|
|
|
107
127
|
NOTE: Requires `grgsm_livemon_headless` (gr-gsm) and `tshark`.
|
|
108
128
|
GSM cannot be decoded from GQRX's 48 kHz audio tap; this
|
|
109
129
|
module drives the SDR directly and reads GSMTAP on lo:4729.
|
|
130
|
+
Set freq_obj[:sdr_args] (e.g. 'rtl=1') if GQRX owns device 0.
|
|
110
131
|
|
|
111
132
|
#{self}.parse_line(line: 'ts|arfcn|chan|imsi|tmsi|mcc|mnc|lac|ci|rr')
|
|
112
133
|
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'shellwords'
|
|
4
|
+
|
|
5
|
+
module PWN
|
|
6
|
+
module SDR
|
|
7
|
+
module Decoder
|
|
8
|
+
# Iridium L-band (1.616–1.6265 GHz) burst decoder. Drives gr-iridium's
|
|
9
|
+
# `iridium-extractor` against the SDR to demodulate 25 kbit/s DE-QPSK
|
|
10
|
+
# simplex/duplex bursts, then pipes them through iridium-toolkit's
|
|
11
|
+
# `iridium-parser.py` for frame classification (IRA/IBC/IDA/ISY/VOC/...).
|
|
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
|
+
raise 'ERROR: :freq_obj is required' unless freq_obj.is_a?(Hash)
|
|
21
|
+
|
|
22
|
+
hz = PWN::SDR.hz_to_i(freq: freq_obj[:freq])
|
|
23
|
+
gain = (freq_obj[:rf_gain] || 40).to_s.to_f
|
|
24
|
+
sdr_args = (freq_obj[:sdr_args] || 'rtl=0').to_s
|
|
25
|
+
|
|
26
|
+
inner = "iridium-extractor -D 4 --multi-frame -c #{hz} -r 2000000 " \
|
|
27
|
+
"-g #{gain} -o - #{Shellwords.escape(sdr_args)} 2>/dev/null " \
|
|
28
|
+
'| iridium-parser.py --harder /dev/stdin'
|
|
29
|
+
direct_cmd = "bash -c #{Shellwords.escape(inner)}"
|
|
30
|
+
|
|
31
|
+
PWN::SDR::Decoder::Base.run_pipeline(
|
|
32
|
+
freq_obj: freq_obj,
|
|
33
|
+
protocol: 'IRIDIUM',
|
|
34
|
+
required_bins: %w[iridium-extractor iridium-parser.py],
|
|
35
|
+
direct_cmd: direct_cmd,
|
|
36
|
+
line_match: /^(IRA|IBC|IDA|ISY|ITL|MSG|VOC|RAW):/,
|
|
37
|
+
parser: proc { |line| parse_line(line: line) }
|
|
38
|
+
)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Supported Method Parameters::
|
|
42
|
+
# PWN::SDR::Decoder::Iridium.parse_line(line: 'IRA: sat:23 beam:14 ...')
|
|
43
|
+
|
|
44
|
+
public_class_method def self.parse_line(opts = {})
|
|
45
|
+
line = opts[:line].to_s
|
|
46
|
+
out = { protocol: 'IRIDIUM' }
|
|
47
|
+
out[:frame_type] = ::Regexp.last_match(1) if line =~ /^([A-Z]{3}):/
|
|
48
|
+
out[:sat] = ::Regexp.last_match(1) if line =~ /sat:(\d+)/
|
|
49
|
+
out[:beam] = ::Regexp.last_match(1) if line =~ /beam:(\d+)/
|
|
50
|
+
out[:pos] = ::Regexp.last_match(1) if line =~ /pos=\(([^)]+)\)/
|
|
51
|
+
out[:ra_id] = ::Regexp.last_match(1) if line =~ /ric:(\d+)/
|
|
52
|
+
out[:summary] = "IRIDIUM #{out[:frame_type]} sat=#{out[:sat]} beam=#{out[:beam]}"
|
|
53
|
+
out.compact
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Author(s):: 0day Inc. <support@0dayinc.com>
|
|
57
|
+
|
|
58
|
+
public_class_method def self.authors
|
|
59
|
+
"AUTHOR(S):\n 0day Inc. <support@0dayinc.com>\n"
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Display Usage for this Module
|
|
63
|
+
|
|
64
|
+
public_class_method def self.help
|
|
65
|
+
puts "USAGE:
|
|
66
|
+
#{self}.decode(
|
|
67
|
+
freq_obj: 'required - freq_obj returned from PWN::SDR::GQRX.init_freq'
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
NOTE: Requires `iridium-extractor` (gr-iridium) and
|
|
71
|
+
`iridium-parser.py` (iridium-toolkit). Owns the SDR.
|
|
72
|
+
|
|
73
|
+
#{self}.parse_line(line: 'IRA: sat:23 beam:14 pos=(+32.1/-097.0) ...')
|
|
74
|
+
|
|
75
|
+
#{self}.authors
|
|
76
|
+
"
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
require 'shellwords'
|
|
5
|
+
|
|
6
|
+
module PWN
|
|
7
|
+
module SDR
|
|
8
|
+
module Decoder
|
|
9
|
+
# LoRa / LoRaWAN CSS decoder for the 433 / 868 / 902–928 MHz ISM
|
|
10
|
+
# allocations. Chirp-spread-spectrum needs raw I/Q, so this drives
|
|
11
|
+
# `rtl_433` (which ships a native LoRa demod, protocol #264) directly.
|
|
12
|
+
# Emits one JSON line per decoded uplink containing SF/BW/CR, DevAddr,
|
|
13
|
+
# FCnt, and raw PHYPayload hex.
|
|
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
|
+
raise 'ERROR: :freq_obj is required' unless freq_obj.is_a?(Hash)
|
|
23
|
+
|
|
24
|
+
hz = PWN::SDR.hz_to_i(freq: freq_obj[:freq])
|
|
25
|
+
sdr_args = freq_obj[:sdr_args].to_s
|
|
26
|
+
|
|
27
|
+
cmd = ['rtl_433', '-f', hz.to_s, '-s', '1024k',
|
|
28
|
+
'-R', '264', '-F', 'json', '-M', 'level']
|
|
29
|
+
cmd.push('-d', sdr_args) unless sdr_args.empty?
|
|
30
|
+
|
|
31
|
+
PWN::SDR::Decoder::Base.run_pipeline(
|
|
32
|
+
freq_obj: freq_obj,
|
|
33
|
+
protocol: 'LoRa',
|
|
34
|
+
required_bins: %w[rtl_433],
|
|
35
|
+
direct_cmd: Shellwords.join(cmd),
|
|
36
|
+
line_match: /^\s*{/,
|
|
37
|
+
parser: proc { |line| parse_line(line: line) }
|
|
38
|
+
)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Supported Method Parameters::
|
|
42
|
+
# PWN::SDR::Decoder::LoRa.parse_line(line: '{"model":"LoRa",...}')
|
|
43
|
+
|
|
44
|
+
public_class_method def self.parse_line(opts = {})
|
|
45
|
+
line = opts[:line].to_s
|
|
46
|
+
h = begin
|
|
47
|
+
JSON.parse(line, symbolize_names: true)
|
|
48
|
+
rescue StandardError
|
|
49
|
+
{ unparsed: line }
|
|
50
|
+
end
|
|
51
|
+
out = { protocol: 'LoRa' }.merge(h)
|
|
52
|
+
bits = []
|
|
53
|
+
bits << "SF#{out[:sf]}" if out[:sf]
|
|
54
|
+
bits << "BW#{out[:bw]}" if out[:bw]
|
|
55
|
+
bits << "CR#{out[:cr]}" if out[:cr]
|
|
56
|
+
bits << "DevAddr=#{out[:devaddr] || out[:id]}" if out[:devaddr] || out[:id]
|
|
57
|
+
bits << "FCnt=#{out[:fcnt]}" if out[:fcnt]
|
|
58
|
+
bits << "len=#{out[:len] || (out[:data].to_s.length / 2)}" if out[:len] || out[:data]
|
|
59
|
+
out[:summary] = "LoRa #{bits.join(' ')}".strip
|
|
60
|
+
out
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Author(s):: 0day Inc. <support@0dayinc.com>
|
|
64
|
+
|
|
65
|
+
public_class_method def self.authors
|
|
66
|
+
"AUTHOR(S):\n 0day Inc. <support@0dayinc.com>\n"
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Display Usage for this Module
|
|
70
|
+
|
|
71
|
+
public_class_method def self.help
|
|
72
|
+
puts "USAGE:
|
|
73
|
+
#{self}.decode(
|
|
74
|
+
freq_obj: 'required - freq_obj returned from PWN::SDR::GQRX.init_freq'
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
NOTE: Requires `rtl_433` >= 23.x (native LoRa demod). Owns SDR.
|
|
78
|
+
|
|
79
|
+
#{self}.parse_line(line: '{\"model\":\"LoRa\",\"sf\":7,\"bw\":125,...}')
|
|
80
|
+
|
|
81
|
+
#{self}.authors
|
|
82
|
+
"
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|