From 8b65648c00446d7d9699d3a934579875a449fe65 Mon Sep 17 00:00:00 2001 From: Eric Betts Date: Fri, 19 Jul 2024 13:20:59 -0700 Subject: [PATCH] APDURunner --- apdu_log.c | 144 ++++++++++++++++++++++++ apdu_log.h | 63 +++++++++++ apdu_runner.c | 178 ++++++++++++++++++++++++++++++ apdu_runner.h | 10 ++ scenes/seader_scene_apdu_runner.c | 78 +++++++++++++ scenes/seader_scene_config.h | 1 + scenes/seader_scene_sam_present.c | 12 ++ seader_i.h | 9 ++ seader_worker.c | 8 ++ seader_worker.h | 5 + 10 files changed, 508 insertions(+) create mode 100644 apdu_log.c create mode 100644 apdu_log.h create mode 100644 apdu_runner.c create mode 100644 apdu_runner.h create mode 100644 scenes/seader_scene_apdu_runner.c diff --git a/apdu_log.c b/apdu_log.c new file mode 100644 index 00000000000..55ed81116d9 --- /dev/null +++ b/apdu_log.c @@ -0,0 +1,144 @@ +#include "apdu_log.h" + +#include +#include +#include +#include +#include + +#define TAG "APDULog" + +struct APDULog { + Stream* stream; + size_t total_lines; +}; + +static inline void apdu_log_add_ending_new_line(APDULog* instance) { + if(stream_seek(instance->stream, -1, StreamOffsetFromEnd)) { + uint8_t last_char = 0; + + // Check if the last char is new line or add a new line + if(stream_read(instance->stream, &last_char, 1) == 1 && last_char != '\n') { + FURI_LOG_D(TAG, "Adding new line ending"); + stream_write_char(instance->stream, '\n'); + } + + stream_rewind(instance->stream); + } +} + +static bool apdu_log_read_log_line(APDULog* instance, FuriString* line, bool* is_endfile) { + if(stream_read_line(instance->stream, line) == false) { + *is_endfile = true; + } + + else { + size_t newline_index = furi_string_search_char(line, '\n', 0); + + if(newline_index != FURI_STRING_FAILURE) { + furi_string_left(line, newline_index); + } + + FURI_LOG_T( + TAG, "Read line: %s, len: %zu", furi_string_get_cstr(line), furi_string_size(line)); + + return true; + } + + return false; +} + +bool apdu_log_check_presence(const char* path) { + furi_check(path); + + Storage* storage = furi_record_open(RECORD_STORAGE); + + bool log_present = storage_common_stat(storage, path, NULL) == FSE_OK; + + furi_record_close(RECORD_STORAGE); + + return log_present; +} + +APDULog* apdu_log_alloc(const char* path, APDULogMode mode) { + furi_check(path); + + APDULog* instance = malloc(sizeof(APDULog)); + + Storage* storage = furi_record_open(RECORD_STORAGE); + instance->stream = buffered_file_stream_alloc(storage); + + FS_OpenMode open_mode = (mode == APDULogModeOpenAlways) ? FSOM_OPEN_ALWAYS : + FSOM_OPEN_EXISTING; + + instance->total_lines = 0; + + bool file_exists = + buffered_file_stream_open(instance->stream, path, FSAM_READ_WRITE, open_mode); + + if(!file_exists) { + buffered_file_stream_close(instance->stream); + } else { + // Eventually add new line character in the last line to avoid skipping lines + apdu_log_add_ending_new_line(instance); + } + + FuriString* line = furi_string_alloc(); + + bool is_endfile = false; + + // In this loop we only count the entries in the file + // We prefer not to load the whole file in memory for space reasons + while(file_exists && !is_endfile) { + bool read_log = apdu_log_read_log_line(instance, line, &is_endfile); + if(read_log) { + instance->total_lines++; + } + } + stream_rewind(instance->stream); + FURI_LOG_I(TAG, "Loaded log with %zu lines", instance->total_lines); + + furi_string_free(line); + + return instance; +} + +void apdu_log_free(APDULog* instance) { + furi_check(instance); + furi_check(instance->stream); + + buffered_file_stream_close(instance->stream); + stream_free(instance->stream); + free(instance); + + furi_record_close(RECORD_STORAGE); +} + +size_t apdu_log_get_total_lines(APDULog* instance) { + furi_check(instance); + + return instance->total_lines; +} + +bool apdu_log_rewind(APDULog* instance) { + furi_check(instance); + furi_check(instance->stream); + + return stream_rewind(instance->stream); +} + +bool apdu_log_get_next_log_str(APDULog* instance, FuriString* log) { + furi_assert(instance); + furi_assert(instance->stream); + furi_assert(log); + + bool log_read = false; + bool is_endfile = false; + + furi_string_reset(log); + + while(!log_read && !is_endfile) + log_read = apdu_log_read_log_line(instance, log, &is_endfile); + + return log_read; +} diff --git a/apdu_log.h b/apdu_log.h new file mode 100644 index 00000000000..8bf94136102 --- /dev/null +++ b/apdu_log.h @@ -0,0 +1,63 @@ +#pragma once + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + APDULogModeOpenExisting, + APDULogModeOpenAlways, +} APDULogMode; + +typedef struct APDULog APDULog; + +/** Check if the file list exists + * + * @param path - list path + * + * @return true if list exists, false otherwise +*/ +bool apdu_log_check_presence(const char* path); + +/** Open or create list + * Depending on mode, list will be opened or created. + * + * @param path - Path of the file that contain the list + * @param mode - ListKeysMode value + * + * @return Returns APDULog list instance +*/ +APDULog* apdu_log_alloc(const char* path, APDULogMode mode); + +/** Close list + * + * @param instance - APDULog list instance +*/ +void apdu_log_free(APDULog* instance); + +/** Get total number of lines in list + * + * @param instance - APDULog list instance + * + * @return Returns total number of lines in list +*/ +size_t apdu_log_get_total_lines(APDULog* instance); + +/** Rewind list + * + * @param instance - APDULog list instance + * + * @return Returns true if rewind was successful, false otherwise +*/ +bool apdu_log_rewind(APDULog* instance); + +bool apdu_log_get_next_log_str(APDULog* instance, FuriString* log); + +#ifdef __cplusplus +} +#endif diff --git a/apdu_runner.c b/apdu_runner.c new file mode 100644 index 00000000000..330c0997a24 --- /dev/null +++ b/apdu_runner.c @@ -0,0 +1,178 @@ +#include "apdu_runner.h" + +#define TAG "APDU_Runner" + +// Max length of firmware upgrade: 731 bytes +#define SEADER_APDU_MAX_LEN 732 + +void seader_apdu_runner_cleanup(Seader* seader, SeaderWorkerEvent event) { + SeaderWorker* seader_worker = seader->worker; + seader_worker_change_state(seader_worker, SeaderWorkerStateReady); + apdu_log_free(seader->apdu_log); + seader->apdu_log = NULL; + if(seader_worker->callback) { + seader_worker->callback(event, seader_worker->context); + } +} + +bool seader_apdu_runner_send_next_line(Seader* seader) { + SeaderWorker* seader_worker = seader->worker; + SeaderUartBridge* seader_uart = seader_worker->uart; + SeaderAPDURunnerContext* apdu_runner_ctx = &(seader->apdu_runner_ctx); + + FuriString* line = furi_string_alloc(); + apdu_log_get_next_log_str(seader->apdu_log, line); + + size_t len = furi_string_size(line) / 2; // String is in HEX, divide by 2 for bytes + uint8_t* apdu = malloc(len); + if(apdu == NULL) { + FURI_LOG_E(TAG, "Failed to allocate memory for APDU"); + seader_apdu_runner_cleanup(seader, SeaderWorkerEventAPDURunnerError); + return false; + } + + if(len > SEADER_UART_RX_BUF_SIZE) { + FURI_LOG_E(TAG, "APDU length is too long"); + seader_apdu_runner_cleanup(seader, SeaderWorkerEventAPDURunnerError); + free(apdu); + return false; + } + + if(!hex_chars_to_uint8(furi_string_get_cstr(line), apdu)) { + FURI_LOG_E(TAG, "Failed to convert line to number"); + seader_apdu_runner_cleanup(seader, SeaderWorkerEventAPDURunnerError); + free(apdu); + return false; + } + FURI_LOG_I( + TAG, + "APDU Runner => (%d/%d): %s", + apdu_runner_ctx->current_line, + apdu_runner_ctx->total_lines, + furi_string_get_cstr(line)); + + if(seader_worker->callback) { + seader_worker->callback(SeaderWorkerEventAPDURunnerUpdate, seader_worker->context); + } + + apdu_runner_ctx->current_line++; + if(seader_uart->T == 1) { + seader_send_t1(seader_uart, apdu, len); + } else { + seader_ccid_XfrBlock(seader_uart, apdu, len); + } + furi_string_free(line); + free(apdu); + + return true; +} + +void seader_apdu_runner_init(Seader* seader) { + SeaderAPDURunnerContext* apdu_runner_ctx = &(seader->apdu_runner_ctx); + + if(apdu_log_check_presence(SEADER_APDU_RUNNER_FILE_NAME)) { + FURI_LOG_I(TAG, "APDU log file exists"); + } else { + FURI_LOG_W(TAG, "APDU log file does not exist"); + return; + } + + seader->apdu_log = apdu_log_alloc(SEADER_APDU_RUNNER_FILE_NAME, APDULogModeOpenExisting); + apdu_runner_ctx->current_line = 0; + apdu_runner_ctx->total_lines = apdu_log_get_total_lines(seader->apdu_log); + FURI_LOG_I(TAG, "APDU log lines: %d", apdu_runner_ctx->total_lines); + + seader_apdu_runner_send_next_line(seader); +} + +bool seader_apdu_runner_response(Seader* seader, uint8_t* r_apdu, size_t r_len) { + SeaderUartBridge* seader_uart = seader->worker->uart; + SeaderAPDURunnerContext* apdu_runner_ctx = &(seader->apdu_runner_ctx); + uint8_t GET_RESPONSE[] = {0x00, 0xc0, 0x00, 0x00, 0xff}; + + uint8_t SW1 = r_apdu[r_len - 2]; + uint8_t SW2 = r_apdu[r_len - 1]; + + switch(SW1) { + case 0x61: + //FURI_LOG_D(TAG, "Request %d bytes", SW2); + GET_RESPONSE[4] = SW2; + seader_ccid_XfrBlock(seader_uart, GET_RESPONSE, sizeof(GET_RESPONSE)); + return true; + } + + if(r_len < SEADER_UART_RX_BUF_SIZE) { + char* display = malloc(r_len * 2 + 1); + if(display == NULL) { + FURI_LOG_E(TAG, "Failed to allocate memory for display"); + seader_apdu_runner_cleanup(seader, SeaderWorkerEventAPDURunnerError); + return false; + } + memset(display, 0, r_len * 2 + 1); + for(uint8_t i = 0; i < r_len; i++) { + snprintf(display + (i * 2), sizeof(display), "%02x", r_apdu[i]); + } + FURI_LOG_I( + TAG, + "APDU Runner <=: (%d/%d): %s", + apdu_runner_ctx->current_line, + apdu_runner_ctx->total_lines, + display); + free(display); + } else { + FURI_LOG_I(TAG, "APDU Runner <=: Response too long to display"); + } + + /** Compare last two bytes to expected line **/ + + FuriString* line = furi_string_alloc(); + apdu_log_get_next_log_str(seader->apdu_log, line); + if(furi_string_size(line) % 2 == 1) { + FURI_LOG_E(TAG, "APDU log file has odd number of characters"); + seader_apdu_runner_cleanup(seader, SeaderWorkerEventAPDURunnerError); + return false; + } + + size_t len = furi_string_size(line) / 2; // String is in HEX, divide by 2 for bytes + uint8_t* apdu = malloc(len); + if(apdu == NULL) { + FURI_LOG_E(TAG, "Failed to allocate memory for APDU"); + seader_apdu_runner_cleanup(seader, SeaderWorkerEventAPDURunnerError); + return false; + } + + if(!hex_chars_to_uint8(furi_string_get_cstr(line), apdu)) { + FURI_LOG_E(TAG, "Failed to convert line to byte array"); + seader_apdu_runner_cleanup(seader, SeaderWorkerEventAPDURunnerError); + free(apdu); + // TODO: Send failed event + return false; + } + + apdu_runner_ctx->current_line++; + furi_string_free(line); + + if(memcmp(r_apdu + r_len - 2, apdu + len - 2, 2) != 0) { + FURI_LOG_W( + TAG, + "APDU runner response does not match. Response %02x%02x != expected %02x%02x", + r_apdu[r_len - 2], + r_apdu[r_len - 1], + apdu[len - 2], + apdu[len - 1]); + seader_apdu_runner_cleanup(seader, SeaderWorkerEventAPDURunnerError); + free(apdu); + return false; + } + free(apdu); + + // Check if we are at the end of the log + if(apdu_runner_ctx->current_line >= apdu_runner_ctx->total_lines) { + FURI_LOG_I(TAG, "APDU runner finished"); + seader_apdu_runner_cleanup(seader, SeaderWorkerEventAPDURunnerSuccess); + return false; + } + + // Send next line + return seader_apdu_runner_send_next_line(seader); +} diff --git a/apdu_runner.h b/apdu_runner.h new file mode 100644 index 00000000000..ce5cbf23557 --- /dev/null +++ b/apdu_runner.h @@ -0,0 +1,10 @@ + +#pragma once + +#include +#include "seader_i.h" + +#define SEADER_APDU_RUNNER_FILE_NAME APP_DATA_PATH("script.apdu") + +void seader_apdu_runner_init(Seader* seader); +bool seader_apdu_runner_response(Seader* seader, uint8_t* r_apdu, size_t r_len); diff --git a/scenes/seader_scene_apdu_runner.c b/scenes/seader_scene_apdu_runner.c new file mode 100644 index 00000000000..a1372000cb0 --- /dev/null +++ b/scenes/seader_scene_apdu_runner.c @@ -0,0 +1,78 @@ +#include "../seader_i.h" +#include + +#define TAG "Seader:Scene:APDURunner" + +char seader_scene_apdu_runner_update_text[24]; + +void seader_apdu_runner_worker_callback(SeaderWorkerEvent event, void* context) { + Seader* seader = context; + view_dispatcher_send_custom_event(seader->view_dispatcher, event); +} + +void seader_scene_apdu_runner_on_enter(void* context) { + Seader* seader = context; + // Setup view + Popup* popup = seader->popup; + popup_set_header(popup, "APDU Runner", 68, 30, AlignLeft, AlignTop); + + // TODO: Make icon for interaction with SAM + popup_set_icon(popup, 0, 3, &I_RFIDDolphinSend_97x61); + + // Start worker + seader_worker_start( + seader->worker, + SeaderWorkerStateAPDURunner, + seader->uart, + seader_apdu_runner_worker_callback, + seader); + + view_dispatcher_switch_to_view(seader->view_dispatcher, SeaderViewPopup); +} + +bool seader_scene_apdu_runner_on_event(void* context, SceneManagerEvent event) { + Seader* seader = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == GuiButtonTypeLeft) { + scene_manager_search_and_switch_to_previous_scene( + seader->scene_manager, SeaderSceneSamPresent); + consumed = true; + } else if(event.event == SeaderWorkerEventAPDURunnerUpdate) { + SeaderAPDURunnerContext apdu_runner_ctx = seader->apdu_runner_ctx; + Popup* popup = seader->popup; + snprintf( + seader_scene_apdu_runner_update_text, + sizeof(seader_scene_apdu_runner_update_text), + "APDU Runner\n%d/%d", + apdu_runner_ctx.current_line, + apdu_runner_ctx.total_lines); + popup_set_header( + popup, seader_scene_apdu_runner_update_text, 68, 30, AlignLeft, AlignTop); + consumed = true; + } else if(event.event == SeaderWorkerEventAPDURunnerSuccess) { + notification_message(seader->notifications, &sequence_success); + Popup* popup = seader->popup; + popup_set_header(popup, "APDU Runner\nSuccess", 68, 30, AlignLeft, AlignTop); + consumed = true; + } else if(event.event == SeaderWorkerEventAPDURunnerError) { + notification_message(seader->notifications, &sequence_error); + Popup* popup = seader->popup; + popup_set_header(popup, "APDU Runner\nError", 68, 30, AlignLeft, AlignTop); + consumed = true; + } + } else if(event.type == SceneManagerEventTypeBack) { + scene_manager_search_and_switch_to_previous_scene( + seader->scene_manager, SeaderSceneSamPresent); + consumed = true; + } + return consumed; +} + +void seader_scene_apdu_runner_on_exit(void* context) { + Seader* seader = context; + + // Clear view + popup_reset(seader->popup); +} diff --git a/scenes/seader_scene_config.h b/scenes/seader_scene_config.h index 0abac9dc642..eeecafbb5b4 100644 --- a/scenes/seader_scene_config.h +++ b/scenes/seader_scene_config.h @@ -17,3 +17,4 @@ ADD_SCENE(seader, sam_info, SamInfo) ADD_SCENE(seader, virtual_credential, VirtualCredential) ADD_SCENE(seader, formats, Formats) ADD_SCENE(seader, read_mfc, ReadMfc) +ADD_SCENE(seader, apdu_runner, APDURunner) diff --git a/scenes/seader_scene_sam_present.c b/scenes/seader_scene_sam_present.c index 728bb889a87..5162c6a5af8 100644 --- a/scenes/seader_scene_sam_present.c +++ b/scenes/seader_scene_sam_present.c @@ -4,6 +4,7 @@ enum SubmenuIndex { SubmenuIndexRead14a, SubmenuIndexReadMfc, SubmenuIndexSaved, + SubmenuIndexAPDURunner, SubmenuIndexSamInfo, SubmenuIndexFwVersion, }; @@ -43,6 +44,14 @@ void seader_scene_sam_present_on_update(void* context) { submenu_add_item( submenu, "Saved", SubmenuIndexSaved, seader_scene_sam_present_submenu_callback, seader); + if(apdu_log_check_presence(SEADER_APDU_RUNNER_FILE_NAME)) { + submenu_add_item( + submenu, + "Run APDUs", + SubmenuIndexAPDURunner, + seader_scene_sam_present_submenu_callback, + seader); + } if(seader_worker->sam_version[0] != 0 && seader_worker->sam_version[1] != 0) { FuriString* fw_str = furi_string_alloc(); furi_string_cat_printf( @@ -100,6 +109,9 @@ bool seader_scene_sam_present_on_event(void* context, SceneManagerEvent event) { } else if(event.event == SeaderWorkerEventSamMissing) { scene_manager_next_scene(seader->scene_manager, SeaderSceneSamMissing); consumed = true; + } else if(event.event == SubmenuIndexAPDURunner) { + scene_manager_next_scene(seader->scene_manager, SeaderSceneAPDURunner); + consumed = true; } } else if(event.type == SceneManagerEventTypeBack) { scene_manager_stop(seader->scene_manager); diff --git a/seader_i.h b/seader_i.h index 76644895fee..90d32801627 100644 --- a/seader_i.h +++ b/seader_i.h @@ -54,6 +54,7 @@ #include "t_1.h" #include "seader_worker.h" #include "seader_credential.h" +#include "apdu_log.h" #define WORKER_ALL_RX_EVENTS \ (WorkerEvtStop | WorkerEvtRxDone | WorkerEvtCfgChange | WorkerEvtLineCfgSet | \ @@ -89,6 +90,11 @@ typedef enum { WorkerEvtCtrlLineSet = (1 << 7), } WorkerEvtFlags; +typedef struct { + uint16_t total_lines; + uint16_t current_line; +} SeaderAPDURunnerContext; + struct Seader { bool revert_power; bool is_debug_enabled; @@ -120,6 +126,9 @@ struct Seader { PluginManager* plugin_manager; PluginWiegand* plugin_wiegand; + + APDULog* apdu_log; + SeaderAPDURunnerContext apdu_runner_ctx; }; struct SeaderPollerContainer { diff --git a/seader_worker.c b/seader_worker.c index afc38e6651c..583fe041d56 100644 --- a/seader_worker.c +++ b/seader_worker.c @@ -116,6 +116,10 @@ bool seader_worker_process_sam_message(Seader* seader, uint8_t* apdu, uint32_t l return false; } + if(seader_worker->state == SeaderWorkerStateAPDURunner) { + return seader_apdu_runner_response(seader, apdu, len); + } + char* display = malloc(len * 2 + 1); memset(display, 0, len * 2 + 1); for(uint8_t i = 0; i < len; i++) { @@ -211,6 +215,10 @@ int32_t seader_worker_task(void* context) { } else if(seader_worker->state == SeaderWorkerStateVirtualCredential) { FURI_LOG_D(TAG, "Virtual Credential"); seader_worker_virtual_credential(seader); + } else if(seader_worker->state == SeaderWorkerStateAPDURunner) { + FURI_LOG_D(TAG, "APDU Runner"); + seader_apdu_runner_init(seader); + return 0; } seader_worker_change_state(seader_worker, SeaderWorkerStateReady); diff --git a/seader_worker.h b/seader_worker.h index 6df18c54719..1634533366c 100644 --- a/seader_worker.h +++ b/seader_worker.h @@ -6,6 +6,7 @@ #include "sam_api.h" #include "seader_credential.h" #include "seader_bridge.h" +#include "apdu_runner.h" typedef struct SeaderWorker SeaderWorker; typedef struct CCID_Message CCID_Message; @@ -19,6 +20,7 @@ typedef enum { // Main worker states SeaderWorkerStateCheckSam, SeaderWorkerStateVirtualCredential, + SeaderWorkerStateAPDURunner, // Transition SeaderWorkerStateStop, } SeaderWorkerState; @@ -35,6 +37,9 @@ typedef enum { SeaderWorkerEventSamMissing, SeaderWorkerEventNoCardDetected, SeaderWorkerEventStartReading, + SeaderWorkerEventAPDURunnerUpdate, + SeaderWorkerEventAPDURunnerSuccess, + SeaderWorkerEventAPDURunnerError, } SeaderWorkerEvent; typedef enum {