pwn 0.5.505 → 0.5.507

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.
@@ -0,0 +1,200 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PWN
4
+ module SDR
5
+ module Decoder
6
+ # SDR Decoder for GSM signals.
7
+ module GSM
8
+ POWER_THRESHOLD = 0.1
9
+ SLEEP_INTERVAL = 0.1
10
+ HEADER_SIZE = 44
11
+ BURST_DURATION_SEC = 0.000577
12
+
13
+ # TSC 0 binary sequence (26 bits): 00100101110000101010011011
14
+ TSC_0 = [0, 0, 1, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 1, 0, 1, 1].freeze
15
+
16
+ # Starts the live decoding thread.
17
+ def self.start(opts = {})
18
+ gqrx_obj = opts[:gqrx_obj]
19
+ raise ':ERROR: :gqrx_obj is required' unless gqrx_obj.is_a?(Hash)
20
+
21
+ record_path = gqrx_obj[:record_path]
22
+ frequency = gqrx_obj[:frequency]
23
+ sample_rate = gqrx_obj[:bandwidth].to_i
24
+ gqrx_sock = gqrx_obj[:gqrx_sock]
25
+
26
+ gqrx_obj[:decoder_stop_flag] ||= [false]
27
+
28
+ sleep 0.1 until File.exist?(record_path)
29
+
30
+ header = File.binread(record_path, HEADER_SIZE)
31
+ raise 'Invalid WAV header' unless header.start_with?('RIFF') && header.include?('WAVE')
32
+
33
+ bytes_read = HEADER_SIZE
34
+
35
+ puts "GSM Decoder started for frequency: #{frequency}, sample_rate: #{sample_rate}"
36
+
37
+ Thread.new do
38
+ loop do
39
+ break if gqrx_obj[:decoder_stop_flag][0]
40
+
41
+ current_size = File.size(record_path)
42
+ if current_size > bytes_read
43
+ new_bytes = current_size - bytes_read
44
+ # Ensure full I/Q pairs (8 bytes)
45
+ new_bytes -= new_bytes % 8
46
+ data = File.binread(record_path, new_bytes, bytes_read)
47
+ process_chunk(
48
+ data: data,
49
+ sample_rate: sample_rate,
50
+ frequency: frequency
51
+ )
52
+ bytes_read = current_size
53
+ end
54
+
55
+ sleep SLEEP_INTERVAL
56
+ end
57
+ rescue StandardError => e
58
+ puts "Decoder error: #{e.message}"
59
+ ensure
60
+ cleanup(record_path: record_path)
61
+ end
62
+ end
63
+
64
+ # Stops the decoding thread.
65
+ def self.stop(opts = {})
66
+ thread = opts[:thread]
67
+ gqrx_obj = opts[:gqrx_obj]
68
+ raise ':ERROR: :thread and :gqrx_obj are required' unless thread && gqrx_obj.is_a?(Hash)
69
+
70
+ gqrx_obj[:decoder_stop_flag][0] = true
71
+ thread.join(1.0)
72
+ end
73
+
74
+ class << self
75
+ private
76
+
77
+ def process_chunk(opts = {})
78
+ data = opts[:data]
79
+ sample_rate = opts[:sample_rate]
80
+ frequency = opts[:frequency]
81
+ raise ':ERROR: :data, :sample_rate, and :frequency are required' unless data && sample_rate && frequency
82
+
83
+ samples = data.unpack('f< *')
84
+ return if samples.length.odd? # Skip incomplete
85
+
86
+ complex_samples = []
87
+ (0...samples.length).step(2) do |i|
88
+ complex_samples << Complex(samples[i], samples[i + 1])
89
+ end
90
+
91
+ window_size = [(sample_rate * BURST_DURATION_SEC).round, complex_samples.length].min
92
+ return if window_size <= 0
93
+
94
+ # Simplified power on sliding windows
95
+ powers = []
96
+ complex_samples.each_cons(window_size) do |window|
97
+ power = window.map { |c| c.abs**2 }.sum / window_size
98
+ powers << power
99
+ end
100
+
101
+ max_power = powers.max
102
+ return unless max_power > POWER_THRESHOLD
103
+
104
+ # Demod the entire chunk (assume burst-aligned roughly)
105
+ bits = demod_gmsk(complex_samples)
106
+ # Synchronize via TSC correlation
107
+ sync_offset = find_tsc_offset(bits, TSC_0)
108
+ return unless sync_offset >= 0
109
+
110
+ # Extract data bits from normal burst structure
111
+ burst_start = sync_offset - 58 # TSC starts at symbol 58 (0-index)
112
+ return unless burst_start >= 0 && burst_start + 148 <= bits.length
113
+
114
+ data_bits = extract_data_bits(bits, burst_start)
115
+ puts "Burst synchronized at offset #{sync_offset} for #{frequency} Hz (power: #{max_power.round(4)})"
116
+ decode_imsi(
117
+ data_bits: data_bits,
118
+ frequency: frequency
119
+ )
120
+ end
121
+
122
+ def demod_gmsk(complex_samples)
123
+ return [] if complex_samples.length < 2
124
+
125
+ bits = []
126
+ (1...complex_samples.length).each do |i|
127
+ prod = complex_samples[i] * complex_samples[i - 1].conj
128
+ # Sign of imaginary part for quadrature differential
129
+ bit = (prod.imag >= 0 ? 0 : 1) # Or adjust polarity
130
+ bits << bit
131
+ end
132
+ bits
133
+ end
134
+
135
+ def find_tsc_offset(bits, tsc)
136
+ max_corr = -1
137
+ best_offset = -1
138
+ tsc_length = tsc.length # 26
139
+ (0...(bits.length - tsc_length + 1)).each do |offset|
140
+ window = bits[offset, tsc_length]
141
+ corr = window.zip(tsc).count { |b1, b2| b1 == b2 }
142
+ if corr > max_corr
143
+ max_corr = corr
144
+ best_offset = offset
145
+ end
146
+ end
147
+ # Threshold: e.g., >20 matches for good sync
148
+ max_corr > 20 ? best_offset : -1
149
+ end
150
+
151
+ # Extract 114 data bits from normal burst (ignoring tails/guard)
152
+ def extract_data_bits(bits, burst_start)
153
+ data1_start = burst_start + 2
154
+ data2_start = burst_start + 88 # After TSC 26 + data1 57 = 85, +3? Wait, structure: tail2(0-1), data(2-58), tsc(59-84), data(85-141), tail(142-143)
155
+ data1 = bits[data1_start, 57]
156
+ data2 = bits[data2_start, 57]
157
+ data1 + data2
158
+ end
159
+
160
+ def decode_imsi(opts = {})
161
+ data_bits = opts[:data_bits]
162
+ frequency = opts[:frequency]
163
+ raise ':ERROR: :data_bits and :frequency are required' unless data_bits && frequency
164
+
165
+ # Simplified "IMSI extraction": Interpret first ~60 bits as packed digits (4 bits per digit, BCD-like).
166
+ # In reality: Deinterleave (over bursts), Viterbi decode convolutional code (polys G0=10011b, G1=11011b),
167
+ # CRC check, parse L3 message (e.g., Paging Req Type 1 has IMSI IE at specific offset, packed BCD).
168
+ # Here: Raw data bits to 15-digit IMSI (first 60 bits -> 15 nibbles).
169
+ return unless data_bits.length >= 60
170
+
171
+ imsi_digits = []
172
+ data_bits[0, 60].each_slice(4) do |nibble|
173
+ digit = nibble.join.to_i(2)
174
+ imsi_digits << (digit % 10) # Mod 10 for digit-like, or keep as is for hex
175
+ end
176
+
177
+ # Format as 3(MCC)+3(MNC)+9(MSIN)
178
+ mcc = imsi_digits[0, 3].join
179
+ mnc = imsi_digits[3, 3].join
180
+ msin = imsi_digits[6, 9].join
181
+ imsi = "#{mcc.ljust(3, '0')}#{mnc.ljust(3, '0')}#{msin.ljust(9, '0')}"
182
+
183
+ puts "Decoded IMSI: #{imsi} at #{frequency} Hz"
184
+ # TODO: Integrate full L3 parser (e.g., from ruby-gsm gem or custom).
185
+ end
186
+
187
+ def cleanup(opts = {})
188
+ record_path = opts[:record_path]
189
+ raise ':ERROR: :record_path is required' unless record_path
190
+
191
+ return unless File.exist?(record_path)
192
+
193
+ File.delete(record_path)
194
+ puts "Cleaned up recording: #{record_path}"
195
+ end
196
+ end
197
+ end
198
+ end
199
+ end
200
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PWN
4
+ # This file, using the autoload directive loads SDR modules
5
+ # into memory only when they're needed. For more information, see:
6
+ # http://www.rubyinside.com/ruby-techniques-revealed-autoload-1652.html
7
+ module SDR
8
+ # Deocder Module for SDR signals.
9
+ module Decoder
10
+ autoload :GSM, 'pwn/sdr/decoder/gsm'
11
+
12
+ # Display a List of Every PWN::AI Module
13
+
14
+ public_class_method def self.help
15
+ constants.sort
16
+ end
17
+ end
18
+ end
19
+ end
@@ -1,13 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PWN
4
- module Plugins
4
+ module SDR
5
5
  # This plugin is used for interacting with Bus Pirate v3.6
