Add support for dumping full GameCardSecurityInformation (#105)

* Add support for dumping full GameCardSecurityInformation

* Add support for dumping LAFW

* Clear out ASIC session hash data

Co-authored-by: Pablo Curiel <pabloacurielz@gmail.com>
This commit is contained in:
Johnson 2022-02-03 02:22:57 +01:00 committed by GitHub
parent 4929330e32
commit e79b03afeb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 283 additions and 103 deletions

View File

@ -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;

View File

@ -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

View File

@ -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;
}