rdp1-bypass/cli/client.py
Daniele Lacamera c6350f2f65 Initial commit
2018-07-26 08:08:29 +02:00

382 lines
10 KiB
Python
Executable file

#!/usr/bin/python3
#
#
# This Source Code Form is subject to the terms of the MIT License.
# If a copy of the MIT License was not distributed with this file,
# you can obtain one at https://opensource.org/licenses/MIT
#
import argparse
import os.path
from datetime import datetime
import subprocess
import signal
import time
from prompt_toolkit import prompt
from prompt_toolkit.contrib.completers import WordCompleter
from prompt_toolkit.history import InMemoryHistory
from prompt_toolkit.auto_suggest import AutoSuggestFromHistory
def auto_int(x):
return int(x, 0)
def parse_args():
parser = argparse.ArgumentParser(
description='Firmware Extractor User Interface',
)
parser.add_argument(
'SerialDeviceFILE',
help='Device File to read from (e.g., /dev/ttyUSB0)'
)
parser.add_argument(
'-i',
'--interactive',
action='store_true',
help='Use interactive mode'
)
parser.add_argument(
'-s',
'--start',
type=auto_int,
default=0x00,
help='Set start address',
)
parser.add_argument(
'-l',
'--length',
type=auto_int,
default=0x10000,
help='Number of bytes to extract',
)
parser.add_argument(
'-e',
'--endianess',
default='little',
choices=['little', 'big'],
help='Set endianess',
)
parser.add_argument(
'-o',
'--outfile',
default='data-{}.bin'.format(datetime.now().strftime('%Y%m%d_%H%M')),
help='Set output file path',
)
return parser.parse_args()
finish = False
def sigalarm_handler(signo, frame):
# I want to print the statistics in the end of the program.
# We can create an infinite loop with this sigalarm_handler,
# as multiline mode of send_cmd() uses SIGALARM as well.
# The global variable finish handles this case. And helps
# us terminate the program when the timeout is fired a second
# time.
global finish
if finish:
print()
print('Programm finished.')
exit()
print('End of data.')
print()
finish = True
UART('/dev/ttyUSB0').send_cmd('P', True)
def decode_ascii(s, outfile):
out = ''
for i in range(0, len(s), 2):
char = int(s[i:i+2], 16)
outfile.write(char.to_bytes(1, 'little'))
if char > 31 and char < 127:
out += chr(char)
else:
out += '.'
return out
def print_error(errcode):
print()
print('StatusCode: {:02X}'.format(errcode))
if errcode == 0x20:
print('Status OK')
elif errcode == 0x40:
print('Wait/Retry requested (bus access was not granted in time)')
elif errcode == 0x06:
print('Wait requested + additional OK (previous command OK, but no bus access)')
elif errcode == 0x80:
print('Fault during command execution (command error (access denied etc.))')
elif errcode == 0xA0:
print('Fault after successful command execution (reading invalid memory address?).')
elif errcode == 0xE0:
print('Failure during communication (check connection, no valid status reply received)')
else:
print('Unknown status code')
def _read_enddata(fd):
buf = ''
nchar = 0
state = 'stat'
print()
# Print remaining data from uart until timeout is reached.
while True:
signal.alarm(1)
char = fd.read(1)
signal.alarm(0)
print(char, end='')
buf += char
if char == '!':
state = 'err'
if state == 'err':
if nchar == 25:
print_error(int(buf[-8:], 16))
nchar += 1
def read_ascii(fd, outfile):
l = 0
c = 0
line = ''
lineraw = ''
outfile = open(outfile, 'ab', 16)
while True:
line += '0x{:08X}: '.format(16*l)
decoded_line = ''
while c < 32:
char = fd.read(1)
if char == ' ':
continue
lineraw += char
if c % 2 == 0 and c != 0:
line += ' '
if c % 16 == 0 and c != 0:
line += ' '
# Reached end of data.
# Flush all buffers and read special stuff at the end.
if char == '\r' or char == '\n':
try:
line += ' |' + decode_ascii(lineraw, outfile) + '|'
except ValueError:
pass
print(line)
outfile.flush()
outfile.close()
_read_enddata(fd)
c += 1
line += char
line += ' |' + decode_ascii(lineraw, outfile) + '|'
print(line)
line = ''
lineraw = ''
l += 1
c = 0
class UART:
def __init__(self, devnode):
self.devnode = devnode
subprocess.call( [
'stty',
'--file={}'.format(self.devnode),
'115200',
'raw',
'-echok',
'-echo',
])
def send_cmd(self, code, multiline=False):
readfd = open(self.devnode, 'r')
writefd = open(self.devnode, 'w')
time.sleep(0.1)
writefd.write(code + '\n')
while True:
try:
if multiline:
signal.alarm(1)
char = readfd.read(1)
signal.alarm(0)
if not multiline:
if char == '\r' or char == '\n':
print()
break
print(char, end='')
except UnicodeDecodeError:
print('Received garbage from target.')
print('Is it already running?')
print('Shutting down...')
exit(1)
writefd.close()
readfd.close()
def open(self, mode):
return open(self.devnode, mode)
class REPL:
def __init__(self, start=0x00, length=0x10000, mode='bin',
byteorder='little', devnode='/dev/ttyUSB0'):
self.history = InMemoryHistory()
self.promt = '> '
self.config = {
'start': start,
'length': length,
'byteorder': byteorder,
'outfile': 'data-{}.bin'.format(datetime.now().strftime('%Y%m%d_%H%M')),
}
self.uart = UART(devnode)
def handle_cmd(self, cmd, *args):
if cmd == 'set':
self.set_config(args[0], args[1])
elif cmd == 'run':
self.apply_config()
self.uart.send_cmd('S')
fd = self.uart.open('r')
print()
read_ascii(fd, self.config['outfile'])
fd.close()
elif cmd == 'cmd':
self.uart.send_cmd(args[0], True)
elif cmd == 'help':
self.show_help()
elif cmd == 'exit':
print('Exit command received. Leaving...')
exit(0)
else:
print('Error: Unknown command.')
print("Try 'help'")
def show_config(self):
print('# Current Configuration')
lines = []
if self.config:
tpl = ' {:<12}: '
for key, val in self.config.items():
if isinstance(val, int):
fstr = tpl + '0x{:X}'
else:
fstr = tpl + '{}'
lines.append(fstr.format(key, val))
print('\n'.join(sorted(lines)))
def show_help(self):
print('# Supported commands')
print(' set KEY VAL : Set configuration value KEY to VAL')
print(' cmd CODE : Send command code to UART')
print(' run : Start reading out code')
print(' help : Show this help page')
print(' exit : Terminate programm')
def set_config(self, key, val):
if key == 'byteorder':
if val not in ('little', 'big'):
print('Error: Wrong byteorder. Choose "little" or "big".')
return
elif key == 'start':
val = int(val, 0)
if val > 0x10000:
print('Error: Start address is too large.')
return
elif key == 'length':
val = int(val, 0)
elif key == 'outfile':
self.config[key] = str(val)
else:
print('Error: Config key does not exist.')
return
self.config[key] = val
self.show_config()
def apply_config(self):
self.uart.send_cmd('A{:X}'.format(self.config['start']))
self.uart.send_cmd('L{:X}'.format(self.config['length']))
# Hardcode HEX mode
self.uart.send_cmd('H')
cmd = 'e' if self.config['byteorder'] == 'little' else 'E'
self.uart.send_cmd(cmd)
def run_loop(self):
self.show_help()
print()
self.show_config()
try:
while True:
cmd = prompt(self.promt,
history=self.history,
auto_suggest=AutoSuggestFromHistory(),
)
cmd = cmd.strip().split()
cmd = cmd if cmd else ''
if cmd != '':
self.handle_cmd(*cmd)
except KeyboardInterrupt:
print('KeyboardInterrupt received. Shutting down...')
exit(1)
except EOFError:
print('Reached end of line. Leaving...')
exit(0)
def main():
args = parse_args()
signal.signal(signal.SIGALRM, sigalarm_handler)
if not os.path.exists(args.SerialDeviceFILE):
print('Error: No such file.')
exit(1)
if args.interactive:
REPL(devnode=args.SerialDeviceFILE).run_loop()
exit(0)
# If the script is not in interactive mode, issue this stuff
# manually.
uart = UART(args.SerialDeviceFILE)
uart.send_cmd('A{:X}'.format(args.start))
uart.send_cmd('L{:X}'.format(args.length))
uart.send_cmd('H') # Hardcode HEX mode
if args.endianess == 'big':
uart.send_cmd('E')
else:
uart.send_cmd('e')
uart.send_cmd('S')
fd = uart.open('r')
print()
try:
read_ascii(fd, args.outfile)
except KeyboardInterrupt:
print('Leaving...')
finally:
fd.close()
if __name__ == '__main__':
main()