pwn 0.5.620 → 0.5.621

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 (74) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/documentation/Agent-Tool-Registry.md +2 -2
  4. data/documentation/Cron.md +13 -0
  5. data/documentation/Diagrams.md +1 -1
  6. data/documentation/Extrospection.md +71 -5
  7. data/documentation/Home.md +2 -2
  8. data/documentation/How-PWN-Works.md +1 -1
  9. data/documentation/Mistakes.md +2 -1
  10. data/documentation/Persistence.md +2 -1
  11. data/documentation/Skills-Memory-Learning.md +1 -0
  12. data/documentation/Transparent-Browser.md +18 -1
  13. data/documentation/diagrams/agent-tool-registry.svg +55 -54
  14. data/documentation/diagrams/ai-integration-tool-calling.svg +24 -24
  15. data/documentation/diagrams/aws-cloud-security.svg +25 -25
  16. data/documentation/diagrams/burp-vs-zap-preference.svg +12 -12
  17. data/documentation/diagrams/code-scanning-sast.svg +25 -25
  18. data/documentation/diagrams/cron-scheduling.svg +23 -23
  19. data/documentation/diagrams/dot/agent-tool-registry.dot +2 -2
  20. data/documentation/diagrams/dot/extrospection-world-awareness.dot +29 -8
  21. data/documentation/diagrams/dot/memory-skills-detailed.dot +4 -0
  22. data/documentation/diagrams/dot/overall-pwn-architecture.dot +2 -2
  23. data/documentation/diagrams/dot/persistence-filesystem.dot +2 -1
  24. data/documentation/diagrams/dot/pwn-ai-feedback-learning-loop.dot +10 -4
  25. data/documentation/diagrams/driver-framework.svg +13 -13
  26. data/documentation/diagrams/extrospection-world-awareness.svg +229 -99
  27. data/documentation/diagrams/fuzzing-workflow.svg +24 -24
  28. data/documentation/diagrams/hardware-hacking.svg +18 -18
  29. data/documentation/diagrams/history-to-drivers.svg +18 -18
  30. data/documentation/diagrams/memory-skills-detailed.svg +124 -101
  31. data/documentation/diagrams/mistakes-negative-feedback.svg +55 -55
  32. data/documentation/diagrams/network-infra-testing.svg +27 -27
  33. data/documentation/diagrams/overall-pwn-architecture.svg +85 -85
  34. data/documentation/diagrams/penetration-testing-workflow.svg +30 -30
  35. data/documentation/diagrams/persistence-filesystem.svg +94 -85
  36. data/documentation/diagrams/plugin-ecosystem.svg +35 -35
  37. data/documentation/diagrams/pwn-ai-feedback-learning-loop.svg +226 -180
  38. data/documentation/diagrams/pwn-repl-prototyping.svg +20 -20
  39. data/documentation/diagrams/reporting-pipeline.svg +18 -18
  40. data/documentation/diagrams/reverse-engineering-flow.svg +21 -21
  41. data/documentation/diagrams/sdr-radio-flow.svg +31 -31
  42. data/documentation/diagrams/sessions-cron-automation.svg +18 -18
  43. data/documentation/diagrams/swarm-multi-agent.svg +39 -39
  44. data/documentation/diagrams/web-application-testing.svg +26 -26
  45. data/documentation/diagrams/zero-day-research-flow.svg +25 -25
  46. data/documentation/pwn-ai-Agent.md +1 -1
  47. data/lib/pwn/ai/agent/extrospection.rb +433 -4
  48. data/lib/pwn/ai/agent/tools/extrospection.rb +88 -3
  49. data/lib/pwn/sdr/decoder/adsb.rb +14 -33
  50. data/lib/pwn/sdr/decoder/apt.rb +97 -36
  51. data/lib/pwn/sdr/decoder/base.rb +258 -171
  52. data/lib/pwn/sdr/decoder/bluetooth.rb +13 -20
  53. data/lib/pwn/sdr/decoder/dect.rb +13 -27
  54. data/lib/pwn/sdr/decoder/dsp.rb +396 -0
  55. data/lib/pwn/sdr/decoder/flex.rb +177 -210
  56. data/lib/pwn/sdr/decoder/gps.rb +12 -21
  57. data/lib/pwn/sdr/decoder/gsm.rb +21 -73
  58. data/lib/pwn/sdr/decoder/iridium.rb +11 -26
  59. data/lib/pwn/sdr/decoder/lora.rb +16 -23
  60. data/lib/pwn/sdr/decoder/lte.rb +11 -32
  61. data/lib/pwn/sdr/decoder/morse.rb +95 -21
  62. data/lib/pwn/sdr/decoder/p25.rb +19 -16
  63. data/lib/pwn/sdr/decoder/pager.rb +22 -38
  64. data/lib/pwn/sdr/decoder/pocsag.rb +175 -68
  65. data/lib/pwn/sdr/decoder/rfid.rb +24 -33
  66. data/lib/pwn/sdr/decoder/rtl433.rb +21 -30
  67. data/lib/pwn/sdr/decoder/rtty.rb +100 -27
  68. data/lib/pwn/sdr/decoder/wifi.rb +21 -42
  69. data/lib/pwn/sdr/decoder/zigbee.rb +18 -40
  70. data/lib/pwn/sdr/decoder.rb +8 -0
  71. data/lib/pwn/version.rb +1 -1
  72. data/spec/lib/pwn/sdr/decoder/dsp_spec.rb +15 -0
  73. data/third_party/pwn_rdoc.jsonl +24 -3
  74. metadata +3 -1
