pwn 0.5.506 → 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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ff5a5cd8f4d9e0ce9d23468de01cb6c0f685ed4c9541e08af39dcb05b8c50d67
4
- data.tar.gz: e9afe048562d6d832972c9990a24db2b78a6d5c76e95959079ac4412e959c152
3
+ metadata.gz: f5f3b2e3e72d554719b28120d3ccb558d563b5d0d70ca79425994eaf20977bc7
4
+ data.tar.gz: 2a59f3fb5bbe1b66192ebd4c4cd7c6528ead94ffb06770089aaff9806dc83130
5
5
  SHA512:
6
- metadata.gz: 30d268063617d51deeed10b035ed8566913eebf114247ea8f4d988347a447faff622105e032f103ece1a56a69145e8ade0a79a6c9b0ec183f075065ca9d25818
7
- data.tar.gz: 1c8deccd120888637f7a257cd3304374caac61bb9276a6f48b7c20a68e00087a7ec952a2c823745e62aa87c720c478b99f69e367ad95151365d06f644cfd49a0
6
+ metadata.gz: 846169897acd7a88a03c71c65f08e1502ad13ab98538000cf613ad9ff0d43844631510ffb75b32f186e6aca75df0761a92cb030c7014466f7c93e3826d1dae1d
7
+ data.tar.gz: 4974d662081fdc274fc8332265fcc41c5b51a8fcd9de3794641f46719002db226c1659bea12d19969bb5b1ef3ab3d93d088a16885de6a337ee6ab1cff86a9224
data/Gemfile CHANGED
@@ -38,7 +38,7 @@ gem 'gem-wrappers', '1.4.0'
38
38
  gem 'geocoder', '1.8.6'
39
39
  gem 'gist', '6.0.0'
40
40
  gem 'gruff', '0.29.0'
41
- gem 'htmlentities', '4.3.4'
41
+ gem 'htmlentities', '4.4.2'
42
42
  gem 'ipaddress', '0.8.3'
43
43
  gem 'jenkins_api_client2', '1.9.0'
44
44
  gem 'js-beautify', '0.1.8'
@@ -90,7 +90,7 @@ gem 'rvm', '1.11.3.9'
90
90
  gem 'savon', '2.15.1'
91
91
  gem 'selenium-devtools', '0.142.0'
92
92
  gem 'selenium-webdriver', '4.38.0'
93
- gem 'slack-ruby-client', '3.0.0'
93
+ gem 'slack-ruby-client', '3.1.0'
94
94
  gem 'socksify', '1.8.1'
95
95
  gem 'spreadsheet', '1.3.4'
96
96
  gem 'sqlite3', '2.8.0'
data/README.md CHANGED
@@ -37,7 +37,7 @@ $ cd /opt/pwn
37
37
  $ ./install.sh
38
38
  $ ./install.sh ruby-gem
39
39
  $ pwn
40
- pwn[v0.5.506]:001 >>> PWN.help
40
+ pwn[v0.5.507]:001 >>> PWN.help
41
41
  ```
42
42
 
43
43
  [![Installing the pwn Security Automation Framework](https://raw.githubusercontent.com/0dayInc/pwn/master/documentation/pwn_install.png)](https://youtu.be/G7iLUY4FzsI)
@@ -52,7 +52,7 @@ $ rvm use ruby-3.4.4@pwn
52
52
  $ gem uninstall --all --executables pwn
53
53
  $ gem install --verbose pwn
54
54
  $ pwn
55
- pwn[v0.5.506]:001 >>> PWN.help
55
+ pwn[v0.5.507]:001 >>> PWN.help
56
56
  ```
57
57
 
58
58
  If you're using a multi-user install of RVM do:
@@ -62,7 +62,7 @@ $ rvm use ruby-3.4.4@pwn
62
62
  $ rvmsudo gem uninstall --all --executables pwn
63
63
  $ rvmsudo gem install --verbose pwn
64
64
  $ pwn