6
6
  # This plugin may be compatible with other versions, however,
7
7
  # has not been tested with anything other than v3.6.
8
8
  module FlipperZero
9
9
  # Supported Method Parameters::
10
- # PWN::Plugins::FlipperZero.connect_via_screen(
10
+ # PWN::SDR::FlipperZero.connect_via_screen(
11
11
  # block_dev: 'optional - serial block device path (defaults to /dev/ttyACM0)'
12
12
  # )
13
13
 
@@ -34,7 +34,7 @@ module PWN
34
34
  end
35
35
 
36
36
  # Supported Method Parameters::
37
- # flipper_zero_obj = PWN::Plugins::FlipperZero.connect(
37
+ # flipper_zero_obj = PWN::SDR::FlipperZero.connect(
38
38
  # block_dev: 'optional serial block device path (defaults to /dev/ttyACM0)',
39
39
  # baud: 'optional (defaults to 9600)',
40
40
  # data_bits: 'optional (defaults to 8)',
@@ -50,7 +50,7 @@ module PWN
50
50
  end
51
51
 
52
52
  # Supported Method Parameters::
53
- # response = PWN::Plugins::FlipperZero.request(
53
+ # response = PWN::SDR::FlipperZero.request(
54
54
  # flipper_zero_obj: 'required - flipper_zero_obj returned from #connect method',
