From 97b715881b739e5cdd4c475bafdf34a01c20c25a Mon Sep 17 00:00:00 2001 From: Conor Patrick Date: Tue, 4 Dec 2018 01:30:53 -0500 Subject: [PATCH] boot directly to solo bootloader for solo hacker --- fido2/ctaphid.c | 12 + fido2/ctaphid.h | 1 + fido2/device.h | 3 +- targets/stm32l442/bootloader/bootloader.c | 1 + targets/stm32l442/bootloader/bootloader.h | 3 + targets/stm32l442/bootloader/main.c | 10 + targets/stm32l442/lib/stm32l4xx_ll_iwdg.h | 361 ++++++++++++++++++++++ targets/stm32l442/src/device.c | 22 +- tools/programmer.py | 75 ++++- 9 files changed, 475 insertions(+), 13 deletions(-) create mode 100644 targets/stm32l442/lib/stm32l4xx_ll_iwdg.h diff --git a/fido2/ctaphid.c b/fido2/ctaphid.c index f586764..7946db6 100644 --- a/fido2/ctaphid.c +++ b/fido2/ctaphid.c @@ -699,6 +699,18 @@ uint8_t ctaphid_handle_packet(uint8_t * pkt_raw) ctaphid_write(&wb, NULL, 0); is_busy = 0; break; +#endif +#if defined(SOLO_HACKER) + case CTAPHID_ENTERBOOT: + printf1(TAG_HID,"CTAPHID_ENTERBOOT\n"); + boot_solo_bootloader(); + ctaphid_write_buffer_init(&wb); + wb.cid = cid; + wb.cmd = CTAPHID_ENTERBOOT; + wb.bcnt = 0; + ctaphid_write(&wb, NULL, 0); + is_busy = 0; + break; #endif default: printf2(TAG_ERR,"error, unimplemented HID cmd: %02x\r\n", buffer_cmd()); diff --git a/fido2/ctaphid.h b/fido2/ctaphid.h index 4336062..5683dbf 100644 --- a/fido2/ctaphid.h +++ b/fido2/ctaphid.h @@ -40,6 +40,7 @@ // Custom commands between 0x40-0x7f #define CTAPHID_BOOT (TYPE_INIT | 0x50) +#define CTAPHID_ENTERBOOT (TYPE_INIT | 0x51) #define ERR_INVALID_CMD 0x01 #define ERR_INVALID_PAR 0x02 diff --git a/fido2/device.h b/fido2/device.h index 8d78df9..83b7f63 100644 --- a/fido2/device.h +++ b/fido2/device.h @@ -94,7 +94,8 @@ void ctap_store_rk(int index,CTAP_residentKey * rk); void ctap_load_rk(int index,CTAP_residentKey * rk); void ctap_overwrite_rk(int index,CTAP_residentKey * rk); - +// For Solo hacker +void boot_solo_bootloader(); diff --git a/targets/stm32l442/bootloader/bootloader.c b/targets/stm32l442/bootloader/bootloader.c index ca9610d..f157ec6 100644 --- a/targets/stm32l442/bootloader/bootloader.c +++ b/targets/stm32l442/bootloader/bootloader.c @@ -12,6 +12,7 @@ #include "ctap_errors.h" #include "log.h" + extern uint8_t REBOOT_FLAG; typedef enum diff --git a/targets/stm32l442/bootloader/bootloader.h b/targets/stm32l442/bootloader/bootloader.h index d9b396a..412a5a2 100644 --- a/targets/stm32l442/bootloader/bootloader.h +++ b/targets/stm32l442/bootloader/bootloader.h @@ -13,6 +13,9 @@ #define IS_BOOTLOADER 1 + +#define SOLO_HACKER + #define ENABLE_U2F_EXTENSIONS // #define ENABLE_U2F diff --git a/targets/stm32l442/bootloader/main.c b/targets/stm32l442/bootloader/main.c index 0a2e4ea..f5aee0c 100644 --- a/targets/stm32l442/bootloader/main.c +++ b/targets/stm32l442/bootloader/main.c @@ -31,6 +31,7 @@ #include "log.h" #include "ctap.h" #include "app.h" +#include "stm32l4xx_ll_rcc.h" #include "stm32l4xx.h" @@ -92,6 +93,14 @@ int main(int argc, char * argv[]) } } +#ifdef SOLO_HACKER + if ( RCC->CSR & (1<<29) )// check if there was independent watchdog reset + { + RCC->CSR |= (1<<23); // clear reset flags + goto start_bootloader; + } +#endif + if (boot && is_authorized_to_boot()) { BOOT_boot(); @@ -100,6 +109,7 @@ int main(int argc, char * argv[]) { printf1(TAG_RED,"Not authorized to boot\r\n"); } + start_bootloader: usbhid_init(); printf1(TAG_GEN,"init usb\n"); diff --git a/targets/stm32l442/lib/stm32l4xx_ll_iwdg.h b/targets/stm32l442/lib/stm32l4xx_ll_iwdg.h new file mode 100644 index 0000000..6fc7ee2 --- /dev/null +++ b/targets/stm32l442/lib/stm32l4xx_ll_iwdg.h @@ -0,0 +1,361 @@ +/** + ****************************************************************************** + * @file stm32l4xx_ll_iwdg.h + * @author MCD Application Team + * @brief Header file of IWDG LL module. + ****************************************************************************** + * @attention + * + *

