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.
Files changed (124) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +2 -2
  3. data/Gemfile +1 -1
  4. data/README.md +7 -1
  5. data/bin/pwn_gqrx_scanner +78 -9
  6. data/documentation/Agent-Tool-Registry.md +3 -3
  7. data/documentation/Configuration.md +301 -47
  8. data/documentation/Cron.md +5 -3
  9. data/documentation/Diagrams.md +1 -1
  10. data/documentation/Extrospection.md +472 -26
  11. data/documentation/FFI.md +72 -8
  12. data/documentation/Home.md +4 -4
  13. data/documentation/How-PWN-Works.md +7 -3
  14. data/documentation/Mistakes.md +9 -0
  15. data/documentation/SDR.md +83 -10
  16. data/documentation/Skills-Memory-Learning.md +101 -11
  17. data/documentation/diagrams/agent-tool-registry.svg +56 -55
  18. data/documentation/diagrams/ai-integration-tool-calling.svg +24 -24
  19. data/documentation/diagrams/aws-cloud-security.svg +25 -25
  20. data/documentation/diagrams/burp-vs-zap-preference.svg +12 -12
  21. data/documentation/diagrams/code-scanning-sast.svg +25 -25
  22. data/documentation/diagrams/cron-scheduling.svg +23 -23
  23. data/documentation/diagrams/dot/agent-tool-registry.dot +2 -2
  24. data/documentation/diagrams/dot/extrospection-world-awareness.dot +33 -6
  25. data/documentation/diagrams/dot/hardware-hacking.dot +3 -1
  26. data/documentation/diagrams/dot/memory-skills-detailed.dot +4 -2
  27. data/documentation/diagrams/dot/mistakes-negative-feedback.dot +4 -2
  28. data/documentation/diagrams/dot/overall-pwn-architecture.dot +3 -3
  29. data/documentation/diagrams/dot/persistence-filesystem.dot +3 -3
  30. data/documentation/diagrams/dot/pwn-ai-feedback-learning-loop.dot +10 -4
  31. data/documentation/diagrams/dot/sdr-radio-flow.dot +39 -16
  32. data/documentation/diagrams/driver-framework.svg +13 -13
  33. data/documentation/diagrams/extrospection-world-awareness.svg +383 -177
  34. data/documentation/diagrams/fuzzing-workflow.svg +24 -24
  35. data/documentation/diagrams/hardware-hacking.svg +84 -69
  36. data/documentation/diagrams/history-to-drivers.svg +18 -18
  37. data/documentation/diagrams/memory-skills-detailed.svg +137 -120
  38. data/documentation/diagrams/mistakes-negative-feedback.svg +152 -137
  39. data/documentation/diagrams/network-infra-testing.svg +27 -27
  40. data/documentation/diagrams/overall-pwn-architecture.svg +97 -97
  41. data/documentation/diagrams/penetration-testing-workflow.svg +30 -30
  42. data/documentation/diagrams/persistence-filesystem.svg +100 -94
  43. data/documentation/diagrams/plugin-ecosystem.svg +35 -35
  44. data/documentation/diagrams/pwn-ai-feedback-learning-loop.svg +254 -207
  45. data/documentation/diagrams/pwn-repl-prototyping.svg +20 -20
  46. data/documentation/diagrams/reporting-pipeline.svg +18 -18
  47. data/documentation/diagrams/reverse-engineering-flow.svg +21 -21
  48. data/documentation/diagrams/sdr-radio-flow.svg +223 -91
  49. data/documentation/diagrams/sessions-cron-automation.svg +18 -18
  50. data/documentation/diagrams/swarm-multi-agent.svg +39 -39
  51. data/documentation/diagrams/web-application-testing.svg +26 -26
  52. data/documentation/diagrams/zero-day-research-flow.svg +25 -25
  53. data/documentation/pwn-ai-Agent.md +16 -10
  54. data/documentation/pwn_silent_help_learn_demo.gif +0 -0
  55. data/lib/pwn/ai/agent/assembly.rb +1 -1
  56. data/lib/pwn/ai/agent/btc.rb +1 -1
  57. data/lib/pwn/ai/agent/burp_suite.rb +1 -1
  58. data/lib/pwn/ai/agent/extrospection.rb +2562 -57
  59. data/lib/pwn/ai/agent/gqrx.rb +2 -2
  60. data/lib/pwn/ai/agent/hacker_one.rb +1 -1
  61. data/lib/pwn/ai/agent/learning.rb +16 -16
  62. data/lib/pwn/ai/agent/loop.rb +1 -1
  63. data/lib/pwn/ai/agent/{introspection.rb → reflect.rb} +12 -12
  64. data/lib/pwn/ai/agent/sast.rb +1 -1
  65. data/lib/pwn/ai/agent/tools/extrospection.rb +311 -8
  66. data/lib/pwn/ai/agent/tools/learning.rb +7 -7
  67. data/lib/pwn/ai/agent/transparent_browser.rb +1 -1
  68. data/lib/pwn/ai/agent/vuln_gen.rb +2 -2
  69. data/lib/pwn/ai/agent.rb +1 -1
  70. data/lib/pwn/ai/open_ai.rb +10 -6
  71. data/lib/pwn/config.rb +5 -4
  72. data/lib/pwn/ffi/adalm_pluto.rb +498 -0
  73. data/lib/pwn/ffi/fftw.rb +192 -0
  74. data/lib/pwn/ffi/hack_rf.rb +193 -0
  75. data/lib/pwn/ffi/liquid.rb +244 -0
  76. data/lib/pwn/ffi/rtl_sdr.rb +210 -0
  77. data/lib/pwn/ffi/soapy_sdr.rb +177 -0
  78. data/lib/pwn/ffi/stdio.rb +9 -1
  79. data/lib/pwn/ffi/volk.rb +241 -0
  80. data/lib/pwn/ffi.rb +45 -2
  81. data/lib/pwn/plugins/burp_suite.rb +2 -2
  82. data/lib/pwn/sast/pom_version.rb +2 -2
  83. data/lib/pwn/sast/test_case_engine.rb +1 -1
  84. data/lib/pwn/sdr/decoder/adsb.rb +201 -12
  85. data/lib/pwn/sdr/decoder/apt.rb +19 -5
  86. data/lib/pwn/sdr/decoder/base.rb +314 -1
  87. data/lib/pwn/sdr/decoder/bluetooth.rb +91 -18
  88. data/lib/pwn/sdr/decoder/dect.rb +81 -23
  89. data/lib/pwn/sdr/decoder/dsp.rb +229 -15
  90. data/lib/pwn/sdr/decoder/flex.rb +23 -6
  91. data/lib/pwn/sdr/decoder/gps.rb +80 -18
  92. data/lib/pwn/sdr/decoder/gsm.rb +83 -28
  93. data/lib/pwn/sdr/decoder/iridium.rb +80 -18
  94. data/lib/pwn/sdr/decoder/lora.rb +84 -28
  95. data/lib/pwn/sdr/decoder/lte.rb +80 -18
  96. data/lib/pwn/sdr/decoder/morse.rb +23 -6
  97. data/lib/pwn/sdr/decoder/p25.rb +90 -31
  98. data/lib/pwn/sdr/decoder/pager.rb +23 -6
  99. data/lib/pwn/sdr/decoder/pocsag.rb +23 -6
  100. data/lib/pwn/sdr/decoder/rds.rb +219 -52
  101. data/lib/pwn/sdr/decoder/rfid.rb +81 -27
  102. data/lib/pwn/sdr/decoder/rtl433.rb +85 -27
  103. data/lib/pwn/sdr/decoder/rtty.rb +23 -6
  104. data/lib/pwn/sdr/decoder/wifi.rb +86 -32
  105. data/lib/pwn/sdr/decoder/zigbee.rb +88 -35
  106. data/lib/pwn/sdr/decoder.rb +6 -5
  107. data/lib/pwn/sdr/frequency_allocation.rb +124 -81
  108. data/lib/pwn/sdr/gqrx.rb +1513 -165
  109. data/lib/pwn/version.rb +1 -1
  110. data/spec/lib/pwn/ai/agent/extrospection_spec.rb +96 -0
  111. data/spec/lib/pwn/ai/agent/{introspection_spec.rb → reflect_spec.rb} +3 -3
  112. data/spec/lib/pwn/ai/agent/tools/extrospection_spec.rb +8 -24
  113. data/spec/lib/pwn/ffi/adalm_pluto_spec.rb +37 -0
  114. data/spec/lib/pwn/ffi/fftw_spec.rb +26 -0
  115. data/spec/lib/pwn/ffi/hack_rf_spec.rb +25 -0
  116. data/spec/lib/pwn/ffi/liquid_spec.rb +42 -0
  117. data/spec/lib/pwn/ffi/rtl_sdr_spec.rb +24 -0
  118. data/spec/lib/pwn/ffi/soapy_sdr_spec.rb +25 -0
  119. data/spec/lib/pwn/ffi/volk_spec.rb +36 -0
  120. data/spec/lib/pwn/sdr/decoder/base_iq_spec.rb +65 -0
  121. data/spec/lib/pwn/sdr/decoder/dsp_spec.rb +50 -4
  122. data/spec/lib/pwn/sdr/decoder/rds_spec.rb +45 -4
  123. data/third_party/pwn_rdoc.jsonl +279 -30
  124. metadata +21 -5