55
55
  # payload: 'optional - payload to send to the device (defaults to help)'
56
56
  # )
@@ -75,7 +75,7 @@ module PWN
75
75
  end
76
76
 
77
77
  # Supported Method Parameters::
78
- # PWN::Plugins::FlipperZero.disconnect(
78
+ # PWN::SDR::FlipperZero.disconnect(
79
79
  # flipper_zero_obj: 'required - flipper_zero_obj returned from #connect method'
80
80
  # )
81
81
 
@@ -0,0 +1,372 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'open3'
4
+ require 'json'
5
+ require 'fileutils'
6
+
7
+ module PWN
8
+ module SDR
9
+ # This plugin interacts with the remote control interface of GQRX.
10
+ module FrequencyAllocation
11
+ # Supported Method Parameters::
12
+ # profiles = PWN::SDR::FrequencyAllocation.profiles
13
+ # Supported Method Parameters::
14
+ # profiles = PWN::SDR::FrequencyAllocation.profiles
15
+ public_class_method def self.profiles
16
+ # TODO: Wifi5 / Wifi6 profiles,
17
+ # migrate to a YAML file, and add
18
+ # rSpec test to ensure all profiles
19
+ # contain consistent key-value pairs
20
+ {
21
+ ads_b978: {
22
+ start_freq: '978.000.000',
23
+ target_freq: '979.000.000',
24
+ demodulator_mode: 'RAW',
25
+ bandwidth: 100_000,
26
+ precision: 5
27
+ },
28
+ ads_b1090: {
29
+ start_freq: '1.090.000.000',
30
+ target_freq: '1.091.000.000',
31
+ demodulator_mode: 'RAW',
32
+ bandwidth: 100_000,
33
+ precision: 5
34
+ },
35
+ analog_tv_vhf: {
36
+ start_freq: '54.000.000',
37
+ target_freq: '216.000.000',
38
+ demodulator_mode: 'WFM',
39
+ bandwidth: 600_000,
40
+ precision: 5
41
+ },
42
+ analog_tv_uhf: {
43
+ start_freq: '470.000.000',
44
+ target_freq: '890.000.000',
45
+ demodulator_mode: 'WFM',
46
+ bandwidth: 600_000,
47
+ precision: 5
48
+ },
49
+ am_radio: {
50
+ start_freq: '540.000',
51
+ target_freq: '1.700.000',
52
+ demodulator_mode: 'AM',
53
+ bandwidth: 10_000,
54
+ precision: 4
55
+ },
56
+ bluetooth: {
57
+ start_freq: '2.402.000.000',
58
+ target_freq: '2.480.000.000',
59
+ demodulator_mode: 'RAW',
60
+ bandwidth: 100_000,
61
+ precision: 5
62
+ },
63
+ cdma: {
64
+ start_freq: '824.000.000',
65
+ target_freq: '849.000.000',
66
+ demodulator_mode: 'RAW',
67
+ bandwidth: 125_000,
68
+ precision: 6
69
+ },
70
+ cw20: {
71
+ start_freq: '14.000.000',
72
+ target_freq: '14.350.000',
73
+ demodulator_mode: 'CW',
74
+ bandwidth: 150,
75
+ precision: 3
76
+ },
77
+ cw40: {
78
+ start_freq: '7.000.000',
79
+ target_freq: '7.300.000',
80
+ demodulator_mode: 'CW',
81
+ bandwidth: 150,
82
+ precision: 3
83
+ },
84
+ cw80: {
85
+ start_freq: '3.500.000',
86
+ target_freq: '3.800.000',
87
+ demodulator_mode: 'CW',
88
+ bandwidth: 150,
89
+ precision: 3
90
+ },
91
+ fm_radio: {
92
+ start_freq: '87.900.000',
93
+ target_freq: '108.000.000',
94
+ demodulator_mode: 'WFM',
95
+ bandwidth: 200_000,
96
+ precision: 6
97
+ },
98
+ frs: {
99
+ start_freq: '462.562.500',
100
+ target_freq: '467.725.000',
101
+ demodulator_mode: 'FM',
102
+ bandwidth: 200_000,
103
+ precision: 3
104
+ },
105
+ gmrs: {
106
+ start_freq: '462.550.000',
107
+ target_freq: '467.725.000',
108
+ demodulator_mode: 'FM',
109
+ bandwidth: 200_000,
110
+ precision: 3
111
+ },
112
+ gprs: {
113
+ start_freq: '880.000.000',
114
+ target_freq: '915.000.000',
115
+ demodulator_mode: 'RAW',
116
+ bandwidth: 200_000,
117
+ precision: 4
118
+ },
119
+ gps_l1: {
120
+ start_freq: '1.574.420.000',
121
+ target_freq: '1.576.420.000',
122
+ demodulator_mode: 'RAW',
123
+ bandwidth: 200_000,
124
+ precision: 6
125
+ },
126
+ gps_l2: {
127
+ start_freq: '1.226.600.000',
128
+ target_freq: '1.228.600.000',
129
+ demodulator_mode: 'RAW',
130
+ bandwidth: 200_000,
131
+ precision: 6
132
+ },
133
+ gsm: {
134
+ start_freq: '824.000.000',
135
+ target_freq: '894.000.000',
136
+ demodulator_mode: 'RAW',
137
+ bandwidth: 200_000,
138
+ precision: 4
139
+ },
140
+ high_rfid: {
141
+ start_freq: '13.560.000',
142
+ target_freq: '13.570.000',
143
+ demodulator_mode: 'RAW',
144
+ bandwidth: 200_000,
145
+ precision: 3
146
+ },
147
+ lora433: {
148
+ start_freq: '432.000.000',
149
+ target_freq: '434.000.000',
150
+ demodulator_mode: 'RAW',
151
+ bandwidth: 50_000,
152
+ precision: 3
153
+ },
154
+ lora915: {
155
+ start_freq: '902.000.000',
156
+ target_freq: '928.000.000',
157
+ demodulator_mode: 'RAW',
158
+ bandwidth: 50_000,
159
+ precision: 3
160
+ },
161
+ low_rfid: {
162
+ start_freq: '125.000',
163
+ target_freq: '134.000',
164
+ demodulator_mode: 'RAW',
165
+ bandwidth: 200_000,
166
+ precision: 1
167
+ },
168
+ keyfob300: {
169
+ start_freq: '300.000.000',
170
+ target_freq: '300.100.000',
171
+ demodulator_mode: 'RAW',
172
+ bandwidth: 50_000,
173
+ precision: 4
174
+ },
175
+ keyfob310: {
176
+ start_freq: '310.000.000',
177
+ target_freq: '310.100.000',
178
+ demodulator_mode: 'RAW',
179
+ bandwidth: 50_000,
180
+ precision: 4
181
+ },
182
+ keyfob315: {
183
+ start_freq: '315.000.000',
184
+ target_freq: '315.100.000',
185
+ demodulator_mode: 'RAW',
186
+ bandwidth: 50_000,
187
+ precision: 4
188
+ },
189
+ keyfob390: {
190
+ start_freq: '390.000.000',
191
+ target_freq: '390.100.000',
192
+ demodulator_mode: 'RAW',
193
+ bandwidth: 50_000,
194
+ precision: 4
195
+ },
196
+ keyfob433: {
197
+ start_freq: '433.000.000',
198
+ target_freq: '434.000.000',
199
+ demodulator_mode: 'RAW',
200
+ bandwidth: 50_000,
201
+ precision: 4
202
+ },
203
+ keyfob868: {
204
+ start_freq: '868.000.000',
205
+ target_freq: '869.000.000',
206
+ demodulator_mode: 'RAW',
207
+ bandwidth: 50_000,
208
+ precision: 4
209
+ },
210
+ rtty20: {
211
+ start_freq: '14.000.000',
212
+ target_freq: '14.350.000',
213
+ demodulator_mode: 'RTTY',
214
+ bandwidth: 170,
215
+ precision: 3
216
+ },
217
+ rtty40: {
218
+ start_freq: '7.000.000',
219
+ target_freq: '7.300.000',
220
+ demodulator_mode: 'RTTY',
221
+ bandwidth: 170,
222
+ precision: 3
223
+ },
224
+ rtty80: {
225
+ start_freq: '3.500.000',
226
+ target_freq: '3.800.000',
227
+ demodulator_mode: 'RTTY',
228
+ bandwidth: 170,
229
+ precision: 3
230
+ },
231
+ ssb10: {
232
+ start_freq: '28.000.000',
233
+ target_freq: '29.700.000',
234
+ demodulator_mode: 'USB',
235
+ bandwidth: 2_700,
236
+ precision: 6
237
+ },
238
+ ssb12: {
239
+ start_freq: '24.890.000',
240
+ target_freq: '24.990.000',
241
+ demodulator_mode: 'USB',
242
+ bandwidth: 2_700,
243
+ precision: 6
244
+ },
245
+ ssb15: {
246
+ start_freq: '21.000.000',
247
+ target_freq: '21.450.000',
248
+ demodulator_mode: 'USB',
249
+ bandwidth: 2_700,
250
+ precision: 6
251
+ },
252
+ ssb17: {
253
+ start_freq: '18.068.000',
254
+ target_freq: '18.168.000',
255
+ demodulator_mode: 'USB',
256
+ bandwidth: 2_700,
257
+ precision: 6
258
+ },
259
+ ssb20: {
260
+ start_freq: '14.000.000',
261
+ target_freq: '14.350.000',
262
+ demodulator_mode: 'USB',
263
+ bandwidth: 2_700,
264
+ precision: 6
265
+ },
266
+ ssb40: {
267
+ start_freq: '7.000.000',
268
+ target_freq: '7.300.000',
269
+ demodulator_mode: 'LSB',
270
+ bandwidth: 2_700,
271
+ precision: 6
272
+ },
273
+ ssb80: {
274
+ start_freq: '3.500.000',
275
+ target_freq: '3.800.000',
276
+ demodulator_mode: 'LSB',
277
+ bandwidth: 2_700,
278
+ precision: 6
279
+ },
280
+ ssb160: {
281
+ start_freq: '1.800.000',
282
+ target_freq: '2.000.000',
283
+ demodulator_mode: 'LSB',
284
+ bandwidth: 2_700,
285
+ precision: 6
286
+ },
287
+ tempest: {
288
+ start_freq: '400.000.000',
289
+ target_freq: '430.000.000',
290
+ demodulator_mode: 'WFM',
291
+ bandwidth: 200_000,
292
+ precision: 4
293
+ },
294
+ uhf_rfid: {
295
+ start_freq: '860.000.000',
296
+ target_freq: '960.000.000',
297
+ demodulator_mode: 'RAW',
298
+ bandwidth: 100_000,
299
+ precision: 5
300
+ },
301
+ wifi24: {
302
+ start_freq: '2.400.000.000',
303
+ target_freq: '2.500.000.000',
304
+ demodulator_mode: 'RAW',
305
+ bandwidth: 200_000,
306
+ precision: 7
307
+ },
308
+ wifi5: {
309
+ start_freq: '5.150.000.000',
310
+ target_freq: '5.850.000.000',
311
+ demodulator_mode: 'RAW',
312
+ bandwidth: 200_000,
313
+ precision: 7
314
+ },
315
+ wifi6: {
316
+ start_freq: '5.925.000.000',
317
+ target_freq: '7.125.000.000',
318
+ demodulator_mode: 'RAW',
319
+ bandwidth: 200_000,
320
+ precision: 7
321
+ },
322
+ zigbee: {
323
+ start_freq: '2.405.000.000',
324
+ target_freq: '2.485.000.000',
325
+ demodulator_mode: 'RAW',
326
+ bandwidth: 200_000,
327
+ precision: 7
328
+ }
329
+ }
330
+ rescue StandardError => e
331
+ raise e
332
+ end
333
+
334
+ # Supported Method Parameters::
335
+ # opts = PWN::SDR::FrequencyAllocation.load(
336
+ # profile: 'required - valid FrequencyAllocation profile name returned from #profiles method'
337
+ # )
338
+ public_class_method def self.load(opts = {})
339
+ profile = opts[:profile]&.to_sym
340
+
341
+ profiles_available = profiles
342
+ raise "ERROR: Invalid profile: #{profile}" unless profiles_available.key?(profile)
343
+
344
+ profiles_available[profile]
345
+ rescue StandardError => e
346
+ raise e
347
+ end
348
+
349
+ # Author(s):: 0day Inc. <support@0dayinc.com>
350
+
351
+ public_class_method def self.authors
352
+ "AUTHOR(S):
353
+ 0day Inc. <support@0dayinc.com>
354
+ "
355
+ end
356
+
357
+ # Display Usage for this Module
358
+
359
+ public_class_method def self.help
360
+ puts "USAGE:
361
+ profiles = #{self}.profiles
362
+
363
+ opts = #{self}.load(
364
+ profile: 'required - valid frequency allocation profile name returned from #profiles method'
365
+ )
366
+
367
+ #{self}.authors
368
+ "
369
+ end
370
+ end
371
+ end
372
+ end