© COPYRIGHT(c) 2017 STMicroelectronics

+ * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of STMicroelectronics nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************** + */ + +/* Define to prevent recursive inclusion -------------------------------------*/ +#ifndef __STM32L4xx_LL_IWDG_H +#define __STM32L4xx_LL_IWDG_H + +#ifdef __cplusplus +extern "C" { +#endif + +/* Includes ------------------------------------------------------------------*/ +#include "stm32l4xx.h" + +/** @addtogroup STM32L4xx_LL_Driver + * @{ + */ + +#if defined(IWDG) + +/** @defgroup IWDG_LL IWDG + * @{ + */ + +/* Private types -------------------------------------------------------------*/ +/* Private variables ---------------------------------------------------------*/ + +/* Private constants ---------------------------------------------------------*/ +/** @defgroup IWDG_LL_Private_Constants IWDG Private Constants + * @{ + */ + +#define LL_IWDG_KEY_RELOAD 0x0000AAAAU /*!< IWDG Reload Counter Enable */ +#define LL_IWDG_KEY_ENABLE 0x0000CCCCU /*!< IWDG Peripheral Enable */ +#define LL_IWDG_KEY_WR_ACCESS_ENABLE 0x00005555U /*!< IWDG KR Write Access Enable */ +#define LL_IWDG_KEY_WR_ACCESS_DISABLE 0x00000000U /*!< IWDG KR Write Access Disable */ + +/** + * @} + */ + +/* Private macros ------------------------------------------------------------*/ + +/* Exported types ------------------------------------------------------------*/ +/* Exported constants --------------------------------------------------------*/ +/** @defgroup IWDG_LL_Exported_Constants IWDG Exported Constants + * @{ + */ + +/** @defgroup IWDG_LL_EC_GET_FLAG Get Flags Defines + * @brief Flags defines which can be used with LL_IWDG_ReadReg function + * @{ + */ +#define LL_IWDG_SR_PVU IWDG_SR_PVU /*!< Watchdog prescaler value update */ +#define LL_IWDG_SR_RVU IWDG_SR_RVU /*!< Watchdog counter reload value update */ +#define LL_IWDG_SR_WVU IWDG_SR_WVU /*!< Watchdog counter window value update */ + +/** + * @} + */ + +/** @defgroup IWDG_LL_EC_PRESCALER Prescaler Divider + * @{ + */ +#define LL_IWDG_PRESCALER_4 0x00000000U /*!< Divider by 4 */ +#define LL_IWDG_PRESCALER_8 (IWDG_PR_PR_0) /*!< Divider by 8 */ +#define LL_IWDG_PRESCALER_16 (IWDG_PR_PR_1) /*!< Divider by 16 */ +#define LL_IWDG_PRESCALER_32 (IWDG_PR_PR_1 | IWDG_PR_PR_0) /*!< Divider by 32 */ +#define LL_IWDG_PRESCALER_64 (IWDG_PR_PR_2) /*!< Divider by 64 */ +#define LL_IWDG_PRESCALER_128 (IWDG_PR_PR_2 | IWDG_PR_PR_0) /*!< Divider by 128 */ +#define LL_IWDG_PRESCALER_256 (IWDG_PR_PR_2 | IWDG_PR_PR_1) /*!< Divider by 256 */ +/** + * @} + */ + +/** + * @} + */ + +/* Exported macro ------------------------------------------------------------*/ +/** @defgroup IWDG_LL_Exported_Macros IWDG Exported Macros + * @{ + */ + +/** @defgroup IWDG_LL_EM_WRITE_READ Common Write and read registers Macros + * @{ + */ + +/** + * @brief Write a value in IWDG register + * @param __INSTANCE__ IWDG Instance + * @param __REG__ Register to be written + * @param __VALUE__ Value to be written in the register + * @retval None + */ +#define LL_IWDG_WriteReg(__INSTANCE__, __REG__, __VALUE__) WRITE_REG(__INSTANCE__->__REG__, (__VALUE__)) + +/** + * @brief Read a value in IWDG register + * @param __INSTANCE__ IWDG Instance + * @param __REG__ Register to be read + * @retval Register value + */ +#define LL_IWDG_ReadReg(__INSTANCE__, __REG__) READ_REG(__INSTANCE__->__REG__) +/** + * @} + */ + +/** + * @} + */ + + +/* Exported functions --------------------------------------------------------*/ +/** @defgroup IWDG_LL_Exported_Functions IWDG Exported Functions + * @{ + */ +/** @defgroup IWDG_LL_EF_Configuration Configuration + * @{ + */ + +/** + * @brief Start the Independent Watchdog + * @note Except if the hardware watchdog option is selected + * @rmtoll KR KEY LL_IWDG_Enable + * @param IWDGx IWDG Instance + * @retval None + */ +__STATIC_INLINE void LL_IWDG_Enable(IWDG_TypeDef *IWDGx) +{ + WRITE_REG(IWDGx->KR, LL_IWDG_KEY_ENABLE); +} + +/** + * @brief Reloads IWDG counter with value defined in the reload register + * @rmtoll KR KEY LL_IWDG_ReloadCounter + * @param IWDGx IWDG Instance + * @retval None + */ +__STATIC_INLINE void LL_IWDG_ReloadCounter(IWDG_TypeDef *IWDGx) +{ + WRITE_REG(IWDGx->KR, LL_IWDG_KEY_RELOAD); +} + +/** + * @brief Enable write access to IWDG_PR, IWDG_RLR and IWDG_WINR registers + * @rmtoll KR KEY LL_IWDG_EnableWriteAccess + * @param IWDGx IWDG Instance + * @retval None + */ +__STATIC_INLINE void LL_IWDG_EnableWriteAccess(IWDG_TypeDef *IWDGx) +{ + WRITE_REG(IWDGx->KR, LL_IWDG_KEY_WR_ACCESS_ENABLE); +} + +/** + * @brief Disable write access to IWDG_PR, IWDG_RLR and IWDG_WINR registers + * @rmtoll KR KEY LL_IWDG_DisableWriteAccess + * @param IWDGx IWDG Instance + * @retval None + */ +__STATIC_INLINE void LL_IWDG_DisableWriteAccess(IWDG_TypeDef *IWDGx) +{ + WRITE_REG(IWDGx->KR, LL_IWDG_KEY_WR_ACCESS_DISABLE); +} + +/** + * @brief Select the prescaler of the IWDG + * @rmtoll PR PR LL_IWDG_SetPrescaler + * @param IWDGx IWDG Instance + * @param Prescaler This parameter can be one of the following values: + * @arg @ref LL_IWDG_PRESCALER_4 + * @arg @ref LL_IWDG_PRESCALER_8 + * @arg @ref LL_IWDG_PRESCALER_16 + * @arg @ref LL_IWDG_PRESCALER_32 + * @arg @ref LL_IWDG_PRESCALER_64 + * @arg @ref LL_IWDG_PRESCALER_128 + * @arg @ref LL_IWDG_PRESCALER_256 + * @retval None + */ +__STATIC_INLINE void LL_IWDG_SetPrescaler(IWDG_TypeDef *IWDGx, uint32_t Prescaler) +{ + WRITE_REG(IWDGx->PR, IWDG_PR_PR & Prescaler); +} + +/** + * @brief Get the selected prescaler of the IWDG + * @rmtoll PR PR LL_IWDG_GetPrescaler + * @param IWDGx IWDG Instance + * @retval Returned value can be one of the following values: + * @arg @ref LL_IWDG_PRESCALER_4 + * @arg @ref LL_IWDG_PRESCALER_8 + * @arg @ref LL_IWDG_PRESCALER_16 + * @arg @ref LL_IWDG_PRESCALER_32 + * @arg @ref LL_IWDG_PRESCALER_64 + * @arg @ref LL_IWDG_PRESCALER_128 + * @arg @ref LL_IWDG_PRESCALER_256 + */ +__STATIC_INLINE uint32_t LL_IWDG_GetPrescaler(IWDG_TypeDef *IWDGx) +{ + return (READ_REG(IWDGx->PR)); +} + +/** + * @brief Specify the IWDG down-counter reload value + * @rmtoll RLR RL LL_IWDG_SetReloadCounter + * @param IWDGx IWDG Instance + * @param Counter Value between Min_Data=0 and Max_Data=0x0FFF + * @retval None + */ +__STATIC_INLINE void LL_IWDG_SetReloadCounter(IWDG_TypeDef *IWDGx, uint32_t Counter) +{ + WRITE_REG(IWDGx->RLR, IWDG_RLR_RL & Counter); +} + +/** + * @brief Get the specified IWDG down-counter reload value + * @rmtoll RLR RL LL_IWDG_GetReloadCounter + * @param IWDGx IWDG Instance + * @retval Value between Min_Data=0 and Max_Data=0x0FFF + */ +__STATIC_INLINE uint32_t LL_IWDG_GetReloadCounter(IWDG_TypeDef *IWDGx) +{ + return (READ_REG(IWDGx->RLR)); +} + +/** + * @brief Specify high limit of the window value to be compared to the down-counter. + * @rmtoll WINR WIN LL_IWDG_SetWindow + * @param IWDGx IWDG Instance + * @param Window Value between Min_Data=0 and Max_Data=0x0FFF + * @retval None + */ +__STATIC_INLINE void LL_IWDG_SetWindow(IWDG_TypeDef *IWDGx, uint32_t Window) +{ + WRITE_REG(IWDGx->WINR, IWDG_WINR_WIN & Window); +} + +/** + * @brief Get the high limit of the window value specified. + * @rmtoll WINR WIN LL_IWDG_GetWindow + * @param IWDGx IWDG Instance + * @retval Value between Min_Data=0 and Max_Data=0x0FFF + */ +__STATIC_INLINE uint32_t LL_IWDG_GetWindow(IWDG_TypeDef *IWDGx) +{ + return (READ_REG(IWDGx->WINR)); +} + +/** + * @} + */ + +/** @defgroup IWDG_LL_EF_FLAG_Management FLAG_Management + * @{ + */ + +/** + * @brief Check if flag Prescaler Value Update is set or not + * @rmtoll SR PVU LL_IWDG_IsActiveFlag_PVU + * @param IWDGx IWDG Instance + * @retval State of bit (1 or 0). + */ +__STATIC_INLINE uint32_t LL_IWDG_IsActiveFlag_PVU(IWDG_TypeDef *IWDGx) +{ + return (uint32_t)(READ_BIT(IWDGx->SR, IWDG_SR_PVU) == (IWDG_SR_PVU)); +} + +/** + * @brief Check if flag Reload Value Update is set or not + * @rmtoll SR RVU LL_IWDG_IsActiveFlag_RVU + * @param IWDGx IWDG Instance + * @retval State of bit (1 or 0). + */ +__STATIC_INLINE uint32_t LL_IWDG_IsActiveFlag_RVU(IWDG_TypeDef *IWDGx) +{ + return (uint32_t)(READ_BIT(IWDGx->SR, IWDG_SR_RVU) == (IWDG_SR_RVU)); +} + +/** + * @brief Check if flag Window Value Update is set or not + * @rmtoll SR WVU LL_IWDG_IsActiveFlag_WVU + * @param IWDGx IWDG Instance + * @retval State of bit (1 or 0). + */ +__STATIC_INLINE uint32_t LL_IWDG_IsActiveFlag_WVU(IWDG_TypeDef *IWDGx) +{ + return (uint32_t)(READ_BIT(IWDGx->SR, IWDG_SR_WVU) == (IWDG_SR_WVU)); +} + +/** + * @brief Check if all flags Prescaler, Reload & Window Value Update are reset or not + * @rmtoll SR PVU LL_IWDG_IsReady\n + * SR WVU LL_IWDG_IsReady\n + * SR RVU LL_IWDG_IsReady + * @param IWDGx IWDG Instance + * @retval State of bits (1 or 0). + */ +__STATIC_INLINE uint32_t LL_IWDG_IsReady(IWDG_TypeDef *IWDGx) +{ + return (uint32_t)(READ_BIT(IWDGx->SR, IWDG_SR_PVU | IWDG_SR_RVU | IWDG_SR_WVU) == 0U); +} + +/** + * @} + */ + + +/** + * @} + */ + +/** + * @} + */ + +#endif /* IWDG) */ + +/** + * @} + */ + +#ifdef __cplusplus +} +#endif + +#endif /* __STM32L4xx_LL_IWDG_H */ + +/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/ diff --git a/targets/stm32l442/src/device.c b/targets/stm32l442/src/device.c index 80ba742..746ed37 100644 --- a/targets/stm32l442/src/device.c +++ b/targets/stm32l442/src/device.c @@ -19,7 +19,7 @@ #include "ctap.h" #include "crypto.h" #include "memory_layout.h" - +#include "stm32l4xx_ll_iwdg.h" @@ -393,8 +393,10 @@ led_rgb(0x001040); delay(50); +#if SKIP_BUTTON_CHECK_FAST done: return 1; +#endif fail: return 0; @@ -484,7 +486,25 @@ void ctap_overwrite_rk(int index,CTAP_residentKey * rk) } } +void boot_solo_bootloader() +{ + LL_IWDG_Enable(IWDG); + LL_IWDG_EnableWriteAccess(IWDG); + + LL_IWDG_SetPrescaler(IWDG, LL_IWDG_PRESCALER_4); + + LL_IWDG_SetWindow(IWDG, 4095); + + LL_IWDG_SetReloadCounter(IWDG, 2000); // ~0.25s + + while (LL_IWDG_IsReady(IWDG) != 1) + { + } + + LL_IWDG_ReloadCounter(IWDG); + +} void _Error_Handler(char *file, int line) { diff --git a/tools/programmer.py b/tools/programmer.py index 81204a7..5b53492 100644 --- a/tools/programmer.py +++ b/tools/programmer.py @@ -9,7 +9,7 @@ from binascii import hexlify from fido2.hid import CtapHidDevice, CTAPHID from fido2.client import Fido2Client, ClientError from fido2.ctap import CtapError -from fido2.ctap1 import CTAP1 +from fido2.ctap1 import CTAP1, ApduError from fido2.utils import Timeout from intelhex import IntelHex @@ -23,7 +23,8 @@ class SoloBootloader: erase = 0x43 version = 0x44 - HIDCommand = 0x50 + HIDCommandBoot = 0x50 + HIDCommandEnterBoot = 0x51 TAG = b'\x8C\x27\x90\xf6' @@ -72,7 +73,7 @@ class Programmer(): def exchange_hid(self,cmd,addr=0,data=b'A'*16): req = Programmer.format_request(cmd,addr,data) - data = self.send_data_hid(SoloBootloader.HIDCommand, req) + data = self.send_data_hid(SoloBootloader.HIDCommandBoot, req) ret = data[0] if ret != CtapError.ERR.SUCCESS: @@ -110,9 +111,20 @@ class Programmer(): """ self.exchange(SoloBootloader.done,0,sig) + def enter_solo_bootloader(self,): + """ + If solo is configured as solo hacker or something similar, + this command will tell the token to boot directly to the bootloader + so it can be reprogrammed + """ + if self.exchange != self.exchange_hid: + self.send_data_hid(CTAPHID.INIT, '\x11\x11\x11\x11\x11\x11\x11\x11') + self.send_data_hid(SoloBootloader.HIDCommandEnterBoot, '') + def program_file(self,name): - data = json.loads(open(name,'r').read()) + if name.lower().endswith('.json'): + data = json.loads(open(name,'r').read()) fw = base64.b64decode(from_websafe(data['firmware']).encode()) sig = base64.b64decode(from_websafe(data['signature']).encode()) ih = IntelHex() @@ -126,7 +138,7 @@ class Programmer(): print('Warning, assuming "%s" is an Intel Hex file.' % name) sig = None ih = IntelHex() - ih.fromfile(tmp.name, format='hex') + ih.fromfile(name, format='hex') if self.exchange == self.exchange_hid: chunk = 2048 @@ -151,12 +163,40 @@ class Programmer(): print('time: %.2f s' % ((t2-t1)/1000.0)) print('Verifying...') - if sig is not None: - self.verify_flash(sig) + if self.reboot: + if sig is not None: + self.verify_flash(sig) + else: + self.verify_flash(b'A'*64) + +def attempt_to_find_device(p): + found = False + for i in range(0,5): + try: + p.find_device() + found = True + break + except RuntimeError: + time.sleep(0.2) + return found + +def attempt_to_boot_bootloader(p): + print('Bootloader not active. Attempting to boot into bootloader mode...') + try: + p.enter_solo_bootloader() + except OSError: + pass + except CtapError as e: + if e.code == CtapError.ERR.INVALID_COMMAND: + print('Solo appears to not be a solo hacker. Try holding down the button for 2 while you plug token in.') + sys.exit(1) else: - self.verify_flash(b'A'*64) - - + raise(e) + print('Solo rebooted. Reconnecting...') + time.sleep(.500) + if not attempt_to_find_device(p): + print('Failed to reconnect!') + sys.exit(1) if __name__ == '__main__': @@ -166,6 +206,7 @@ if __name__ == '__main__': parser.add_argument("--use-u2f", action="store_true", help = 'Programs using U2F authenticate. This is what a web application will use.') parser.add_argument("--no-reset", action="store_true", help = 'Don\'t reset after writing firmware. Stay in bootloader mode.') parser.add_argument("--reset-only", action="store_true", help = 'Don\'t write anything, try to boot without a signature.') + parser.add_argument("--enter-bootloader", action="store_true", help = 'Don\'t write anything, try to enter bootloader. Typically only supported by Solo Hacker builds.') args = parser.parse_args() print() @@ -178,7 +219,19 @@ if __name__ == '__main__': if args.no_reset: p.set_reboot(False) - print('version is ', p.version()) + if args.enter_bootloader: + attempt_to_boot_bootloader(p) + sys.exit(0) + + try: + print('version is ', p.version()) + except CtapError as e: + if e.code == CtapError.ERR.INVALID_COMMAND: + attempt_to_boot_bootloader(p) + else: + raise(e) + except ApduError: + attempt_to_boot_bootloader(p) if not args.reset_only: p.program_file(args.__dict__[''])