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.
Files changed (120) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +2 -2
  3. data/README.md +101 -83
  4. data/bin/pwn_gqrx_scanner +25 -5
  5. data/documentation/AI-Integration.md +42 -26
  6. data/documentation/AWS.md +57 -0
  7. data/documentation/Agent-Tool-Registry.md +56 -0
  8. data/documentation/Banner.md +17 -0
  9. data/documentation/Blockchain.md +18 -0
  10. data/documentation/Bounty.md +21 -0
  11. data/documentation/BurpSuite.md +41 -16
  12. data/documentation/CLI-Drivers.md +58 -0
  13. data/documentation/Configuration.md +66 -0
  14. data/documentation/Contributing.md +33 -19
  15. data/documentation/Cron.md +47 -0
  16. data/documentation/Diagrams.md +6 -2
  17. data/documentation/Drivers.md +43 -16
  18. data/documentation/Extrospection.md +73 -0
  19. data/documentation/FFI.md +14 -0
  20. data/documentation/Fuzzing.md +36 -0
  21. data/documentation/Hardware.md +40 -0
  22. data/documentation/Home.md +5 -4
  23. data/documentation/How-PWN-Works.md +7 -4
  24. data/documentation/Metasploit.md +34 -0
  25. data/documentation/Mistakes.md +96 -0
  26. data/documentation/NmapIt.md +18 -12
  27. data/documentation/PWN.png +0 -0
  28. data/documentation/PWN_Contributors_and_Users.png +0 -0
  29. data/documentation/Persistence.md +40 -0
  30. data/documentation/Plugins.md +97 -47
  31. data/documentation/Reporting.md +25 -18
  32. data/documentation/SAST.md +39 -22
  33. data/documentation/SDR.md +58 -0
  34. data/documentation/Sessions.md +39 -0
  35. data/documentation/Skills-Memory-Learning.md +59 -29
  36. data/documentation/Swarm.md +71 -0
  37. data/documentation/Transparent-Browser.md +26 -22
  38. data/documentation/Troubleshooting.md +44 -25
  39. data/documentation/WWW.md +32 -0
  40. data/documentation/diagrams/agent-tool-registry.svg +286 -0
  41. data/documentation/diagrams/aws-cloud-security.svg +166 -0
  42. data/documentation/diagrams/cron-scheduling.svg +148 -0
  43. data/documentation/diagrams/dot/agent-tool-registry.dot +2 -2
  44. data/documentation/diagrams/dot/extrospection-world-awareness.dot +9 -7
  45. data/documentation/diagrams/dot/hardware-hacking.dot +48 -0
  46. data/documentation/diagrams/dot/memory-skills-detailed.dot +12 -5
  47. data/documentation/diagrams/dot/mistakes-negative-feedback.dot +93 -0
  48. data/documentation/diagrams/dot/overall-pwn-architecture.dot +7 -5
  49. data/documentation/diagrams/dot/persistence-filesystem.dot +30 -0
  50. data/documentation/diagrams/dot/pwn-ai-feedback-learning-loop.dot +19 -7
  51. data/documentation/diagrams/dot/sdr-radio-flow.dot +13 -11
  52. data/documentation/diagrams/extrospection-world-awareness.svg +203 -0
  53. data/documentation/diagrams/hardware-hacking.svg +163 -0
  54. data/documentation/diagrams/memory-skills-detailed.svg +146 -97
  55. data/documentation/diagrams/mistakes-negative-feedback.svg +266 -0
  56. data/documentation/diagrams/overall-pwn-architecture.svg +184 -173
  57. data/documentation/diagrams/persistence-filesystem.svg +189 -0
  58. data/documentation/diagrams/pwn-ai-feedback-learning-loop.svg +217 -149
  59. data/documentation/diagrams/sdr-radio-flow.svg +166 -0
  60. data/documentation/diagrams/swarm-multi-agent.svg +225 -0
  61. data/documentation/fax-spectrogram.png +0 -0
  62. data/documentation/fax-waveform.png +0 -0
  63. data/documentation/pwn-REPL.md +40 -24
  64. data/documentation/pwn-ai-Agent.md +72 -30
  65. data/documentation/pwn_android_war_dialer_session.png +0 -0
  66. data/documentation/pwn_install.png +0 -0
  67. data/documentation/pwn_wallpaper.jpg +0 -0
  68. data/documentation/ringing-spectrogram.png +0 -0
  69. data/documentation/ringing-waveform.png +0 -0
  70. data/lib/pwn/ai/agent/extrospection.rb +65 -6
  71. data/lib/pwn/ai/agent/learning.rb +67 -7
  72. data/lib/pwn/ai/agent/loop.rb +72 -6
  73. data/lib/pwn/ai/agent/mistakes.rb +369 -0
  74. data/lib/pwn/ai/agent/prompt_builder.rb +10 -1
  75. data/lib/pwn/ai/agent/tools/extrospection.rb +2 -2
  76. data/lib/pwn/ai/agent/tools/mistakes.rb +132 -0
  77. data/lib/pwn/ai/agent.rb +1 -0
  78. data/lib/pwn/plugins/jira_data_center.rb +2 -2
  79. data/lib/pwn/sdr/decoder/adsb.rb +101 -0
  80. data/lib/pwn/sdr/decoder/apt.rb +75 -0
  81. data/lib/pwn/sdr/decoder/bluetooth.rb +78 -0
  82. data/lib/pwn/sdr/decoder/dect.rb +84 -0
  83. data/lib/pwn/sdr/decoder/gps.rb +83 -0
  84. data/lib/pwn/sdr/decoder/gsm.rb +50 -29
  85. data/lib/pwn/sdr/decoder/iridium.rb +81 -0
  86. data/lib/pwn/sdr/decoder/lora.rb +87 -0
  87. data/lib/pwn/sdr/decoder/lte.rb +88 -0
  88. data/lib/pwn/sdr/decoder/morse.rb +64 -0
  89. data/lib/pwn/sdr/decoder/p25.rb +71 -0
  90. data/lib/pwn/sdr/decoder/pager.rb +77 -0
  91. data/lib/pwn/sdr/decoder/pocsag.rb +26 -11
  92. data/lib/pwn/sdr/decoder/rfid.rb +88 -0
  93. data/lib/pwn/sdr/decoder/rtl433.rb +95 -0
  94. data/lib/pwn/sdr/decoder/rtty.rb +66 -0
  95. data/lib/pwn/sdr/decoder/wifi.rb +98 -0
  96. data/lib/pwn/sdr/decoder/zigbee.rb +96 -0
  97. data/lib/pwn/sdr/decoder.rb +67 -7
  98. data/lib/pwn/sdr/frequency_allocation.rb +82 -41
  99. data/lib/pwn/sdr/gqrx.rb +186 -95
  100. data/lib/pwn/version.rb +1 -1
  101. data/spec/lib/pwn/ai/agent/mistakes_spec.rb +36 -0
  102. data/spec/lib/pwn/ai/agent/tools/mistakes_spec.rb +12 -0
  103. data/spec/lib/pwn/sdr/decoder/adsb_spec.rb +15 -0
  104. data/spec/lib/pwn/sdr/decoder/apt_spec.rb +15 -0
  105. data/spec/lib/pwn/sdr/decoder/bluetooth_spec.rb +15 -0
  106. data/spec/lib/pwn/sdr/decoder/dect_spec.rb +15 -0
  107. data/spec/lib/pwn/sdr/decoder/gps_spec.rb +15 -0
  108. data/spec/lib/pwn/sdr/decoder/iridium_spec.rb +15 -0
  109. data/spec/lib/pwn/sdr/decoder/lora_spec.rb +15 -0
  110. data/spec/lib/pwn/sdr/decoder/lte_spec.rb +15 -0
  111. data/spec/lib/pwn/sdr/decoder/morse_spec.rb +15 -0
  112. data/spec/lib/pwn/sdr/decoder/p25_spec.rb +15 -0
  113. data/spec/lib/pwn/sdr/decoder/pager_spec.rb +15 -0
  114. data/spec/lib/pwn/sdr/decoder/rfid_spec.rb +15 -0
  115. data/spec/lib/pwn/sdr/decoder/rtl433_spec.rb +15 -0
  116. data/spec/lib/pwn/sdr/decoder/rtty_spec.rb +15 -0
  117. data/spec/lib/pwn/sdr/decoder/wifi_spec.rb +15 -0
  118. data/spec/lib/pwn/sdr/decoder/zigbee_spec.rb +15 -0
  119. data/third_party/pwn_rdoc.jsonl +114 -8
  120. 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
