Added native output grabbing for Python and Lua: less hacky than output_grabber.py, should work on ProxSpace as well

This commit is contained in:
Philippe Teuwen 2024-08-14 13:06:21 +02:00
commit 369c5bb5db
23 changed files with 387 additions and 198 deletions

View file

@ -19,7 +19,6 @@ import time
import subprocess
import argparse
import pm3
from output_grabber import OutputGrabber
# optional color support
try:
# pip install ansicolors
@ -49,14 +48,6 @@ for tool, bin in tools.items():
if not os.path.isfile(bin):
if os.path.isfile(bin + ".exe"):
tools[tool] = bin + ".exe"
print(f"Native Windows/ProxSpace detected.")
print(f"Currently, our Python output grabbing does not work properly on this environment.")
print(f"Use WSL or Linux.")
# cf https://stackoverflow.com/questions/52373180/python-on-windows-handle-invalid-when-redirecting-stdout-writing-to-file
# for ref : https://docs.python.org/3/c-api/init_config.html#c.PyConfig.legacy_windows_stdio
exit()
else:
print(f"Cannot find {bin}, abort!")
exit()
@ -69,27 +60,24 @@ parser.add_argument('-d', '--debug', action='store_true', help='Enable debug mod
args = parser.parse_args()
start_time = time.time()
out = OutputGrabber()
p = pm3.pm3()
restore_color = False
with out:
p.console("prefs get color")
p.console("prefs set color --off")
for line in out.captured_output.split('\n'):
p.console("prefs get color")
p.console("prefs set color --off")
for line in p.grabbed_output.split('\n'):
if "ansi" in line:
restore_color = True
with out:
p.console("hf 14a read")
p.console("hf 14a read")
uid = None
for line in out.captured_output.split('\n'):
for line in p.grabbed_output.split('\n'):
if "UID:" in line:
uid = int(line[10:].replace(' ', ''), 16)
if uid is None:
print("Card not found")
if restore_color:
with out:
p.console("prefs set color --ansi")
p.console("prefs set color --ansi")
_ = p.grabbed_output
exit()
print("UID: " + color(f"{uid:08X}", fg="green"))
@ -102,9 +90,8 @@ def print_key(sec, key_type, key):
found_keys = [["", ""] for _ in range(NUM_SECTORS)]
if not args.no_init_check:
print("Checking default keys...")
with out:
p.console("hf mf fchk")
for line in out.captured_output.split('\n'):
p.console("hf mf fchk")
for line in p.grabbed_output.split('\n'):
if "[+] 0" in line:
res = [x.strip() for x in line.split('|')]
sec = int(res[0][4:])
@ -119,18 +106,17 @@ nt = [["", ""] for _ in range(NUM_SECTORS)]
nt_enc = [["", ""] for _ in range(NUM_SECTORS)]
par_err = [["", ""] for _ in range(NUM_SECTORS)]
print("Getting nonces...")
with out:
for sec in range(NUM_SECTORS):
blk = sec * 4
if found_keys[sec][0] == "" or found_keys[sec][1] == "":
# Even if one key already found, we'll need both nt
for key_type in [0, 1]:
cmd = f"hf mf isen -n1 --blk {blk} -c {key_type+4} --key {BACKDOOR_RF08S}"
p.console(cmd)
cmd += f" --c2 {key_type}"
p.console(cmd)
for sec in range(NUM_SECTORS):
blk = sec * 4
if found_keys[sec][0] == "" or found_keys[sec][1] == "":
# Even if one key already found, we'll need both nt
for key_type in [0, 1]:
cmd = f"hf mf isen -n1 --blk {blk} -c {key_type+4} --key {BACKDOOR_RF08S}"
p.console(cmd)
cmd += f" --c2 {key_type}"
p.console(cmd)
print("Processing traces...")
for line in out.captured_output.split('\n'):
for line in p.grabbed_output.split('\n'):
if "nested cmd: 64" in line or "nested cmd: 65" in line:
sec = int(line[24:26], 16)//4
key_type = int(line[21:23], 16) - 0x64
@ -232,9 +218,8 @@ for sec in range(NUM_SECTORS):
cmd = f"hf mf fchk --blk {sec * 4} -{kt} -f {dic} --no-default"
if args.debug:
print(cmd)
with out:
p.console(cmd)
for line in out.captured_output.split('\n'):
p.console(cmd)
for line in p.grabbed_output.split('\n'):
if "aborted via keyboard" in line:
abort = True
if "found:" in line:
@ -259,9 +244,8 @@ for sec in range(NUM_SECTORS):
cmd = f"hf mf fchk --blk {sec * 4} -{kt} -f {dic} --no-default"
if args.debug:
print(cmd)
with out:
p.console(cmd)
for line in out.captured_output.split('\n'):
p.console(cmd)
for line in p.grabbed_output.split('\n'):
if "aborted via keyboard" in line:
abort = True
if "found:" in line:
@ -281,9 +265,8 @@ for sec in range(NUM_SECTORS):
cmd = f"hf mf fchk --blk {sec * 4} -{kt} -f {dic} --no-default"
if args.debug:
print(cmd)
with out:
p.console(cmd)
for line in out.captured_output.split('\n'):
p.console(cmd)
for line in p.grabbed_output.split('\n'):
if "aborted via keyboard" in line:
abort = True
if "found:" in line:
@ -324,9 +307,8 @@ for sec in range(NUM_SECTORS):
cmd += f" -k {k}"
if args.debug:
print(cmd)
with out:
p.console(cmd)
for line in out.captured_output.split('\n'):
p.console(cmd)
for line in p.grabbed_output.split('\n'):
if "aborted via keyboard" in line:
abort = True
if "found:" in line:
@ -338,8 +320,8 @@ for sec in range(NUM_SECTORS):
if abort:
break
if restore_color:
with out:
p.console("prefs set color --ansi")
p.console("prefs set color --ansi")
_ = p.grabbed_output
if abort:
print("Brute-forcing phase aborted via keyboard!")
@ -389,10 +371,7 @@ else:
cmd = f"hf mf fchk -f keys_{uid:08x}.dic --no-default --dump"
if args.debug:
print(cmd)
with out:
p.console(cmd)
for line in out.captured_output.split('\n'):
print(line)
p.console(cmd, passthru = True)
elapsed_time = time.time() - start_time
minutes = int(elapsed_time // 60)

View file

@ -1,81 +0,0 @@
import os
import sys
import threading
import time
# From https://stackoverflow.com/a/29834357
class OutputGrabber(object):
"""
Class used to grab standard output or another stream.
"""
escape_char = "\b"
def __init__(self, stream=None, threaded=False):
self.origstream = stream
self.threaded = threaded
if self.origstream is None:
self.origstream = sys.stdout
self.origstreamfd = self.origstream.fileno()
self.captured_output = ""
def __enter__(self):
self.start()
return self
def __exit__(self, type, value, traceback):
self.stop()
def start(self):
"""
Start capturing the stream data.
"""
self.captured_output = ""
# Create a pipe so the stream can be captured:
self.pipe_out, self.pipe_in = os.pipe()
# Save a copy of the stream:
self.streamfd = os.dup(self.origstreamfd)
# Replace the original stream with our write pipe:
os.dup2(self.pipe_in, self.origstreamfd)
if self.threaded:
# Start thread that will read the stream:
self.workerThread = threading.Thread(target=self.readOutput)
self.workerThread.start()
# Make sure that the thread is running and os.read() has executed:
time.sleep(0.01)
def stop(self):
"""
Stop capturing the stream data and save the text in `captured_output`.
"""
# Print the escape character to make the readOutput method stop:
self.origstream.write(self.escape_char)
# Flush the stream to make sure all our data goes in before
# the escape character:
self.origstream.flush()
if self.threaded:
# wait until the thread finishes so we are sure that
# we have until the last character:
self.workerThread.join()
else:
self.readOutput()
# Close the pipe:
os.close(self.pipe_in)
os.close(self.pipe_out)
# Restore the original stream:
os.dup2(self.streamfd, self.origstreamfd)
# Close the duplicate stream:
os.close(self.streamfd)
def readOutput(self):
"""
Read the stream data (one byte at a time)
and save the text in `captured_output`.
"""
while True:
char = os.read(self.pipe_out,1).decode(self.origstream.encoding, errors='replace')
if not char or self.escape_char in char:
break
self.captured_output += char
if __name__ == "__main__":
print("This is a library, don't use it as a script")

View file

@ -1,13 +1,10 @@
# This file was automatically generated by SWIG (http://www.swig.org).
# Version 4.0.2
# This file was automatically generated by SWIG (https://www.swig.org).
# Version 4.2.1
#
# Do not make changes to this file unless you know what you are doing--modify
# Do not make changes to this file unless you know what you are doing - modify
# the SWIG interface file instead.
from sys import version_info as _swig_python_version_info
if _swig_python_version_info < (2, 7, 0):
raise RuntimeError("Python 2.7 or later required")
# Import the low-level C/C++ module
if __package__ or "." in __name__:
from . import _pm3
@ -29,10 +26,10 @@ def _swig_repr(self):
def _swig_setattr_nondynamic_instance_variable(set):
def set_instance_attr(self, name, value):
if name == "thisown":
self.this.own(value)
elif name == "this":
if name == "this":
set(self, name, value)
elif name == "thisown":
self.this.own(value)
elif hasattr(self, name) and isinstance(getattr(type(self), name), property):
set(self, name, value)
else:
@ -69,9 +66,10 @@ class pm3(object):
_pm3.pm3_swiginit(self, _pm3.new_pm3(*args))
__swig_destroy__ = _pm3.delete_pm3
def console(self, cmd):
return _pm3.pm3_console(self, cmd)
def console(self, cmd, passthru=False):
return _pm3.pm3_console(self, cmd, passthru)
name = property(_pm3.pm3_name_get)
grabbed_output = property(_pm3.pm3_grabbed_output_get)
# Register pm3 in _pm3:
_pm3.pm3_swigregister(pm3)