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,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'shellwords'
4
+
5
+ module PWN
6
+ module SDR
7
+ module Decoder
8
+ # LTE / UMTS / CDMA / PCS / AWS wideband cellular cell-search decoder.
9
+ #
10
+ # 1.4–20 MHz OFDMA/WCDMA cannot be recovered from GQRX audio; this
11
+ # module drives srsRAN's `cell_search` (or `LTE-Cell-Scanner`'s
12
+ # `CellSearch`) directly against the SDR to enumerate physical cells:
13
+ # EARFCN, PCI, CP type, RSRP/RSRQ, PSS/SSS correlation.
14
+ module LTE
15
+ # Supported Method Parameters::
16
+ # PWN::SDR::Decoder::LTE.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
+ gain = (freq_obj[:rf_gain] || 40).to_s.to_f
26
+ sdr_args = (freq_obj[:sdr_args] || 'driver=rtlsdr').to_s
27
+
28
+ direct_cmd =
29
+ if PWN::SDR::Decoder::Base.bin_available?(bin: 'cell_search')
30
+ # srsRAN cell_search takes a band number; fall back to explicit
31
+ # DL frequency via -s/-e range when unavailable.
32
+ "cell_search -a #{Shellwords.escape(sdr_args)} -g #{gain} -s #{hz} -e #{hz}"
33
+ else
34
+ "CellSearch -s #{hz / 1_000_000.0} -e #{hz / 1_000_000.0} -g #{gain}"
35
+ end
36
+
37
+ PWN::SDR::Decoder::Base.run_pipeline(
38
+ freq_obj: freq_obj,
39
+ protocol: 'LTE',
40
+ required_bins: %w[cell_search],
41
+ direct_cmd: direct_cmd,
42
+ line_match: /(CELL|PCI|EARFCN|Found|RSRP|CID)/i,
43
+ parser: proc { |line| parse_line(line: line) }
44
+ )
45
+ end
46
+
47
+ # Supported Method Parameters::
48
+ # PWN::SDR::Decoder::LTE.parse_line(line: 'Found CELL ... PCI: 123 ... PRB: 50 ... -85.0 dBm')
49
+
50
+ public_class_method def self.parse_line(opts = {})
51
+ line = opts[:line].to_s
52
+ out = { protocol: 'LTE' }
53
+ out[:earfcn] = ::Regexp.last_match(1) if line =~ /EARFCN[:= ]+(\d+)/i
54
+ out[:pci] = ::Regexp.last_match(1) if line =~ /(?:PCI|N_id_cell|Id)[:= ]+(\d{1,3})/i
55
+ out[:prb] = (::Regexp.last_match(1) || ::Regexp.last_match(2)) if line =~ /(?:PRB[:= ]+(\d+)|(\d+)\s*PRB)/i
56
+ out[:cp] = ::Regexp.last_match(1) if line =~ /\b(Normal|Extended)\b\s*CP/i
57
+ out[:rsrp] = ::Regexp.last_match(1) if line =~ /(-?\d+(?:\.\d+)?)\s*dBm/
58
+ out[:freq_mhz] = ::Regexp.last_match(1) if line =~ /(\d{3,4}\.\d)\s*MHz/
59
+ out[:summary] = "LTE PCI=#{out[:pci]} EARFCN=#{out[:earfcn]} PRB=#{out[:prb]} RSRP=#{out[:rsrp]}dBm"
60
+ out.compact
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 srsRAN `cell_search` (or LTE-Cell-Scanner
78
+ `CellSearch`). Owns the SDR — set freq_obj[:sdr_args].
79
+
80
+ #{self}.parse_line(line: 'Found CELL 739.0 MHz, EARFCN=5110, PCI=123, 50 PRB, -85.2 dBm')
81
+
82
+ #{self}.authors
83
+ "
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PWN
4
+ module SDR
5
+ module Decoder
6
+ # CW / Morse decoder for the amateur CW sub-bands (cw20/cw40/cw80,
7
+ # amateur_30m). GQRX's CW demodulator produces a ~700 Hz sidetone in the
8
+ # 48 kHz UDP audio stream; multimon-ng's MORSE_CW demod recovers the
9
+ # dit/dah timing and prints decoded characters one line at a time.
10
+ module Morse
11
+ # Supported Method Parameters::
12
+ # PWN::SDR::Decoder::Morse.decode(
13
+ # freq_obj: 'required - freq_obj returned from PWN::SDR::GQRX.init_freq'
14
+ # )
15
+
16
+ public_class_method def self.decode(opts = {})
17
+ freq_obj = opts[:freq_obj]
18
+
19
+ PWN::SDR::Decoder::Base.run_pipeline(
20
+ freq_obj: freq_obj,
21
+ protocol: 'MORSE-CW',
22
+ required_bins: %w[sox multimon-ng],
23
+ decode_cmd: 'multimon-ng -q -t raw -a MORSE_CW -',
24
+ line_match: /\S/,
25
+ parser: proc { |line| parse_line(line: line) }
26
+ )
27
+ end
28
+
29
+ # Supported Method Parameters::
30
+ # PWN::SDR::Decoder::Morse.parse_line(line: 'CQ CQ DE W1AW')
31
+
32
+ public_class_method def self.parse_line(opts = {})
33
+ line = opts[:line].to_s.gsub(/^MORSE(?:_CW)?:\s*/i, '').strip
34
+ out = { protocol: 'MORSE-CW', text: line }
35
+ if (m = line.match(/\b([A-Z0-9]{1,3}\d[A-Z]{1,4})\b/))
36
+ out[:callsign] = m[1]
37
+ end
38
+ out[:summary] = "CW #{line}"[0, 120]
39
+ out
40
+ end
41
+
42
+ # Author(s):: 0day Inc. <support@0dayinc.com>
43
+
44
+ public_class_method def self.authors
45
+ "AUTHOR(S):\n 0day Inc. <support@0dayinc.com>\n"
46
+ end
47
+
48
+ # Display Usage for this Module
49
+
50
+ public_class_method def self.help
51
+ puts "USAGE:
52
+ #{self}.decode(
53
+ freq_obj: 'required - freq_obj returned from PWN::SDR::GQRX.init_freq'
54
+ )
55
+
56
+ #{self}.parse_line(line: 'CQ CQ DE W1AW W1AW K')
57
+
58
+ #{self}.authors
59
+ "
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PWN
4
+ module SDR
5
+ module Decoder
6
+ # APCO Project 25 (P25) Phase-1 C4FM decoder for the 700/800 MHz
7
+ # public-safety allocations. GQRX supplies NBFM-discriminator audio via
8
+ # the UDP tap; `dsd` (Digital Speech Decoder) recovers the C4FM symbol
9
+ # stream and prints trunking-control frames (NAC, TGID, RID, DUID, ...).
10
+ module P25
11
+ # Supported Method Parameters::
12
+ # PWN::SDR::Decoder::P25.decode(
13
+ # freq_obj: 'required - freq_obj returned from PWN::SDR::GQRX.init_freq'
14
+ # )
15
+
16
+ public_class_method def self.decode(opts = {})
17
+ freq_obj = opts[:freq_obj]
18
+
19
+ # dsd expects 48 kHz s16le on stdin — bypass Base's 22 050 Hz resample
20
+ # by asking for 48 000 (sox becomes a passthrough / format guard).
21
+ PWN::SDR::Decoder::Base.run_pipeline(
22
+ freq_obj: freq_obj,
23
+ protocol: 'P25',
24
+ required_bins: %w[sox dsd],
25
+ resample_hz: 48_000,
26
+ decode_cmd: 'dsd -q -i - -o /dev/null -f1',
27
+ line_match: /(NAC|TGID|TG:|RID|src:|P25|Sync:)/i,
28
+ parser: proc { |line| parse_line(line: line) }
29
+ )
30
+ end
31
+
32
+ # Supported Method Parameters::
33
+ # PWN::SDR::Decoder::P25.parse_line(line: 'Sync: +P25p1 NAC: 293 src: 1234 tg: 5678')
34
+
35
+ public_class_method def self.parse_line(opts = {})
36
+ line = opts[:line].to_s
37
+ out = { protocol: 'P25' }
38
+ out[:nac] = ::Regexp.last_match(1) if line =~ /NAC:\s*([0-9A-Fa-f]+)/
39
+ out[:talkgroup] = ::Regexp.last_match(1) if line =~ /(?:TGID|tg[: ])\s*(\d+)/i
40
+ out[:radio_id] = ::Regexp.last_match(1) if line =~ /(?:RID|src[: ])\s*(\d+)/i
41
+ out[:duid] = ::Regexp.last_match(1) if line =~ /DUID:\s*(\S+)/
42
+ out[:sync] = ::Regexp.last_match(1) if line =~ /Sync:\s*(\S+)/
43
+ out[:summary] = "P25 NAC=#{out[:nac]} TG=#{out[:talkgroup]} RID=#{out[:radio_id]}".squeeze(' ')
44
+ out.compact
45
+ end
46
+
47
+ # Author(s):: 0day Inc. <support@0dayinc.com>
48
+
49
+ public_class_method def self.authors
50
+ "AUTHOR(S):\n 0day Inc. <support@0dayinc.com>\n"
51
+ end
52
+
53
+ # Display Usage for this Module
54
+
55
+ public_class_method def self.help
56
+ puts "USAGE:
57
+ #{self}.decode(
58
+ freq_obj: 'required - freq_obj returned from PWN::SDR::GQRX.init_freq'
59
+ )
60
+
61
+ NOTE: Requires `dsd`. Set GQRX demod to Narrow FM, ~12.5 kHz.
62
+
63
+ #{self}.parse_line(line: 'Sync: +P25p1 NAC: 293 src: 1234 tg: 5678')
64
+
65
+ #{self}.authors
66
+ "
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PWN
4
+ module SDR
5
+ module Decoder
6
+ # Combined pager decoder for the mixed-protocol `pager_all` band plan.
7
+ #
8
+ # Runs multimon-ng with FLEX + FLEX_NEXT + POCSAG512/1200/2400 enabled
9
+ # simultaneously and dispatches each output line to the appropriate
10
+ # per-protocol parser (::Flex-style or ::POCSAG.parse_line).
11
+ module Pager
12
+ # Supported Method Parameters::
13
+ # PWN::SDR::Decoder::Pager.decode(
14
+ # freq_obj: 'required - freq_obj returned from PWN::SDR::GQRX.init_freq'
15
+ # )
16
+
17
+ public_class_method def self.decode(opts = {})
18
+ freq_obj = opts[:freq_obj]
19
+
20
+ decode_cmd = 'multimon-ng -q -t raw -e -u -p ' \
21
+ '-a FLEX -a FLEX_NEXT ' \
22
+ '-a POCSAG512 -a POCSAG1200 -a POCSAG2400 -'
23
+
24
+ PWN::SDR::Decoder::Base.run_pipeline(
25
+ freq_obj: freq_obj,
26
+ protocol: 'PAGER',
27
+ required_bins: %w[sox multimon-ng],
28
+ decode_cmd: decode_cmd,
29
+ line_match: /^(FLEX|POCSAG)/,
30
+ parser: proc { |line| parse_line(line: line) }
31
+ )
32
+ end
33
+
34
+ # Supported Method Parameters::
35
+ # PWN::SDR::Decoder::Pager.parse_line(line: 'FLEX|... or POCSAG1200: ...')
36
+
37
+ public_class_method def self.parse_line(opts = {})
38
+ line = opts[:line].to_s
39
+ return PWN::SDR::Decoder::POCSAG.parse_line(line: line) if line.start_with?('POCSAG')
40
+
41
+ # FLEX / FLEX_NEXT — pipe- or space-delimited (see ::Flex for format).
42
+ delim = line.start_with?('FLEX: ') ? ' ' : '|'
43
+ parts = line.split(delim)
44
+ proto = line.start_with?('FLEX_NEXT') ? 'FLEX_NEXT' : 'FLEX'
45
+ types = %w[ALN BIN HEX NUM TON TONE UNK]
46
+ t_idx = parts.index { |p| types.include?(p) }
47
+ out = { protocol: proto, raw_inspected: line.inspect }
48
+ out[:capcode] = parts.find { |p| p.match?(/^\[?\d{7,10}\]?$/) }
49
+ out[:type] = t_idx ? parts[t_idx] : nil
50
+ out[:type_payload] = t_idx ? parts[(t_idx + 1)..].join(delim) : nil
51
+ out[:summary] = "#{proto} RIC=#{out[:capcode]} #{out[:type]}: #{out[:type_payload]}"
52
+ out.compact
53
+ end
54
+
55
+ # Author(s):: 0day Inc. <support@0dayinc.com>
56
+
57
+ public_class_method def self.authors
58
+ "AUTHOR(S):\n 0day Inc. <support@0dayinc.com>\n"
59
+ end
60
+
61
+ # Display Usage for this Module
62
+
63
+ public_class_method def self.help
64
+ puts "USAGE:
65
+ #{self}.decode(
66
+ freq_obj: 'required - freq_obj returned from PWN::SDR::GQRX.init_freq'
67
+ )
68
+
69
+ #{self}.parse_line(line: 'FLEX|3200|...|ALN|MESSAGE')
70
+
71
+ #{self}.authors
72
+ "
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -5,7 +5,8 @@ module PWN
5
5
  module Decoder