@@ -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, then reads them back with
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 = PWN::SDR.hz_to_i(freq: freq_obj[:freq])
28
- gain = (freq_obj[:rf_gain] || 40).to_s.to_f
29
-
30
- tshark_fields = %w[
31
- frame.time
32
- gsmtap.arfcn
33
- gsmtap.chan_type
34
- gsm_a.imsi
35
- gsm_a.tmsi
36
- e212.mcc
37
- e212.mnc
38
- gsm_a.lac
39
- gsm_a.bssmap.cell_ci
40
- gsm_a.dtap.msg_rr_type
41
- ]
42
-
43
- direct_cmd = "sh -c 'grgsm_livemon_headless -f #{hz} -g #{gain} >/dev/null 2>&1 & " \
44
- 'LMPID=$!; trap "kill $LMPID 2>/dev/null" EXIT; sleep 2; ' \
45
- 'tshark -i lo -l -Y gsmtap -T fields -E separator=\| ' \
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
- summary_bits = []
84
- summary_bits << "ARFCN=#{out[:arfcn]}" if out[:arfcn]
85
- summary_bits << "MCC/MNC=#{out[:mcc]}/#{out[:mnc]}" if out[:mcc]
86
- summary_bits << "LAC=#{out[:lac]} CI=#{out[:cell_id]}" if out[:lac]
87
- summary_bits << "IMSI=#{out[:imsi]}" if out[:imsi]
88
- summary_bits << "TMSI=#{out[:tmsi]}" if out[:tmsi]
89
- out[:summary] = "GSM #{summary_bits.join(' ')}".strip
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