65
- pwn[v0.5.506]:001 >>> PWN.help
65
+ pwn[v0.5.507]:001 >>> PWN.help
66
66
  ```
67
67
 
68
68
  PWN periodically upgrades to the latest version of Ruby which is reflected in `/opt/pwn/.ruby-version`. The easiest way to upgrade to the latest version of Ruby from a previous PWN installation is to run the following script:
data/bin/pwn_gqrx_scanner CHANGED
@@ -9,8 +9,8 @@ PWN::Driver::Parser.new do |options|
9
9
  opts[:profile] = p
10
10
  end
11
11
 
12
- options.on('-l', '--list-profiles', '<Optional - List supported profiles and exit>') do |l|
13
- opts[:list_profiles] = l
12
+ options.on('-l', '--list-scan-profiles', '<Optional - List supported scan profiles and exit>') do |l|
13
+ opts[:list_scan_profiles] = l
14
14
  end
15
15
 
16
16
  options.on('-tFREQ', '--target-freq=FREQ', '<Required if "--assume-profile" is Nil - Frequency to Conclude Scanning (e.g. 900.000.000 == 900 mHz>') do |e|
@@ -74,15 +74,15 @@ begin
74
74
  pwn_provider = 'ruby-gem'
75
75
  pwn_provider = ENV.fetch('PWN_PROVIDER') if ENV.keys.any? { |s| s == 'PWN_PROVIDER' }
76
76
 
77
- list_profiles = opts[:list_profiles]
78
- if list_profiles
79
- profiles_available = PWN::Plugins::GQRX.list_profiles
77
+ list_scan_profiles = opts[:list_scan_profiles]
78
+ if list_scan_profiles
79
+ profiles_available = PWN::SDR::GQRX.list_scan_profiles
80
80
  puts JSON.pretty_generate(profiles_available)
81
81
  exit 0
82
82
  end
83
83
 
84
84
  profile = opts[:profile]
85
- opts = PWN::Plugins::GQRX.assume_profile(profile: profile) unless profile.nil?
85
+ opts = PWN::SDR::GQRX.assume_profile(profile: profile) unless profile.nil?
86
86
 
87
87
  target_freq = opts[:target_freq]
88
88
  target_freq = target_freq.to_s.delete('.') unless target_freq.nil?
@@ -93,13 +93,13 @@ begin
93
93
  port = opts[:port]
94
94
 
95
95
  puts "Connecting to GQRX at #{host}:#{port}..."
96
- gqrx_sock = PWN::Plugins::GQRX.connect(target: host, port: port)
96
+ gqrx_sock = PWN::SDR::GQRX.connect(target: host, port: port)
97
97
 
98
98
  start_freq = opts[:start_freq]
99
99
  start_freq = start_freq.to_s.delete('.') unless start_freq.nil?
100
100
  start_freq = start_freq.to_i
101
101
  if start_freq.zero?
102
- start_freq = PWN::Plugins::GQRX.gqrx_cmd(
102
+ start_freq = PWN::SDR::GQRX.gqrx_cmd(
103
103
  gqrx_sock: gqrx_sock, cmd: 'f',
104
104
  resp_ok: 'RPRT 0'
105
105
  ).to_i
@@ -115,7 +115,7 @@ begin
115
115
 
116
116
  puts "Setting demodulator mode to #{demodulator_mode} and bandwidth to #{bandwidth}..."
117
117
  bandwidth = bandwidth.to_s.delete('.').to_i unless bandwidth.nil?
118
- demod_resp = PWN::Plugins::GQRX.gqrx_cmd(
118
+ demod_resp = PWN::SDR::GQRX.gqrx_cmd(
119
119
  gqrx_sock: gqrx_sock,
120
120
  cmd: "M #{demodulator_mode} #{bandwidth}",
121
121
  resp_ok: 'RPRT 0'
@@ -123,7 +123,7 @@ begin
123
123
 
124
124
  audio_gain_db = opts[:audio_gain_db] ||= 1.0
125
125
  audio_gain_db = audio_gain_db.to_f
126
- audio_gain_db_resp = PWN::Plugins::GQRX.gqrx_cmd(
126
+ audio_gain_db_resp = PWN::SDR::GQRX.gqrx_cmd(
127
127
  gqrx_sock: gqrx_sock,
128
128
  cmd: "L AF #{audio_gain_db}",
129
129
  resp_ok: 'RPRT 0'
@@ -131,7 +131,7 @@ begin
131
131
 
132
132
  squelch = opts[:squelch] ||= -63.0
133
133
  squelch = squelch.to_f
134
- squelch_resp = PWN::Plugins::GQRX.gqrx_cmd(
134
+ squelch_resp = PWN::SDR::GQRX.gqrx_cmd(
135
135
  gqrx_sock: gqrx_sock,
136
136
  cmd: "L SQL #{squelch}",
137
137
  resp_ok: 'RPRT 0'
@@ -149,7 +149,7 @@ begin
149
149
 
150
150
  rf_gain = opts[:rf_gain] ||= 0.0
151
151
  rf_gain = rf_gain.to_f
152
- rf_gain_resp = PWN::Plugins::GQRX.gqrx_cmd(
152
+ rf_gain_resp = PWN::SDR::GQRX.gqrx_cmd(
153
153
  gqrx_sock: gqrx_sock,
154
154
  cmd: "L RF_GAIN #{rf_gain}",
155
155
  resp_ok: 'RPRT 0'
@@ -157,7 +157,7 @@ begin
157
157
 
158
158
  intermediate_gain = opts[:intermediate_gain] ||= 32.0
159
159
  intermediate_gain = intermediate_gain.to_f
160
- intermediate_resp = PWN::Plugins::GQRX.gqrx_cmd(
160
+ intermediate_resp = PWN::SDR::GQRX.gqrx_cmd(
161
161
  gqrx_sock: gqrx_sock,
162
162
  cmd: "L IF_GAIN #{intermediate_gain}",
163
163
  resp_ok: 'RPRT 0'
@@ -165,7 +165,7 @@ begin
165
165
 
166
166
  baseband_gain = opts[:baseband_gain] ||= 10.0
167
167
  baseband_gain = baseband_gain.to_f
168
- baseband_resp = PWN::Plugins::GQRX.gqrx_cmd(
168
+ baseband_resp = PWN::SDR::GQRX.gqrx_cmd(
169
169
  gqrx_sock: gqrx_sock,
170
170
  cmd: "L BB_GAIN #{baseband_gain}",
171
171
  resp_ok: 'RPRT 0'
@@ -175,7 +175,7 @@ begin
175
175
  t_freq_pretty = target_freq.to_s.chars.insert(-4, '.').insert(-8, '.').join
176
176
  puts "*** Scanning from #{s_freq_pretty} to #{t_freq_pretty}\n\n\n"
177
177
 
178
- PWN::Plugins::GQRX.scan_range(
178
+ PWN::SDR::GQRX.scan_range(
179
179
  gqrx_sock: gqrx_sock,
180
180
  demodulator_mode: demodulator_mode,
181
181
  bandwidth: bandwidth,
@@ -192,5 +192,5 @@ rescue StandardError => e
192
192
  rescue Interrupt, SystemExit
193
193
  puts "\nGoodbye."
194
194
  ensure
195
- gqrx_sock = PWN::Plugins::GQRX.disconnect(gqrx_sock: gqrx_sock) unless gqrx_sock.nil?
195
+ gqrx_sock = PWN::SDR::GQRX.disconnect(gqrx_sock: gqrx_sock) unless gqrx_sock.nil?
196
196
  end
@@ -44,7 +44,7 @@ begin
44
44
  parity = opts[:parity]
45
45
  flow_control = opts[:flow_control]
46
46
 
47
- son_micro_rfid_obj = PWN::Plugins::SonMicroRFID.connect(
47
+ son_micro_rfid_obj = PWN::SDR::SonMicroRFID.connect(
48
48
  block_dev: block_dev,
49
49
  baud: baud,
50
50
  data_bits: data_bits,
@@ -60,7 +60,7 @@ begin
60
60
  puts "Signals: #{son_micro_rfid_obj[:serial_conn].signals}"
61
61
  end
62
62
 
63
- exec_resp = PWN::Plugins::SonMicroRFID.exec(
63
+ exec_resp = PWN::SDR::SonMicroRFID.exec(
64
64
  son_micro_rfid_obj: son_micro_rfid_obj,
65
65
  cmd: :firmware
66
66
  )
@@ -76,7 +76,7 @@ begin
76
76
  menu_msg = ''
77
77
  loop do
78
78
  unless menu_msg.include?('ERROR')
79
- exec_resp = PWN::Plugins::SonMicroRFID.exec(
79
+ exec_resp = PWN::SDR::SonMicroRFID.exec(
80
80
  son_micro_rfid_obj: son_micro_rfid_obj,
81
81
  cmd: :antenna_power,
82
82
  params: :on
@@ -100,7 +100,7 @@ begin
100
100
  when :R
101
101
  menu_msg = 'READ TAG'
102
102
  begin
103
- rfid_data = PWN::Plugins::SonMicroRFID.read_tag(son_micro_rfid_obj: son_micro_rfid_obj)
103
+ rfid_data = PWN::SDR::SonMicroRFID.read_tag(son_micro_rfid_obj: son_micro_rfid_obj)
104
104
  puts "\nRFID Data:"
105
105
  puts "Tag ID: #{rfid_data[:tag_id]}"
106
106
  puts "Block Data: #{rfid_data[:block_data]}" if rfid_data[:block_data]
@@ -110,7 +110,7 @@ begin
110
110
  when :B
111
111
  menu_msg = 'BACKUP TAG TO FILE'
112
112
  begin
113
- rfid_data = PWN::Plugins::SonMicroRFID.backup_tag(son_micro_rfid_obj: son_micro_rfid_obj)
113
+ rfid_data = PWN::SDR::SonMicroRFID.backup_tag(son_micro_rfid_obj: son_micro_rfid_obj)
114
114
  puts "\nBackup successful. RFID Data:"
115
115
  puts "Tag ID: #{rfid_data[:tag_id]}"
116
116
  puts "Block Data: #{rfid_data[:block_data]}" if rfid_data[:block_data]
@@ -123,7 +123,7 @@ begin
123
123
  next unless gets.chomp.strip.upcase == 'Y'
124
124
 
125
125
  begin
126
- rfid_data = PWN::Plugins::SonMicroRFID.clone_tag(son_micro_rfid_obj: son_micro_rfid_obj)
126
+ rfid_data = PWN::SDR::SonMicroRFID.clone_tag(son_micro_rfid_obj: son_micro_rfid_obj)
127
127
  puts "\nClone successful. RFID Data:"
128
128
  puts "Tag ID: #{rfid_data[:tag_id]}"
129
129
  puts "Block Data: #{rfid_data[:block_data]}" if rfid_data[:block_data]
@@ -136,7 +136,7 @@ begin
136
136
  next unless gets.chomp.strip.upcase == 'Y'
137
137
 
138
138
  begin
139
- rfid_data = PWN::Plugins::SonMicroRFID.load_tag_from_file(son_micro_rfid_obj: son_micro_rfid_obj)
139
+ rfid_data = PWN::SDR::SonMicroRFID.load_tag_from_file(son_micro_rfid_obj: son_micro_rfid_obj)
140
140
  puts "\nLoad successful. RFID Data:"
141
141
  puts "Tag ID: #{rfid_data[:tag_id]}"
142
142
  puts "Block Data: #{rfid_data[:block_data]}" if rfid_data[:block_data]
@@ -149,7 +149,7 @@ begin
149
149
  next unless gets.chomp.strip.upcase == 'Y'
150
150
 
151
151
  begin
152
- rfid_data = PWN::Plugins::SonMicroRFID.update_tag(son_micro_rfid_obj: son_micro_rfid_obj)
152
+ rfid_data = PWN::SDR::SonMicroRFID.update_tag(son_micro_rfid_obj: son_micro_rfid_obj)
153
153
  puts "\nUpdate successful. RFID Data:"
154
154
  puts "Tag ID: #{rfid_data[:tag_id]}"
155
155
  puts "Block Data: #{rfid_data[:block_data]}" if rfid_data[:block_data]
@@ -159,7 +159,7 @@ begin
159
159
  when :W
160
160
  menu_msg = 'WARM RESET'
161
161
  begin
162
- exec_resp = PWN::Plugins::SonMicroRFID.exec(
162
+ exec_resp = PWN::SDR::SonMicroRFID.exec(
163
163
  son_micro_rfid_obj: son_micro_rfid_obj,
164
164
  cmd: :reset
165
165
  )
@@ -171,7 +171,7 @@ begin
171
171
  exit
172
172
  else
173
173
  menu_msg = '****** ERROR: Invalid Menu Option Selected ******'
174
- exec_resp = PWN::Plugins::SonMicroRFID.exec(
174
+ exec_resp = PWN::SDR::SonMicroRFID.exec(
175
175
  son_micro_rfid_obj: son_micro_rfid_obj,
176
176
  cmd: :antenna_power,
177
177
  params: :off
@@ -184,5 +184,5 @@ rescue StandardError => e
184
184
  rescue SystemExit, Interrupt
185
185
  puts "\nGoodbye."
186
186
  ensure
187
- PWN::Plugins::SonMicroRFID.disconnect(son_micro_rfid_obj: son_micro_rfid_obj) if son_micro_rfid_obj
187
+ PWN::SDR::SonMicroRFID.disconnect(son_micro_rfid_obj: son_micro_rfid_obj) if son_micro_rfid_obj
188
188
  end
data/lib/pwn/ai.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PWN
4
- # This file, using the autoload directive loads SP plugins
4
+ # This file, using the autoload directive loads AI modules
5
5
  # into memory only when they're needed. For more information, see:
6
6
  # http://www.rubyinside.com/ruby-techniques-revealed-autoload-1652.html
7
7
  module AI
data/lib/pwn/aws.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PWN
4
- # This file, using the autoload directive loads SP aws
4
+ # This file, using the autoload directive loads AWS modules
5
5
  # into memory only when they're needed. For more information, see:
6
6
  # http://www.rubyinside.com/ruby-techniques-revealed-autoload-1652.html
7
7
  module AWS
data/lib/pwn/banner.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PWN
4
- # This file, using the autoload directive loads SP reports
4
+ # This file, using the autoload directive loads Banner modules
5
5
  # into memory only when they're needed. For more information, see:
6
6
  # http://www.rubyinside.com/ruby-techniques-revealed-autoload-1652.html
7
7
  module Banner
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PWN
4
- # This file, using the autoload directive loads SP plugins
4
+ # This file, using the autoload directive loads Blockchain modules
5
5
  # into memory only when they're needed. For more information, see:
6
6
  # http://www.rubyinside.com/ruby-techniques-revealed-autoload-1652.html
7
7
  module Blockchain
data/lib/pwn/ffi.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PWN
4
- # This file, using the autoload directive loads SP reports
4
+ # This file, using the autoload directive loads FFI modules
5
5
  # into memory only when they're needed. For more information, see:
6
6
  # http://www.rubyinside.com/ruby-techniques-revealed-autoload-1652.html
7
7
  module FFI
@@ -265,13 +265,13 @@ module PWN
265
265
  end
266
266
  end
267
267
  rescue Errno::ECONNREFUSED
268
- puts 'BurpSuite AI Introspection Thread >>> Terminating API Calls...'
268
+ puts "BurpSuite:#{type} AI Introspection Thread >>> Terminating API Calls..."
269
269
  rescue StandardError => e
270
270
  puts "BurpSuite AI Introspection Thread Error: #{e}"
271
271
  puts e.backtrace
272
272
  raise e
273
273
  ensure
274
- puts 'BurpSuite AI Introspection Thread >>> Goodbye.'
274
+ puts "BurpSuite:#{type} AI Introspection Thread >>> Goodbye."
275
275
  end
276
276
 
277
277
  burp_obj[:introspection_threads] = introspection_thread_arr.push(introspection_thread)
data/lib/pwn/plugins.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PWN
4
- # This file, using the autoload directive loads SP plugins
4
+ # This file, using the autoload directive loads Plugins modules
5
5
  # into memory only when they're needed. For more information, see:
6
6
  # http://www.rubyinside.com/ruby-techniques-revealed-autoload-1652.html
7
7
  module Plugins
@@ -25,11 +25,9 @@ module PWN
25
25
  autoload :DetectOS, 'pwn/plugins/detect_os'
26
26
  autoload :EIN, 'pwn/plugins/ein'
27
27
  autoload :FileFu, 'pwn/plugins/file_fu'
28
- autoload :FlipperZero, 'pwn/plugins/flipper_zero'
29
28
  autoload :Fuzz, 'pwn/plugins/fuzz'
30
29
  autoload :Git, 'pwn/plugins/git'
31
30
  autoload :Github, 'pwn/plugins/github'
32
- autoload :GQRX, 'pwn/plugins/gqrx'
33
31
  autoload :HackerOne, 'pwn/plugins/hacker_one'
34
32
  autoload :Hunter, 'pwn/plugins/hunter'
35
33
  autoload :IPInfo, 'pwn/plugins/ip_info'
@@ -47,8 +45,6 @@ module PWN
47
45
  autoload :NmapIt, 'pwn/plugins/nmap_it'
48
46
  autoload :OAuth2, 'pwn/plugins/oauth2'
49
47
  autoload :OCR, 'pwn/plugins/ocr'
50
- autoload :Ollama, 'pwn/plugins/ollama'
51
- autoload :OpenAI, 'pwn/plugins/open_ai'
52
48
  autoload :OpenAPI, 'pwn/plugins/open_api'
53
49
  autoload :OpenVAS, 'pwn/plugins/openvas'
54
50
  autoload :Packet, 'pwn/plugins/packet'
@@ -57,13 +53,11 @@ module PWN
57
53
  autoload :PS, 'pwn/plugins/ps'
58
54
  autoload :RabbitMQ, 'pwn/plugins/rabbit_mq'
59
55
  autoload :REPL, 'pwn/plugins/repl'
60
- autoload :RFIDler, 'pwn/plugins/rfidler'
61
56
  autoload :ScannableCodes, 'pwn/plugins/scannable_codes'
62
57
  autoload :Serial, 'pwn/plugins/serial'
63
58
  autoload :Shodan, 'pwn/plugins/shodan'
64
59
  autoload :SlackClient, 'pwn/plugins/slack_client'
65
60
  autoload :Sock, 'pwn/plugins/sock'
66
- autoload :SonMicroRFID, 'pwn/plugins/son_micro_rfid'
67
61
  autoload :Spider, 'pwn/plugins/spider'
68
62
  autoload :SSN, 'pwn/plugins/ssn'
69
63
  autoload :ThreadPool, 'pwn/plugins/thread_pool'
data/lib/pwn/reports.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PWN
4
- # This file, using the autoload directive loads SP reports
4
+ # This file, using the autoload directive loads Report modules
5
5
  # into memory only when they're needed. For more information, see:
6
6
  # http://www.rubyinside.com/ruby-techniques-revealed-autoload-1652.html
7
7
  module Reports
data/lib/pwn/sast.rb CHANGED
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PWN
4
- # This file, using the autoload directive loads SP static code analysis
5
- # modules into memory only when they're needed. For more information, see:
4
+ # This file, using the autoload directive loads SAST modules
5
+ # into memory only when they're needed. For more information, see:
6
6
  # http://www.rubyinside.com/ruby-techniques-revealed-autoload-1652.html
7
7
  module SAST
8
8
  # Zero False Negative SAST Modules
@@ -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