1
0
Fork 0
mirror of https://github.com/DarkMatterCore/nxdumptool.git synced 2024-09-16 20:13:25 +01:00

nso: update read logic

Changes include:

* mem: change all references to `Rodata` to `RoData`.

* nso: change all references to `Rodata` to `RoData`.
* nso: update `NsoModStart` struct.
* nso: rename `NsoModuleInfo` struct to `NsoModulePath`.
* nso: add `NsoRoDataStart` struct.
* nso: update logic in both nsoIsNnSdkVersionWithinSegment() and nsoGetNnSdkVersion() functions to work entirely with memory-based offsets.
* nso: rename nsoGetModuleInfoName() to nsoGetModulePath().
* nso: update logic in nsoInitializeContext() to always validate and use the NsoNnSdkVersion block offset from the NsoModStart block.
This commit is contained in:
Pablo Curiel 2024-08-08 12:27:54 +02:00
parent 414780ada8
commit 00497b5181
3 changed files with 90 additions and 68 deletions

View file

@ -32,9 +32,9 @@ extern "C" {
typedef enum {
MemoryProgramSegmentType_None = 0,
MemoryProgramSegmentType_Text = BIT(0),
MemoryProgramSegmentType_Rodata = BIT(1),
MemoryProgramSegmentType_RoData = BIT(1),
MemoryProgramSegmentType_Data = BIT(2),
MemoryProgramSegmentType_All = (MemoryProgramSegmentType_Data | MemoryProgramSegmentType_Rodata | MemoryProgramSegmentType_Text),
MemoryProgramSegmentType_All = (MemoryProgramSegmentType_Data | MemoryProgramSegmentType_RoData | MemoryProgramSegmentType_Text),
MemoryProgramSegmentType_Limit = (MemoryProgramSegmentType_All + 1) ///< Placed here for convenience.
} MemoryProgramSegmentType;

View file

@ -89,17 +89,20 @@ typedef struct {
NXDT_ASSERT(NsoHeader, 0x100);
/// Placed at the very start of the decompressed .text segment.
/// All offsets are relative to the start of this header, but they only apply to uncompressed + contiguous NSO segments.
typedef struct {
u32 version; ///< Usually set to 0 or a branch instruction (0x14000002). Set to 1 or 0x14000003 if a NsoNnSdkVersion block is available.
s32 mod_offset; ///< NsoModHeader block offset (relative to the start of this header). Almost always set to 0x8 (the size of this struct).
u32 version; ///< Usually set to 0 or a branch instruction (0x14000002). Set to 1 or 0x14000003 if a NsoNnSdkVersion block is available.
s32 mod_offset; ///< NsoModHeader block offset. Almost always set to 0x8 (the size of this struct), but it could also reference another segment (e.g. .rodata).
s32 nnsdk_version_offset; ///< NsoNnSdkVersion block offset. Only valid if version is set to 1 or 0x14000003.
u8 reserved[0x4];
} NsoModStart;
NXDT_ASSERT(NsoModStart, 0x8);
NXDT_ASSERT(NsoModStart, 0x10);
/// This is essentially a replacement for the PT_DYNAMIC program header available in ELF binaries.
/// All offsets are signed 32-bit values relative to the start of this header.
/// This is usually placed at the start of the decompressed .text segment, right after a NsoModStart block.
/// However, in some NSOs, it can instead be placed at the start of the decompressed .rodata segment, right after its NsoModuleInfo block.
/// However, in some NSOs, it can instead be placed at the start of the decompressed .rodata segment, right after its NsoRoDataStart block.
/// In these cases, the 'mod_offset' value from the NsoModStart block will point to an offset within the .rodata segment.
typedef struct {
u32 magic; ///< "MOD0".
@ -123,14 +126,24 @@ typedef struct {
NXDT_ASSERT(NsoNnSdkVersion, 0xC);
/// Placed at the start of the decompressed .rodata segment + 0x4.
/// If the 'name_length' element is greater than 0, 'name' will hold the module name.
/// If 'zero' is 0 and 'path_length' is greater than 0, 'path' will hold the module path.
typedef struct {
u32 name_length;
char name[];
} NsoModuleInfo;
u32 zero; ///< Always 0.
u32 path_length;
char path[];
} NsoModulePath;
NXDT_ASSERT(NsoModuleInfo, 0x4);
NXDT_ASSERT(NsoModulePath, 0x8);
/// Placed at the very start of the decompressed .rodata segment.
typedef struct {
union {
u64 data_segment_offset; ///< Deprecated.
NsoModulePath module_path;
};
} NsoRoDataStart;
NXDT_ASSERT(NsoRoDataStart, 0x8);
typedef struct {
PartitionFileSystemContext *pfs_ctx; ///< PartitionFileSystemContext for the Program NCA FS section #0, which is where this NSO is stored.
@ -139,7 +152,7 @@ typedef struct {
NsoHeader nso_header; ///< NSO header.
char *module_name; ///< Pointer to a dynamically allocated buffer that holds the NSO module name, if available. Otherwise, this is set to NULL.
NsoNnSdkVersion *nnsdk_version; ///< Pointer to a dynamically allocated buffer that holds the nnSdk version info, if available. Otherwise, this is set to NULL.
char *module_info_name; ///< Pointer to a dynamically allocated buffer that holds the .rodata module info module name, if available. Otherwise, this is set to NULL.
char *module_path; ///< Pointer to a dynamically allocated buffer that holds the .rodata module path, if available. Otherwise, this is set to NULL.
char *rodata_api_info_section; ///< Pointer to a dynamically allocated buffer that holds the .rodata API info section data, if available. Otherwise, this is set to NULL.
///< Middleware and GuidelineApi entries are retrieved from this section.
u64 rodata_api_info_section_size; ///< .rodata API info section size, if available. Otherwise, this is set to 0. Kept here for convenience - this is part of 'nso_header'.
@ -159,7 +172,7 @@ NX_INLINE void nsoFreeContext(NsoContext *nso_ctx)
if (!nso_ctx) return;
if (nso_ctx->module_name) free(nso_ctx->module_name);
if (nso_ctx->nnsdk_version) free(nso_ctx->nnsdk_version);
if (nso_ctx->module_info_name) free(nso_ctx->module_info_name);
if (nso_ctx->module_path) free(nso_ctx->module_path);
if (nso_ctx->rodata_api_info_section) free(nso_ctx->rodata_api_info_section);
if (nso_ctx->rodata_dynstr_section) free(nso_ctx->rodata_dynstr_section);
if (nso_ctx->rodata_dynsym_section) free(nso_ctx->rodata_dynsym_section);

View file

@ -26,7 +26,7 @@
typedef enum {
NsoSegmentType_Text = 0,
NsoSegmentType_Rodata = 1,
NsoSegmentType_RoData = 1,
NsoSegmentType_Data = 2,
NsoSegmentType_Count = 3 ///< Total values supported by this enum.
} NsoSegmentType;
@ -43,7 +43,7 @@ typedef struct {
#if LOG_LEVEL < LOG_LEVEL_NONE
static const char *g_nsoSegmentTypeNames[NsoSegmentType_Count] = {
[NsoSegmentType_Text] = ".text",
[NsoSegmentType_Rodata] = ".rodata",
[NsoSegmentType_RoData] = ".rodata",
[NsoSegmentType_Data] = ".data",
};
#endif
@ -55,17 +55,18 @@ static bool nsoGetModuleName(NsoContext *nso_ctx);
static bool nsoGetSegment(NsoContext *nso_ctx, NsoSegment *out, u8 type);
NX_INLINE void nsoFreeSegment(NsoSegment *segment);
NX_INLINE bool nsoIsNnSdkVersionWithinSegment(const NsoModStart *mod_start, const NsoSegment *segment);
static bool nsoGetNnSdkVersion(NsoContext *nso_ctx, const NsoModStart *mod_start, const NsoSegment *segment);
NX_INLINE bool nsoIsNnSdkVersionWithinSegment(const NsoModStart *mod_start, const NsoSegment *segment, u32 nnsdk_version_memory_offset);
static bool nsoGetNnSdkVersion(NsoContext *nso_ctx, const NsoModStart *mod_start, const NsoSegment *segment, u32 nnsdk_version_memory_offset);
static bool nsoGetModuleInfoName(NsoContext *nso_ctx, const NsoSegment *segment);
static bool nsoGetModulePath(NsoContext *nso_ctx, const NsoSegment *segment);
static bool nsoGetSectionFromRodataSegment(NsoContext *nso_ctx, const NsoSectionInfo *section_info, const NsoSegment *segment, u8 **out_ptr);
static bool nsoGetSectionFromRoDataSegment(NsoContext *nso_ctx, const NsoSectionInfo *section_info, const NsoSegment *segment, u8 **out_ptr);
bool nsoInitializeContext(NsoContext *out, PartitionFileSystemContext *pfs_ctx, PartitionFileSystemEntry *pfs_entry)
{
NsoModStart mod_start = {0};
NsoSegment segment = {0};
u32 nnsdk_version_memory_offset = 0;
bool success = false, dump_nso_header = false, read_nnsdk_version = false;
if (!out || !pfs_ctx || !ncaStorageIsValidContext(&(pfs_ctx->storage_ctx)) || !pfs_ctx->nca_fs_ctx->nca_ctx || \
@ -124,7 +125,7 @@ bool nsoInitializeContext(NsoContext *out, PartitionFileSystemContext *pfs_ctx,
#define NSO_GET_RODATA_SECTION(name) \
do { \
if (!nsoGetSectionFromRodataSegment(out, &(out->nso_header.name##_section_info), &segment, (u8**)&(out->rodata_##name##_section))) goto end; \
if (!nsoGetSectionFromRoDataSegment(out, &(out->nso_header.name##_section_info), &segment, (u8**)&(out->rodata_##name##_section))) goto end; \
out->rodata_##name##_section_size = out->nso_header.name##_section_info.size; \
} while(0)
@ -154,32 +155,37 @@ bool nsoInitializeContext(NsoContext *out, PartitionFileSystemContext *pfs_ctx,
/* Get NsoModStart block. */
memcpy(&mod_start, segment.data, sizeof(NsoModStart));
/* Check if a nnSdk version struct exists within this NRO. */
read_nnsdk_version = ((mod_start.version & 1) != 0);
/* Check if a NsoNnSdkVersion block exists within this NRO. */
read_nnsdk_version = ((mod_start.version & 1) != 0 && mod_start.nnsdk_version_offset >= (s32)sizeof(NsoModStart));
if (read_nnsdk_version)
{
/* Calculate memory offset for the NsoNnSdkVersion block. */
nnsdk_version_memory_offset = (segment.info.memory_offset + (u32)mod_start.nnsdk_version_offset);
/* Check if the nnSdk version struct is located within the .text segment. */
/* If so, we'll retrieve it immediately. */
if (read_nnsdk_version && nsoIsNnSdkVersionWithinSegment(&mod_start, &segment) && !nsoGetNnSdkVersion(out, &mod_start, &segment)) goto end;
/* Check if the NsoNnSdkVersion block is located within the .text segment. */
/* If so, we'll retrieve it immediately. */
if (nsoIsNnSdkVersionWithinSegment(&mod_start, &segment, nnsdk_version_memory_offset) && !nsoGetNnSdkVersion(out, &mod_start, &segment, nnsdk_version_memory_offset)) goto end;
}
/* Get .rodata segment. */
if (!nsoGetSegment(out, &segment, NsoSegmentType_Rodata)) goto end;
if (!nsoGetSegment(out, &segment, NsoSegmentType_RoData)) goto end;
/* Check if we didn't read the nnSdk version struct from the .text segment. */
/* Check if we didn't read the NsoNnSdkVersion block from the .text segment. */
if (read_nnsdk_version && !out->nnsdk_version)
{
/* Check if the nnSdk version struct is located within the .rodata segment. */
if (!nsoIsNnSdkVersionWithinSegment(&mod_start, &segment))
/* Check if the NsoNnSdkVersion block is located within the .rodata segment. */
if (!nsoIsNnSdkVersionWithinSegment(&mod_start, &segment, nnsdk_version_memory_offset))
{
LOG_MSG_ERROR("nnSdk version struct not located within .text or .rodata segments in NSO \"%s\".", out->nso_filename);
goto end;
}
/* Retrieve nnSdk version struct data from the .rodata segment. */
if (!nsoGetNnSdkVersion(out, &mod_start, &segment)) goto end;
/* Retrieve NsoNnSdkVersion block from the .rodata segment. */
if (!nsoGetNnSdkVersion(out, &mod_start, &segment, nnsdk_version_memory_offset)) goto end;
}
/* Get module info name from the .rodata segment. */
if (!nsoGetModuleInfoName(out, &segment)) goto end;
/* Get module path from the .rodata segment. */
if (!nsoGetModulePath(out, &segment)) goto end;
/* Get sections from the .rodata segment. */
NSO_GET_RODATA_SECTION(api_info);
@ -238,22 +244,22 @@ static bool nsoGetSegment(NsoContext *nso_ctx, NsoSegment *out, u8 type)
const char *segment_name = g_nsoSegmentTypeNames[type];
const NsoSegmentInfo *segment_info = (type == NsoSegmentType_Text ? &(nso_ctx->nso_header.text_segment_info) : \
(type == NsoSegmentType_Rodata ? &(nso_ctx->nso_header.rodata_segment_info) : &(nso_ctx->nso_header.data_segment_info)));
(type == NsoSegmentType_RoData ? &(nso_ctx->nso_header.rodata_segment_info) : &(nso_ctx->nso_header.data_segment_info)));
u32 segment_file_size = (type == NsoSegmentType_Text ? nso_ctx->nso_header.text_file_size : \
(type == NsoSegmentType_Rodata ? nso_ctx->nso_header.rodata_file_size : nso_ctx->nso_header.data_file_size));
(type == NsoSegmentType_RoData ? nso_ctx->nso_header.rodata_file_size : nso_ctx->nso_header.data_file_size));
const u8 *segment_hash = (type == NsoSegmentType_Text ? nso_ctx->nso_header.text_segment_hash : \
(type == NsoSegmentType_Rodata ? nso_ctx->nso_header.rodata_segment_hash : nso_ctx->nso_header.data_segment_hash));
(type == NsoSegmentType_RoData ? nso_ctx->nso_header.rodata_segment_hash : nso_ctx->nso_header.data_segment_hash));
int lz4_res = 0;
bool compressed = (nso_ctx->nso_header.flags & BIT(type)), verify = (nso_ctx->nso_header.flags & BIT(type + 3));
u8 *buf = NULL;
u64 buf_size = (compressed ? LZ4_DECOMPRESS_INPLACE_BUFFER_SIZE(segment_info->size) : segment_info->size);
u32 buf_size = (compressed ? LZ4_DECOMPRESS_INPLACE_BUFFER_SIZE(segment_info->size) : segment_info->size);
u8 *read_ptr = NULL;
u64 read_size = (compressed ? segment_file_size : segment_info->size);
u32 read_size = (compressed ? segment_file_size : segment_info->size);
u8 hash[SHA256_HASH_SIZE] = {0};
@ -265,7 +271,7 @@ static bool nsoGetSegment(NsoContext *nso_ctx, NsoSegment *out, u8 type)
/* Allocate memory for the segment buffer. */
if (!(buf = calloc(buf_size, sizeof(u8))))
{
LOG_MSG_ERROR("Failed to allocate 0x%lX bytes for the %s segment in NSO \"%s\"!", buf_size, segment_name, nso_ctx->nso_filename);
LOG_MSG_ERROR("Failed to allocate 0x%X bytes for the %s segment in NSO \"%s\"!", buf_size, segment_name, nso_ctx->nso_filename);
return NULL;
}
@ -317,13 +323,13 @@ NX_INLINE void nsoFreeSegment(NsoSegment *segment)
memset(segment, 0, sizeof(NsoSegment));
}
NX_INLINE bool nsoIsNnSdkVersionWithinSegment(const NsoModStart *mod_start, const NsoSegment *segment)
NX_INLINE bool nsoIsNnSdkVersionWithinSegment(const NsoModStart *mod_start, const NsoSegment *segment, u32 nnsdk_version_memory_offset)
{
return (mod_start && segment && mod_start->mod_offset >= (s32)segment->info.memory_offset && \
(mod_start->mod_offset + (s32)sizeof(NsoModHeader) + (s32)sizeof(NsoNnSdkVersion)) <= (s32)(segment->info.memory_offset + segment->info.size));
return (mod_start && segment && nnsdk_version_memory_offset >= segment->info.memory_offset && \
(nnsdk_version_memory_offset + sizeof(NsoNnSdkVersion)) <= (segment->info.memory_offset + segment->info.size));
}
static bool nsoGetNnSdkVersion(NsoContext *nso_ctx, const NsoModStart *mod_start, const NsoSegment *segment)
static bool nsoGetNnSdkVersion(NsoContext *nso_ctx, const NsoModStart *mod_start, const NsoSegment *segment, u32 nnsdk_version_memory_offset)
{
if (!nso_ctx || !mod_start || !segment || !segment->data)
{
@ -331,19 +337,18 @@ static bool nsoGetNnSdkVersion(NsoContext *nso_ctx, const NsoModStart *mod_start
return false;
}
/* Return immediately if the nnSdk struct has already been retrieved. */
if (nso_ctx->nnsdk_version) return 0;
/* Return immediately if the NsoNnSdkVersion block has already been retrieved. */
if (nso_ctx->nnsdk_version) return true;
/* Calculate virtual offset for the nnSdk version struct and check if it is within range. */
u32 nnsdk_ver_virt_offset = (u32)(mod_start->mod_offset + (s32)sizeof(NsoModHeader));
if (mod_start->mod_offset < (s32)segment->info.memory_offset || (nnsdk_ver_virt_offset + sizeof(NsoNnSdkVersion)) > (segment->info.memory_offset + segment->info.size))
/* Make sure we're targetting the right NSO segment. */
if (!nsoIsNnSdkVersionWithinSegment(mod_start, segment, nnsdk_version_memory_offset))
{
LOG_MSG_ERROR("nnSdk version struct isn't located within %s segment in NSO \"%s\"! ([0x%X, 0x%X] not within [0x%X, 0x%X]).", segment->name, nso_ctx->nso_filename, \
mod_start->mod_offset, nnsdk_ver_virt_offset + (u32)sizeof(NsoNnSdkVersion), segment->info.memory_offset, segment->info.memory_offset + segment->info.size);
nnsdk_version_memory_offset, nnsdk_version_memory_offset + (u32)sizeof(NsoNnSdkVersion), segment->info.memory_offset, segment->info.memory_offset + segment->info.size);
return false;
}
/* Allocate memory for the nnSdk version struct. */
/* Allocate memory for the NsoNnSdkVersion block. */
nso_ctx->nnsdk_version = malloc(sizeof(NsoNnSdkVersion));
if (!nso_ctx->nnsdk_version)
{
@ -351,45 +356,49 @@ static bool nsoGetNnSdkVersion(NsoContext *nso_ctx, const NsoModStart *mod_start
return false;
}
/* Calculate segment-relative offset for the nnSdk version struct and copy its data. */
u32 nnsdk_ver_phys_offset = (nnsdk_ver_virt_offset - segment->info.memory_offset);
memcpy(nso_ctx->nnsdk_version, segment->data + nnsdk_ver_phys_offset, sizeof(NsoNnSdkVersion));
/* Calculate segment-relative offset for the NsoNnSdkVersion block and copy its data. */
u32 nnsdk_version_segment_offset = (nnsdk_version_memory_offset - segment->info.memory_offset);
memcpy(nso_ctx->nnsdk_version, segment->data + nnsdk_version_segment_offset, sizeof(NsoNnSdkVersion));
LOG_MSG_DEBUG("nnSdk version (NSO \"%s\", %s segment, virtual offset 0x%X, physical offset 0x%X): %u.%u.%u.", nso_ctx->nso_filename, segment->name, \
nnsdk_ver_virt_offset, nnsdk_ver_phys_offset, nso_ctx->nnsdk_version->major, nso_ctx->nnsdk_version->minor, nso_ctx->nnsdk_version->micro);
nnsdk_version_memory_offset, nnsdk_version_segment_offset, nso_ctx->nnsdk_version->major, nso_ctx->nnsdk_version->minor, nso_ctx->nnsdk_version->micro);
return true;
}
static bool nsoGetModuleInfoName(NsoContext *nso_ctx, const NsoSegment *segment)
static bool nsoGetModulePath(NsoContext *nso_ctx, const NsoSegment *segment)
{
if (!nso_ctx || !segment || segment->type != NsoSegmentType_Rodata || !segment->data)
if (!nso_ctx || !segment || segment->type != NsoSegmentType_RoData || !segment->data)
{
LOG_MSG_ERROR("Invalid parameters!");
return false;
}
const NsoModuleInfo *module_info = (const NsoModuleInfo*)(segment->data + 0x4);
if (!module_info->name_length || !module_info->name[0]) return true;
/* Get data from the start of the .rodata segment. */
const NsoRoDataStart *rodata_start = (const NsoRoDataStart*)segment->data;
/* Allocate memory for the module info name. */
nso_ctx->module_info_name = calloc(module_info->name_length + 1, sizeof(char));
if (!nso_ctx->module_info_name)
/* Perform sanity checks. */
if ((nso_ctx->nso_header.text_segment_info.memory_offset + rodata_start->data_segment_offset) == nso_ctx->nso_header.data_segment_info.memory_offset || \
rodata_start->module_path.zero != 0 || !rodata_start->module_path.path_length || !rodata_start->module_path.path[0]) return true;
/* Allocate memory for the module path string. */
nso_ctx->module_path = calloc(rodata_start->module_path.path_length + 1, sizeof(char));
if (!nso_ctx->module_path)
{
LOG_MSG_ERROR("Failed to allocate memory for NSO \"%s\" module info name!", nso_ctx->nso_filename);
LOG_MSG_ERROR("Failed to allocate memory for NSO \"%s\" module path!", nso_ctx->nso_filename);
return false;
}
/* Copy module info name. */
sprintf(nso_ctx->module_info_name, "%.*s", (int)module_info->name_length, module_info->name);
LOG_MSG_DEBUG("Module info name (NSO \"%s\"): \"%s\".", nso_ctx->nso_filename, nso_ctx->module_info_name);
/* Copy module path string. */
sprintf(nso_ctx->module_path, "%.*s", (int)rodata_start->module_path.path_length, rodata_start->module_path.path);
LOG_MSG_DEBUG("Module path (NSO \"%s\"): \"%s\".", nso_ctx->nso_filename, nso_ctx->module_path);
return true;
}
static bool nsoGetSectionFromRodataSegment(NsoContext *nso_ctx, const NsoSectionInfo *section_info, const NsoSegment *segment, u8 **out_ptr)
static bool nsoGetSectionFromRoDataSegment(NsoContext *nso_ctx, const NsoSectionInfo *section_info, const NsoSegment *segment, u8 **out_ptr)
{
if (!nso_ctx || !section_info || !segment || segment->type != NsoSegmentType_Rodata || !segment->data || !out_ptr)
if (!nso_ctx || !section_info || !segment || segment->type != NsoSegmentType_RoData || !segment->data || !out_ptr)
{
LOG_MSG_ERROR("Invalid parameters!");
return false;