@@ -0,0 +1,210 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ffi'
4
+
5
+ PubFFI = ::FFI unless defined?(PubFFI) # rubocop:disable Style/RedundantConstantBase
6
+
7
+ module PWN
8
+ module FFI
9
+ # Thin librtlsdr binding (no -dev headers required — symbols resolved
10
+ # from the installed shared object). Control-plane + blocking
11
+ # `read_sync` so PWN::SDR::Decoder::* / Extrospection probe_rf can pull
12
+ # raw u8 I/Q without shelling out to `rtl_sdr`.
13
+ #
14
+ # If librtlsdr is missing `.available?` is false and callers fall back.
15
+ module RTLSdr
16
+ extend PubFFI::Library
17
+
18
+ @load_error = nil
19
+ begin
20
+ ffi_lib %w[rtlsdr librtlsdr.so.0 librtlsdr.so.2 librtlsdr.so]
21
+ rescue LoadError => e
22
+ @load_error = e
23
+ end
24
+
25
+ class << self
26
+ attr_reader :load_error
27
+ end
28
+
29
+ unless @load_error
30
+ attach_function :rtlsdr_get_device_count, [], :uint32
31
+ attach_function :rtlsdr_get_device_name, [:uint32], :string
32
+ attach_function :rtlsdr_get_device_usb_strings,
33
+ %i[uint32 pointer pointer pointer],
34
+ :int
35
+ attach_function :rtlsdr_open, %i[pointer uint32], :int
36
+ attach_function :rtlsdr_close, [:pointer], :int
37
+ attach_function :rtlsdr_set_center_freq, %i[pointer uint32], :int
38
+ attach_function :rtlsdr_get_center_freq, [:pointer], :uint32
39
+ attach_function :rtlsdr_set_sample_rate, %i[pointer uint32], :int
40
+ attach_function :rtlsdr_get_sample_rate, [:pointer], :uint32
41
+ attach_function :rtlsdr_set_tuner_gain_mode, %i[pointer int], :int
42
+ attach_function :rtlsdr_set_tuner_gain, %i[pointer int], :int
43
+ attach_function :rtlsdr_get_tuner_gains, %i[pointer pointer], :int
44
+ attach_function :rtlsdr_set_freq_correction, %i[pointer int], :int
45
+ attach_function :rtlsdr_reset_buffer, [:pointer], :int
46
+ attach_function :rtlsdr_read_sync,
47
+ %i[pointer pointer int pointer],
48
+ :int
49
+ attach_function :rtlsdr_set_agc_mode, %i[pointer int], :int
50
+ end
51
+
52
+ # Supported Method Parameters::
53
+ # PWN::FFI::RTLSdr.available?
54
+
55
+ public_class_method def self.available?
56
+ !@load_error && respond_to?(:rtlsdr_get_device_count, true)
57
+ rescue StandardError
58
+ false
59
+ end
60
+
61
+ # Supported Method Parameters::
62
+ # devices = PWN::FFI::RTLSdr.list_devices
63
+ # Returns Array<Hash> of { index:, name:, manufacturer:, product:, serial: }.
64
+
65
+ public_class_method def self.list_devices
66
+ raise 'ERROR: librtlsdr not available' unless available?
67
+
68
+ count = rtlsdr_get_device_count
69
+ Array.new(count) do |i|
70
+ mfr = PubFFI::MemoryPointer.new(:char, 256)
71
+ prod = PubFFI::MemoryPointer.new(:char, 256)
72
+ ser = PubFFI::MemoryPointer.new(:char, 256)
73
+ rtlsdr_get_device_usb_strings(i, mfr, prod, ser)
74
+ {
75
+ index: i,
76
+ name: (rtlsdr_get_device_name(i) || '').to_s,
77
+ manufacturer: mfr.read_string,
78
+ product: prod.read_string,
79
+ serial: ser.read_string
80
+ }
81
+ end
82
+ end
83
+
84
+ # Supported Method Parameters::
85
+ # dev = PWN::FFI::RTLSdr.open(index: 0)
86
+ # Returns opaque FFI::Pointer (rtlsdr_dev_t*).
87
+
88
+ public_class_method def self.open(opts = {})
89
+ raise 'ERROR: librtlsdr not available' unless available?
90
+
91
+ idx = (opts[:index] || 0).to_i
92
+ dev_ptr = PubFFI::MemoryPointer.new(:pointer)
93
+ rc = rtlsdr_open(dev_ptr, idx)
94
+ raise "ERROR: rtlsdr_open rc=#{rc}" unless rc.zero?
95
+
96
+ dev_ptr.read_pointer
97
+ end
98
+
99
+ # Supported Method Parameters::
100
+ # PWN::FFI::RTLSdr.close(device: pointer)
101
+
102
+ public_class_method def self.close(opts = {})
103
+ dev = opts[:device]
104
+ return unless dev && !dev.null?
105
+
106
+ rtlsdr_close(dev)
107
+ nil
108
+ end
109
+
110
+ # Supported Method Parameters::
111
+ # PWN::FFI::RTLSdr.configure(
112
+ # device: 'required - pointer from .open',
113
+ # freq_hz: 'required - center frequency Hz',
114
+ # rate_hz: 'optional - sample rate (default 2.048e6)',
115
+ # gain_db: 'optional - tenths of dB (e.g. 496 = 49.6 dB). nil = auto',
116
+ # ppm: 'optional - freq correction ppm (default 0)'
117
+ # )
118
+
119
+ public_class_method def self.configure(opts = {}) # rubocop:disable Naming/PredicateMethod
120
+ dev = opts[:device]
121
+ raise 'ERROR: :device required' if dev.nil? || dev.null?
122
+
123
+ freq = opts[:freq_hz].to_i
124
+ rate = (opts[:rate_hz] || 2_048_000).to_i
125
+ ppm = (opts[:ppm] || 0).to_i
126
+
127
+ check!(rtlsdr_set_sample_rate(dev, rate), 'set_sample_rate')
128
+ check!(rtlsdr_set_center_freq(dev, freq), 'set_center_freq')
129
+ check!(rtlsdr_set_freq_correction(dev, ppm), 'set_freq_correction') if ppm != 0
130
+
131
+ if opts.key?(:gain_db) && !opts[:gain_db].nil?
132
+ check!(rtlsdr_set_tuner_gain_mode(dev, 1), 'gain_mode manual')
133
+ check!(rtlsdr_set_tuner_gain(dev, opts[:gain_db].to_i), 'set_tuner_gain')
134
+ else
135
+ check!(rtlsdr_set_tuner_gain_mode(dev, 0), 'gain_mode auto')
136
+ rtlsdr_set_agc_mode(dev, 1)
137
+ end
138
+ check!(rtlsdr_reset_buffer(dev), 'reset_buffer')
139
+ true
140
+ end
141
+
142
+ # Supported Method Parameters::
143
+ # iq_u8 = PWN::FFI::RTLSdr.read_sync(
144
+ # device: 'required - pointer from .open',
145
+ # bytes: 'optional - number of raw bytes to read (default 262144)'
146
+ # )
147
+ # Returns a binary String of unsigned 8-bit interleaved I/Q samples.
148
+
149
+ public_class_method def self.read_sync(opts = {})
150
+ dev = opts[:device]
151
+ raise 'ERROR: :device required' if dev.nil? || dev.null?
152
+
153
+ nbytes = (opts[:bytes] || 262_144).to_i
154
+ buf = PubFFI::MemoryPointer.new(:uint8, nbytes)
155
+ n_read = PubFFI::MemoryPointer.new(:int)
156
+ rc = rtlsdr_read_sync(dev, buf, nbytes, n_read)
157
+ raise "ERROR: rtlsdr_read_sync rc=#{rc}" unless rc.zero?
158
+
159
+ got = n_read.read_int
160
+ buf.read_string(got)
161
+ end
162
+
163
+ # Supported Method Parameters::
164
+ # gains = PWN::FFI::RTLSdr.tuner_gains(device: pointer)
165
+ # Returns Array<Integer> of supported gains in tenths of a dB.
166
+
167
+ public_class_method def self.tuner_gains(opts = {})
168
+ dev = opts[:device]
169
+ raise 'ERROR: :device required' if dev.nil? || dev.null?
170
+
171
+ n = rtlsdr_get_tuner_gains(dev, nil)
172
+ return [] if n <= 0
173
+
174
+ ptr = PubFFI::MemoryPointer.new(:int, n)
175
+ rtlsdr_get_tuner_gains(dev, ptr)
176
+ ptr.read_array_of_int(n)
177
+ end
178
+
179
+ # Author(s):: 0day Inc. <support@0dayinc.com>
180
+
181
+ public_class_method def self.authors
182
+ "AUTHOR(S):\n 0day Inc. <support@0dayinc.com>\n"
183
+ end
184
+
185
+ # Display Usage for this Module
186
+
187
+ public_class_method def self.help
188
+ puts "USAGE:
189
+ #{self}.available?
190
+ #{self}.list_devices
191
+ dev = #{self}.open(index: 0)
192
+ #{self}.configure(device: dev, freq_hz:, rate_hz: 2_048_000, gain_db: nil, ppm: 0)
193
+ #{self}.tuner_gains(device: dev)
194
+ iq = #{self}.read_sync(device: dev, bytes: 262_144) # u8 I/Q String
195
+ #{self}.close(device: dev)
196
+
197
+ #{self}.authors
198
+ "
199
+ end
200
+
201
+ class << self
202
+ private
203
+
204
+ def check!(code, what)
205
+ raise "ERROR: rtlsdr_#{what} rc=#{code}" unless code.zero? || code.positive?
206
+ end
207
+ end
208
+ end
209
+ end
210
+ end
@@ -0,0 +1,177 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ffi'
4
+
5
+ PubFFI = ::FFI unless defined?(PubFFI) # rubocop:disable Style/RedundantConstantBase
6
+
7
+ module PWN
8
+ module FFI
9
+ # Thin SoapySDR C-API binding (libSoapySDR). Inventory-focused first —
10
+ # enumerate devices and report API version so Extrospection `probe_rf`
11
+ # can list every Soapy-backed front-end (RTL-SDR, HackRF, Airspy,
12
+ # Pluto, UHD, …) without shelling out to `SoapySDRUtil`.
13
+ #
14
+ # Full streaming can be layered later; today this is the discovery
15
+ # surface. Missing lib → `.available?` false, pure-Ruby / CLI fallbacks.
16
+ module SoapySDR
17
+ extend PubFFI::Library
18
+
19
+ @load_error = nil
20
+ begin
21
+ ffi_lib %w[SoapySDR libSoapySDR.so.0.8 libSoapySDR.so.0 libSoapySDR.so]
22
+ rescue LoadError => e
23
+ @load_error = e
24
+ end
25
+
26
+ class << self
27
+ attr_reader :load_error
28
+ end
29
+
30
+ # SoapySDRKwargs is {size_t size; char** keys; char** vals;}
31
+ class Kwargs < PubFFI::Struct
32
+ layout :size, :size_t,
33
+ :keys, :pointer,
34
+ :vals, :pointer
35
+ end
36
+
37
+ unless @load_error
38
+ attach_function :SoapySDR_getAPIVersion, [], :string
39
+ attach_function :SoapySDR_getABIVersion, [], :string
40
+ attach_function :SoapySDR_getLibVersion, [], :string
41
+
42
+ # SoapySDRKwargs* SoapySDRDevice_enumerate(const SoapySDRKwargs* args, size_t* length)
43
+ attach_function :SoapySDRDevice_enumerate,
44
+ %i[pointer pointer],
45
+ :pointer
46
+
47
+ # void SoapySDRKwargsList_clear(SoapySDRKwargs *kwargs, size_t length)
48
+ attach_function :SoapySDRKwargsList_clear,
49
+ %i[pointer size_t],
50
+ :void
51
+
52
+ # SoapySDRDevice* SoapySDRDevice_make(const SoapySDRKwargs* args)
53
+ attach_function :SoapySDRDevice_make, [:pointer], :pointer
54
+ # SoapySDRDevice* SoapySDRDevice_makeStrArgs(const char* args)
55
+ attach_function :SoapySDRDevice_makeStrArgs, [:string], :pointer
56
+ attach_function :SoapySDRDevice_unmake, [:pointer], :int
57
+
58
+ attach_function :SoapySDRDevice_getDriverKey, [:pointer], :string
59
+ attach_function :SoapySDRDevice_getHardwareKey, [:pointer], :string
60
+ # char* SoapySDRDevice_getHardwareInfo — returns freeable dict as string? skip
61
+ end
62
+
63
+ # Supported Method Parameters::
64
+ # PWN::FFI::SoapySDR.available?
65
+
66
+ public_class_method def self.available?
67
+ !@load_error && respond_to?(:SoapySDR_getAPIVersion, true)
68
+ rescue StandardError
69
+ false
70
+ end
71
+
72
+ # Supported Method Parameters::
73
+ # info = PWN::FFI::SoapySDR.info
74
+ # Returns { available:, api:, abi:, lib: }.
75
+
76
+ public_class_method def self.info
77
+ return { available: false, error: @load_error&.message } unless available?
78
+
79
+ {
80
+ available: true,
81
+ api: SoapySDR_getAPIVersion().to_s,
82
+ abi: SoapySDR_getABIVersion().to_s,
83
+ lib: SoapySDR_getLibVersion().to_s
84
+ }
85
+ end
86
+
87
+ # Supported Method Parameters::
88
+ # devices = PWN::FFI::SoapySDR.list_devices
89
+ # Returns Array<Hash> of keyword→value maps for every enumerated device.
90
+
91
+ public_class_method def self.list_devices
92
+ raise 'ERROR: libSoapySDR not available' unless available?
93
+
94
+ len_ptr = PubFFI::MemoryPointer.new(:size_t)
95
+ # pass NULL args → enumerate everything
96
+ list_ptr = SoapySDRDevice_enumerate(nil, len_ptr)
97
+ length = len_ptr.read_ulong
98
+ return [] if list_ptr.null? || length.zero?
99
+
100
+ results = []
101
+ length.times do |i|
102
+ kw = Kwargs.new(list_ptr + (i * Kwargs.size))
103
+ h = {}
104
+ kw[:size].times do |j|
105
+ key = kw[:keys].get_pointer(j * PubFFI::Pointer.size)
106
+ val = kw[:vals].get_pointer(j * PubFFI::Pointer.size)
107
+ h[key.read_string.to_sym] = val.read_string unless key.null?
108
+ end
109
+ results << h
110
+ end
111
+ SoapySDRKwargsList_clear(list_ptr, length)
112
+ results
113
+ end
114
+
115
+ # Supported Method Parameters::
116
+ # dev = PWN::FFI::SoapySDR.make(args: 'driver=rtlsdr')
117
+ # Returns opaque pointer (SoapySDRDevice*) or raises.
118
+ # ALWAYS pair with .unmake.
119
+
120
+ public_class_method def self.make(opts = {})
121
+ raise 'ERROR: libSoapySDR not available' unless available?
122
+
123
+ args = opts[:args].to_s
124
+ dev = SoapySDRDevice_makeStrArgs(args)
125
+ raise "ERROR: SoapySDRDevice_makeStrArgs(#{args.inspect}) returned NULL" if dev.null?
126
+
127
+ dev
128
+ end
129
+
130
+ # Supported Method Parameters::
131
+ # PWN::FFI::SoapySDR.unmake(device: pointer)
132
+
133
+ public_class_method def self.unmake(opts = {})
134
+ dev = opts[:device]
135
+ return unless dev && !dev.null?
136
+
137
+ SoapySDRDevice_unmake(dev)
138
+ nil
139
+ end
140
+
141
+ # Supported Method Parameters::
142
+ # meta = PWN::FFI::SoapySDR.device_keys(device: pointer)
143
+ # Returns { driver:, hardware: }.
144
+
145
+ public_class_method def self.device_keys(opts = {})
146
+ dev = opts[:device]
147
+ raise 'ERROR: :device required' if dev.nil? || dev.null?
148
+
149
+ {
150
+ driver: SoapySDRDevice_getDriverKey(dev).to_s,
151
+ hardware: SoapySDRDevice_getHardwareKey(dev).to_s
152
+ }
153
+ end
154
+
155
+ # Author(s):: 0day Inc. <support@0dayinc.com>
156
+
157
+ public_class_method def self.authors
158
+ "AUTHOR(S):\n 0day Inc. <support@0dayinc.com>\n"
159
+ end
160
+
161
+ # Display Usage for this Module
162
+
163
+ public_class_method def self.help
164
+ puts "USAGE:
165
+ #{self}.available?
166
+ #{self}.info
167
+ #{self}.list_devices
168
+ dev = #{self}.make(args: 'driver=rtlsdr')
169
+ #{self}.device_keys(device: dev)
170
+ #{self}.unmake(device: dev)
171
+
172
+ #{self}.authors
173
+ "
174
+ end
175
+ end
176
+ end
177
+ end
data/lib/pwn/ffi/stdio.rb CHANGED
@@ -2,7 +2,8 @@
2
2
 
