pwn 0.5.256 → 0.5.257
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/Gemfile +1 -1
- data/README.md +3 -3
- data/bin/pwn_serial_msr206 +17 -17
- data/lib/pwn/plugins/msr206.rb +605 -540
- data/lib/pwn/version.rb +1 -1
- metadata +3 -3
data/lib/pwn/plugins/msr206.rb
CHANGED
@@ -1,14 +1,22 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'logger'
|
4
|
+
require 'timeout'
|
5
|
+
|
3
6
|
module PWN
|
4
7
|
module Plugins
|
5
8
|
# This plugin is used for interacting with a three track
|
6
9
|
# MSR206 Magnetic Stripe Reader / Writer
|
7
10
|
module MSR206
|
11
|
+
# Logger instance for auditing and debugging
|
12
|
+
private_class_method def self.logger
|
13
|
+
@logger ||= Logger.new('msr206.log')
|
14
|
+
end
|
15
|
+
|
8
16
|
# Supported Method Parameters::
|
9
17
|
# msr206_obj = PWN::Plugins::MSR206.connect(
|
10
18
|
# block_dev: 'optional - serial block device path (defaults to /dev/ttyUSB0)',
|
11
|
-
# baud: 'optional - (defaults to
|
19
|
+
# baud: 'optional - (defaults to 19200)',
|
12
20
|
# data_bits: 'optional - (defaults to 8)',
|
13
21
|
# stop_bits: 'optional - (defaults to 1)',
|
14
22
|
# parity: 'optional - :even|:mark|:odd|:space|:none (defaults to :none),'
|
@@ -16,24 +24,27 @@ module PWN
|
|
16
24
|
# )
|
17
25
|
|
18
26
|
public_class_method def self.connect(opts = {})
|
19
|
-
|
20
|
-
opts[:
|
21
|
-
opts[:
|
22
|
-
opts[:
|
23
|
-
opts[:
|
24
|
-
opts[:
|
25
|
-
|
27
|
+
opts[:block_dev] ||= '/dev/ttyUSB0'
|
28
|
+
opts[:baud] ||= 19_200 # Align with device default
|
29
|
+
opts[:data_bits] ||= 8
|
30
|
+
opts[:stop_bits] ||= 1
|
31
|
+
opts[:parity] ||= :none
|
32
|
+
opts[:flow_control] ||= :soft
|
33
|
+
|
34
|
+
logger.info("Connecting to #{opts[:block_dev]} at baud #{opts[:baud]}")
|
26
35
|
msr206_obj = PWN::Plugins::Serial.connect(opts)
|
36
|
+
set_protocol(msr206_obj: msr206_obj, protocol: :usi0) # Default to USI0
|
37
|
+
msr206_obj
|
27
38
|
rescue StandardError => e
|
39
|
+
logger.error("Connection failed: #{e.message}")
|
28
40
|
disconnect(msr206_obj: msr206_obj) unless msr206_obj.nil?
|
29
41
|
raise e
|
30
42
|
end
|
31
43
|
|
32
44
|
# Supported Method Parameters::
|
33
|
-
#
|
45
|
+
# cmds = PWN::Plugins::MSR206.list_cmds
|
34
46
|
public_class_method def self.list_cmds
|
35
|
-
|
36
|
-
cmds = %i[
|
47
|
+
%i[
|
37
48
|
proto_usi0
|
38
49
|
proto_usi1
|
39
50
|
version_report
|
@@ -92,6 +103,7 @@ module PWN
|
|
92
103
|
arm_to_write_with_raw_speed_prompts
|
93
104
|
]
|
94
105
|
rescue StandardError => e
|
106
|
+
logger.error("Error listing commands: #{e.message}")
|
95
107
|
raise e
|
96
108
|
end
|
97
109
|
|
@@ -102,212 +114,216 @@ module PWN
|
|
102
114
|
|
103
115
|
private_class_method def self.decode(opts = {})
|
104
116
|
raw_byte_arr = opts[:raw_byte_arr]
|
105
|
-
|
117
|
+
parity = opts[:parity] || :none
|
118
|
+
parity_not_none = %i[odd even]
|
106
119
|
decoded_data_str = ''
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
120
|
+
return decoded_data_str unless raw_byte_arr
|
121
|
+
|
122
|
+
raw_byte_arr.first.split.each do |byte_str|
|
123
|
+
byte = byte_str.to_i(16)
|
124
|
+
# Strip parity bit for odd/even parity
|
125
|
+
byte &= 0x7F if parity_not_none.include?(parity)
|
126
|
+
|
127
|
+
case byte_str
|
128
|
+
when '1B'
|
129
|
+
decoded_data_str += "\e" # ESC character
|
130
|
+
when '20'
|
131
|
+
decoded_data_str += ' '
|
132
|
+
when '21'
|
133
|
+
decoded_data_str += '!'
|
134
|
+
when '22'
|
135
|
+
decoded_data_str += '"'
|
136
|
+
when '23'
|
137
|
+
decoded_data_str += '#'
|
138
|
+
when '24'
|
139
|
+
decoded_data_str += '$'
|
140
|
+
when '25'
|
141
|
+
decoded_data_str += '%'
|
142
|
+
when '26'
|
143
|
+
decoded_data_str += '&'
|
144
|
+
when '27'
|
145
|
+
decoded_data_str += "'"
|
146
|
+
when '28'
|
147
|
+
decoded_data_str += '('
|
148
|
+
when '29'
|
149
|
+
decoded_data_str += ')'
|
150
|
+
when '2A', 'AA'
|
151
|
+
decoded_data_str += '*'
|
152
|
+
when '2B', 'AB'
|
153
|
+
decoded_data_str += '+'
|
154
|
+
when '2C', 'AC'
|
155
|
+
decoded_data_str += ','
|
156
|
+
when '2D', 'AD'
|
157
|
+
decoded_data_str += '-'
|
158
|
+
when '2E', 'AE'
|
159
|
+
decoded_data_str += '.'
|
160
|
+
when '2F', 'AF'
|
161
|
+
decoded_data_str += '/'
|
162
|
+
when '30', 'B0'
|
163
|
+
decoded_data_str += '0'
|
164
|
+
when '31', 'B1'
|
165
|
+
decoded_data_str += '1'
|
166
|
+
when '32', 'B2'
|
167
|
+
decoded_data_str += '2'
|
168
|
+
when '33', 'B3'
|
169
|
+
decoded_data_str += '3'
|
170
|
+
when '34', 'B4'
|
171
|
+
decoded_data_str += '4'
|
172
|
+
when '35', 'B5'
|
173
|
+
decoded_data_str += '5'
|
174
|
+
when '36', 'B6'
|
175
|
+
decoded_data_str += '6'
|
176
|
+
when '37', 'B7'
|
177
|
+
decoded_data_str += '7'
|
178
|
+
when '38', 'B8'
|
179
|
+
decoded_data_str += '8'
|
180
|
+
when '39', 'B9'
|
181
|
+
decoded_data_str += '9'
|
182
|
+
when '3A', 'BA'
|
183
|
+
decoded_data_str += ':'
|
184
|
+
when '3B', 'BB'
|
185
|
+
decoded_data_str += ';'
|
186
|
+
when '3C', 'BC'
|
187
|
+
decoded_data_str += '<'
|
188
|
+
when '3D', 'BD'
|
189
|
+
decoded_data_str += '='
|
190
|
+
when '3E', 'BE'
|
191
|
+
decoded_data_str += '>'
|
192
|
+
when '3F', 'BF'
|
193
|
+
decoded_data_str += '?'
|
194
|
+
when '40', 'C0'
|
195
|
+
decoded_data_str += '@'
|
196
|
+
when '41', 'C1'
|
197
|
+
decoded_data_str += 'A'
|
198
|
+
when '42', 'C2'
|
199
|
+
decoded_data_str += 'B'
|
200
|
+
when '43', 'C3'
|
201
|
+
decoded_data_str += 'C'
|
202
|
+
when '44', 'C4'
|
203
|
+
decoded_data_str += 'D'
|
204
|
+
when '45', 'C5'
|
205
|
+
decoded_data_str += 'E'
|
206
|
+
when '46', 'C6'
|
207
|
+
decoded_data_str += 'F'
|
208
|
+
when '47', 'C7'
|
209
|
+
decoded_data_str += 'G'
|
210
|
+
when '48', 'C8'
|
211
|
+
decoded_data_str += 'H'
|
212
|
+
when '49', 'C9'
|
213
|
+
decoded_data_str += 'I'
|
214
|
+
when '4A', 'CA'
|
215
|
+
decoded_data_str += 'J'
|
216
|
+
when '4B', 'CB'
|
217
|
+
decoded_data_str += 'K'
|
218
|
+
when '4C', 'CC'
|
219
|
+
decoded_data_str += 'L'
|
220
|
+
when '4D', 'CD'
|
221
|
+
decoded_data_str += 'M'
|
222
|
+
when '4E', 'CE'
|
223
|
+
decoded_data_str += 'N'
|
224
|
+
when '4F', 'CF'
|
225
|
+
decoded_data_str += 'O'
|
226
|
+
when '50', 'D0'
|
227
|
+
decoded_data_str += 'P'
|
228
|
+
when '51', 'D1'
|
229
|
+
decoded_data_str += 'Q'
|
230
|
+
when '52', 'D2'
|
231
|
+
decoded_data_str += 'R'
|
232
|
+
when '53', 'D3'
|
233
|
+
decoded_data_str += 'S'
|
234
|
+
when '54', 'D4'
|
235
|
+
decoded_data_str += 'T'
|
236
|
+
when '55', 'D5'
|
237
|
+
decoded_data_str += 'U'
|
238
|
+
when '56', 'D6'
|
239
|
+
decoded_data_str += 'V'
|
240
|
+
when '57', 'D7'
|
241
|
+
decoded_data_str += 'W'
|
242
|
+
when '58', 'D8'
|
243
|
+
decoded_data_str += 'X'
|
244
|
+
when '59', 'D9'
|
245
|
+
decoded_data_str += 'Y'
|
246
|
+
when '5A', 'DA'
|
247
|
+
decoded_data_str += 'Z'
|
248
|
+
when '5B', 'DB'
|
249
|
+
decoded_data_str += '['
|
250
|
+
when '5C', 'DC'
|
251
|
+
decoded_data_str += '\\'
|
252
|
+
when '5D', 'DD'
|
253
|
+
decoded_data_str += ']'
|
254
|
+
when '5E', 'DE'
|
255
|
+
decoded_data_str += '^'
|
256
|
+
when '5F', 'DF'
|
257
|
+
decoded_data_str += '_'
|
258
|
+
when '60', 'E0'
|
259
|
+
decoded_data_str += '`'
|
260
|
+
when '61', 'E1'
|
261
|
+
decoded_data_str += 'a'
|
262
|
+
when '62', 'E2'
|
263
|
+
decoded_data_str += 'b'
|
264
|
+
when '63', 'E3'
|
265
|
+
decoded_data_str += 'c'
|
266
|
+
when '64', 'E4'
|
267
|
+
decoded_data_str += 'd'
|
268
|
+
when '65', 'E5'
|
269
|
+
decoded_data_str += 'e'
|
270
|
+
when '66', 'E6'
|
271
|
+
decoded_data_str += 'f'
|
272
|
+
when '67', 'E7'
|
273
|
+
decoded_data_str += 'g'
|
274
|
+
when '68', 'E8'
|
275
|
+
decoded_data_str += 'h'
|
276
|
+
when '69', 'E9'
|
277
|
+
decoded_data_str += 'i'
|
278
|
+
when '6A', 'EA'
|
279
|
+
decoded_data_str += 'j'
|
280
|
+
when '6B', 'EB'
|
281
|
+
decoded_data_str += 'k'
|
282
|
+
when '6C', 'EC'
|
283
|
+
decoded_data_str += 'l'
|
284
|
+
when '6D', 'ED'
|
285
|
+
decoded_data_str += 'm'
|
286
|
+
when '6E', 'EE'
|
287
|
+
decoded_data_str += 'n'
|
288
|
+
when '6F', 'EF'
|
289
|
+
decoded_data_str += 'o'
|
290
|
+
when '70', 'F0'
|
291
|
+
decoded_data_str += 'p'
|
292
|
+
when '71', 'F1'
|
293
|
+
decoded_data_str += 'q'
|
294
|
+
when '72', 'F2'
|
295
|
+
decoded_data_str += 'r'
|
296
|
+
when '73', 'F3'
|
297
|
+
decoded_data_str += 's'
|
298
|
+
when '74', 'F4'
|
299
|
+
decoded_data_str += 't'
|
300
|
+
when '75', 'F5'
|
301
|
+
decoded_data_str += 'u'
|
302
|
+
when '76', 'F6'
|
303
|
+
decoded_data_str += 'v'
|
304
|
+
when '77', 'F7'
|
305
|
+
decoded_data_str += 'w'
|
306
|
+
when '78', 'F8'
|
307
|
+
decoded_data_str += 'x'
|
308
|
+
when '79', 'F9'
|
309
|
+
decoded_data_str += 'y'
|
310
|
+
when '7A', 'FA'
|
311
|
+
decoded_data_str += 'z'
|
312
|
+
when '7B', 'FB'
|
313
|
+
decoded_data_str += '{'
|
314
|
+
when '7C', 'FC'
|
315
|
+
decoded_data_str += '|'
|
316
|
+
when '7D', 'FD'
|
317
|
+
decoded_data_str += '}'
|
318
|
+
when '7E', 'FE'
|
319
|
+
decoded_data_str += '~'
|
320
|
+
else
|
321
|
+
decoded_data_str += "\u00BF" # Unknown character
|
306
322
|
end
|
307
323
|
end
|
308
|
-
|
309
324
|
decoded_data_str
|
310
325
|
rescue StandardError => e
|
326
|
+
logger.error("Error decoding response: #{e.message}")
|
311
327
|
raise e
|
312
328
|
end
|
313
329
|
|
@@ -318,16 +334,15 @@ module PWN
|
|
318
334
|
|
319
335
|
private_class_method def self.binary(opts = {})
|
320
336
|
raw_byte_arr = opts[:raw_byte_arr]
|
321
|
-
|
322
337
|
binary_byte_arr = []
|
323
338
|
if raw_byte_arr
|
324
339
|
raw_byte_arr.first.split.each do |byte_str|
|
325
340
|
binary_byte_arr.push([byte_str].pack('H*').unpack1('B*'))
|
326
341
|
end
|
327
342
|
end
|
328
|
-
|
329
343
|
binary_byte_arr
|
330
344
|
rescue StandardError => e
|
345
|
+
logger.error("Error converting to binary: #{e.message}")
|
331
346
|
raise e
|
332
347
|
end
|
333
348
|
|
@@ -341,99 +356,114 @@ module PWN
|
|
341
356
|
cmd = opts[:cmd]
|
342
357
|
cmd_bytes = opts[:cmd_bytes]
|
343
358
|
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
359
|
+
Timeout.timeout(5) do # 5-second timeout
|
360
|
+
keep_parsing_responses = true
|
361
|
+
next_response_detected = false
|
362
|
+
response = {}
|
363
|
+
response[:cmd] = cmd || :na
|
364
|
+
response[:cmd_bytes] = cmd_bytes&.map { |b| "0x#{b.to_s(16).rjust(2, '0')}" } || :na
|
365
|
+
|
366
|
+
raw_byte_arr = []
|
367
|
+
raw_byte_arr_len = 0
|
368
|
+
last_raw_byte_arr_len = 0
|
369
|
+
cmd_resp = ''
|
370
|
+
|
371
|
+
while keep_parsing_responses
|
372
|
+
until next_response_detected
|
373
|
+
last_raw_byte_arr_len = raw_byte_arr_len
|
374
|
+
raw_byte_arr = PWN::Plugins::Serial.response(serial_obj: msr206_obj)
|
375
|
+
cmd_resp = raw_byte_arr.last
|
376
|
+
raw_byte_arr_len = raw_byte_arr.length
|
377
|
+
next_response_detected = true if raw_byte_arr_len > last_raw_byte_arr_len
|
378
|
+
end
|
360
379
|
|
361
|
-
|
362
|
-
|
380
|
+
case cmd_resp
|
381
|
+
when '21', 'A1'
|
382
|
+
response[:msg] = :invalid_command
|
383
|
+
when '28', 'A8'
|
384
|
+
response[:msg] = :card_speed_measurement_start
|
385
|
+
when '29', 'A9'
|
386
|
+
response[:msg] = :card_speed_measurement_end
|
387
|
+
when '2A', 'AA'
|
388
|
+
response[:msg] = :error
|
389
|
+
when '2B', 'AB'
|
390
|
+
response[:msg] = :no_data_found
|
391
|
+
when '2D', 'AD'
|
392
|
+
response[:msg] = :insufficient_leading_zeros_for_custom_writing
|
393
|
+
when '2F', 'AF'
|
394
|
+
response[:msg] = :first_lsb_char_not_one_for_custom_writing
|
395
|
+
when '31', 'B1'
|
396
|
+
response[:msg] = :unsuccessful_read_after_write_track1
|
397
|
+
when '32', 'B2'
|
398
|
+
response[:msg] = :unsuccessful_read_after_write_track2
|
399
|
+
when '33', 'B3'
|
400
|
+
response[:msg] = :unsuccessful_read_after_write_track3
|
401
|
+
when '3A', 'BA'
|
402
|
+
response[:msg] = :power_on_report
|
403
|
+
when '3E', 'BE'
|
404
|
+
response[:msg] = :card_edge_detected
|
405
|
+
when '3F', 'BF'
|
406
|
+
response[:msg] = :communications_error
|
407
|
+
when '5E'
|
408
|
+
response[:msg] = :ack_command_completed
|
409
|
+
when '7E'
|
410
|
+
response[:msg] = :command_not_supported_by_hardware
|
411
|
+
else
|
412
|
+
response[:msg] = :response
|
413
|
+
end
|
363
414
|
|
364
|
-
|
365
|
-
until next_response_detected
|
415
|
+
next_response_detected = false
|
366
416
|
last_raw_byte_arr_len = raw_byte_arr_len
|
367
|
-
|
368
|
-
cmd_resp = raw_byte_arr.last
|
369
|
-
raw_byte_arr_len = raw_byte_arr.length
|
370
|
-
|
371
|
-
next_response_detected = true if raw_byte_arr_len > last_raw_byte_arr_len
|
417
|
+
keep_parsing_responses = false
|
372
418
|
end
|
373
419
|
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
response[:msg] = :card_speed_measurement_start
|
379
|
-
when '29', 'A9'
|
380
|
-
response[:msg] = :card_speed_measurement_end
|
381
|
-
when '2A', 'AA'
|
382
|
-
response[:msg] = :error
|
383
|
-
when '2B', 'AB'
|
384
|
-
response[:msg] = :no_data_found
|
385
|
-
when '2D', 'AD'
|
386
|
-
response[:msg] = :insufficient_leading_zeros_for_custom_writing
|
387
|
-
when '2F', 'AF'
|
388
|
-
response[:msg] = :first_lsb_char_not_one_for_custom_writing
|
389
|
-
when '31', 'B1'
|
390
|
-
response[:msg] = :unsuccessful_read_after_write_track1
|
391
|
-
when '32', 'B2'
|
392
|
-
response[:msg] = :unsuccessful_read_after_write_track2
|
393
|
-
when '33', 'B3'
|
394
|
-
response[:msg] = :unsuccessful_read_after_write_track3
|
395
|
-
when '3A', 'BA'
|
396
|
-
response[:msg] = :power_on_report
|
397
|
-
when '3E', 'BE'
|
398
|
-
response[:msg] = :card_edge_detected
|
399
|
-
when '3F', 'BF'
|
400
|
-
response[:msg] = :communications_error
|
401
|
-
when '5E'
|
402
|
-
response[:msg] = :ack_command_completed
|
403
|
-
when '7E'
|
404
|
-
response[:msg] = :command_not_supported_by_hardware
|
405
|
-
else
|
406
|
-
response[:msg] = :response
|
407
|
-
end
|
408
|
-
|
409
|
-
next_response_detected = false
|
410
|
-
last_raw_byte_arr_len = raw_byte_arr_len
|
411
|
-
keep_parsing_responses = false
|
420
|
+
response[:hex] = raw_byte_arr
|
421
|
+
response[:binary] = binary(raw_byte_arr: raw_byte_arr)
|
422
|
+
response[:decoded] = decode(raw_byte_arr: raw_byte_arr)
|
423
|
+
response
|
412
424
|
end
|
413
|
-
|
414
|
-
response
|
415
|
-
|
416
|
-
response[:decoded] = decode(raw_byte_arr: raw_byte_arr)
|
417
|
-
response
|
425
|
+
rescue Timeout::Error
|
426
|
+
logger.error("Device response timed out for command: #{cmd}")
|
427
|
+
raise 'ERROR: Device response timed out'
|
418
428
|
rescue StandardError => e
|
429
|
+
logger.error("Error parsing response for command #{cmd}: #{e.message}")
|
419
430
|
raise e
|
420
431
|
ensure
|
421
|
-
# Flush Responses for Next Request
|
422
432
|
PWN::Plugins::Serial.flush_session_data
|
423
433
|
end
|
424
434
|
|
425
435
|
# Supported Method Parameters::
|
426
|
-
#
|
436
|
+
# PWN::Plugins::MSR206.set_protocol(
|
437
|
+
# msr206_obj: 'required - msr206_obj returned from #connect method',
|
438
|
+
# protocol: 'optional - :usi0 or :usi1 (defaults to :usi0)'
|
439
|
+
# )
|
440
|
+
|
441
|
+
public_class_method def self.set_protocol(opts = {})
|
442
|
+
msr206_obj = opts[:msr206_obj]
|
443
|
+
protocol = opts[:protocol] || :usi0
|
444
|
+
cmd = protocol == :usi0 ? :proto_usi0 : :proto_usi1
|
445
|
+
logger.info("Setting protocol to #{protocol}")
|
446
|
+
exec(msr206_obj: msr206_obj, cmd: cmd)
|
447
|
+
rescue StandardError => e
|
448
|
+
logger.error("Error setting protocol: #{e.message}")
|
449
|
+
raise e
|
450
|
+
end
|
451
|
+
|
452
|
+
# Supported Method Parameters::
|
453
|
+
# PWN::Plugins::MSR206.exec(
|
427
454
|
# msr206_obj: 'required - msr206_obj returned from #connect method'
|
428
455
|
# cmd: 'required - cmd returned from #list_cmds method',
|
429
456
|
# params: 'optional - parameters for specific command returned from #list_params method'
|
430
457
|
# )
|
458
|
+
|
431
459
|
public_class_method def self.exec(opts = {})
|
432
460
|
msr206_obj = opts[:msr206_obj]
|
433
461
|
cmd = opts[:cmd].to_s.scrub.strip.chomp
|
434
462
|
params = opts[:params]
|
435
463
|
raise 'ERROR: params argument must be a byte array (e.g. [0x41]).' if params && !params.instance_of?(Array)
|
436
464
|
|
465
|
+
logger.info("Executing command: #{cmd} with params: #{params.inspect}")
|
466
|
+
|
437
467
|
params_bytes = []
|
438
468
|
case cmd.to_sym
|
439
469
|
when :proto_usi0
|
@@ -549,28 +579,27 @@ module PWN
|
|
549
579
|
when :simulate_power_cycle_warm_reset
|
550
580
|
cmd_bytes = [0x7F]
|
551
581
|
else
|
552
|
-
|
582
|
+
logger.error("Unsupported command: #{cmd}")
|
583
|
+
raise "Unsupported Command: #{cmd}. Supported commands are:\n#{list_cmds.join("\n")}\n"
|
553
584
|
end
|
554
585
|
|
555
|
-
# If parameters to a command are set, append them.
|
556
586
|
cmd_bytes += params if params
|
557
|
-
# Execute the command.
|
558
587
|
PWN::Plugins::Serial.request(
|
559
588
|
serial_obj: msr206_obj,
|
560
589
|
payload: cmd_bytes
|
561
590
|
)
|
562
591
|
|
563
|
-
|
564
|
-
# Return an array of hashes.
|
565
|
-
parse_responses(
|
592
|
+
response = parse_responses(
|
566
593
|
msr206_obj: msr206_obj,
|
567
594
|
cmd: cmd.to_sym,
|
568
595
|
cmd_bytes: cmd_bytes
|
569
596
|
)
|
597
|
+
logger.info("Response for #{cmd}: #{response.inspect}")
|
598
|
+
response
|
570
599
|
rescue StandardError => e
|
600
|
+
logger.error("Error executing command #{cmd}: #{e.message}")
|
571
601
|
raise e
|
572
602
|
ensure
|
573
|
-
# Flush Responses for Next Request
|
574
603
|
PWN::Plugins::Serial.flush_session_data
|
575
604
|
end
|
576
605
|
|
@@ -588,45 +617,35 @@ module PWN
|
|
588
617
|
encoding = opts[:encoding].to_s.scrub.strip.chomp.to_sym
|
589
618
|
track_data = opts[:track_data]
|
590
619
|
|
591
|
-
|
592
|
-
msr206_obj: msr206_obj,
|
593
|
-
cmd: :red_off
|
594
|
-
)
|
620
|
+
logger.info("Waiting for swipe: type=#{type}, encoding=#{encoding}")
|
595
621
|
|
596
|
-
exec_resp = exec(
|
597
|
-
|
598
|
-
|
599
|
-
)
|
600
|
-
|
601
|
-
exec_resp = exec(
|
602
|
-
msr206_obj: msr206_obj,
|
603
|
-
cmd: :green_on
|
604
|
-
)
|
622
|
+
exec_resp = exec(msr206_obj: msr206_obj, cmd: :red_off)
|
623
|
+
exec_resp = exec(msr206_obj: msr206_obj, cmd: :yellow_off)
|
624
|
+
exec_resp = exec(msr206_obj: msr206_obj, cmd: :green_on)
|
605
625
|
|
606
626
|
track_data_arr = []
|
607
627
|
|
608
|
-
|
609
|
-
|
610
|
-
|
628
|
+
# Check for card edge detection
|
629
|
+
exec_resp = exec(msr206_obj: msr206_obj, cmd: :card_edge_detect)
|
630
|
+
unless exec_resp[:msg] == :card_edge_detected
|
631
|
+
logger.warn('Card not detected')
|
632
|
+
puts 'WARNING: Card not detected. Please ensure card is ready.'
|
633
|
+
end
|
611
634
|
|
612
|
-
|
613
|
-
|
614
|
-
|
615
|
-
)
|
616
|
-
puts exec_resp.inspect
|
635
|
+
case type
|
636
|
+
when :arm_to_read, :arm_to_read_w_speed_prompts
|
637
|
+
exec_resp = exec(msr206_obj: msr206_obj, cmd: type)
|
638
|
+
logger.info("Arm to read response: #{exec_resp.inspect}")
|
617
639
|
|
618
|
-
print 'Reader Activated.
|
640
|
+
print 'Reader Activated. Please Swipe Card...'
|
619
641
|
loop do
|
620
|
-
exec_resp = parse_responses(
|
621
|
-
msr206_obj: msr206_obj,
|
622
|
-
cmd: type
|
623
|
-
)
|
624
|
-
|
642
|
+
exec_resp = parse_responses(msr206_obj: msr206_obj, cmd: type)
|
625
643
|
puts exec_resp[:msg]
|
626
644
|
break if exec_resp[:msg] == :ack_command_completed
|
627
645
|
end
|
628
646
|
|
629
|
-
|
647
|
+
case encoding
|
648
|
+
when :iso
|
630
649
|
cmds_arr = %i[
|
631
650
|
tx_iso_std_data_track1
|
632
651
|
tx_iso_std_data_track2
|
@@ -634,33 +653,23 @@ module PWN
|
|
634
653
|
]
|
635
654
|
cmds_arr.each do |cmd|
|
636
655
|
puts "\n*** #{cmd.to_s.gsub('_', ' ').upcase} #{'*' * 17}"
|
637
|
-
exec_resp = exec(
|
638
|
-
msr206_obj: msr206_obj,
|
639
|
-
cmd: cmd
|
640
|
-
)
|
656
|
+
exec_resp = exec(msr206_obj: msr206_obj, cmd: cmd)
|
641
657
|
exec_resp[:encoding] = encoding
|
642
658
|
puts exec_resp[:decoded]
|
643
659
|
puts exec_resp.inspect
|
644
660
|
track_data_arr.push(exec_resp)
|
645
661
|
end
|
646
|
-
|
647
|
-
|
648
|
-
if encoding == :iso_alt
|
662
|
+
when :iso_alt
|
649
663
|
cmds_arr = %i[
|
650
664
|
alt_tx_iso_std_data_track1
|
651
665
|
alt_tx_iso_std_data_track2
|
652
666
|
alt_tx_iso_std_data_track3
|
653
667
|
]
|
654
|
-
|
655
668
|
cmds_arr.each do |cmd|
|
656
669
|
params_arr = [0x31, 0x32, 0x33]
|
657
670
|
params_arr.each do |param|
|
658
671
|
puts "\n*** #{cmd.to_s.gsub('_', ' ').upcase} #{'*' * 17}"
|
659
|
-
exec_resp = exec(
|
660
|
-
msr206_obj: msr206_obj,
|
661
|
-
cmd: cmd,
|
662
|
-
params: [param]
|
663
|
-
)
|
672
|
+
exec_resp = exec(msr206_obj: msr206_obj, cmd: cmd, params: [param])
|
664
673
|
exec_resp[:encoding] = encoding
|
665
674
|
exec_resp[:track_format] = [param]
|
666
675
|
puts exec_resp[:decoded]
|
@@ -668,38 +677,25 @@ module PWN
|
|
668
677
|
track_data_arr.push(exec_resp)
|
669
678
|
end
|
670
679
|
end
|
671
|
-
|
672
|
-
|
673
|
-
if encoding == :raw
|
680
|
+
when :raw
|
674
681
|
cmds_arr = %i[
|
675
682
|
tx_custom_data_forward_track1
|
676
683
|
tx_custom_data_forward_track2
|
677
684
|
tx_custom_data_forward_track3
|
678
685
|
]
|
679
|
-
|
680
686
|
cmds_arr.each do |cmd|
|
681
687
|
params_arr = [0x33, 0x34, 0x35, 0x36, 0x37]
|
682
688
|
params_arr.each do |param|
|
683
689
|
puts "\n*** #{cmd.to_s.gsub('_', ' ').upcase} #{'*' * 17}"
|
684
|
-
|
685
|
-
exec_resp = exec(
|
686
|
-
msr206_obj: msr206_obj,
|
687
|
-
cmd: cmd,
|
688
|
-
params: [param]
|
689
|
-
)
|
690
|
+
exec_resp = exec(msr206_obj: msr206_obj, cmd: cmd, params: [param])
|
690
691
|
exec_resp[:encoding] = encoding
|
691
692
|
exec_resp[:track_format] = [param]
|
692
693
|
puts exec_resp[:decoded]
|
693
694
|
puts exec_resp.inspect
|
694
695
|
track_data_arr.push(exec_resp)
|
695
696
|
|
696
|
-
# 3 byte command
|
697
697
|
param = [0x5f] + [param]
|
698
|
-
exec_resp = exec(
|
699
|
-
msr206_obj: msr206_obj,
|
700
|
-
cmd: cmd,
|
701
|
-
params: param
|
702
|
-
)
|
698
|
+
exec_resp = exec(msr206_obj: msr206_obj, cmd: cmd, params: param)
|
703
699
|
exec_resp[:encoding] = encoding
|
704
700
|
exec_resp[:track_format] = param
|
705
701
|
puts exec_resp[:decoded]
|
@@ -707,26 +703,27 @@ module PWN
|
|
707
703
|
track_data_arr.push(exec_resp)
|
708
704
|
end
|
709
705
|
end
|
706
|
+
else
|
707
|
+
logger.error("Unsupported encoding: #{encoding}")
|
708
|
+
raise "Unsupported encoding: #{encoding}"
|
710
709
|
end
|
711
|
-
when :arm_to_write_no_raw,
|
712
|
-
:arm_to_write_with_raw,
|
713
|
-
:arm_to_write_with_raw_speed_prompts
|
714
710
|
|
715
|
-
|
716
|
-
|
711
|
+
when :arm_to_write_no_raw, :arm_to_write_with_raw, :arm_to_write_with_raw_speed_prompts
|
712
|
+
unless track_data.is_a?(Array) && track_data.all? { |t| t.is_a?(Hash) && t.key?(:decoded) }
|
713
|
+
logger.error('Invalid track_data: must be an array of hashes with :decoded key')
|
714
|
+
raise 'Invalid track_data: must be an array of hashes with :decoded key'
|
715
|
+
end
|
717
716
|
|
718
|
-
|
717
|
+
case encoding
|
718
|
+
when :iso
|
719
719
|
cmds_arr = %i[
|
720
720
|
load_iso_std_data_for_writing_track1
|
721
721
|
load_iso_std_data_for_writing_track2
|
722
722
|
load_iso_std_data_for_writing_track3
|
723
723
|
]
|
724
|
-
|
725
|
-
# TODO: Get Data by cmd (e.g. load_iso_std_data_for_writing_track1)
|
726
724
|
cmds_arr.each_with_index do |cmd, track|
|
727
725
|
puts "\n*** #{cmd.to_s.gsub('_', ' ').upcase} #{'*' * 17}"
|
728
|
-
|
729
|
-
next if track_data[track][:decoded] == '+'
|
726
|
+
# next unless track_data[track]&.key?(:decoded) && track_data[track][:decoded]&.strip&.length&.positive?
|
730
727
|
|
731
728
|
this_track = track_data[track][:decoded].chars.map do |c|
|
732
729
|
c.unpack1('H*').to_i(16)
|
@@ -734,106 +731,79 @@ module PWN
|
|
734
731
|
track_eot = [0x04]
|
735
732
|
track_payload = this_track + track_eot
|
736
733
|
puts track_payload.inspect
|
737
|
-
exec_resp = exec(
|
738
|
-
msr206_obj: msr206_obj,
|
739
|
-
cmd: cmd,
|
740
|
-
params: track_payload
|
741
|
-
)
|
734
|
+
exec_resp = exec(msr206_obj: msr206_obj, cmd: cmd, params: track_payload)
|
742
735
|
exec_resp[:encoding] = encoding
|
743
736
|
puts exec_resp.inspect
|
744
737
|
track_data_arr.push(exec_resp)
|
745
738
|
end
|
746
|
-
|
747
|
-
|
748
|
-
if encoding == :iso_alt
|
739
|
+
when :iso_alt
|
749
740
|
cmds_arr = %i[
|
750
741
|
alt_load_iso_std_data_for_writing_track1
|
751
742
|
alt_load_iso_std_data_for_writing_track2
|
752
743
|
alt_load_iso_std_data_for_writing_track3
|
753
744
|
]
|
754
|
-
|
755
|
-
# TODO: Get Data by cmd (e.g. alt_load_iso_std_data_for_writing_track1)
|
756
745
|
cmds_arr.each_with_index do |cmd, track|
|
757
746
|
puts "\n*** #{cmd.to_s.gsub('_', ' ').upcase} #{'*' * 17}"
|
758
|
-
|
759
|
-
next if track_data[track][:decoded] == '+'
|
747
|
+
# next unless track_data[track]&.key?(:decoded) && track_data[track][:decoded]&.strip&.length&.positive?
|
760
748
|
|
761
749
|
this_track = track_data[track][:decoded].chars.map do |c|
|
762
750
|
c.unpack1('H*').to_i(16)
|
763
751
|
end
|
764
|
-
track_format = track_data[track][:track_format]
|
752
|
+
track_format = track_data[track][:track_format] || [0x31]
|
765
753
|
track_eot = [0x04]
|
766
754
|
track_payload = track_format + this_track + track_eot
|
767
755
|
puts track_payload.inspect
|
768
|
-
exec_resp = exec(
|
769
|
-
msr206_obj: msr206_obj,
|
770
|
-
cmd: cmd,
|
771
|
-
params: track_payload
|
772
|
-
)
|
756
|
+
exec_resp = exec(msr206_obj: msr206_obj, cmd: cmd, params: track_payload)
|
773
757
|
exec_resp[:encoding] = encoding
|
774
758
|
puts exec_resp.inspect
|
775
759
|
track_data_arr.push(exec_resp)
|
776
760
|
end
|
777
|
-
|
778
|
-
|
779
|
-
if encoding == :raw
|
761
|
+
when :raw
|
780
762
|
cmds_arr = %i[
|
781
763
|
load_custom_data_for_writing_track1
|
782
764
|
load_custom_data_for_writing_track2
|
783
765
|
load_custom_data_for_writing_track3
|
784
766
|
]
|
785
|
-
|
786
|
-
# TODO: Get Data by cmd (e.g. load_custom_data_for_writing_track1)
|
787
767
|
cmds_arr.each_with_index do |cmd, track|
|
788
768
|
puts "\n*** #{cmd.to_s.gsub('_', ' ').upcase} #{'*' * 17}"
|
789
|
-
|
790
|
-
next if track_data[track][:decoded] == '+'
|
769
|
+
# next unless track_data[track]&.key?(:decoded) && track_data[track][:decoded]&.strip&.length&.positive?
|
791
770
|
|
792
771
|
this_track = track_data[track][:decoded].chars.map do |c|
|
793
772
|
c.unpack1('H*').to_i(16)
|
794
773
|
end
|
795
|
-
track_format = track_data[track][:track_format]
|
774
|
+
track_format = track_data[track][:track_format] || [0x33]
|
796
775
|
track_eot = [0x04]
|
797
776
|
track_payload = track_format + this_track + track_eot
|
798
777
|
puts track_payload.inspect
|
799
|
-
exec_resp = exec(
|
800
|
-
msr206_obj: msr206_obj,
|
801
|
-
cmd: cmd,
|
802
|
-
params: track_payload
|
803
|
-
)
|
778
|
+
exec_resp = exec(msr206_obj: msr206_obj, cmd: cmd, params: track_payload)
|
804
779
|
exec_resp[:encoding] = encoding
|
805
780
|
puts exec_resp.inspect
|
806
781
|
track_data_arr.push(exec_resp)
|
807
782
|
end
|
783
|
+
else
|
784
|
+
logger.error("Unsupported encoding: #{encoding}")
|
785
|
+
raise "Unsupported encoding: #{encoding}"
|
808
786
|
end
|
809
787
|
|
810
|
-
exec_resp =
|
811
|
-
|
812
|
-
cmd: type
|
813
|
-
)
|
814
|
-
puts exec_resp.inspect
|
788
|
+
exec_resp = exec(msr206_obj: msr206_obj, cmd: type)
|
789
|
+
logger.info("Arm to write response: #{exec_resp.inspect}")
|
815
790
|
|
816
|
-
print 'Writer Activated.
|
791
|
+
print 'Writer Activated. Please Swipe Card...'
|
817
792
|
loop do
|
818
|
-
exec_resp = parse_responses(
|
819
|
-
msr206_obj: msr206_obj,
|
820
|
-
cmd: type
|
821
|
-
)
|
822
|
-
|
793
|
+
exec_resp = parse_responses(msr206_obj: msr206_obj, cmd: type)
|
823
794
|
break if exec_resp[:msg] == :ack_command_completed
|
824
795
|
end
|
825
796
|
else
|
826
|
-
|
797
|
+
logger.error("Unsupported swipe type: #{type}")
|
798
|
+
raise "Unsupported type in #wait_for_swipe: #{type}"
|
827
799
|
end
|
828
800
|
|
829
801
|
track_data_arr
|
830
802
|
rescue StandardError => e
|
803
|
+
logger.error("Error in wait_for_swipe: #{e.message}")
|
831
804
|
raise e
|
832
805
|
ensure
|
833
|
-
|
834
|
-
msr206_obj: msr206_obj,
|
835
|
-
cmd: :green_off
|
836
|
-
)
|
806
|
+
exec(msr206_obj: msr206_obj, cmd: :green_off)
|
837
807
|
end
|
838
808
|
|
839
809
|
# Supported Method Parameters::
|
@@ -843,7 +813,7 @@ module PWN
|
|
843
813
|
|
844
814
|
public_class_method def self.read_card(opts = {})
|
845
815
|
msr206_obj = opts[:msr206_obj]
|
846
|
-
|
816
|
+
logger.info('Starting read_card')
|
847
817
|
|
848
818
|
encoding = :waiting_for_selection
|
849
819
|
loop do
|
@@ -867,12 +837,15 @@ module PWN
|
|
867
837
|
end
|
868
838
|
end
|
869
839
|
|
870
|
-
wait_for_swipe(
|
840
|
+
track_data = wait_for_swipe(
|
871
841
|
msr206_obj: msr206_obj,
|
872
842
|
type: :arm_to_read,
|
873
843
|
encoding: encoding
|
874
844
|
)
|
845
|
+
logger.info("Read card successful: #{track_data.inspect}")
|
846
|
+
track_data
|
875
847
|
rescue StandardError => e
|
848
|
+
logger.error("Error reading card: #{e.message}")
|
876
849
|
raise e
|
877
850
|
end
|
878
851
|
|
@@ -883,22 +856,14 @@ module PWN
|
|
883
856
|
|
884
857
|
public_class_method def self.backup_card(opts = {})
|
885
858
|
msr206_obj = opts[:msr206_obj]
|
886
|
-
|
859
|
+
logger.info('Starting backup_card')
|
887
860
|
|
888
|
-
|
889
|
-
track_data = read_card(
|
890
|
-
msr206_obj: msr206_obj
|
891
|
-
)
|
861
|
+
track_data = read_card(msr206_obj: msr206_obj)
|
892
862
|
|
893
863
|
file = ''
|
894
864
|
backup_msg = ''
|
895
865
|
loop do
|
896
|
-
if backup_msg.empty?
|
897
|
-
exec_resp = exec(
|
898
|
-
msr206_obj: msr206_obj,
|
899
|
-
cmd: :green_flash
|
900
|
-
)
|
901
|
-
end
|
866
|
+
exec(msr206_obj: msr206_obj, cmd: :green_flash) if backup_msg.empty?
|
902
867
|
|
903
868
|
print 'Enter File Name to Save Backup: '
|
904
869
|
file = gets.scrub.chomp.strip
|
@@ -907,34 +872,25 @@ module PWN
|
|
907
872
|
|
908
873
|
backup_msg = "\n****** ERROR: Directory #{file_dir} for #{file} does not exist ******"
|
909
874
|
puts backup_msg
|
910
|
-
|
911
|
-
|
912
|
-
cmd: :green_off
|
913
|
-
)
|
914
|
-
exec_resp = exec(
|
915
|
-
msr206_obj: msr206_obj,
|
916
|
-
cmd: :yellow_flash
|
917
|
-
)
|
875
|
+
exec(msr206_obj: msr206_obj, cmd: :green_off)
|
876
|
+
exec(msr206_obj: msr206_obj, cmd: :yellow_flash)
|
918
877
|
end
|
919
878
|
|
920
879
|
File.write(file, "#{JSON.pretty_generate(track_data)}\n")
|
921
|
-
|
922
|
-
|
923
|
-
|
924
|
-
)
|
925
|
-
|
926
|
-
puts 'complete.'
|
927
|
-
|
880
|
+
exec(msr206_obj: msr206_obj, cmd: :yellow_off)
|
881
|
+
logger.info("Backup saved to #{file}")
|
882
|
+
puts 'Backup complete.'
|
928
883
|
track_data
|
929
884
|
rescue StandardError => e
|
885
|
+
logger.error("Error backing up card: #{e.message}")
|
930
886
|
raise e
|
931
887
|
end
|
932
888
|
|
933
889
|
# Supported Method Parameters::
|
934
890
|
# PWN::Plugins::MSR206.write_card(
|
935
891
|
# msr206_obj: 'required - msr206_obj returned from #connect method',
|
936
|
-
# encoding: 'required - :iso || :
|
937
|
-
# track_data: '
|
892
|
+
# encoding: 'required - :iso || :iso_alt || :raw',
|
893
|
+
# track_data: 'required - track data to write (see #backup_card for structure)'
|
938
894
|
# )
|
939
895
|
|
940
896
|
public_class_method def self.write_card(opts = {})
|
@@ -942,51 +898,56 @@ module PWN
|
|
942
898
|
encoding = opts[:encoding].to_s.scrub.strip.chomp.to_sym
|
943
899
|
track_data = opts[:track_data]
|
944
900
|
|
901
|
+
logger.info("Starting write_card with encoding: #{encoding}")
|
945
902
|
puts 'IN ORDER TO GET BLANK TRACKS, A STRONG MAGNETIC FIELD MUST BE PRESENT TO FIRST WIPE THE CARD TARGETED FOR WRITING.'
|
946
|
-
# puts 'Default Write Current:'
|
947
|
-
# exec_resp = exec(
|
948
|
-
# msr206_obj: msr206_obj,
|
949
|
-
# cmd: :view_default_write_current
|
950
|
-
# )
|
951
|
-
# puts exec_resp.inspect
|
952
|
-
|
953
|
-
# puts 'Temporary Write Current:'
|
954
|
-
# exec_resp = exec(
|
955
|
-
# msr206_obj: msr206_obj,
|
956
|
-
# cmd: :view_temp_write_current
|
957
|
-
# )
|
958
|
-
# puts exec_resp.inspect
|
959
903
|
|
904
|
+
# Set track density
|
905
|
+
density = :waiting_for_selection
|
906
|
+
loop do
|
907
|
+
puts "\nTRACK DENSITY OPTIONS:"
|
908
|
+
puts '[(H)igh (210 BPI, Tracks 1 & 3)]'
|
909
|
+
puts '[(L)ow (75 BPI, Tracks 1 & 3)]'
|
910
|
+
puts '[(T2H)igh (210 BPI, Track 2)]'
|
911
|
+
puts '[(T2L)ow (75 BPI, Track 2)]'
|
912
|
+
print 'TRACK DENSITY >>> '
|
913
|
+
density_choice = gets.scrub.chomp.strip.upcase.to_sym
|
914
|
+
|
915
|
+
case density_choice
|
916
|
+
when :H
|
917
|
+
exec(msr206_obj: msr206_obj, cmd: :set_write_density_210_bpi_tracks13)
|
918
|
+
break
|
919
|
+
when :L
|
920
|
+
exec(msr206_obj: msr206_obj, cmd: :set_write_density_75_bpi_tracks13)
|
921
|
+
break
|
922
|
+
when :T2H
|
923
|
+
exec(msr206_obj: msr206_obj, cmd: :set_write_density_210_bpi_tracks2)
|
924
|
+
break
|
925
|
+
when :T2L
|
926
|
+
exec(msr206_obj: msr206_obj, cmd: :set_write_density_75_bpi_tracks2)
|
927
|
+
break
|
928
|
+
end
|
929
|
+
end
|
930
|
+
|
931
|
+
# Set coercivity
|
960
932
|
coercivity = :waiting_for_selection
|
961
933
|
loop do
|
962
934
|
puts "\nCOERCIVITY OPTIONS:"
|
963
935
|
puts '[(H)igh (Most Often Black Stripe)]'
|
964
|
-
puts '[(L)ow
|
936
|
+
puts '[(L)ow (Most Often Brown Stripe)]'
|
965
937
|
print 'COERCIVITY LEVEL >>> '
|
966
938
|
coercivity_choice = gets.scrub.chomp.strip.upcase.to_sym
|
967
939
|
|
968
|
-
# Write Current Settings vs. Media Coercivties
|
969
|
-
# Media Coercivity (Oersteds)|Write Current Setting*|Typical Usage
|
970
|
-
# 300 |36 |Low coercivity
|
971
|
-
# 600 | |
|
972
|
-
# 1800 | |
|
973
|
-
# 3600+ |255 |Typical high corcivity
|
974
|
-
|
975
940
|
case coercivity_choice
|
976
941
|
when :H
|
977
|
-
coercivity = [0x32, 0x35, 0x35]
|
942
|
+
coercivity = [0x32, 0x35, 0x35] # 255 for high coercivity
|
978
943
|
break
|
979
944
|
when :L
|
980
|
-
coercivity = [0x30, 0x33, 0x36]
|
945
|
+
coercivity = [0x30, 0x33, 0x36] # 36 for low coercivity
|
981
946
|
break
|
982
947
|
end
|
983
948
|
end
|
984
949
|
|
985
|
-
|
986
|
-
msr206_obj: msr206_obj,
|
987
|
-
cmd: :set_temp_write_current,
|
988
|
-
params: coercivity
|
989
|
-
)
|
950
|
+
exec(msr206_obj: msr206_obj, cmd: :set_temp_write_current, params: coercivity)
|
990
951
|
|
991
952
|
track_data = wait_for_swipe(
|
992
953
|
msr206_obj: msr206_obj,
|
@@ -995,29 +956,43 @@ module PWN
|
|
995
956
|
track_data: track_data
|
996
957
|
)
|
997
958
|
|
998
|
-
|
999
|
-
|
1000
|
-
|
1001
|
-
|
959
|
+
# Verify write
|
960
|
+
exec_resp = exec(msr206_obj: msr206_obj, cmd: :write_verify)
|
961
|
+
if exec_resp[:msg] == :ack_command_completed
|
962
|
+
puts 'Write verification successful.'
|
963
|
+
logger.info('Write verification successful')
|
964
|
+
else
|
965
|
+
puts "Write verification failed: #{exec_resp[:msg]}"
|
966
|
+
logger.error("Write verification failed: #{exec_resp[:msg]}")
|
967
|
+
end
|
1002
968
|
|
969
|
+
# Re-read card to confirm
|
970
|
+
read_data = read_card(msr206_obj: msr206_obj)
|
971
|
+
if read_data.map { |t| t[:decoded] } == track_data.map { |t| t[:decoded] }
|
972
|
+
puts 'Card data matches written data.'
|
973
|
+
logger.info('Card data matches written data')
|
974
|
+
else
|
975
|
+
puts 'ERROR: Written data does not match read data.'
|
976
|
+
logger.error('Written data does not match read data')
|
977
|
+
end
|
978
|
+
|
979
|
+
exec(msr206_obj: msr206_obj, cmd: :simulate_power_cycle_warm_reset)
|
1003
980
|
track_data
|
1004
981
|
rescue StandardError => e
|
982
|
+
logger.error("Error writing card: #{e.message}")
|
1005
983
|
raise e
|
1006
984
|
end
|
1007
985
|
|
1008
986
|
# Supported Method Parameters::
|
1009
|
-
# PWN::Plugins::MSR206.
|
987
|
+
# PWN::Plugins::MSR206.clone_card(
|
1010
988
|
# msr206_obj: 'required - msr206_obj returned from #connect method'
|
1011
989
|
# )
|
1012
990
|
|
1013
|
-
public_class_method def self.
|
991
|
+
public_class_method def self.clone_card(opts = {})
|
1014
992
|
msr206_obj = opts[:msr206_obj]
|
993
|
+
logger.info('Starting clone_card')
|
1015
994
|
|
1016
|
-
|
1017
|
-
track_data = backup_card(
|
1018
|
-
msr206_obj: msr206_obj
|
1019
|
-
)
|
1020
|
-
|
995
|
+
track_data = backup_card(msr206_obj: msr206_obj)
|
1021
996
|
encoding = track_data.first[:encoding] if track_data.length == 3
|
1022
997
|
write_card(
|
1023
998
|
msr206_obj: msr206_obj,
|
@@ -1025,6 +1000,7 @@ module PWN
|
|
1025
1000
|
track_data: track_data
|
1026
1001
|
)
|
1027
1002
|
rescue StandardError => e
|
1003
|
+
logger.error("Error cloning card: #{e.message}")
|
1028
1004
|
raise e
|
1029
1005
|
end
|
1030
1006
|
|
@@ -1035,16 +1011,12 @@ module PWN
|
|
1035
1011
|
|
1036
1012
|
public_class_method def self.load_card_from_file(opts = {})
|
1037
1013
|
msr206_obj = opts[:msr206_obj]
|
1014
|
+
logger.info('Starting load_card_from_file')
|
1038
1015
|
|
1039
1016
|
file = ''
|
1040
1017
|
restore_msg = ''
|
1041
1018
|
loop do
|
1042
|
-
if restore_msg.empty?
|
1043
|
-
exec_resp = exec(
|
1044
|
-
msr206_obj: msr206_obj,
|
1045
|
-
cmd: :green_flash
|
1046
|
-
)
|
1047
|
-
end
|
1019
|
+
exec(msr206_obj: msr206_obj, cmd: :green_flash) if restore_msg.empty?
|
1048
1020
|
|
1049
1021
|
print 'Enter File Name to Restore to Card: '
|
1050
1022
|
file = gets.scrub.chomp.strip
|
@@ -1052,90 +1024,147 @@ module PWN
|
|
1052
1024
|
|
1053
1025
|
restore_msg = "\n****** ERROR: #{file} does not exist ******"
|
1054
1026
|
puts restore_msg
|
1055
|
-
|
1056
|
-
|
1057
|
-
cmd: :green_off
|
1058
|
-
)
|
1059
|
-
exec_resp = exec(
|
1060
|
-
msr206_obj: msr206_obj,
|
1061
|
-
cmd: :yellow_flash
|
1062
|
-
)
|
1027
|
+
exec(msr206_obj: msr206_obj, cmd: :green_off)
|
1028
|
+
exec(msr206_obj: msr206_obj, cmd: :yellow_flash)
|
1063
1029
|
end
|
1064
1030
|
|
1065
|
-
|
1066
|
-
|
1067
|
-
File.read(file),
|
1068
|
-
symbolize_names: true
|
1069
|
-
)
|
1031
|
+
track_data = JSON.parse(File.read(file), symbolize_names: true)
|
1032
|
+
exec(msr206_obj: msr206_obj, cmd: :yellow_off)
|
1070
1033
|
|
1071
|
-
|
1072
|
-
msr206_obj: msr206_obj,
|
1073
|
-
cmd: :yellow_off
|
1074
|
-
)
|
1075
|
-
|
1076
|
-
encoding = track_data.first[:encoding] if track_data.length == 3
|
1034
|
+
encoding = track_data.first[:encoding] || :iso
|
1077
1035
|
write_card(
|
1078
1036
|
msr206_obj: msr206_obj,
|
1079
1037
|
encoding: encoding,
|
1080
1038
|
track_data: track_data
|
1081
1039
|
)
|
1082
1040
|
rescue StandardError => e
|
1041
|
+
logger.error("Error loading card from file: #{e.message}")
|
1083
1042
|
raise e
|
1084
1043
|
end
|
1085
1044
|
|
1086
1045
|
# Supported Method Parameters::
|
1087
|
-
# PWN::Plugins::MSR206.
|
1046
|
+
# PWN::Plugins::MSR206.update_card(
|
1088
1047
|
# msr206_obj: 'required - msr206_obj returned from #connect method'
|
1089
1048
|
# )
|
1090
1049
|
|
1091
|
-
public_class_method def self.
|
1050
|
+
public_class_method def self.update_card(opts = {})
|
1092
1051
|
msr206_obj = opts[:msr206_obj]
|
1052
|
+
logger.info('Starting update_card')
|
1093
1053
|
|
1094
|
-
#
|
1095
|
-
|
1096
|
-
|
1097
|
-
|
1098
|
-
|
1099
|
-
|
1100
|
-
# 3 |not used – should be 0 |not used
|
1101
|
-
# 4 |Track 3 Write not present|Track 3 Write present
|
1102
|
-
# 5 |Track 2 Write not present|Track 2 Write present
|
1103
|
-
# 6 |Track 1 Write not present|Track 1 Write present
|
1104
|
-
# 7 |parity bit** |parity bit**
|
1105
|
-
exec_resp = PWN::Plugins::MSR206.exec(
|
1106
|
-
msr206_obj: msr206_obj,
|
1107
|
-
cmd: :configuration_request
|
1108
|
-
)
|
1054
|
+
# Check for card presence
|
1055
|
+
exec_resp = exec(msr206_obj: msr206_obj, cmd: :card_edge_detect)
|
1056
|
+
unless exec_resp[:msg] == :card_edge_detected
|
1057
|
+
logger.error('Card not detected')
|
1058
|
+
raise 'Card not detected. Please ensure a card is inserted.'
|
1059
|
+
end
|
1109
1060
|
|
1110
|
-
|
1111
|
-
|
1112
|
-
|
1113
|
-
|
1114
|
-
|
1115
|
-
|
1061
|
+
# Read card to backup
|
1062
|
+
track_data = backup_card(msr206_obj: msr206_obj)
|
1063
|
+
unless track_data.is_a?(Array) && track_data.all? { |t| t.is_a?(Hash) && t.key?(:decoded) }
|
1064
|
+
logger.error('Invalid track data structure')
|
1065
|
+
raise 'Invalid track data structure'
|
1066
|
+
end
|
1116
1067
|
|
1117
|
-
|
1118
|
-
|
1068
|
+
# Update each track's decoded value
|
1069
|
+
track_data.each_with_index do |track, index|
|
1070
|
+
decoded = track[:decoded]
|
1071
|
+
puts "\nTrack #{index + 1} Current Value: #{decoded}"
|
1072
|
+
print 'Enter Updated Value (press Enter to keep original): '
|
1073
|
+
updated_value = gets.scrub.chomp.strip
|
1074
|
+
|
1075
|
+
# Retain original value if empty
|
1076
|
+
if updated_value.empty?
|
1077
|
+
puts "Keeping original value: #{decoded}"
|
1078
|
+
logger.info("Track #{index + 1}: Keeping original value: #{decoded}")
|
1079
|
+
updated_value = decoded
|
1080
|
+
else
|
1081
|
+
# Validate input for ISO encoding
|
1082
|
+
if track[:encoding] == :iso
|
1083
|
+
max_length = case index
|
1084
|
+
when 0 then 79 # Track 1
|
1085
|
+
when 1 then 40 # Track 2
|
1086
|
+
when 2 then 107 # Track 3
|
1087
|
+
end
|
1088
|
+
unless updated_value.length <= max_length
|
1089
|
+
logger.error("Track #{index + 1}: Input too long (max #{max_length} characters)")
|
1090
|
+
raise "Track #{index + 1}: Input too long (max #{max_length} characters)"
|
1091
|
+
end
|
1092
|
+
unless updated_value.match?(/\A[ -~]*\z/) # ASCII printable characters only
|
1093
|
+
logger.error("Track #{index + 1}: Invalid characters for ISO encoding")
|
1094
|
+
raise "Track #{index + 1}: Invalid characters for ISO encoding"
|
1095
|
+
end
|
1096
|
+
end
|
1097
|
+
logger.info("Track #{index + 1}: Updated value: #{updated_value}")
|
1098
|
+
end
|
1119
1099
|
|
1120
|
-
|
1121
|
-
|
1100
|
+
track[:decoded] = updated_value
|
1101
|
+
end
|
1122
1102
|
|
1123
|
-
|
1103
|
+
# Confirm changes
|
1104
|
+
puts "\nUpdated Track Data:"
|
1105
|
+
track_data.each_with_index { |t, i| puts "Track #{i + 1}: #{t[:decoded]}" }
|
1106
|
+
print 'Confirm writing these changes to the card? [y/N]: '
|
1107
|
+
unless gets.chomp.strip.upcase == 'Y'
|
1108
|
+
logger.info('Update cancelled by user')
|
1109
|
+
puts 'Update cancelled.'
|
1110
|
+
return track_data
|
1111
|
+
end
|
1124
1112
|
|
1125
|
-
|
1126
|
-
|
1113
|
+
# Write updated data
|
1114
|
+
encoding = track_data.first[:encoding] || :iso
|
1115
|
+
track_data = write_card(
|
1116
|
+
msr206_obj: msr206_obj,
|
1117
|
+
encoding: encoding,
|
1118
|
+
track_data: track_data
|
1119
|
+
)
|
1127
1120
|
|
1128
|
-
|
1129
|
-
|
1121
|
+
logger.info("Update card successful: #{track_data.inspect}")
|
1122
|
+
puts 'Card updated successfully.'
|
1123
|
+
track_data
|
1124
|
+
rescue StandardError => e
|
1125
|
+
logger.error("Error updating card: #{e.message}")
|
1126
|
+
puts "ERROR: Failed to update card - #{e.message}"
|
1127
|
+
raise e
|
1128
|
+
end
|
1130
1129
|
|
1131
|
-
|
1132
|
-
|
1130
|
+
# Supported Method Parameters::
|
1131
|
+
# PWN::Plugins::MSR206.get_config(
|
1132
|
+
# msr206_obj: 'required - msr206_obj returned from #connect method'
|
1133
|
+
# )
|
1134
|
+
|
1135
|
+
public_class_method def self.get_config(opts = {})
|
1136
|
+
msr206_obj = opts[:msr206_obj]
|
1137
|
+
logger.info('Retrieving configuration')
|
1138
|
+
|
1139
|
+
exec_resp = exec(msr206_obj: msr206_obj, cmd: :configuration_request)
|
1140
|
+
config_arr = exec_resp[:binary].first.reverse.chars
|
1141
|
+
config_hash = {
|
1142
|
+
track1_read: false,
|
1143
|
+
track2_read: false,
|
1144
|
+
track3_read: false,
|
1145
|
+
track1_write: false,
|
1146
|
+
track2_write: false,
|
1147
|
+
track3_write: false,
|
1148
|
+
not_used: false,
|
1149
|
+
parity: false
|
1150
|
+
}
|
1133
1151
|
|
1134
|
-
|
1152
|
+
config_arr.each_with_index do |bit_str, i|
|
1153
|
+
bit = bit_str.to_i
|
1154
|
+
config_hash[:track1_read] = true if i.zero? && bit == 1
|
1155
|
+
config_hash[:track2_read] = true if i == 1 && bit == 1
|
1156
|
+
config_hash[:track3_read] = true if i == 2 && bit == 1
|
1157
|
+
config_hash[:not_used] = true if i == 3 && bit == 1
|
1158
|
+
config_hash[:track3_write] = true if i == 4 && bit == 1
|
1159
|
+
config_hash[:track2_write] = true if i == 5 && bit == 1
|
1160
|
+
config_hash[:track1_write] = true if i == 6 && bit == 1
|
1161
|
+
config_hash[:parity] = true if i == 7 && bit == 1
|
1135
1162
|
end
|
1136
1163
|
|
1164
|
+
logger.info("Configuration: #{config_hash.inspect}")
|
1137
1165
|
config_hash
|
1138
1166
|
rescue StandardError => e
|
1167
|
+
logger.error("Error getting configuration: #{e.message}")
|
1139
1168
|
raise e
|
1140
1169
|
end
|
1141
1170
|
|
@@ -1145,10 +1174,10 @@ module PWN
|
|
1145
1174
|
# )
|
1146
1175
|
|
1147
1176
|
public_class_method def self.disconnect(opts = {})
|
1148
|
-
|
1149
|
-
|
1150
|
-
)
|
1177
|
+
logger.info('Disconnecting from device')
|
1178
|
+
PWN::Plugins::Serial.disconnect(serial_obj: opts[:msr206_obj])
|
1151
1179
|
rescue StandardError => e
|
1180
|
+
logger.error("Error disconnecting: #{e.message}")
|
1152
1181
|
raise e
|
1153
1182
|
end
|
1154
1183
|
|
@@ -1166,10 +1195,16 @@ module PWN
|
|
1166
1195
|
puts "USAGE:
|
1167
1196
|
msr206_obj = #{self}.connect(
|
1168
1197
|
block_dev: 'optional serial block device path (defaults to /dev/ttyUSB0)',
|
1169
|
-
baud: 'optional (defaults to
|
1198
|
+
baud: 'optional (defaults to 19200)',
|
1170
1199
|
data_bits: 'optional (defaults to 8)',
|
1171
1200
|
stop_bits: 'optional (defaults to 1)',
|
1172
|
-
parity: 'optional - :even||:odd|:none (defaults to :none)'
|
1201
|
+
parity: 'optional - :even||:odd|:none (defaults to :none)',
|
1202
|
+
flow_control: 'optional - :none||:hard||:soft (defaults to :soft)'
|
1203
|
+
)
|
1204
|
+
|
1205
|
+
#{self}.set_protocol(
|
1206
|
+
msr206_obj: 'required - msr206_obj returned from #connect method',
|
1207
|
+
protocol: 'optional - :usi0 or :usi1 (defaults to :usi0)'
|
1173
1208
|
)
|
1174
1209
|
|
1175
1210
|
cmds = #{self}.list_cmds
|
@@ -1177,7 +1212,37 @@ module PWN
|
|
1177
1212
|
parsed_cmd_resp_arr = #{self}.exec(
|
1178
1213
|
msr206_obj: 'required msr206_obj returned from #connect method',
|
1179
1214
|
cmd: 'required - cmd returned from #list_cmds method',
|
1180
|
-
params: 'optional - parameters for specific command
|
1215
|
+
params: 'optional - parameters for specific command'
|
1216
|
+
)
|
1217
|
+
|
1218
|
+
track_data = #{self}.read_card(
|
1219
|
+
msr206_obj: 'required msr206_obj returned from #connect method'
|
1220
|
+
)
|
1221
|
+
|
1222
|
+
track_data = #{self}.backup_card(
|
1223
|
+
msr206_obj: 'required msr206_obj returned from #connect method'
|
1224
|
+
)
|
1225
|
+
|
1226
|
+
track_data = #{self}.write_card(
|
1227
|
+
msr206_obj: 'required msr206_obj returned from #connect method',
|
1228
|
+
encoding: 'required - :iso || :iso_alt || :raw',
|
1229
|
+
track_data: 'required - track data to write'
|
1230
|
+
)
|
1231
|
+
|
1232
|
+
track_data = #{self}.clone_card(
|
1233
|
+
msr206_obj: 'required msr206_obj returned from #connect method'
|
1234
|
+
)
|
1235
|
+
|
1236
|
+
track_data = #{self}.load_card_from_file(
|
1237
|
+
msr206_obj: 'required msr206_obj returned from #connect method'
|
1238
|
+
)
|
1239
|
+
|
1240
|
+
track_data = #{self}.update_card(
|
1241
|
+
msr206_obj: 'required msr206_obj returned from #connect method'
|
1242
|
+
)
|
1243
|
+
|
1244
|
+
config = #{self}.get_config(
|
1245
|
+
msr206_obj: 'required msr206_obj returned from #connect method'
|
1181
1246
|
)
|
1182
1247
|
|
1183
1248
|
#{self}.disconnect(
|