diff --git a/code_templates/usb_gc_dumper.c b/code_templates/usb_gc_dumper.c index 9d469da..4020ea3 100644 --- a/code_templates/usb_gc_dumper.c +++ b/code_templates/usb_gc_dumper.c @@ -74,8 +74,10 @@ static void consolePrint(const char *text, ...); static u32 menuGetElementCount(const Menu *menu); static bool sendGameCardKeyAreaViaUsb(void); +static bool sendGameCardSpecificDataViaUsb(void); static bool sendGameCardCertificateViaUsb(void); static bool sendGameCardImageViaUsb(void); +static bool sendConsoleLafwViaUsb(void); static void changeKeyAreaOption(u32 idx); static void changeCertificateOption(u32 idx); @@ -150,23 +152,35 @@ static Menu g_xciMenu = { static MenuElement *g_rootMenuElements[] = { &(MenuElement){ - .str = "dump key area (initial data)", + .str = "dump gamecard initial data", .child_menu = NULL, .task_func = &sendGameCardKeyAreaViaUsb, .element_options = NULL }, &(MenuElement){ - .str = "dump certificate", + .str = "dump gamecard specific data", + .child_menu = NULL, + .task_func = &sendGameCardSpecificDataViaUsb, + .element_options = NULL + }, + &(MenuElement){ + .str = "dump gamecard certificate", .child_menu = NULL, .task_func = &sendGameCardCertificateViaUsb, .element_options = NULL }, &(MenuElement){ - .str = "dump xci", + .str = "dump gamecard xci", .child_menu = &g_xciMenu, .task_func = NULL, .element_options = NULL }, + &(MenuElement){ + .str = "dump console LAFW", + .child_menu = NULL, + .task_func = &sendConsoleLafwViaUsb, + .element_options = NULL + }, NULL }; @@ -454,6 +468,24 @@ static bool dumpGameCardKeyArea(GameCardKeyArea *out) return true; } +static bool dumpGameCardSecurityInformation(GameCardSecurityInformation *out) +{ + if (!out) + { + consolePrint("invalid parameters to dump gamecard security information!\n"); + return false; + } + + if (!gamecardGetSecurityInformation(out)) + { + consolePrint("failed to get gamecard security information\n"); + return false; + } + + consolePrint("get gamecard security information ok\n"); + return true; +} + static bool sendGameCardKeyAreaViaUsb(void) { if (!waitForGameCardAndUsb()) return false; @@ -489,6 +521,38 @@ end: return success; } +static bool sendGameCardSpecificDataViaUsb(void) +{ + if (!waitForGameCardAndUsb()) return false; + + utilsSetLongRunningProcessState(true); + + GameCardSecurityInformation gc_security_information = {0}; + bool success = false; + u32 crc = 0; + char *filename = titleGenerateGameCardFileName(TitleNamingConvention_Full, TitleFileNameIllegalCharReplaceType_IllegalFsChars); + + if (!dumpGameCardSecurityInformation(&gc_security_information) || !filename) goto end; + + crc = crc32Calculate(&(gc_security_information.specific_data), sizeof(GameCardSpecificData)); + snprintf(path, MAX_ELEMENTS(path), "%s (Specific Data) (%08X).bin", filename, crc); + + if (!sendFileData(path, &(gc_security_information.specific_data), sizeof(GameCardSpecificData))) goto end; + + printf("successfully sent specific data as \"%s\"\n", path); + success = true; + +end: + if (filename) free(filename); + + utilsSetLongRunningProcessState(false); + + consolePrint("press any button to continue"); + utilsWaitForButtonPress(0); + + return success; +} + static bool sendGameCardCertificateViaUsb(void) { if (!waitForGameCardAndUsb()) return false; @@ -527,6 +591,53 @@ end: return success; } +static bool sendConsoleLafwViaUsb(void) +{ + if (!waitForGameCardAndUsb()) return false; + + utilsSetLongRunningProcessState(true); + + LotusAsicFirmware lafw = {0}; + bool success = false; + u32 crc = 0; + + if (!gamecardGetLotusAsicFirmware(&lafw)) + { + consolePrint("failed to get console LAFW\n"); + goto end; + } + + u32 lafw_version = gamecardConvertLotusAsicFirmwareVersionBitmask(&lafw); + + const char* filename = ""; + switch(lafw.fw_type) { + case LotusAsicFirmwareType_ReadFw: filename = "ReadFw"; break; + case LotusAsicFirmwareType_ReadDevFw: filename = "ReadDevFw"; break; + case LotusAsicFirmwareType_WriterFw: filename = "WriterFw"; break; + case LotusAsicFirmwareType_RmaFw: filename = "RmaFw"; break; + default: filename = "Unknown"; break; + } + + consolePrint("get console LAFW ok\n"); + + crc = crc32Calculate(&lafw, sizeof(LotusAsicFirmware)); + snprintf(path, MAX_ELEMENTS(path), "LAFW (%s) (v%d) (%08X).bin", filename, lafw_version, crc); + + if (!sendFileData(path, &lafw, sizeof(LotusAsicFirmware))) goto end; + + printf("successfully sent lafw as \"%s\"\n", path); + success = true; + +end: + utilsSetLongRunningProcessState(false); + + consolePrint("press any button to continue"); + utilsWaitForButtonPress(0); + + return success; +} + + static bool sendGameCardImageViaUsb(void) { if (!waitForGameCardAndUsb()) return false; diff --git a/include/core/gamecard.h b/include/core/gamecard.h index 44ad88b..f90a114 100644 --- a/include/core/gamecard.h +++ b/include/core/gamecard.h @@ -86,6 +86,30 @@ typedef struct { NXDT_ASSERT(GameCardKeyArea, 0x1000); +typedef struct { + u32 asic_security_mode; + u32 asic_status; + u32 cardid1; + u32 cardid2; + u8 card_uid[0x40]; + u8 reserved[0x190]; + u8 asic_session_hash[0x20]; +} GameCardSpecificData; + +NXDT_ASSERT(GameCardSpecificData, 0x200); + +/// This struct is returned by Lotus command "ChangeToSecureMode" (0xF) +/// A copy of the gamecard header without the RSA-2048 signature and a plaintext GameCardInfo precedes this struct in FS program memory. +typedef struct { + GameCardSpecificData specific_data; + FsGameCardCertificate certificate; + u8 ffpadding[0x200]; + GameCardInitialData initial_data; +} GameCardSecurityInformation; + +NXDT_ASSERT(GameCardSecurityInformation, 0x800); + + typedef enum { GameCardKekIndex_Version0 = 0, GameCardKekIndex_VersionForDev = 1 @@ -212,6 +236,40 @@ typedef enum { GameCardHashFileSystemPartitionType_Count = 7 ///< Not a real value. } GameCardHashFileSystemPartitionType; +typedef enum { + LotusAsicFirmwareType_ReadFw = 0xFF, + LotusAsicFirmwareType_ReadDevFw = 0xFFFF, + LotusAsicFirmwareType_WriterFw = 0xFFFFFF, + LotusAsicFirmwareType_RmaFw = 0xFFFFFFFF +} LotusAsicFirmwareType; + +typedef enum { + LotusAsicDeviceType_Test = 0, + LotusAsicDeviceType_Dev = 1, + LotusAsicDeviceType_Prod = 2, + LotusAsicDeviceType_Prod2Dev = 3 +} LotusAsicDeviceType; + +typedef struct { + u8 signature[0x100]; + u32 magic; ///< "LAFW". + u32 fw_type; ///< LotusAsicFirmwareType. + u8 reserved_1[0x8]; + struct { + u64 fw_version : 62; ///< Stored using a bitmask. + u64 device_type : 2; ///< LotusAsicDeviceType. + }; + u32 data_size; + u8 reserved_2[0x4]; + u8 data_iv[AES_128_KEY_SIZE]; + char placeholder_str[0x10]; ///< "IDIDIDIDIDIDIDID". + u8 reserved_3[0x40]; + u8 data[0x7680]; +} LotusAsicFirmware; + +NXDT_ASSERT(LotusAsicFirmware, 0x7800); + + /// Initializes data needed to access raw gamecard storage areas. /// Also spans a background thread to automatically detect gamecard status changes and to cache data from the inserted gamecard. bool gamecardInitialize(void); @@ -236,6 +294,10 @@ bool gamecardReadStorage(void *out, u64 read_size, u64 offset); /// This area can't be read using gamecardReadStorage(). bool gamecardGetKeyArea(GameCardKeyArea *out); +/// Fills the provided GameCardSecurityInformation pointer +/// This area can't be read using gamecardReadStorage(). +bool gamecardGetSecurityInformation(GameCardSecurityInformation* out); + /// Fills the provided FsGameCardIdSet pointer. /// This area can't be read using gamecardReadStorage(). bool gamecardGetIdSet(FsGameCardIdSet *out); @@ -280,6 +342,12 @@ const char *gamecardGetRequiredHosVersionString(u64 fw_version); /// Returns NULL if the provided value is out of range. const char *gamecardGetCompatibilityTypeString(u8 compatibility_type); +/// Fills the provided LotusAsicFirmware pointer with FS's current LAFW blob. +bool gamecardGetLotusAsicFirmware(LotusAsicFirmware* out); + +/// Convert LAFW version bitmask to an integer +u32 gamecardConvertLotusAsicFirmwareVersionBitmask(LotusAsicFirmware* lafw); + #ifdef __cplusplus } #endif diff --git a/source/core/gamecard.c b/source/core/gamecard.c index 706696d..6d94566 100644 --- a/source/core/gamecard.c +++ b/source/core/gamecard.c @@ -56,52 +56,6 @@ typedef enum { GameCardCapacity_32GiB = BIT_LONG(35) } GameCardCapacity; -/// Only kept for documentation purposes, not really used. -/// A copy of the gamecard header without the RSA-2048 signature and a plaintext GameCardInfo precedes this struct in FS program memory. -typedef struct { - u32 memory_interface_mode; - u32 asic_status; - u8 card_id_area[0x48]; - u8 reserved[0x1B0]; - FsGameCardCertificate certificate; - GameCardInitialData initial_data; -} GameCardSecurityInformation; - -NXDT_ASSERT(GameCardSecurityInformation, 0x600); - -typedef enum { - LotusAsicFirmwareType_ReadFw = 0xFF, - LotusAsicFirmwareType_ReadDevFw = 0xFFFF, - LotusAsicFirmwareType_WriterFw = 0xFFFFFF, - LotusAsicFirmwareType_RmaFw = 0xFFFFFFFF -} LotusAsicFirmwareType; - -typedef enum { - LotusAsicDeviceType_Test = 0, - LotusAsicDeviceType_Dev = 1, - LotusAsicDeviceType_Prod = 2, - LotusAsicDeviceType_Prod2Dev = 3 -} LotusAsicDeviceType; - -typedef struct { - u8 signature[0x100]; - u32 magic; ///< "LAFW". - u32 fw_type; ///< LotusAsicFirmwareType. - u8 reserved_1[0x8]; - struct { - u64 fw_version : 62; ///< Stored using a bitmask. - u64 device_type : 2; ///< LotusAsicDeviceType. - }; - u32 data_size; - u8 reserved_2[0x4]; - u8 data_iv[AES_128_KEY_SIZE]; - char placeholder_str[0x10]; ///< "IDIDIDIDIDIDIDID". - u8 reserved_3[0x40]; - u8 data[0x7680]; -} LotusAsicFirmwareBlob; - -NXDT_ASSERT(LotusAsicFirmwareBlob, 0x7800); - /* Global variables. */ static Mutex g_gameCardMutex = 0; @@ -180,7 +134,7 @@ static bool gamecardReadHeader(void); static bool _gamecardGetDecryptedCardInfoArea(void); -static bool gamecardReadInitialData(GameCardKeyArea *out); +static bool gamecardReadSecurityInformation(GameCardSecurityInformation *out); static bool gamecardGetHandleAndStorage(u32 partition); NX_INLINE void gamecardCloseHandle(void); @@ -336,14 +290,30 @@ bool gamecardReadStorage(void *out, u64 read_size, u64 offset) return ret; } -/* Read full FS program memory to retrieve the GameCardInitialData block, which is part of the GameCardKeyArea block. */ -/* In FS program memory, this is stored as part of the GameCardSecurityInformation struct, which is returned by Lotus command "ChangeToSecureMode" (0xF). */ -/* This means it is only available *after* the gamecard secure area has been mounted, which is taken care of in gamecardReadInitialData(). */ -/* The GameCardSecurityInformation struct is only kept for documentation purposes. It isn't used at all to retrieve the GameCardInitialData block. */ +/* Read full FS program memory by calling gamecardGetSecurityInformation, and then extracts out the GameCardInitialData block. */ bool gamecardGetKeyArea(GameCardKeyArea *out) +{ + GameCardSecurityInformation securityInformation; + + /* Clear output. */ + memset(out, 0, sizeof(GameCardKeyArea)); + + if (!gamecardGetSecurityInformation(&securityInformation)) { + return false; + } + + memcpy(&out->initial_data, &securityInformation.initial_data, sizeof(GameCardInitialData)); + + return true; +} + +/* Read full FS program memory to retrieve the GameCardSecurityInformation block. */ +/* In FS program memory, this is returned by Lotus command "ChangeToSecureMode" (0xF). */ +/* This means it is only available *after* the gamecard secure area has been mounted, which is taken care of in gamecardReadSecurityInformation(). */ +bool gamecardGetSecurityInformation(GameCardSecurityInformation *out) { bool ret = false; - SCOPED_LOCK(&g_gameCardMutex) ret = gamecardReadInitialData(out); + SCOPED_LOCK(&g_gameCardMutex) ret = gamecardReadSecurityInformation(out); return ret; } @@ -559,66 +529,93 @@ const char *gamecardGetCompatibilityTypeString(u8 compatibility_type) return (compatibility_type < GameCardCompatibilityType_Count ? g_gameCardCompatibilityTypeStrings[compatibility_type] : NULL); } +bool gamecardGetLotusAsicFirmware(LotusAsicFirmware* out) +{ + bool ret = false; + SCOPED_LOCK(&g_gameCardMutex) + { + bool found = false, dev_unit = utilsIsDevelopmentUnit(); + + /* Temporarily set the segment mask to .data. */ + g_fsProgramMemory.mask = MemoryProgramSegmentType_Data; + + /* Retrieve full FS program memory dump. */ + ret = memRetrieveProgramMemorySegment(&g_fsProgramMemory); + + /* Clear segment mask. */ + g_fsProgramMemory.mask = 0; + + if (!ret) + { + LOG_MSG("Failed to retrieve FS .data segment dump!"); + goto end; + } + + /* Look for the LAFW ReadFw blob in the FS .data memory dump. */ + for(u64 offset = 0; offset < g_fsProgramMemory.data_size; offset++) + { + if ((g_fsProgramMemory.data_size - offset) < sizeof(LotusAsicFirmware)) break; + + LotusAsicFirmware *lafw_blob = (LotusAsicFirmware*)(g_fsProgramMemory.data + offset); + u32 magic = __builtin_bswap32(lafw_blob->magic), fw_type = lafw_blob->fw_type; + + if (magic == LAFW_MAGIC && ((!dev_unit && fw_type == LotusAsicFirmwareType_ReadFw) || (dev_unit && fw_type == LotusAsicFirmwareType_ReadDevFw))) + { + /* Jackpot. */ + memcpy(out, lafw_blob, sizeof(LotusAsicFirmware)); + found = true; + break; + } + } + + if (!found) + { + LOG_MSG("Unable to locate Lotus %s blob in FS .data segment!", dev_unit ? "ReadDevFw" : "ReadFw"); + goto end; + } + + /* Update flag. */ + ret = true; + + end: + memFreeMemoryLocation(&g_fsProgramMemory); + } + return ret; +} + +u32 gamecardConvertLotusAsicFirmwareVersionBitmask(LotusAsicFirmware* lafw) { + u64 fw_version_bitmask = lafw->fw_version; + u32 fw_version = 0; + + while(fw_version_bitmask) + { + fw_version += (fw_version_bitmask & 1); + fw_version_bitmask >>= 1; + } + return fw_version; +} + static bool gamecardGetLotusAsicFirmwareVersion(void) { - u64 fw_version = 0; - bool ret = false, found = false, dev_unit = utilsIsDevelopmentUnit(); - - /* Temporarily set the segment mask to .data. */ - g_fsProgramMemory.mask = MemoryProgramSegmentType_Data; - - /* Retrieve full FS program memory dump. */ - ret = memRetrieveProgramMemorySegment(&g_fsProgramMemory); - - /* Clear segment mask. */ - g_fsProgramMemory.mask = 0; + bool ret = false; + LotusAsicFirmware lafw; + /* Retrieve LAFW. */ + ret = gamecardGetLotusAsicFirmware(&lafw); if (!ret) { - LOG_MSG("Failed to retrieve FS .data segment dump!"); - goto end; - } - - /* Look for the LAFW ReadFw blob in the FS .data memory dump. */ - for(u64 offset = 0; offset < g_fsProgramMemory.data_size; offset++) - { - if ((g_fsProgramMemory.data_size - offset) < sizeof(LotusAsicFirmwareBlob)) break; - - LotusAsicFirmwareBlob *lafw_blob = (LotusAsicFirmwareBlob*)(g_fsProgramMemory.data + offset); - u32 magic = __builtin_bswap32(lafw_blob->magic), fw_type = lafw_blob->fw_type; - - if (magic == LAFW_MAGIC && ((!dev_unit && fw_type == LotusAsicFirmwareType_ReadFw) || (dev_unit && fw_type == LotusAsicFirmwareType_ReadDevFw))) - { - /* Jackpot. */ - fw_version = lafw_blob->fw_version; - found = true; - break; - } - } - - if (!found) - { - LOG_MSG("Unable to locate Lotus %s blob in FS .data segment!", dev_unit ? "ReadDevFw" : "ReadFw"); + LOG_MSG("Failed to retrieve Lotus Asic Firmware!"); goto end; } /* Convert LAFW version bitmask to an integer. */ - g_lafwVersion = 0; - - while(fw_version) - { - g_lafwVersion += (fw_version & 1); - fw_version >>= 1; - } - + g_lafwVersion = gamecardConvertLotusAsicFirmwareVersionBitmask(&lafw); LOG_MSG("LAFW version: %lu.", g_lafwVersion); /* Update flag. */ ret = true; end: - memFreeMemoryLocation(&g_fsProgramMemory); - return ret; } @@ -901,7 +898,7 @@ static bool _gamecardGetDecryptedCardInfoArea(void) return true; } -static bool gamecardReadInitialData(GameCardKeyArea *out) +static bool gamecardReadSecurityInformation(GameCardSecurityInformation *out) { if (!g_gameCardInterfaceInit || g_gameCardStatus != GameCardStatus_InsertedAndInfoLoaded || !out) { @@ -910,7 +907,7 @@ static bool gamecardReadInitialData(GameCardKeyArea *out) } /* Clear output. */ - memset(out, 0, sizeof(GameCardKeyArea)); + memset(out, 0, sizeof(GameCardSecurityInformation)); /* Open secure storage area. */ if (!gamecardOpenStorageArea(GameCardStorageArea_Secure)) @@ -941,7 +938,11 @@ static bool gamecardReadInitialData(GameCardKeyArea *out) if (!memcmp(tmp_hash, g_gameCardHeader.initial_data_hash, SHA256_HASH_SIZE)) { /* Jackpot. */ - memcpy(&(out->initial_data), g_fsProgramMemory.data + offset, sizeof(GameCardInitialData)); + memcpy(out, g_fsProgramMemory.data + offset - 0x600, sizeof(GameCardSecurityInformation)); + + // Clear out the asic session hash of the current Lotus session with the console. + // It's not actually part of the gamecard data, and this changes every time the gamecard is reinserted. + memset(out->specific_data.asic_session_hash, 0xFF, 32); found = true; break; }