3
3
  require 'ffi'
4
4
 
5
- PubFFI = FFI
5
+ PubFFI = ::FFI unless defined?(PubFFI) # rubocop:disable Style/RedundantConstantBase
6
+
6
7
  module PWN
7
8
  module FFI
8
9
  # This plugin is a wrapper for the standard I/O functions in libc.
@@ -15,6 +16,13 @@ module PWN
15
16
  attach_function(:printf, %i[string varargs], :int, convention: :default)
16
17
  attach_function(:scanf, %i[string varargs], :int)
17
18
 
19
+ # Supported Method Parameters::
20
+ # PWN::FFI::Stdio.available?
21
+
22
+ public_class_method def self.available?
23
+ true
24
+ end
25
+
18
26
  # Author(s):: 0day Inc. <support@0dayinc.com>
19
27
 
20
28
  public_class_method def self.authors
@@ -0,0 +1,241 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ffi'
4
+
5
+ PubFFI = ::FFI unless defined?(PubFFI) # rubocop:disable Style/RedundantConstantBase
6
+
7
+ module PWN
8
+ module FFI
9
+ # Thin VOLK (Vector-Optimized Library of Kernels) binding.
10
+ #
11
+ # Dispatches to SIMD kernels (SSE/AVX/NEON/…) when the host provides them.
12
+ # Used by PWN::SDR::Decoder::DSP hot paths so MHz-rate I/Q work stays
13
+ # off the Ruby GC heap while orchestration remains pure Ruby.
14
+ #
15
+ # Nothing here shells out and nothing is compiled at gem-install time —
16
+ # if libvolk is missing, `.available?` is false and callers fall back.
17
+ module Volk
18
+ extend PubFFI::Library
19
+
20
+ @load_error = nil
21
+ begin
22
+ ffi_lib %w[volk libvolk.so.3.3 libvolk.so.3 libvolk.so]
23
+ rescue LoadError => e
24
+ @load_error = e
25
+ end
26
+
27
+ class << self
28
+ attr_reader :load_error
29
+ end
30
+
31
+ unless @load_error
32
+ # Memory helpers (always present as T symbols)
33
+ attach_function :volk_get_alignment, [], :size_t
34
+ attach_function :volk_malloc, %i[size_t size_t], :pointer
35
+ attach_function :volk_free, [:pointer], :void
36
+
37
+ # VOLK dispatches are *data* symbols (function pointers). Bind them
38
+ # with attach_variable then wrap in FFI::Function on first use so we
39
+ # always hit the CPU-tuned kernel selected by the dispatcher.
40
+ attach_variable :volk_16i_s32f_convert_32f, :pointer
41
+ attach_variable :volk_32f_accumulator_s32f, :pointer
42
+ attach_variable :volk_32fc_magnitude_squared_32f, :pointer
43
+ attach_variable :volk_32f_sqrt_32f, :pointer
44
+ attach_variable :volk_32f_s32f_multiply_32f, :pointer
45
+ attach_variable :volk_32f_x2_dot_prod_32f, :pointer
46
+ end
47
+
48
+ # Supported Method Parameters::
49
+ # PWN::FFI::Volk.available?
50
+
51
+ public_class_method def self.available?
52
+ !@load_error && respond_to?(:volk_get_alignment, true)
53
+ rescue StandardError
54
+ false
55
+ end
56
+
57
+ # Supported Method Parameters::
58
+ # samples = PWN::FFI::Volk.unpack_s16le(
59
+ # data: 'required - raw String of little-endian signed 16-bit PCM'
60
+ # )
61
+ # Returns Array<Float> normalised to -1.0..1.0 (same contract as
62
+ # PWN::SDR::Decoder::DSP.unpack_s16le). VOLK treats `scalar` as a
63
+ # divisor, so pass 32768.0 to get unit-range floats.
64
+
65
+ public_class_method def self.unpack_s16le(opts = {})
66
+ raise 'ERROR: libvolk not available' unless available?
67
+
68
+ data = opts[:data].to_s
69
+ n = data.bytesize / 2
70
+ return [] if n.zero?
71
+
72
+ in_ptr = PubFFI::MemoryPointer.from_string(data)
73
+ # from_string null-terminates — reclaim only the payload region
74
+ out_ptr = PubFFI::MemoryPointer.new(:float, n)
75
+ convert_fn.call(out_ptr, in_ptr, 32_768.0, n)
76
+ out_ptr.read_array_of_float(n)
77
+ end
78
+
79
+ # Supported Method Parameters::
80
+ # sum = PWN::FFI::Volk.accumulate(samples: Array<Float>)
81
+
82
+ public_class_method def self.accumulate(opts = {})
83
+ raise 'ERROR: libvolk not available' unless available?
84
+
85
+ samples = opts[:samples]
86
+ n = samples.length
87
+ return 0.0 if n.zero?
88
+
89
+ in_ptr = float_ptr(samples)
90
+ out_ptr = PubFFI::MemoryPointer.new(:float, 1)
91
+ accum_fn.call(out_ptr, in_ptr, n)
92
+ out_ptr.read_float
93
+ end
94
+
95
+ # Supported Method Parameters::
96
+ # power = PWN::FFI::Volk.magnitude_squared(
97
+ # iq: 'required - Array of interleaved I/Q Floats (even length)'
98
+ # )
99
+ # Returns Array<Float> of |z|^2 per complex sample.
100
+
101
+ public_class_method def self.magnitude_squared(opts = {})
102
+ raise 'ERROR: libvolk not available' unless available?
103
+
104
+ iq = opts[:iq]
105
+ n_c = iq.length / 2
106
+ return [] if n_c.zero?
107
+
108
+ in_ptr = float_ptr(iq)
109
+ out_ptr = PubFFI::MemoryPointer.new(:float, n_c)
110
+ mag2_fn.call(out_ptr, in_ptr, n_c)
111
+ out_ptr.read_array_of_float(n_c)
112
+ end
113
+
114
+ # Supported Method Parameters::
115
+ # out = PWN::FFI::Volk.sqrt(samples: Array<Float>)
116
+
117
+ public_class_method def self.sqrt(opts = {})
118
+ raise 'ERROR: libvolk not available' unless available?
119
+
120
+ samples = opts[:samples]
121
+ n = samples.length
122
+ return [] if n.zero?
123
+
124
+ in_ptr = float_ptr(samples)
125
+ out_ptr = PubFFI::MemoryPointer.new(:float, n)
126
+ sqrt_fn.call(out_ptr, in_ptr, n)
127
+ out_ptr.read_array_of_float(n)
128
+ end
129
+
130
+ # Supported Method Parameters::
131
+ # out = PWN::FFI::Volk.scale(samples: Array<Float>, factor: Float)
132
+ # Multiplies every sample by factor.
133
+
134
+ public_class_method def self.scale(opts = {})
135
+ raise 'ERROR: libvolk not available' unless available?
136
+
137
+ samples = opts[:samples]
138
+ factor = opts[:factor].to_f
139
+ n = samples.length
140
+ return [] if n.zero?
141
+
142
+ in_ptr = float_ptr(samples)
143
+ out_ptr = PubFFI::MemoryPointer.new(:float, n)
144
+ scale_fn.call(out_ptr, in_ptr, factor, n)
145
+ out_ptr.read_array_of_float(n)
146
+ end
147
+
148
+ # Supported Method Parameters::
149
+ # sum = PWN::FFI::Volk.dot_prod(a: Array<Float>, b: Array<Float>)
150
+
151
+ public_class_method def self.dot_prod(opts = {})
152
+ raise 'ERROR: libvolk not available' unless available?
153
+
154
+ a = opts[:a]
155
+ b = opts[:b]
156
+ n = [a.length, b.length].min
157
+ return 0.0 if n.zero?
158
+
159
+ a_ptr = float_ptr(a)
160
+ b_ptr = float_ptr(b)
161
+ out_ptr = PubFFI::MemoryPointer.new(:float, 1)
162
+ dot_fn.call(out_ptr, a_ptr, b_ptr, n)
163
+ out_ptr.read_float
164
+ end
165
+
166
+ # Author(s):: 0day Inc. <support@0dayinc.com>
167
+
168
+ public_class_method def self.authors
169
+ "AUTHOR(S):\n 0day Inc. <support@0dayinc.com>\n"
170
+ end
171
+
172
+ # Display Usage for this Module
173
+
174
+ public_class_method def self.help
175
+ puts "USAGE:
176
+ #{self}.available? # => true/false
177
+ #{self}.unpack_s16le(data: raw_bytes) # s16le → Float[-1,1]
178
+ #{self}.accumulate(samples:) # Σ samples
179
+ #{self}.magnitude_squared(iq:) # |z|² per complex pair
180
+ #{self}.sqrt(samples:)
181
+ #{self}.scale(samples:, factor:)
182
+ #{self}.dot_prod(a:, b:)
183
+
184
+ #{self}.authors
185
+ "
186
+ end
187
+
188
+ class << self
189
+ private
190
+
191
+ def float_ptr(arr)
192
+ ptr = PubFFI::MemoryPointer.new(:float, arr.length)
193
+ ptr.write_array_of_float(arr.map(&:to_f))
194
+ ptr
195
+ end
196
+
197
+ def convert_fn
198
+ @convert_fn ||= PubFFI::Function.new(
199
+ :void, %i[pointer pointer float uint],
200
+ volk_16i_s32f_convert_32f
201
+ )
202
+ end
203
+
204
+ def accum_fn
205
+ @accum_fn ||= PubFFI::Function.new(
206
+ :void, %i[pointer pointer uint],
207
+ volk_32f_accumulator_s32f
208
+ )
209
+ end
210
+
211
+ def mag2_fn
212
+ @mag2_fn ||= PubFFI::Function.new(
213
+ :void, %i[pointer pointer uint],
214
+ volk_32fc_magnitude_squared_32f
215
+ )
216
+ end
217
+
218
+ def sqrt_fn
219
+ @sqrt_fn ||= PubFFI::Function.new(
220
+ :void, %i[pointer pointer uint],
221
+ volk_32f_sqrt_32f
222
+ )
223
+ end
224
+
225
+ def scale_fn
226
+ @scale_fn ||= PubFFI::Function.new(
227
+ :void, %i[pointer pointer float uint],
228
+ volk_32f_s32f_multiply_32f
229
+ )
230
+ end
231
+
232
+ def dot_fn
233
+ @dot_fn ||= PubFFI::Function.new(
234
+ :void, %i[pointer pointer pointer uint],
235
+ volk_32f_x2_dot_prod_32f
236
+ )
237
+ end
238
+ end
239
+ end
240
+ end
241
+ end