From b2f9730f586b348e60c8440d5b246c794de8b88d Mon Sep 17 00:00:00 2001 From: greateggsgreg Date: Tue, 19 Mar 2019 11:19:59 -0700 Subject: [PATCH 01/10] Fixed PycryptodomeAuthSigner's implementation of sign() --- adb/sign_pycryptodome.py | 46 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 43 insertions(+), 3 deletions(-) diff --git a/adb/sign_pycryptodome.py b/adb/sign_pycryptodome.py index 6a61ce9..9f68006 100644 --- a/adb/sign_pycryptodome.py +++ b/adb/sign_pycryptodome.py @@ -1,8 +1,8 @@ from adb import adb_protocol -from Crypto.Hash import SHA256 from Crypto.PublicKey import RSA from Crypto.Signature import pkcs1_15 +from Crypto.Util import number class PycryptodomeAuthSigner(adb_protocol.AuthSigner): @@ -18,8 +18,48 @@ def __init__(self, rsa_key_path=None): self.rsa_key = RSA.import_key(rsa_priv_file.read()) def Sign(self, data): - h = SHA256.new(data) - return pkcs1_15.new(self.rsa_key).sign(h) + # Prepend precomputed ASN1 hash code for SHA1 + data = b'\x30\x21\x30\x09\x06\x05\x2b\x0e\x03\x02\x1a\x05\x00\x04\x14' + data + pkcs = pkcs1_15.new(self.rsa_key) + + # See 8.2.1 in RFC3447 + modBits = number.size(pkcs._key.n) + k = pkcs1_15.ceil_div(modBits,8) # Convert from bits to bytes + + # Step 2a (OS2IP) + em_int = pkcs1_15.bytes_to_long(PycryptodomeAuthSigner._pad_for_signing(data, k)) + # Step 2b (RSASP1) + m_int = pkcs._key._decrypt(em_int) + # Step 2c (I2OSP) + signature = pkcs1_15.long_to_bytes(m_int, k) + + return signature def GetPublicKey(self): return self.public_key + + @staticmethod + def _pad_for_signing(message, target_length): + """Pads the message for signing, returning the padded message. + + The padding is always a repetition of FF bytes. + + Function from python-rsa to replace _EMSA_PKCS1_V1_5_ENCODE's for our use case + + :return: 00 01 PADDING 00 MESSAGE + + """ + + max_msglength = target_length - 11 + msglength = len(message) + + if msglength > max_msglength: + raise OverflowError('%i bytes needed for message, but there is only' + ' space for %i' % (msglength, max_msglength)) + + padding_length = target_length - msglength - 3 + + return b''.join([b'\x00\x01', + padding_length * b'\xff', + b'\x00', + message]) From 236f61d5e00ad3ead7b40c7037e49fa702b34287 Mon Sep 17 00:00:00 2001 From: greateggsgreg Date: Tue, 19 Mar 2019 11:20:33 -0700 Subject: [PATCH 02/10] Fixed partial delimiter stripping in InteractiveShell --- adb/adb_protocol.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/adb/adb_protocol.py b/adb/adb_protocol.py index 4ff28c7..0848eae 100644 --- a/adb/adb_protocol.py +++ b/adb/adb_protocol.py @@ -19,6 +19,7 @@ import struct import time +import re from io import BytesIO from adb import usb_exceptions @@ -551,8 +552,11 @@ def InteractiveShellCommand(cls, conn, cmd=None, strip_cmd=True, delim=None, str stdout = stdout.split(b'\r\r\n')[1] # Strip delim if requested - # TODO: Handling stripping partial delims here - not a deal breaker the way we're handling it now if delim and strip_delim: + prefix_exp = re.compile(r'(?P\d{1,3}\|)'+delim.decode('utf-8', errors='ignore')) + match = re.match(prefix_exp, stdout.decode('utf-8', errors='ignore')) + if match: + stdout = stdout.replace(str(match.group('prefix') + delim).encode('utf-8'), b'') stdout = stdout.replace(delim, b'') stdout = stdout.rstrip() From bc7b879230b3d6d32b983af6e5309bc04fdf8fd4 Mon Sep 17 00:00:00 2001 From: greateggsgreg Date: Tue, 19 Mar 2019 11:20:45 -0700 Subject: [PATCH 03/10] Minor docstring order fix --- adb/adb_commands.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adb/adb_commands.py b/adb/adb_commands.py index f3667c8..49ade7d 100644 --- a/adb/adb_commands.py +++ b/adb/adb_commands.py @@ -256,9 +256,9 @@ def Push(self, source_file, device_filename, mtime='0', timeout_ms=None, progres device_filename: Destination on the device to write to. mtime: Optional, modification time to set on the file. timeout_ms: Expected timeout for any part of the push. - st_mode: stat mode for filename progress_callback: callback method that accepts filename, bytes_written and total_bytes, total_bytes will be -1 for file-like objects + st_mode: stat mode for filename """ if isinstance(source_file, str): From fc28f552a60eaba50b01ba13fe01eed0a1b04cb5 Mon Sep 17 00:00:00 2001 From: greateggsgreg Date: Tue, 23 Jul 2019 05:39:25 -0700 Subject: [PATCH 04/10] Ensure rsa_key during auth process is null terminated --- adb/adb_protocol.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adb/adb_protocol.py b/adb/adb_protocol.py index 0848eae..79b439f 100644 --- a/adb/adb_protocol.py +++ b/adb/adb_protocol.py @@ -324,7 +324,7 @@ def Connect(cls, usb, banner=b'notadb', rsa_keys=None, auth_timeout_ms=100): 'Unknown AUTH response: %s %s %s' % (arg0, arg1, banner)) # Do not mangle the banner property here by converting it to a string - signed_token = rsa_key.Sign(banner) + signed_token = rsa_key.Sign(banner) + b'\0' msg = cls( command=b'AUTH', arg0=AUTH_SIGNATURE, arg1=0, data=signed_token) msg.Send(usb) From 8e2178beeae2c873dc4508f367ce8235cbb8ddc2 Mon Sep 17 00:00:00 2001 From: greateggsgreg Date: Tue, 23 Jul 2019 05:54:14 -0700 Subject: [PATCH 05/10] Improved cli error output --- adb/common_cli.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/adb/common_cli.py b/adb/common_cli.py index b4ab5e8..ccecfda 100644 --- a/adb/common_cli.py +++ b/adb/common_cli.py @@ -27,6 +27,7 @@ import re import sys import types +import traceback from adb import usb_exceptions @@ -158,7 +159,7 @@ def StartCli(args, adb_commands, extra=None, **device_kwargs): try: return _RunMethod(dev, args, extra or {}) except Exception as e: # pylint: disable=broad-except - sys.stdout.write(str(e)) + sys.stdout.write(traceback.format_exc()) return 1 finally: dev.Close() From b2bf67224f8cbf4eeb2408bcc220dbd055828725 Mon Sep 17 00:00:00 2001 From: greateggsgreg Date: Tue, 23 Jul 2019 05:54:58 -0700 Subject: [PATCH 06/10] Added support to mimic 'fastboot boot' command --- adb/fastboot.py | 60 ++++++++++++++++++++++++++++++------------- adb/fastboot_debug.py | 3 +++ 2 files changed, 45 insertions(+), 18 deletions(-) diff --git a/adb/fastboot.py b/adb/fastboot.py index 1507494..fcfb6a9 100644 --- a/adb/fastboot.py +++ b/adb/fastboot.py @@ -19,6 +19,7 @@ import logging import os import struct +from io import BytesIO from adb import common from adb import usb_exceptions @@ -99,7 +100,7 @@ def HandleSimpleResponses( info_cb: Optional callback for text sent from the bootloader. Returns: - OKAY packet's message. + Tuple - OKAY packet's message, List of preceding Fastboot Messages """ return self._AcceptResponses(b'OKAY', info_cb, timeout_ms=timeout_ms) @@ -123,9 +124,9 @@ def HandleDataSending(self, source_file, source_len, FastbootInvalidResponse: Fastboot responded with an unknown packet type. Returns: - OKAY packet's message. + Tuple - OKAY packet's message, List of preceding Fastboot Messages """ - accepted_size = self._AcceptResponses( + accepted_size, _msgs = self._AcceptResponses( b'DATA', info_cb, timeout_ms=timeout_ms) accepted_size = binascii.unhexlify(accepted_size[:8]) @@ -151,24 +152,33 @@ def _AcceptResponses(self, expected_header, info_cb, timeout_ms=None): FastbootInvalidResponse: Fastboot responded with an unknown packet type. Returns: - OKAY packet's message. + Tuple - OKAY packet's message, List of preceding Fastboot Messages """ + + messages = [] + while True: response = self.usb.BulkRead(64, timeout_ms=timeout_ms) header = bytes(response[:4]) remaining = bytes(response[4:]) if header == b'INFO': - info_cb(FastbootMessage(remaining, header)) + fbm = FastbootMessage(remaining, header) + messages.append(fbm) + info_cb(fbm) elif header in self.FINAL_HEADERS: if header != expected_header: raise FastbootStateMismatch( 'Expected %s, got %s', expected_header, header) if header == b'OKAY': - info_cb(FastbootMessage(remaining, header)) - return remaining + fbm = FastbootMessage(remaining, header) + messages.append(fbm) + info_cb(fbm) + return remaining, messages elif header == b'FAIL': - info_cb(FastbootMessage(remaining, header)) + fbm = FastbootMessage(remaining, header) + messages.append(fbm) + info_cb(fbm) raise FastbootRemoteFailure('FAIL: %s', remaining) else: raise FastbootInvalidResponse( @@ -188,6 +198,7 @@ def _HandleProgress(self, total, progress_callback): def _Write(self, data, length, progress_callback=None): """Sends the data to the device, tracking progress with the callback.""" + progress = None if progress_callback: progress = self._HandleProgress(length, progress_callback) next(progress) @@ -310,20 +321,19 @@ def Download(self, source_file, source_len=0, Returns: Response to a download request, normally nothing. """ + if isinstance(source_file, str): + source_file_path = str(source_file) source_len = os.stat(source_file).st_size - source_file = open(source_file) + with open(source_file_path, 'rb') as fh: + source_file = BytesIO(fh.read()) - with source_file: - if source_len == 0: - # Fall back to storing it all in memory :( - data = source_file.read() - source_file = io.BytesIO(data.encode('utf8')) - source_len = len(data) + if not source_len: + source_len = len(source_file) - self._protocol.SendCommand(b'download', b'%08x' % source_len) - return self._protocol.HandleDataSending( - source_file, source_len, info_cb, progress_callback=progress_callback) + self._protocol.SendCommand(b'download', b'%08x' % source_len) + return self._protocol.HandleDataSending( + source_file, source_len, info_cb, progress_callback=progress_callback) def Flash(self, partition, timeout_ms=0, info_cb=DEFAULT_MESSAGE_CALLBACK): """Flashes the last downloaded file to the given partition. @@ -396,3 +406,17 @@ def Reboot(self, target_mode=b'', timeout_ms=None): def RebootBootloader(self, timeout_ms=None): """Reboots into the bootloader, usually equiv to Reboot('bootloader').""" return self._SimpleCommand(b'reboot-bootloader', timeout_ms=timeout_ms) + + def Boot(self, source_file): + """ + Fastboot boot image by sending image from local file system then issuing the boot command + + :param source_file: + :return: + """ + + if not os.path.exists(source_file): + raise ValueError("source_file must exist") + + self.Download(source_file) + self._SimpleCommand(b'boot') diff --git a/adb/fastboot_debug.py b/adb/fastboot_debug.py index e168f69..7d83c68 100755 --- a/adb/fastboot_debug.py +++ b/adb/fastboot_debug.py @@ -86,6 +86,9 @@ def main(): subparsers, parents, fastboot.FastbootCommands.Oem) common_cli.MakeSubparser( subparsers, parents, fastboot.FastbootCommands.Reboot) + common_cli.MakeSubparser( + subparsers, parents, fastboot.FastbootCommands.Boot, + {'source_file': 'Image file on the host to push and boot'}) if len(sys.argv) == 1: parser.print_help() From 9689e500e5de765c9d4ba2d7459304d11ff4cd49 Mon Sep 17 00:00:00 2001 From: greateggsgreg Date: Tue, 23 Jul 2019 06:07:32 -0700 Subject: [PATCH 07/10] bug fixes --- adb/fastboot.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/adb/fastboot.py b/adb/fastboot.py index fcfb6a9..d1e8cfd 100644 --- a/adb/fastboot.py +++ b/adb/fastboot.py @@ -328,6 +328,9 @@ def Download(self, source_file, source_len=0, with open(source_file_path, 'rb') as fh: source_file = BytesIO(fh.read()) + elif isinstance(source_file, StringIO): + source_file = BytesIO(source_file.read()) + if not source_len: source_len = len(source_file) From e1db3c29235fc6702470d471d2186a8b58edb637 Mon Sep 17 00:00:00 2001 From: greateggsgreg Date: Tue, 23 Jul 2019 06:07:50 -0700 Subject: [PATCH 08/10] bug fixes --- adb/fastboot.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/adb/fastboot.py b/adb/fastboot.py index d1e8cfd..7665000 100644 --- a/adb/fastboot.py +++ b/adb/fastboot.py @@ -19,7 +19,7 @@ import logging import os import struct -from io import BytesIO +from io import BytesIO, StringIO from adb import common from adb import usb_exceptions @@ -100,9 +100,9 @@ def HandleSimpleResponses( info_cb: Optional callback for text sent from the bootloader. Returns: - Tuple - OKAY packet's message, List of preceding Fastboot Messages + OKAY packet's message """ - return self._AcceptResponses(b'OKAY', info_cb, timeout_ms=timeout_ms) + return self._AcceptResponses(b'OKAY', info_cb, timeout_ms=timeout_ms)[0] def HandleDataSending(self, source_file, source_len, info_cb=DEFAULT_MESSAGE_CALLBACK, @@ -336,7 +336,7 @@ def Download(self, source_file, source_len=0, self._protocol.SendCommand(b'download', b'%08x' % source_len) return self._protocol.HandleDataSending( - source_file, source_len, info_cb, progress_callback=progress_callback) + source_file, source_len, info_cb, progress_callback=progress_callback)[0] def Flash(self, partition, timeout_ms=0, info_cb=DEFAULT_MESSAGE_CALLBACK): """Flashes the last downloaded file to the given partition. From 43ee20516fe9cf7142f35f07d1e1cf22a3cf15e6 Mon Sep 17 00:00:00 2001 From: greateggsgreg Date: Tue, 23 Jul 2019 06:13:27 -0700 Subject: [PATCH 09/10] length fix for stringio --- adb/fastboot.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/adb/fastboot.py b/adb/fastboot.py index 7665000..96dde29 100644 --- a/adb/fastboot.py +++ b/adb/fastboot.py @@ -328,11 +328,13 @@ def Download(self, source_file, source_len=0, with open(source_file_path, 'rb') as fh: source_file = BytesIO(fh.read()) - elif isinstance(source_file, StringIO): - source_file = BytesIO(source_file.read()) - if not source_len: - source_len = len(source_file) + if isinstance(source_file, StringIO): + source_file.seek(0, os.SEEK_END) + source_len = source_file.tell() + source_file.seek(0) + else: + source_len = len(source_file) self._protocol.SendCommand(b'download', b'%08x' % source_len) return self._protocol.HandleDataSending( From 4e29501e9034e6dd542b773eb5e1b454f11881d4 Mon Sep 17 00:00:00 2001 From: greateggsgreg Date: Thu, 19 Sep 2019 09:35:25 -0400 Subject: [PATCH 10/10] Comment style changes requested in PR review --- adb/fastboot.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/adb/fastboot.py b/adb/fastboot.py index 96dde29..a83684d 100644 --- a/adb/fastboot.py +++ b/adb/fastboot.py @@ -124,7 +124,7 @@ def HandleDataSending(self, source_file, source_len, FastbootInvalidResponse: Fastboot responded with an unknown packet type. Returns: - Tuple - OKAY packet's message, List of preceding Fastboot Messages + tuple (OKAY packet's message, List of preceding Fastboot Messages) """ accepted_size, _msgs = self._AcceptResponses( b'DATA', info_cb, timeout_ms=timeout_ms) @@ -152,7 +152,7 @@ def _AcceptResponses(self, expected_header, info_cb, timeout_ms=None): FastbootInvalidResponse: Fastboot responded with an unknown packet type. Returns: - Tuple - OKAY packet's message, List of preceding Fastboot Messages + tuple (OKAY packet's message, List of preceding Fastboot Messages) """ messages = [] @@ -413,11 +413,13 @@ def RebootBootloader(self, timeout_ms=None): return self._SimpleCommand(b'reboot-bootloader', timeout_ms=timeout_ms) def Boot(self, source_file): - """ - Fastboot boot image by sending image from local file system then issuing the boot command + """Fastboot boot image by sending image from local file system then issuing the boot command + + Args: + source_file: String file path to the image to send and boot - :param source_file: - :return: + Returns: + None """ if not os.path.exists(source_file):