6
6
  # POCSAG (CCIR Radiopaging Code No. 1) decoder for pager networks.
7
7
  #
8
- # Pipeline: GQRX UDP audio → sox 48k→22.05k → multimon-ng POCSAG512/1200/2400.
8
+ # Pipeline (identical shape to PWN::SDR::Decoder::Flex):
9
+ # GQRX 48 kHz s16le UDP audio → sox → 22 050 Hz → multimon-ng.
9
10
  #
10
11
  # multimon-ng emits lines of the form:
11
12
  # POCSAG1200: Address: 123456 Function: 3 Alpha: FIRE ALARM ZONE 4
@@ -13,8 +14,8 @@ module PWN
13
14
  # POCSAG2400: Address: 987654 Function: 1 Skyper: ...
14
15
  # POCSAG1200: Address: 123456 Function: 2 Alpha: <partial><EOT>
15
16
  #
16
- # This module parses those into { protocol, baud, address, function,
17
- # function_desc, type, type_desc, message } and JSON-logs each page.
17
+ # Each line is parsed into { protocol, baud, address/capcode, function,
18
+ # function_desc, type, type_desc, message } and JSON-logged.
18
19
  module POCSAG
19
20
  FUNCTION_DESC = {
20
21
  0 => 'Numeric (Tone/A)',
@@ -38,12 +39,19 @@ module PWN
38
39
  public_class_method def self.decode(opts = {})
39
40
  freq_obj = opts[:freq_obj]
40
41
 
42
+ # -e hide empty messages
43
+ # -u heuristically prune unlikely decodes (cuts BCH false-positives)
44
+ # -p emit partially received messages (still useful intel)
45
+ # -f alpha force Alpha framing when function bits are ambiguous —
46
+ # mirrors the behaviour operators expect from Flex ALN.
47
+ decode_cmd = 'multimon-ng -q -t raw -e -u -p -f alpha ' \
48
+ '-a POCSAG512 -a POCSAG1200 -a POCSAG2400 -'
49
+
41
50
  PWN::SDR::Decoder::Base.run_pipeline(
42
51
  freq_obj: freq_obj,
43
52
  protocol: 'POCSAG',
44
53
  required_bins: %w[sox multimon-ng],
45
- decode_cmd: 'multimon-ng -q -t raw -e -u -p ' \
46
- '-a POCSAG512 -a POCSAG1200 -a POCSAG2400 -f auto -',
54
+ decode_cmd: decode_cmd,
47
55
  line_match: /^POCSAG(?:512|1200|2400):/,
48
56
  parser: proc { |line| parse_line(line: line) }
49
57
  )
@@ -54,14 +62,14 @@ module PWN
54
62
 
55
63
  public_class_method def self.parse_line(opts = {})
56
64
  line = opts[:line].to_s
57
- out = { protocol: 'POCSAG' }
65
+ out = { protocol: 'POCSAG', raw_inspected: line.inspect }
58
66
 
59
67
  if (m = line.match(/^POCSAG(\d+):/))
60
68
  out[:baud] = m[1].to_i
61
69
  end
62
70
  if (m = line.match(/Address:\s*(\d+)/i))
63
71
  out[:address] = m[1].to_i
64
- out[:capcode] = m[1]
72
+ out[:capcode] = m[1].rjust(7, '0')
65
73
  end
66
74
  if (m = line.match(/Function:\s*(\d+)/i))
67
75
  fn = m[1].to_i
@@ -70,11 +78,18 @@ module PWN
70
78
  end
71
79
  if (m = line.match(/\b(Alpha|Numeric|Skyper|Tone):\s*(.*)$/i))
72
80
  type = m[1].capitalize
73
- out[:type] = type
74
- out[:type_desc] = TYPE_DESC[type] || 'Unknown'
75
- out[:message] = m[2].to_s.gsub(/<[A-Z]{2,3}>/, '').strip
81
+ out[:type] = type
82
+ out[:type_desc] = TYPE_DESC[type] || 'Unknown'
83
+ out[:type_payload] = m[2].to_s.gsub(/<[A-Z]{2,4}>/, '').strip
84
+ out[:message] = out[:type_payload]
76
85
  end
77
- out[:summary] = "POCSAG#{out[:baud]} RIC=#{out[:capcode]} F#{out[:function]} #{out[:type]}: #{out[:message]}"
86
+
87
+ bits = []
88
+ bits << "POCSAG#{out[:baud]}" if out[:baud]
89
+ bits << "RIC=#{out[:capcode]}" if out[:capcode]
90
+ bits << "F#{out[:function]}(#{out[:function_desc]})" if out[:function]
91
+ bits << "#{out[:type]}: #{out[:message]}" if out[:type]
92
+ out[:summary] = bits.join(' ')
78
93
  out
79
94
  end
80
95
 
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'shellwords'
4
+
5
+ module PWN
6
+ module SDR
7
+ module Decoder
8
+ # RFID decoder covering LF (125/134 kHz), HF (13.56 MHz) and UHF
9
+ # (860–960 MHz EPC Gen2). Near-field ASK/load-modulation is not
10
+ # recoverable from GQRX audio, so this drives a Proxmark3 (LF/HF) via
11
+ # `pm3 -c '...'` or `nfc-list` (HF), and `rtl_433` in flex/analyzer mode
12
+ # for UHF backscatter — whichever tool is present.
13
+ module RFID
14
+ # Supported Method Parameters::
15
+ # PWN::SDR::Decoder::RFID.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
+
25
+ direct_cmd, bins, proto =
26
+ if hz < 1_000_000
27
+ ["pm3 -c 'lf search'", %w[pm3], 'RFID-LF']
28
+ elsif hz.between?(13_000_000, 14_000_000)
29
+ if PWN::SDR::Decoder::Base.bin_available?(bin: 'pm3')
30
+ ["pm3 -c 'hf search'", %w[pm3], 'RFID-HF']
31
+ else
32
+ ['nfc-list -v', %w[nfc-list], 'RFID-HF']
33
+ end
34
+ else
35
+ ["rtl_433 -f #{hz} -A -F json", %w[rtl_433], 'RFID-UHF']
36
+ end
37
+
38
+ PWN::SDR::Decoder::Base.run_pipeline(
39
+ freq_obj: freq_obj,
40
+ protocol: proto,
41
+ required_bins: bins,
42
+ direct_cmd: direct_cmd,
43
+ line_match: /(UID|EPC|TAG|ATQA|SAK|EM4|HID|ISO|Chipset|"model")/i,
44
+ parser: proc { |line| parse_line(line: line) }
45
+ )
46
+ end
47
+
48
+ # Supported Method Parameters::
49
+ # PWN::SDR::Decoder::RFID.parse_line(line: 'UID: 04 A1 B2 C3 D4 E5 F6')
50
+
51
+ public_class_method def self.parse_line(opts = {})
52
+ line = opts[:line].to_s
53
+ out = { protocol: 'RFID' }
54
+ out[:uid] = ::Regexp.last_match(1).delete(' ') if line =~ /UID[:=]?\s*((?:[0-9A-Fa-f]{2}\s*){4,10})/
55
+ out[:epc] = ::Regexp.last_match(1) if line =~ /EPC[:=]?\s*([0-9A-Fa-f]+)/
56
+ out[:atqa] = ::Regexp.last_match(1) if line =~ /ATQA[:=]?\s*([0-9A-Fa-f ]+)/
57
+ out[:sak] = ::Regexp.last_match(1) if line =~ /SAK[:=]?\s*([0-9A-Fa-f]+)/
58
+ out[:tag] = ::Regexp.last_match(1) if line =~ /(EM4\w+|HID\w*|Mifare\w*|NTAG\w*|ISO\s?\d+)/i
59
+ out[:summary] = "RFID #{out[:tag]} UID=#{out[:uid] || out[:epc]}".squeeze(' ')
60
+ out.compact
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: LF/HF need a Proxmark3 (`pm3`) or libnfc reader; UHF uses
78
+ `rtl_433 -A`. Band chosen automatically from freq_obj[:freq].
79
+
80
+ #{self}.parse_line(line: 'UID: 04 A1 B2 C3 D4 E5 F6 SAK: 08 Mifare Classic 1K')
81
+
82
+ #{self}.authors
83
+ "
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require 'shellwords'
5
+
6
+ module PWN
7
+ module SDR
8
+ module Decoder
9
+ # Generic ISM/keyfob/sensor decoder backed by `rtl_433`.
10
+ #
11
+ # Covers the OOK/ASK/FSK device zoo on 300–315 / 390 / 433.92 / 868 /
12
+ # 902–928 MHz: car/garage keyfobs, TPMS, weather stations, utility
13
+ # meters, doorbells, alarm PIRs, etc. rtl_433 owns the SDR directly and
14
+ # emits one JSON object per decoded frame (`-F json`), which this module
15
+ # merges verbatim into the freq_obj log line.
16
+ module RTL433
17
+ # Supported Method Parameters::
18
+ # PWN::SDR::Decoder::RTL433.decode(
19
+ # freq_obj: 'required - freq_obj returned from PWN::SDR::GQRX.init_freq'
20
+ # )
21
+
22
+ public_class_method def self.decode(opts = {})
23
+ freq_obj = opts[:freq_obj]
24
+ raise 'ERROR: :freq_obj is required' unless freq_obj.is_a?(Hash)
25
+
26
+ hz = PWN::SDR.hz_to_i(freq: freq_obj[:freq])
27
+ gain = freq_obj[:rf_gain]
28
+ sdr_args = freq_obj[:sdr_args].to_s
29
+
30
+ cmd = ['rtl_433', '-f', hz.to_s, '-F', 'json', '-M', 'level', '-M', 'protocol']
31
+ cmd.push('-g', gain.to_s) if gain
32
+ cmd.push('-d', sdr_args) unless sdr_args.empty?
33
+ direct_cmd = Shellwords.join(cmd)
34
+
35
+ PWN::SDR::Decoder::Base.run_pipeline(
36
+ freq_obj: freq_obj,
37
+ protocol: 'RTL433',
38
+ required_bins: %w[rtl_433],
39
+ direct_cmd: direct_cmd,
40
+ line_match: /^\s*{/,
41
+ parser: proc { |line| parse_line(line: line) }
42
+ )
43
+ end
44
+
45
+ # Supported Method Parameters::
46
+ # PWN::SDR::Decoder::RTL433.parse_line(line: '{"time":"...","model":"..."}')
47
+
48
+ public_class_method def self.parse_line(opts = {})
49
+ line = opts[:line].to_s
50
+ h = begin
51
+ JSON.parse(line, symbolize_names: true)
52
+ rescue StandardError
53
+ { unparsed: line }
54
+ end
55
+ out = { protocol: 'RTL433' }.merge(h)
56
+
57
+ bits = []
58
+ bits << out[:model].to_s if out[:model]
59
+ bits << "id=#{out[:id]}" if out[:id]
60
+ bits << "ch=#{out[:channel]}" if out[:channel]
61
+ bits << "code=#{out[:code]}" if out[:code]
62
+ bits << "cmd=#{out[:cmd] || out[:button]}" if out[:cmd] || out[:button]
63
+ bits << "temp=#{out[:temperature_C]}C" if out[:temperature_C]
64
+ bits << "rssi=#{out[:rssi]}" if out[:rssi]
65
+ out[:summary] = bits.empty? ? line[0, 120] : bits.join(' ')
66
+ out
67
+ end
68
+
69
+ # Author(s):: 0day Inc. <support@0dayinc.com>
70
+
71
+ public_class_method def self.authors
72
+ "AUTHOR(S):\n 0day Inc. <support@0dayinc.com>\n"
73
+ end
74
+
75
+ # Display Usage for this Module
76
+
77
+ public_class_method def self.help
78
+ puts "USAGE:
79
+ #{self}.decode(
80
+ freq_obj: 'required - freq_obj returned from PWN::SDR::GQRX.init_freq'
81
+ )
82
+
83
+ NOTE: Requires `rtl_433`. Owns the SDR directly (pass
84
+ freq_obj[:sdr_args] like ':1' or 'driver=hackrf' to select
85
+ a device other than the one GQRX is holding).
86
+
87
+ #{self}.parse_line(line: '{\"model\":\"Acurite-Tower\",\"id\":1234,...}')
88
+
89
+ #{self}.authors
90
+ "
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PWN
4
+ module SDR
5
+ module Decoder
6
+ # RTTY (Radioteletype, ITA2/Baudot) decoder for the amateur RTTY
7
+ # sub-bands (rtty20/rtty40/rtty80). GQRX supplies FM-demodulated audio
8
+ # containing the classic 170 Hz-shift 45.45-baud mark/space tones;
9
+ # `minimodem` recovers the 5-bit Baudot stream and prints ASCII.
10
+ module RTTY
11
+ # Supported Method Parameters::
12
+ # PWN::SDR::Decoder::RTTY.decode(
13
+ # freq_obj: 'required - freq_obj returned from PWN::SDR::GQRX.init_freq'
14
+ # )
15
+
16
+ public_class_method def self.decode(opts = {})
17
+ freq_obj = opts[:freq_obj]
18
+
19
+ # minimodem reads s16le mono from stdin at the rate we hand it; keep
20
+ # sox at 22 050 Hz (Base default) and tell minimodem the same.
21
+ PWN::SDR::Decoder::Base.run_pipeline(
22
+ freq_obj: freq_obj,
23
+ protocol: 'RTTY',
24
+ required_bins: %w[sox minimodem],
25
+ decode_cmd: 'minimodem --rx --quiet --rtty -R 22050 -f -',
26
+ line_match: /\S/,
27
+ parser: proc { |line| parse_line(line: line) }
28
+ )
29
+ end
30
+
31
+ # Supported Method Parameters::
32
+ # PWN::SDR::Decoder::RTTY.parse_line(line: 'RYRYRY DE W1AW')
33
+
34
+ public_class_method def self.parse_line(opts = {})
35
+ line = opts[:line].to_s.strip
36
+ out = { protocol: 'RTTY', text: line }
37
+ if (m = line.match(/\bDE\s+([A-Z0-9]{1,3}\d[A-Z]{1,4})\b/))
38
+ out[:callsign] = m[1]
39
+ end
40
+ out[:summary] = "RTTY #{line}"[0, 120]
41
+ out
42
+ end
43
+
44
+ # Author(s):: 0day Inc. <support@0dayinc.com>
45
+
46
+ public_class_method def self.authors
47
+ "AUTHOR(S):\n 0day Inc. <support@0dayinc.com>\n"
48
+ end
49
+
50
+ # Display Usage for this Module
51
+
52
+ public_class_method def self.help
53
+ puts "USAGE:
54
+ #{self}.decode(
55
+ freq_obj: 'required - freq_obj returned from PWN::SDR::GQRX.init_freq'
56
+ )
57
+
58
+ #{self}.parse_line(line: 'RYRYRY CQ CQ DE W1AW')
59
+
60
+ #{self}.authors
61
+ "
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end