From 37c61c1381bf9f060687e6f7eea503ae77acb9ba Mon Sep 17 00:00:00 2001 From: matejcik Date: Tue, 3 May 2022 11:34:34 +0200 Subject: [PATCH] feat(core): implement firmware dumping --- common/protob/messages-management.proto | 22 +++++++++ common/protob/messages.proto | 3 ++ .../extmod/modtrezorutils/modtrezorutils.c | 48 +++++++++++++++++++ core/mocks/generated/trezorutils.pyi | 15 ++++++ core/src/all_modules.py | 2 + core/src/apps/misc/get_firmware.py | 47 ++++++++++++++++++ core/src/apps/workflow_handlers.py | 2 + core/src/trezor/enums/MessageType.py | 3 ++ core/src/trezor/enums/__init__.py | 3 ++ core/src/trezor/messages.py | 26 ++++++++++ core/src/trezor/utils.py | 3 ++ python/src/trezorlib/messages.py | 25 ++++++++++ 12 files changed, 199 insertions(+) create mode 100644 core/src/apps/misc/get_firmware.py diff --git a/common/protob/messages-management.proto b/common/protob/messages-management.proto index ae19c4c67..8cab05d26 100644 --- a/common/protob/messages-management.proto +++ b/common/protob/messages-management.proto @@ -252,6 +252,28 @@ message FirmwareHash { required bytes hash = 1; } +/** + * Request: get firmware image. The firmware will send all chunks in sequence. + * @start + * @next FirmwareChunk + */ +message GetFirmware {} + +/** + * Response: firmware chunk. + * @next FirmwareChunkAck + */ +message FirmwareChunk { + required bytes chunk = 1; +} + +/** + * Request: acknowledge firmware chunk. + * @next FirmwareChunk + * @next Success + */ +message FirmwareChunkAck {} + /** * Request: Request device to wipe all sensitive data and settings * @start diff --git a/common/protob/messages.proto b/common/protob/messages.proto index a8967c01b..c1abf0b99 100644 --- a/common/protob/messages.proto +++ b/common/protob/messages.proto @@ -116,6 +116,9 @@ enum MessageType { MessageType_RebootToBootloader = 87 [(bitcoin_only) = true, (wire_in) = true]; MessageType_GetFirmwareHash = 88 [(bitcoin_only) = true, (wire_in) = true]; MessageType_FirmwareHash = 89 [(bitcoin_only) = true, (wire_out) = true]; + MessageType_GetFirmware = 90 [(bitcoin_only) = true, (wire_in) = true]; + MessageType_FirmwareChunk = 91 [(bitcoin_only) = true, (wire_out) = true]; + MessageType_FirmwareChunkAck = 92 [(bitcoin_only) = true, (wire_in) = true]; MessageType_SetU2FCounter = 63 [(wire_in) = true]; MessageType_GetNextU2FCounter = 80 [(wire_in) = true]; diff --git a/core/embed/extmod/modtrezorutils/modtrezorutils.c b/core/embed/extmod/modtrezorutils/modtrezorutils.c index 3b665454a..41d203906 100644 --- a/core/embed/extmod/modtrezorutils/modtrezorutils.c +++ b/core/embed/extmod/modtrezorutils/modtrezorutils.c @@ -205,6 +205,46 @@ STATIC mp_obj_t mod_trezorutils_firmware_vendor(void) { STATIC MP_DEFINE_CONST_FUN_OBJ_0(mod_trezorutils_firmware_vendor_obj, mod_trezorutils_firmware_vendor); +/// def firmware_sector_size(sector: int) -> int: +/// """ +/// Returns the size of the firmware sector. +/// """ +STATIC mp_obj_t mod_trezorutils_firmware_sector_size(mp_obj_t sector) { + mp_uint_t sector_id = trezor_obj_get_uint(sector); + if (sector_id >= FIRMWARE_SECTORS_COUNT) { + mp_raise_msg(&mp_type_ValueError, "Invalid sector."); + } + return mp_obj_new_int(flash_sector_size(FIRMWARE_SECTORS[sector_id])); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_trezorutils_firmware_sector_size_obj, + mod_trezorutils_firmware_sector_size); + +/// def get_firmware_chunk(index: int, offset: int, buffer: bytearray) -> None: +/// """ +/// Reads a chunk of the firmware into `buffer`. +/// """ +STATIC mp_obj_t mod_trezorutils_get_firmware_chunk(const mp_obj_t index_obj, + const mp_obj_t offset_obj, + const mp_obj_t buffer) { + mp_uint_t index = trezor_obj_get_uint(index_obj); + if (index >= FIRMWARE_SECTORS_COUNT) { + mp_raise_msg(&mp_type_ValueError, "Invalid sector."); + } + int sector = FIRMWARE_SECTORS[index]; + mp_uint_t offset = trezor_obj_get_uint(offset_obj); + mp_buffer_info_t buf = {0}; + mp_get_buffer_raise(buffer, &buf, MP_BUFFER_WRITE); + const void *data = flash_get_address(sector, offset, buf.len); + if (data == NULL) { + mp_raise_msg(&mp_type_ValueError, "Invalid read."); + } + memcpy(buf.buf, data, buf.len); + + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_3(mod_trezorutils_get_firmware_chunk_obj, + mod_trezorutils_get_firmware_chunk); + STATIC mp_obj_str_t mod_trezorutils_revision_obj = { {&mp_type_bytes}, 0, sizeof(SCM_REVISION) - 1, (const byte *)SCM_REVISION}; @@ -215,6 +255,7 @@ STATIC mp_obj_str_t mod_trezorutils_revision_obj = { /// MODEL: str /// EMULATOR: bool /// BITCOIN_ONLY: bool +/// FIRMWARE_SECTORS_COUNT: int STATIC const mp_rom_map_elem_t mp_module_trezorutils_globals_table[] = { {MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_trezorutils)}, @@ -225,6 +266,13 @@ STATIC const mp_rom_map_elem_t mp_module_trezorutils_globals_table[] = { MP_ROM_PTR(&mod_trezorutils_firmware_hash_obj)}, {MP_ROM_QSTR(MP_QSTR_firmware_vendor), MP_ROM_PTR(&mod_trezorutils_firmware_vendor_obj)}, + {MP_ROM_QSTR(MP_QSTR_get_firmware_chunk), + MP_ROM_PTR(&mod_trezorutils_get_firmware_chunk_obj)}, + {MP_ROM_QSTR(MP_QSTR_firmware_sector_size), + MP_ROM_PTR(&mod_trezorutils_firmware_sector_size_obj)}, + {MP_ROM_QSTR(MP_QSTR_FIRMWARE_SECTORS_COUNT), + MP_ROM_INT(FIRMWARE_SECTORS_COUNT)}, + // various built-in constants {MP_ROM_QSTR(MP_QSTR_SCM_REVISION), MP_ROM_PTR(&mod_trezorutils_revision_obj)}, diff --git a/core/mocks/generated/trezorutils.pyi b/core/mocks/generated/trezorutils.pyi index f587af589..b9252708f 100644 --- a/core/mocks/generated/trezorutils.pyi +++ b/core/mocks/generated/trezorutils.pyi @@ -58,6 +58,20 @@ def firmware_vendor() -> str: """ Returns the firmware vendor string from the vendor header. """ + + +# extmod/modtrezorutils/modtrezorutils.c +def firmware_sector_size(sector: int) -> int: + """ + Returns the size of the firmware sector. + """ + + +# extmod/modtrezorutils/modtrezorutils.c +def get_firmware_chunk(index: int, offset: int, buffer: bytearray) -> None: + """ + Reads a chunk of the firmware into `buffer`. + """ SCM_REVISION: bytes VERSION_MAJOR: int VERSION_MINOR: int @@ -65,3 +79,4 @@ VERSION_PATCH: int MODEL: str EMULATOR: bool BITCOIN_ONLY: bool +FIRMWARE_SECTORS_COUNT: int diff --git a/core/src/all_modules.py b/core/src/all_modules.py index ad48b7d60..163737621 100644 --- a/core/src/all_modules.py +++ b/core/src/all_modules.py @@ -394,6 +394,8 @@ apps.misc.get_ecdh_session_key import apps.misc.get_ecdh_session_key apps.misc.get_entropy import apps.misc.get_entropy +apps.misc.get_firmware +import apps.misc.get_firmware apps.misc.get_firmware_hash import apps.misc.get_firmware_hash apps.misc.sign_identity diff --git a/core/src/apps/misc/get_firmware.py b/core/src/apps/misc/get_firmware.py new file mode 100644 index 000000000..115d67c1f --- /dev/null +++ b/core/src/apps/misc/get_firmware.py @@ -0,0 +1,47 @@ +from micropython import const +from typing import TYPE_CHECKING + +from trezor import utils, wire, workflow +from trezor.messages import FirmwareChunk, FirmwareChunkAck, GetFirmware, Success +from trezor.ui.layouts import confirm_action, draw_simple_text + +from .get_firmware_hash import _render_progress + +if TYPE_CHECKING: + from trezor.wire import Context + +CHUNK_SIZE = const(1024 * 4) +# assuming that all sectors are of size 128 kB +PROGRESS_TOTAL = utils.FIRMWARE_SECTORS_COUNT * 128 * 1024 + + +async def get_firmware(ctx: Context, _msg: GetFirmware) -> Success: + await confirm_action( + ctx, + "dump_firmware", + title="Extract firmware", + action="Do you want to extract device firmware?", + description="Your seed will not be revealed.", + ) + if not utils.DISABLE_ANIMATION: + workflow.close_others() + draw_simple_text("Please wait") + + sector_buffer = bytearray(CHUNK_SIZE) + packet = FirmwareChunk(chunk=sector_buffer) + progress = 0 + _render_progress(progress, PROGRESS_TOTAL) + for i in range(utils.FIRMWARE_SECTORS_COUNT): + size = utils.firmware_sector_size(i) + try: + for ofs in range(0, size, CHUNK_SIZE): + utils.get_firmware_chunk(i, ofs, sector_buffer) + await ctx.call(packet, FirmwareChunkAck) + progress += CHUNK_SIZE + _render_progress(progress, PROGRESS_TOTAL) + # reset progress to known point, in case some sectors are not 128 kB + progress = (i + 1) * 128 * 1024 + _render_progress(progress, PROGRESS_TOTAL) + except ValueError: + raise wire.DataError("Failed to dump firmware.") + return Success(message="Firmware dumped.") diff --git a/core/src/apps/workflow_handlers.py b/core/src/apps/workflow_handlers.py index 901eec887..a76849834 100644 --- a/core/src/apps/workflow_handlers.py +++ b/core/src/apps/workflow_handlers.py @@ -82,6 +82,8 @@ def find_message_handler_module(msg_type: int) -> str: return "apps.misc.cipher_key_value" if msg_type == MessageType.GetFirmwareHash: return "apps.misc.get_firmware_hash" + if msg_type == MessageType.GetFirmware: + return "apps.misc.get_firmware" if not utils.BITCOIN_ONLY: if msg_type == MessageType.SetU2FCounter: diff --git a/core/src/trezor/enums/MessageType.py b/core/src/trezor/enums/MessageType.py index 4509699f7..e818340aa 100644 --- a/core/src/trezor/enums/MessageType.py +++ b/core/src/trezor/enums/MessageType.py @@ -43,6 +43,9 @@ CancelAuthorization = 86 RebootToBootloader = 87 GetFirmwareHash = 88 FirmwareHash = 89 +GetFirmware = 90 +FirmwareChunk = 91 +FirmwareChunkAck = 92 FirmwareErase = 6 FirmwareUpload = 7 FirmwareRequest = 8 diff --git a/core/src/trezor/enums/__init__.py b/core/src/trezor/enums/__init__.py index bc7bc8e0b..5b4b74780 100644 --- a/core/src/trezor/enums/__init__.py +++ b/core/src/trezor/enums/__init__.py @@ -60,6 +60,9 @@ if TYPE_CHECKING: RebootToBootloader = 87 GetFirmwareHash = 88 FirmwareHash = 89 + GetFirmware = 90 + FirmwareChunk = 91 + FirmwareChunkAck = 92 SetU2FCounter = 63 GetNextU2FCounter = 80 NextU2FCounter = 81 diff --git a/core/src/trezor/messages.py b/core/src/trezor/messages.py index 08b5cdba8..f45f9409c 100644 --- a/core/src/trezor/messages.py +++ b/core/src/trezor/messages.py @@ -2218,6 +2218,32 @@ if TYPE_CHECKING: def is_type_of(cls, msg: protobuf.MessageType) -> TypeGuard["FirmwareHash"]: return isinstance(msg, cls) + class GetFirmware(protobuf.MessageType): + + @classmethod + def is_type_of(cls, msg: protobuf.MessageType) -> TypeGuard["GetFirmware"]: + return isinstance(msg, cls) + + class FirmwareChunk(protobuf.MessageType): + chunk: "bytes" + + def __init__( + self, + *, + chunk: "bytes", + ) -> None: + pass + + @classmethod + def is_type_of(cls, msg: protobuf.MessageType) -> TypeGuard["FirmwareChunk"]: + return isinstance(msg, cls) + + class FirmwareChunkAck(protobuf.MessageType): + + @classmethod + def is_type_of(cls, msg: protobuf.MessageType) -> TypeGuard["FirmwareChunkAck"]: + return isinstance(msg, cls) + class WipeDevice(protobuf.MessageType): @classmethod diff --git a/core/src/trezor/utils.py b/core/src/trezor/utils.py index d7d55ddb9..ab8f4ff5c 100644 --- a/core/src/trezor/utils.py +++ b/core/src/trezor/utils.py @@ -3,6 +3,7 @@ import sys from trezorutils import ( # noqa: F401 BITCOIN_ONLY, EMULATOR, + FIRMWARE_SECTORS_COUNT, MODEL, SCM_REVISION, VERSION_MAJOR, @@ -10,7 +11,9 @@ from trezorutils import ( # noqa: F401 VERSION_PATCH, consteq, firmware_hash, + firmware_sector_size, firmware_vendor, + get_firmware_chunk, halt, memcpy, ) diff --git a/python/src/trezorlib/messages.py b/python/src/trezorlib/messages.py index a2e8660f7..b292db97d 100644 --- a/python/src/trezorlib/messages.py +++ b/python/src/trezorlib/messages.py @@ -68,6 +68,9 @@ class MessageType(IntEnum): RebootToBootloader = 87 GetFirmwareHash = 88 FirmwareHash = 89 + GetFirmware = 90 + FirmwareChunk = 91 + FirmwareChunkAck = 92 SetU2FCounter = 63 GetNextU2FCounter = 80 NextU2FCounter = 81 @@ -3517,6 +3520,28 @@ class FirmwareHash(protobuf.MessageType): self.hash = hash +class GetFirmware(protobuf.MessageType): + MESSAGE_WIRE_TYPE = 90 + + +class FirmwareChunk(protobuf.MessageType): + MESSAGE_WIRE_TYPE = 91 + FIELDS = { + 1: protobuf.Field("chunk", "bytes", repeated=False, required=True), + } + + def __init__( + self, + *, + chunk: "bytes", + ) -> None: + self.chunk = chunk + + +class FirmwareChunkAck(protobuf.MessageType): + MESSAGE_WIRE_TYPE = 92 + + class WipeDevice(protobuf.MessageType): MESSAGE_WIRE_TYPE = 5