pwn 0.5.621 → 0.5.626
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rubocop.yml +2 -2
- data/Gemfile +1 -1
- data/README.md +7 -1
- data/bin/pwn_gqrx_scanner +78 -9
- data/documentation/Agent-Tool-Registry.md +3 -3
- data/documentation/Configuration.md +301 -47
- data/documentation/Cron.md +5 -3
- data/documentation/Diagrams.md +1 -1
- data/documentation/Extrospection.md +472 -26
- data/documentation/FFI.md +72 -8
- data/documentation/Home.md +4 -4
- data/documentation/How-PWN-Works.md +7 -3
- data/documentation/Mistakes.md +9 -0
- data/documentation/SDR.md +83 -10
- data/documentation/Skills-Memory-Learning.md +101 -11
- data/documentation/diagrams/agent-tool-registry.svg +56 -55
- data/documentation/diagrams/ai-integration-tool-calling.svg +24 -24
- data/documentation/diagrams/aws-cloud-security.svg +25 -25
- data/documentation/diagrams/burp-vs-zap-preference.svg +12 -12
- data/documentation/diagrams/code-scanning-sast.svg +25 -25
- data/documentation/diagrams/cron-scheduling.svg +23 -23
- data/documentation/diagrams/dot/agent-tool-registry.dot +2 -2
- data/documentation/diagrams/dot/extrospection-world-awareness.dot +33 -6
- data/documentation/diagrams/dot/hardware-hacking.dot +3 -1
- data/documentation/diagrams/dot/memory-skills-detailed.dot +4 -2
- data/documentation/diagrams/dot/mistakes-negative-feedback.dot +4 -2
- data/documentation/diagrams/dot/overall-pwn-architecture.dot +3 -3
- data/documentation/diagrams/dot/persistence-filesystem.dot +3 -3
- data/documentation/diagrams/dot/pwn-ai-feedback-learning-loop.dot +10 -4
- data/documentation/diagrams/dot/sdr-radio-flow.dot +39 -16
- data/documentation/diagrams/driver-framework.svg +13 -13
- data/documentation/diagrams/extrospection-world-awareness.svg +383 -177
- data/documentation/diagrams/fuzzing-workflow.svg +24 -24
- data/documentation/diagrams/hardware-hacking.svg +84 -69
- data/documentation/diagrams/history-to-drivers.svg +18 -18
- data/documentation/diagrams/memory-skills-detailed.svg +137 -120
- data/documentation/diagrams/mistakes-negative-feedback.svg +152 -137
- data/documentation/diagrams/network-infra-testing.svg +27 -27
- data/documentation/diagrams/overall-pwn-architecture.svg +97 -97
- data/documentation/diagrams/penetration-testing-workflow.svg +30 -30
- data/documentation/diagrams/persistence-filesystem.svg +100 -94
- data/documentation/diagrams/plugin-ecosystem.svg +35 -35
- data/documentation/diagrams/pwn-ai-feedback-learning-loop.svg +254 -207
- data/documentation/diagrams/pwn-repl-prototyping.svg +20 -20
- data/documentation/diagrams/reporting-pipeline.svg +18 -18
- data/documentation/diagrams/reverse-engineering-flow.svg +21 -21
- data/documentation/diagrams/sdr-radio-flow.svg +223 -91
- data/documentation/diagrams/sessions-cron-automation.svg +18 -18
- data/documentation/diagrams/swarm-multi-agent.svg +39 -39
- data/documentation/diagrams/web-application-testing.svg +26 -26
- data/documentation/diagrams/zero-day-research-flow.svg +25 -25
- data/documentation/pwn-ai-Agent.md +16 -10
- data/documentation/pwn_silent_help_learn_demo.gif +0 -0
- data/lib/pwn/ai/agent/assembly.rb +1 -1
- data/lib/pwn/ai/agent/btc.rb +1 -1
- data/lib/pwn/ai/agent/burp_suite.rb +1 -1
- data/lib/pwn/ai/agent/extrospection.rb +2562 -57
- data/lib/pwn/ai/agent/gqrx.rb +2 -2
- data/lib/pwn/ai/agent/hacker_one.rb +1 -1
- data/lib/pwn/ai/agent/learning.rb +16 -16
- data/lib/pwn/ai/agent/loop.rb +1 -1
- data/lib/pwn/ai/agent/{introspection.rb → reflect.rb} +12 -12
- data/lib/pwn/ai/agent/sast.rb +1 -1
- data/lib/pwn/ai/agent/tools/extrospection.rb +311 -8
- data/lib/pwn/ai/agent/tools/learning.rb +7 -7
- data/lib/pwn/ai/agent/transparent_browser.rb +1 -1
- data/lib/pwn/ai/agent/vuln_gen.rb +2 -2
- data/lib/pwn/ai/agent.rb +1 -1
- data/lib/pwn/ai/open_ai.rb +10 -6
- data/lib/pwn/config.rb +5 -4
- data/lib/pwn/ffi/adalm_pluto.rb +498 -0
- data/lib/pwn/ffi/fftw.rb +192 -0
- data/lib/pwn/ffi/hack_rf.rb +193 -0
- data/lib/pwn/ffi/liquid.rb +244 -0
- data/lib/pwn/ffi/rtl_sdr.rb +210 -0
- data/lib/pwn/ffi/soapy_sdr.rb +177 -0
- data/lib/pwn/ffi/stdio.rb +9 -1
- data/lib/pwn/ffi/volk.rb +241 -0
- data/lib/pwn/ffi.rb +45 -2
- data/lib/pwn/plugins/burp_suite.rb +2 -2
- data/lib/pwn/sast/pom_version.rb +2 -2
- data/lib/pwn/sast/test_case_engine.rb +1 -1
- data/lib/pwn/sdr/decoder/adsb.rb +201 -12
- data/lib/pwn/sdr/decoder/apt.rb +19 -5
- data/lib/pwn/sdr/decoder/base.rb +314 -1
- data/lib/pwn/sdr/decoder/bluetooth.rb +91 -18
- data/lib/pwn/sdr/decoder/dect.rb +81 -23
- data/lib/pwn/sdr/decoder/dsp.rb +229 -15
- data/lib/pwn/sdr/decoder/flex.rb +23 -6
- data/lib/pwn/sdr/decoder/gps.rb +80 -18
- data/lib/pwn/sdr/decoder/gsm.rb +83 -28
- data/lib/pwn/sdr/decoder/iridium.rb +80 -18
- data/lib/pwn/sdr/decoder/lora.rb +84 -28
- data/lib/pwn/sdr/decoder/lte.rb +80 -18
- data/lib/pwn/sdr/decoder/morse.rb +23 -6
- data/lib/pwn/sdr/decoder/p25.rb +90 -31
- data/lib/pwn/sdr/decoder/pager.rb +23 -6
- data/lib/pwn/sdr/decoder/pocsag.rb +23 -6
- data/lib/pwn/sdr/decoder/rds.rb +219 -52
- data/lib/pwn/sdr/decoder/rfid.rb +81 -27
- data/lib/pwn/sdr/decoder/rtl433.rb +85 -27
- data/lib/pwn/sdr/decoder/rtty.rb +23 -6
- data/lib/pwn/sdr/decoder/wifi.rb +86 -32
- data/lib/pwn/sdr/decoder/zigbee.rb +88 -35
- data/lib/pwn/sdr/decoder.rb +6 -5
- data/lib/pwn/sdr/frequency_allocation.rb +124 -81
- data/lib/pwn/sdr/gqrx.rb +1513 -165
- data/lib/pwn/version.rb +1 -1
- data/spec/lib/pwn/ai/agent/extrospection_spec.rb +96 -0
- data/spec/lib/pwn/ai/agent/{introspection_spec.rb → reflect_spec.rb} +3 -3
- data/spec/lib/pwn/ai/agent/tools/extrospection_spec.rb +8 -24
- data/spec/lib/pwn/ffi/adalm_pluto_spec.rb +37 -0
- data/spec/lib/pwn/ffi/fftw_spec.rb +26 -0
- data/spec/lib/pwn/ffi/hack_rf_spec.rb +25 -0
- data/spec/lib/pwn/ffi/liquid_spec.rb +42 -0
- data/spec/lib/pwn/ffi/rtl_sdr_spec.rb +24 -0
- data/spec/lib/pwn/ffi/soapy_sdr_spec.rb +25 -0
- data/spec/lib/pwn/ffi/volk_spec.rb +36 -0
- data/spec/lib/pwn/sdr/decoder/base_iq_spec.rb +65 -0
- data/spec/lib/pwn/sdr/decoder/dsp_spec.rb +50 -4
- data/spec/lib/pwn/sdr/decoder/rds_spec.rb +45 -4
- data/third_party/pwn_rdoc.jsonl +279 -30
- metadata +21 -5
|
@@ -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
|
data/lib/pwn/ffi/volk.rb
ADDED
|
@@ -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
|