@@ -0,0 +1,396 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PWN
4
+ module SDR
5
+ module Decoder
6
+ # Pure-Ruby DSP primitives shared by every PWN::SDR::Decoder::* module.
7
+ #
8
+ # Nothing in here shells out. Everything operates on plain Ruby Arrays
9
+ # of Float samples (normalised -1.0..1.0), so decoders can consume the
10
+ # 48 kHz s16le mono audio that PWN::SDR::GQRX streams over UDP without
11
+ # any external `sox` / `multimon-ng` / `minimodem` / etc dependency.
12
+ #
13
+ # These are intentionally simple, readable, allocation-heavy reference
14
+ # implementations — good enough for ≤48 kHz audio-rate work on a modern
15
+ # CPU. For MHz-rate raw I/Q you would want SIMD/native code, which is
16
+ # out of scope for a pure-Ruby decoder namespace.
17
+ module DSP
18
+ TWO_PI = Math::PI * 2
19
+
20
+ # ITA2 / Baudot 5-bit → ASCII (LTRS + FIGS shift tables). Index = code.
21
+ BAUDOT_LTRS = [
22
+ "\0", 'E', "\n", 'A', ' ', 'S', 'I', 'U',
23
+ "\r", 'D', 'R', 'J', 'N', 'F', 'C', 'K',
24
+ 'T', 'Z', 'L', 'W', 'H', 'Y', 'P', 'Q',
25
+ 'O', 'B', 'G', nil, 'M', 'X', 'V', nil
26
+ ].freeze
27
+
28
+ BAUDOT_FIGS = [
29
+ "\0", '3', "\n", '-', ' ', "'", '8', '7',
30
+ "\r", '$', '4', "\a", ',', '!', ':', '(',
31
+ '5', '+', ')', '2', '#', '6', '0', '1',
32
+ '9', '?', '&', nil, '.', '/', ';', nil
33
+ ].freeze
34
+
35
+ # International Morse Code (dit='.', dah='-') → ASCII.
36
+ MORSE_TABLE = {
37
+ '.-' => 'A', '-...' => 'B', '-.-.' => 'C', '-..' => 'D', '.' => 'E',
38
+ '..-.' => 'F', '--.' => 'G', '....' => 'H', '..' => 'I', '.---' => 'J',
39
+ '-.-' => 'K', '.-..' => 'L', '--' => 'M', '-.' => 'N', '---' => 'O',
40
+ '.--.' => 'P', '--.-' => 'Q', '.-.' => 'R', '...' => 'S', '-' => 'T',
41
+ '..-' => 'U', '...-' => 'V', '.--' => 'W', '-..-' => 'X', '-.--' => 'Y',
42
+ '--..' => 'Z', '-----' => '0', '.----' => '1', '..---' => '2',
43
+ '...--' => '3', '....-' => '4', '.....' => '5', '-....' => '6',
44
+ '--...' => '7', '---..' => '8', '----.' => '9', '.-.-.-' => '.',
45
+ '--..--' => ',', '..--..' => '?', '-..-.' => '/', '-....-' => '-',
46
+ '-.--.' => '(', '-.--.-' => ')', '.-...' => '&', '---...' => ':',
47
+ '-...-' => '=', '.-.-.' => '+', '.--.-.' => '@'
48
+ }.freeze
49
+
50
+ # Supported Method Parameters::
51
+ # samples = PWN::SDR::Decoder::DSP.unpack_s16le(
52
+ # data: 'required - raw String of little-endian signed 16-bit PCM'
53
+ # )
54
+
55
+ public_class_method def self.unpack_s16le(opts = {})
56
+ data = opts[:data].to_s
57
+ norm = 1.0 / 32_768.0
58
+ data.unpack('s<*').map { |v| v * norm }
59
+ end
60
+
61
+ # Supported Method Parameters::
62
+ # out = PWN::SDR::Decoder::DSP.resample(
63
+ # samples: 'required - Array<Float>',
64
+ # src_rate: 'required - input sample rate (Hz)',
65
+ # dst_rate: 'required - output sample rate (Hz)'
66
+ # )
67
+
68
+ public_class_method def self.resample(opts = {})
69
+ samples = opts[:samples]
70
+ src_rate = opts[:src_rate].to_f
71
+ dst_rate = opts[:dst_rate].to_f
72
+ return samples.dup if (src_rate - dst_rate).abs < 1e-6
73
+
74
+ ratio = src_rate / dst_rate
75
+ out_len = (samples.length / ratio).floor
76
+ out = Array.new(out_len)
77
+ i = 0
78
+ while i < out_len
79
+ pos = i * ratio
80
+ idx = pos.floor
81
+ frac = pos - idx
82
+ a = samples[idx] || 0.0
83
+ b = samples[idx + 1] || a
84
+ out[i] = a + ((b - a) * frac)
85
+ i += 1
86
+ end
87
+ out
88
+ end
89
+
90
+ # Supported Method Parameters::
91
+ # power = PWN::SDR::Decoder::DSP.goertzel(
92
+ # samples: 'required - Array<Float>',
93
+ # rate: 'required - sample rate (Hz)',
94
+ # freq: 'required - target tone frequency (Hz)'
95
+ # )
96
+
97
+ public_class_method def self.goertzel(opts = {})
98
+ samples = opts[:samples]
99
+ rate = opts[:rate].to_f
100
+ freq = opts[:freq].to_f
101
+ n = samples.length
102
+ return 0.0 if n.zero?
103
+
104
+ k = (0.5 + ((n * freq) / rate)).floor
105
+ w = (TWO_PI * k) / n
106
+ coeff = 2.0 * Math.cos(w)
107
+ s1 = 0.0
108
+ s2 = 0.0
109
+ samples.each do |x|
110
+ s0 = x + (coeff * s1) - s2
111
+ s2 = s1
112
+ s1 = s0
113
+ end
114
+ ((s1 * s1) + (s2 * s2) - (coeff * s1 * s2)) / n
115
+ end
116
+
117
+ # Supported Method Parameters::
118
+ # env = PWN::SDR::Decoder::DSP.envelope(
119
+ # samples: 'required - Array<Float>',
120
+ # window: 'optional - moving-average window in samples (default 32)'
121
+ # )
122
+
123
+ public_class_method def self.envelope(opts = {})
124
+ samples = opts[:samples]
125
+ window = (opts[:window] || 32).to_i
126
+ window = 1 if window < 1
127
+ acc = 0.0
128
+ buf = Array.new(window, 0.0)
129
+ out = Array.new(samples.length)
130
+ samples.each_with_index do |x, i|
131
+ v = x.abs
132
+ slot = i % window
133
+ acc += v - buf[slot]
134
+ buf[slot] = v
135
+ out[i] = acc / window
136
+ end
137
+ out
138
+ end
139
+
140
+ # Supported Method Parameters::
141
+ # y = PWN::SDR::Decoder::DSP.dc_block(
142
+ # samples: 'required - Array<Float>',
143
+ # alpha: 'optional - pole (default 0.995)'
144
+ # )
145
+
146
+ public_class_method def self.dc_block(opts = {})
147
+ samples = opts[:samples]
148
+ alpha = (opts[:alpha] || 0.995).to_f
149
+ y_prev = 0.0
150
+ x_prev = 0.0
151
+ samples.map do |x|
152
+ y = x - x_prev + (alpha * y_prev)
153
+ x_prev = x
154
+ y_prev = y
155
+ y
156
+ end
157
+ end
158
+
159
+ # Supported Method Parameters::
160
+ # bits = PWN::SDR::Decoder::DSP.nrz_slice(
161
+ # samples: 'required - Array<Float> (post-FM-discriminator baseband)',
162
+ # rate: 'required - sample rate (Hz)',
163
+ # baud: 'required - symbol rate',
164
+ # invert: 'optional - flip bit polarity (default false)'
165
+ # )
166
+ # Simple mid-bit sampler with zero-crossing resync. Returns Array<0|1>.
167
+
168
+ public_class_method def self.nrz_slice(opts = {})
169
+ samples = opts[:samples]
170
+ rate = opts[:rate].to_f
171
+ baud = opts[:baud].to_f
172
+ invert = opts[:invert]
173
+ spb = rate / baud
174
+ return [] if spb < 2.0 || samples.empty?
175
+
176
+ # DC-block then low-pass via short moving average (~1/4 symbol).
177
+ lp_win = [(spb / 4.0).round, 1].max
178
+ filt = envelope_signed(samples: dc_block(samples: samples), window: lp_win)
179
+
180
+ bits = []
181
+ phase = spb / 2.0
182
+ prev = filt.first.to_f
183
+ filt.each do |v|
184
+ # zero-crossing → resync to mid-symbol
185
+ phase = spb / 2.0 if (prev.negative? && v >= 0) || (prev.positive? && v.negative?)
186
+ phase -= 1.0
187
+ if phase <= 0
188
+ b = v.negative? ? 0 : 1
189
+ b ^= 1 if invert
190
+ bits << b
191
+ phase += spb
192
+ end
193
+ prev = v
194
+ end
195
+ bits
196
+ end
197
+
198
+ # Signed moving average (like envelope but keeps sign).
199
+ # Supported Method Parameters::
200
+ # y = PWN::SDR::Decoder::DSP.envelope_signed(samples:, window:)
201
+
202
+ public_class_method def self.envelope_signed(opts = {})
203
+ samples = opts[:samples]
204
+ window = (opts[:window] || 8).to_i
205
+ window = 1 if window < 1
206
+ acc = 0.0
207
+ buf = Array.new(window, 0.0)
208
+ out = Array.new(samples.length)
209
+ samples.each_with_index do |x, i|
210
+ slot = i % window
211
+ acc += x - buf[slot]
212
+ buf[slot] = x
213
+ out[i] = acc / window
214
+ end
215
+ out
216
+ end
217
+
218
+ # Supported Method Parameters::
219
+ # bits = PWN::SDR::Decoder::DSP.fsk_slice(
220
+ # samples: 'required - Array<Float>',
221
+ # rate: 'required - sample rate (Hz)',
222
+ # baud: 'required - symbol rate',
223
+ # mark_hz: 'required - mark tone (bit=1)',
224
+ # space_hz:'required - space tone (bit=0)'
225
+ # )
226
+ # Non-coherent 2-FSK: per-symbol Goertzel on mark/space, pick the winner.
227
+
228
+ public_class_method def self.fsk_slice(opts = {})
229
+ samples = opts[:samples]
230
+ rate = opts[:rate].to_f
231
+ baud = opts[:baud].to_f
232
+ mark_hz = opts[:mark_hz].to_f
233
+ space_hz = opts[:space_hz].to_f
234
+ spb = rate / baud
235
+ nsym = (samples.length / spb).floor
236
+ bits = Array.new(nsym)
237
+ i = 0
238
+ while i < nsym
239
+ a = (i * spb).floor
240
+ b = ((i + 1) * spb).floor
241
+ win = samples[a...b]
242
+ pm = goertzel(samples: win, rate: rate, freq: mark_hz)
243
+ ps = goertzel(samples: win, rate: rate, freq: space_hz)
244
+ bits[i] = pm >= ps ? 1 : 0
245
+ i += 1
246
+ end
247
+ bits
248
+ end
249
+
250
+ # Supported Method Parameters::
251
+ # idx = PWN::SDR::Decoder::DSP.find_sync(
252
+ # bits: 'required - Array<0|1>',
253
+ # pattern: 'required - Array<0|1> or Integer (MSB-first)',
254
+ # width: 'optional - bit-width when pattern is Integer',
255
+ # max_err: 'optional - allowed bit errors (default 0)',
256
+ # from: 'optional - start index (default 0)'
257
+ # )
258
+
259
+ public_class_method def self.find_sync(opts = {})
260
+ bits = opts[:bits]
261
+ pattern = opts[:pattern]
262
+ width = opts[:width]
263
+ max_err = (opts[:max_err] || 0).to_i
264
+ from = (opts[:from] || 0).to_i
265
+ pat = if pattern.is_a?(Integer)
266
+ w = width || pattern.bit_length
267
+ Array.new(w) { |i| (pattern >> (w - 1 - i)) & 1 }
268
+ else
269
+ pattern
270
+ end
271
+ plen = pat.length
272
+ upto = bits.length - plen
273
+ i = from
274
+ while i <= upto
275
+ err = 0
276
+ j = 0
277
+ while j < plen
278
+ err += 1 if bits[i + j] != pat[j]
279
+ break if err > max_err
280
+
281
+ j += 1
282
+ end
283
+ return i if err <= max_err
284
+
285
+ i += 1
286
+ end
287
+ nil
288
+ end
289
+
290
+ # Supported Method Parameters::
291
+ # int = PWN::SDR::Decoder::DSP.bits_to_int(bits: [1,0,1,...])
292
+
293
+ public_class_method def self.bits_to_int(opts = {})
294
+ bits = opts[:bits]
295
+ v = 0
296
+ bits.each { |b| v = (v << 1) | (b & 1) }
297
+ v
298
+ end
299
+
300
+ # Supported Method Parameters::
301
+ # ok = PWN::SDR::Decoder::DSP.even_parity_ok?(word: Integer, width: 32)
302
+
303
+ public_class_method def self.even_parity_ok?(opts = {})
304
+ word = opts[:word].to_i
305
+ width = (opts[:width] || 32).to_i
306
+ p = 0
307
+ width.times { |i| p ^= (word >> i) & 1 }
308
+ p.zero?
309
+ end
310
+
311
+ # BCH(31,21) syndrome — generator poly 0b11101101001 (0x769).
312
+ # Used by POCSAG and FLEX codewords (bits 31..1 are BCH, bit 0 parity).
313
+ # Supported Method Parameters::
314
+ # syn = PWN::SDR::Decoder::DSP.bch_31_21_syndrome(word: Integer)
315
+
316
+ public_class_method def self.bch_31_21_syndrome(opts = {})
317
+ word = (opts[:word].to_i >> 1) & 0x7FFFFFFF
318
+ gen = 0x769
319
+ reg = word
320
+ 30.downto(10) do |i|
321
+ reg ^= (gen << (i - 10)) if (reg >> i).odd?
322
+ end
323
+ reg & 0x3FF
324
+ end
325
+
326
+ # Supported Method Parameters::
327
+ # txt = PWN::SDR::Decoder::DSP.baudot_decode(bits: Array<0|1>)
328
+ # 5-bit ITA2 with LTRS(31)/FIGS(27) shift, LSB-first per character.
329
+
330
+ public_class_method def self.baudot_decode(opts = {})
331
+ bits = opts[:bits]
332
+ figs = false
333
+ out = +''
334
+ bits.each_slice(5) do |ch|
335
+ next if ch.length < 5
336
+
337
+ code = ch.each_with_index.sum { |b, i| b << i }
338
+ case code
339
+ when 31 then figs = false
340
+ when 27 then figs = true
341
+ else
342
+ tbl = figs ? BAUDOT_FIGS : BAUDOT_LTRS
343
+ c = tbl[code]
344
+ out << c if c
345
+ end
346
+ end
347
+ out
348
+ end
349
+
350
+ # Supported Method Parameters::
351
+ # dbfs = PWN::SDR::Decoder::DSP.rms_dbfs(samples: Array<Float>)
352
+
353
+ public_class_method def self.rms_dbfs(opts = {})
354
+ samples = opts[:samples]
355
+ return -120.0 if samples.nil? || samples.empty?
356
+
357
+ ms = samples.sum { |v| v * v } / samples.length
358
+ return -120.0 if ms <= 0
359
+
360
+ 10.0 * Math.log10(ms)
361
+ end
362
+
363
+ # Author(s):: 0day Inc. <support@0dayinc.com>
364
+
365
+ public_class_method def self.authors
366
+ "AUTHOR(S):\n 0day Inc. <support@0dayinc.com>\n"
367
+ end
368
+
369
+ # Display Usage for this Module
370
+
371
+ public_class_method def self.help
372
+ puts "USAGE (pure-Ruby DSP primitives — no external binaries):
373
+ #{self}.unpack_s16le(data: raw_bytes)
374
+ #{self}.resample(samples:, src_rate:, dst_rate:)
375
+ #{self}.goertzel(samples:, rate:, freq:)
376
+ #{self}.envelope(samples:, window: 32)
377
+ #{self}.envelope_signed(samples:, window: 8)
378
+ #{self}.dc_block(samples:, alpha: 0.995)
379
+ #{self}.nrz_slice(samples:, rate:, baud:, invert: false)
380
+ #{self}.fsk_slice(samples:, rate:, baud:, mark_hz:, space_hz:)
381
+ #{self}.find_sync(bits:, pattern:, width:, max_err: 0, from: 0)
382
+ #{self}.bits_to_int(bits:)
383
+ #{self}.even_parity_ok?(word:, width: 32)
384
+ #{self}.bch_31_21_syndrome(word:)
385
+ #{self}.baudot_decode(bits:)
386
+ #{self}.rms_dbfs(samples:)
387
+
388
+ Constants: MORSE_TABLE, BAUDOT_LTRS, BAUDOT_FIGS
389
+
390
+ #{self}.authors
391
+ "
392
+ end
393
+ end
394
+ end
395
+ end
396
+ end