mirror of
https://github.com/SciresM/hactool
synced 2024-11-22 12:03:11 +00:00
925 lines
34 KiB
C
925 lines
34 KiB
C
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include "npdm.h"
|
|
#include "utils.h"
|
|
#include "settings.h"
|
|
#include "rsa.h"
|
|
#include "cJSON.h"
|
|
|
|
static const char * const svc_names[0x80] = {
|
|
"svcUnknown",
|
|
"svcSetHeapSize",
|
|
"svcSetMemoryPermission",
|
|
"svcSetMemoryAttribute",
|
|
"svcMapMemory",
|
|
"svcUnmapMemory",
|
|
"svcQueryMemory",
|
|
"svcExitProcess",
|
|
"svcCreateThread",
|
|
"svcStartThread",
|
|
"svcExitThread",
|
|
"svcSleepThread",
|
|
"svcGetThreadPriority",
|
|
"svcSetThreadPriority",
|
|
"svcGetThreadCoreMask",
|
|
"svcSetThreadCoreMask",
|
|
"svcGetCurrentProcessorNumber",
|
|
"svcSignalEvent",
|
|
"svcClearEvent",
|
|
"svcMapSharedMemory",
|
|
"svcUnmapSharedMemory",
|
|
"svcCreateTransferMemory",
|
|
"svcCloseHandle",
|
|
"svcResetSignal",
|
|
"svcWaitSynchronization",
|
|
"svcCancelSynchronization",
|
|
"svcArbitrateLock",
|
|
"svcArbitrateUnlock",
|
|
"svcWaitProcessWideKeyAtomic",
|
|
"svcSignalProcessWideKey",
|
|
"svcGetSystemTick",
|
|
"svcConnectToNamedPort",
|
|
"svcSendSyncRequestLight",
|
|
"svcSendSyncRequest",
|
|
"svcSendSyncRequestWithUserBuffer",
|
|
"svcSendAsyncRequestWithUserBuffer",
|
|
"svcGetProcessId",
|
|
"svcGetThreadId",
|
|
"svcBreak",
|
|
"svcOutputDebugString",
|
|
"svcReturnFromException",
|
|
"svcGetInfo",
|
|
"svcFlushEntireDataCache",
|
|
"svcFlushDataCache",
|
|
"svcMapPhysicalMemory",
|
|
"svcUnmapPhysicalMemory",
|
|
"svcGetDebugFutureThreadInfo",
|
|
"svcGetLastThreadInfo",
|
|
"svcGetResourceLimitLimitValue",
|
|
"svcGetResourceLimitCurrentValue",
|
|
"svcSetThreadActivity",
|
|
"svcGetThreadContext3",
|
|
"svcWaitForAddress",
|
|
"svcSignalToAddress",
|
|
"svcSynchronizePreemptionState",
|
|
"svcUnknown",
|
|
"svcUnknown",
|
|
"svcUnknown",
|
|
"svcUnknown",
|
|
"svcUnknown",
|
|
"svcKernelDebug",
|
|
"svcChangeKernelTraceState",
|
|
"svcUnknown",
|
|
"svcUnknown",
|
|
"svcCreateSession",
|
|
"svcAcceptSession",
|
|
"svcReplyAndReceiveLight",
|
|
"svcReplyAndReceive",
|
|
"svcReplyAndReceiveWithUserBuffer",
|
|
"svcCreateEvent",
|
|
"svcUnknown",
|
|
"svcUnknown",
|
|
"svcMapPhysicalMemoryUnsafe",
|
|
"svcUnmapPhysicalMemoryUnsafe",
|
|
"svcSetUnsafeLimit",
|
|
"svcCreateCodeMemory",
|
|
"svcControlCodeMemory",
|
|
"svcSleepSystem",
|
|
"svcReadWriteRegister",
|
|
"svcSetProcessActivity",
|
|
"svcCreateSharedMemory",
|
|
"svcMapTransferMemory",
|
|
"svcUnmapTransferMemory",
|
|
"svcCreateInterruptEvent",
|
|
"svcQueryPhysicalAddress",
|
|
"svcQueryIoMapping",
|
|
"svcCreateDeviceAddressSpace",
|
|
"svcAttachDeviceAddressSpace",
|
|
"svcDetachDeviceAddressSpace",
|
|
"svcMapDeviceAddressSpaceByForce",
|
|
"svcMapDeviceAddressSpaceAligned",
|
|
"svcMapDeviceAddressSpace",
|
|
"svcUnmapDeviceAddressSpace",
|
|
"svcInvalidateProcessDataCache",
|
|
"svcStoreProcessDataCache",
|
|
"svcFlushProcessDataCache",
|
|
"svcDebugActiveProcess",
|
|
"svcBreakDebugProcess",
|
|
"svcTerminateDebugProcess",
|
|
"svcGetDebugEvent",
|
|
"svcContinueDebugEvent",
|
|
"svcGetProcessList",
|
|
"svcGetThreadList",
|
|
"svcGetDebugThreadContext",
|
|
"svcSetDebugThreadContext",
|
|
"svcQueryDebugProcessMemory",
|
|
"svcReadDebugProcessMemory",
|
|
"svcWriteDebugProcessMemory",
|
|
"svcSetHardwareBreakPoint",
|
|
"svcGetDebugThreadParam",
|
|
"svcUnknown",
|
|
"svcGetSystemInfo",
|
|
"svcCreatePort",
|
|
"svcManageNamedPort",
|
|
"svcConnectToPort",
|
|
"svcSetProcessMemoryPermission",
|
|
"svcMapProcessMemory",
|
|
"svcUnmapProcessMemory",
|
|
"svcQueryProcessMemory",
|
|
"svcMapProcessCodeMemory",
|
|
"svcUnmapProcessCodeMemory",
|
|
"svcCreateProcess",
|
|
"svcStartProcess",
|
|
"svcTerminateProcess",
|
|
"svcGetProcessInfo",
|
|
"svcCreateResourceLimit",
|
|
"svcSetResourceLimitLimitValue",
|
|
"svcCallSecureMonitor"
|
|
};
|
|
|
|
#define MAX_FS_PERM_RW 0x27
|
|
#define MAX_FS_PERM_BOOL 0x1B
|
|
#define FS_PERM_MASK_NODEBUG 0xBFFFFFFFFFFFFFFFULL
|
|
|
|
static const fs_perm_t fs_permissions_rw[MAX_FS_PERM_RW] = {
|
|
{"MountContentType2", 0x8000000000000801},
|
|
{"MountContentType5", 0x8000000000000801},
|
|
{"MountContentType3", 0x8000000000000801},
|
|
{"MountContentType4", 0x8000000000000801},
|
|
{"MountContentType6", 0x8000000000000801},
|
|
{"MountContentType7", 0x8000000000000801},
|
|
{"Unknown (0x6)", 0x8000000000000000},
|
|
{"ContentStorageAccess", 0x8000000000000800},
|
|
{"ImageDirectoryAccess", 0x8000000000001000},
|
|
{"MountBisType28", 0x8000000000000084},
|
|
{"MountBisType29", 0x8000000000000080},
|
|
{"MountBisType30", 0x8000000000008080},
|
|
{"MountBisType31", 0x8000000000008080},
|
|
{"Unknown (0xD)", 0x8000000000000080},
|
|
{"SdCardAccess", 0xC000000000200000},
|
|
{"GameCardUser", 0x8000000000000010},
|
|
{"SaveDataAccess0", 0x8000000000040020},
|
|
{"SystemSaveDataAccess0", 0x8000000000000028},
|
|
{"SaveDataAccess1", 0x8000000000000020},
|
|
{"SystemSaveDataAccess1", 0x8000000000000020},
|
|
{"BisPartition0", 0x8000000000010082},
|
|
{"BisPartition10", 0x8000000000010080},
|
|
{"BisPartition20", 0x8000000000010080},
|
|
{"BisPartition21", 0x8000000000010080},
|
|
{"BisPartition22", 0x8000000000010080},
|
|
{"BisPartition23", 0x8000000000010080},
|
|
{"BisPartition24", 0x8000000000010080},
|
|
{"BisPartition25", 0x8000000000010080},
|
|
{"BisPartition26", 0x8000000000000080},
|
|
{"BisPartition27", 0x8000000000000084},
|
|
{"BisPartition28", 0x8000000000000084},
|
|
{"BisPartition29", 0x8000000000000080},
|
|
{"BisPartition30", 0x8000000000000080},
|
|
{"BisPartition31", 0x8000000000000080},
|
|
{"BisPartition32", 0x8000000000000080},
|
|
{"Unknown (0x23)", 0xC000000000200000},
|
|
{"GameCard_System", 0x8000000000000100},
|
|
{"MountContent_System", 0x8000000000100008},
|
|
{"HostAccess", 0xC000000000400000}
|
|
};
|
|
|
|
static const fs_perm_t fs_permissions_bool[MAX_FS_PERM_BOOL] = {
|
|
{"BisCache", 0x8000000000000080},
|
|
{"EraseMmc", 0x8000000000000080},
|
|
{"GameCardCertificate", 0x8000000000000010},
|
|
{"GameCardIdSet", 0x8000000000000010},
|
|
{"GameCardDriver", 0x8000000000000200},
|
|
{"GameCardAsic", 0x8000000000000200},
|
|
{"SaveDataCreate", 0x8000000000002020},
|
|
{"SaveDataDelete0", 0x8000000000000060},
|
|
{"SystemSaveDataCreate0", 0x8000000000000028},
|
|
{"SystemSaveDataCreate1", 0x8000000000000020},
|
|
{"SaveDataDelete1", 0x8000000000004028},
|
|
{"SaveDataIterators0", 0x8000000000000060},
|
|
{"SaveDataIterators1", 0x8000000000004020},
|
|
{"SaveThumbnails", 0x8000000000020000},
|
|
{"PosixTime", 0x8000000000000400},
|
|
{"SaveDataExtraData", 0x8000000000004060},
|
|
{"GlobalMode", 0x8000000000080000},
|
|
{"SpeedEmulation", 0x8000000000080000},
|
|
{"(NULL)", 0},
|
|
{"PaddingFiles", 0xC000000000800000},
|
|
{"SaveData_Debug", 0xC000000001000000},
|
|
{"SaveData_SystemManagement", 0xC000000002000000},
|
|
{"Unknown (0x16)", 0x8000000004000000},
|
|
{"Unknown (0x17)", 0x8000000008000000},
|
|
{"Unknown (0x18)", 0x8000000010000000},
|
|
{"Unknown (0x19)", 0x8000000000000800},
|
|
{"Unknown (0x1A)", 0x8000000000004020}
|
|
};
|
|
|
|
const char *npdm_get_proc_category(int process_category) {
|
|
switch (process_category) {
|
|
case 0:
|
|
return "Regular Title";
|
|
case 1:
|
|
return "Kernel Built-In";
|
|
default:
|
|
return "Unknown";
|
|
}
|
|
}
|
|
|
|
static const char *kac_get_app_type(uint32_t app_type) {
|
|
switch (app_type) {
|
|
case 0:
|
|
return "System Module";
|
|
case 1:
|
|
return "Application";
|
|
case 2:
|
|
return "Applet";
|
|
default:
|
|
return "Unknown";
|
|
}
|
|
}
|
|
|
|
static void kac_add_mmio(kac_t *kac, kac_mmio_t *mmio) {
|
|
/* Perform an ordered insertion. */
|
|
if (kac->mmio == NULL || mmio->address < kac->mmio->address) {
|
|
mmio->next = kac->mmio;
|
|
kac->mmio = mmio;
|
|
} else {
|
|
kac_mmio_t *ins_mmio = kac->mmio;
|
|
while (ins_mmio != NULL) {
|
|
if (ins_mmio->address < mmio->address) {
|
|
if (ins_mmio->next != NULL) {
|
|
if (ins_mmio->next->address > mmio->address) {
|
|
mmio->next = ins_mmio->next;
|
|
ins_mmio->next = mmio;
|
|
break;
|
|
}
|
|
} else {
|
|
ins_mmio->next = mmio;
|
|
break;
|
|
}
|
|
}
|
|
if (ins_mmio->next == NULL) {
|
|
ins_mmio->next = mmio;
|
|
break;
|
|
}
|
|
ins_mmio = ins_mmio->next;
|
|
}
|
|
}
|
|
}
|
|
|
|
void kac_print(const uint32_t *descriptors, uint32_t num_descriptors) {
|
|
kac_t kac;
|
|
kac_mmio_t *cur_mmio = NULL;
|
|
kac_mmio_t *page_mmio = NULL;
|
|
kac_irq_t *cur_irq = NULL;
|
|
unsigned int syscall_base;
|
|
memset(&kac, 0, sizeof(kac));
|
|
for (uint32_t i = 0; i < num_descriptors; i++) {
|
|
uint32_t desc = descriptors[i];
|
|
if (desc == 0xFFFFFFFF) {
|
|
continue;
|
|
}
|
|
unsigned int low_bits = 0;
|
|
while (desc & 1) {
|
|
desc >>= 1;
|
|
low_bits++;
|
|
}
|
|
desc >>= 1;
|
|
switch (low_bits) {
|
|
case 3: /* Kernel flags. */
|
|
kac.has_kern_flags = 1;
|
|
kac.highest_thread_prio = desc & 0x3F;
|
|
desc >>= 6;
|
|
kac.lowest_thread_prio = desc & 0x3F;
|
|
desc >>= 6;
|
|
kac.lowest_cpu_id = desc & 0xFF;
|
|
desc >>= 8;
|
|
kac.highest_cpu_id = desc & 0xFF;
|
|
break;
|
|
case 4: /* Syscall mask. */
|
|
syscall_base = (desc >> 24) * 0x18;
|
|
for (unsigned int sc = 0; sc < 0x18 && syscall_base + sc < 0x80; sc++) {
|
|
kac.svcs_allowed[syscall_base+sc] = desc & 1;
|
|
desc >>= 1;
|
|
}
|
|
break;
|
|
case 6: /* Map IO/Normal. */
|
|
cur_mmio = calloc(1, sizeof(kac_mmio_t));
|
|
cur_mmio->address = (desc & 0xFFFFFF) << 12;
|
|
cur_mmio->is_ro = desc >> 24;
|
|
if (i == num_descriptors - 1) {
|
|
fprintf(stderr, "Error: Invalid Kernel Access Control Descriptors!\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
desc = descriptors[++i];
|
|
if ((desc & 0x7F) != 0x3F) {
|
|
fprintf(stderr, "Error: Invalid Kernel Access Control Descriptors!\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
desc >>= 7;
|
|
cur_mmio->size = (desc & 0xFFFFFF) << 12;
|
|
cur_mmio->is_norm = desc >> 24;
|
|
kac_add_mmio(&kac, cur_mmio);
|
|
break;
|
|
case 7: /* Map Normal Page. */
|
|
page_mmio = calloc(1, sizeof(kac_mmio_t));
|
|
if (page_mmio == NULL) {
|
|
fprintf(stderr, "Failed to allocate MMIO descriptor!\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
page_mmio->address = desc << 12;
|
|
page_mmio->size = 0x1000;
|
|
page_mmio->is_ro = 0;
|
|
page_mmio->is_norm = 0;
|
|
page_mmio->next = NULL;
|
|
kac_add_mmio(&kac, page_mmio);
|
|
page_mmio = NULL;
|
|
break;
|
|
case 11: /* IRQ Pair. */
|
|
cur_irq = calloc(1, sizeof(kac_irq_t));
|
|
if (cur_irq == NULL) {
|
|
fprintf(stderr, "Failed to allocate IRQ descriptor!\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
cur_irq->irq0 = desc & 0x3FF;
|
|
cur_irq->irq1 = (desc >> 10) & 0x3FF;
|
|
if (kac.irqs == NULL) {
|
|
kac.irqs = cur_irq;
|
|
} else {
|
|
kac_irq_t *tail_irq = kac.irqs;
|
|
while (tail_irq->next != NULL) {
|
|
tail_irq = tail_irq->next;
|
|
}
|
|
tail_irq->next = cur_irq;
|
|
}
|
|
cur_irq = NULL;
|
|
break;
|
|
case 13: /* App Type. */
|
|
kac.has_app_type = 1;
|
|
kac.application_type = desc & 7;
|
|
break;
|
|
case 14: /* Kernel Release Version. */
|
|
kac.has_kern_ver = 1;
|
|
kac.kernel_release_version = desc;
|
|
break;
|
|
case 15: /* Handle Table Size. */
|
|
kac.has_handle_table_size = 1;
|
|
kac.handle_table_size = desc;
|
|
break;
|
|
case 16: /* Debug Flags. */
|
|
kac.has_debug_flags = 1;
|
|
kac.allow_debug = desc & 1;
|
|
kac.force_debug = (desc >> 1) & 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (kac.has_kern_flags) {
|
|
printf(" Lowest Allowed Priority: %"PRId32"\n", kac.lowest_thread_prio);
|
|
printf(" Highest Allowed Priority: %"PRId32"\n", kac.highest_thread_prio);
|
|
printf(" Lowest Allowed CPU ID: %"PRId32"\n", kac.lowest_cpu_id);
|
|
printf(" Highest Allowed CPU ID: %"PRId32"\n", kac.highest_cpu_id);
|
|
|
|
}
|
|
|
|
int first_svc = 1;
|
|
for (unsigned int i = 0; i < 0x80; i++) {
|
|
if (kac.svcs_allowed[i]) {
|
|
printf(first_svc ? " Allowed SVCs: %-35s (0x%02"PRIx32")\n" : " %-35s (0x%02"PRIx32")\n", svc_names[i], i);
|
|
first_svc = 0;
|
|
}
|
|
}
|
|
|
|
int first_mmio = 1;
|
|
if (kac.mmio != NULL) {
|
|
kac_mmio_t *cur_mmio;
|
|
while (kac.mmio != NULL) {
|
|
cur_mmio = kac.mmio;
|
|
printf(first_mmio ? " Mapped IO: " : " ");
|
|
first_mmio = 0;
|
|
printf("(%09"PRIx64"-%09"PRIx64", %s, %s)\n", cur_mmio->address, cur_mmio->address + cur_mmio->size, cur_mmio->is_ro ? "RO" : "RW", cur_mmio->is_norm ? "Normal" : "IO");
|
|
kac.mmio = kac.mmio->next;
|
|
free(cur_mmio);
|
|
}
|
|
}
|
|
|
|
if (kac.irqs != NULL) {
|
|
printf(" Mapped Interrupts: ");
|
|
int num_irqs = 0;
|
|
while (kac.irqs != NULL) {
|
|
cur_irq = kac.irqs;
|
|
if (cur_irq->irq0 != 0x3FF) {
|
|
if (num_irqs % 8 == 0) {
|
|
if (num_irqs) printf("\n ");
|
|
} else {
|
|
printf(", ");
|
|
}
|
|
printf("0x%03"PRIx32, cur_irq->irq0);
|
|
num_irqs++;
|
|
}
|
|
if (cur_irq->irq1 != 0x3FF) {
|
|
if (num_irqs % 8 == 0) {
|
|
if (num_irqs) printf("\n ");
|
|
} else {
|
|
printf(", ");
|
|
}
|
|
printf("0x%03"PRIx32, cur_irq->irq1);
|
|
num_irqs++;
|
|
}
|
|
kac.irqs = kac.irqs->next;
|
|
free(cur_irq);
|
|
}
|
|
printf("\n");
|
|
}
|
|
|
|
if (kac.has_app_type) {
|
|
printf(" Application Type: %s\n", kac_get_app_type(kac.application_type));
|
|
}
|
|
|
|
if (kac.has_handle_table_size) {
|
|
printf(" Handle Table Size: %"PRId32"\n", kac.handle_table_size);
|
|
}
|
|
|
|
if (kac.has_kern_ver) {
|
|
printf(" Minimum Kernel Version: %"PRIu32"\n", kac.kernel_release_version);
|
|
}
|
|
|
|
if (kac.has_debug_flags) {
|
|
printf(" Allow Debug: %s\n", kac.allow_debug ? "YES" : "NO");
|
|
printf(" Force Debug: %s\n", kac.force_debug ? "YES" : "NO");
|
|
}
|
|
}
|
|
|
|
/* Modified from https://stackoverflow.com/questions/23457305/compare-strings-with-wildcard */
|
|
static int match(const char *pattern, const char *candidate, int p, int c) {
|
|
if (pattern[p] == '\0') {
|
|
return candidate[c] == '\0';
|
|
} else if (pattern[p] == '*') {
|
|
for (; candidate[c] != '\0'; c++) {
|
|
if (match(pattern, candidate, p+1, c))
|
|
return 1;
|
|
}
|
|
return match(pattern, candidate, p+1, c);
|
|
} else {
|
|
return match(pattern, candidate, p+1, c+1);
|
|
}
|
|
}
|
|
|
|
static int sac_matches(sac_entry_t *lst, char *service) {
|
|
sac_entry_t *cur = lst;
|
|
while (cur != NULL) {
|
|
if (match(cur->service, service, 0, 0)) return 1;
|
|
cur = cur->next;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void sac_parse(char *sac, uint32_t sac_size, sac_entry_t *r_host, sac_entry_t *r_accesses, sac_entry_t **out_hosts, sac_entry_t **out_accesses) {
|
|
sac_entry_t *accesses = NULL;
|
|
sac_entry_t *hosts = NULL;
|
|
sac_entry_t *cur_entry = NULL;
|
|
sac_entry_t *temp = NULL;
|
|
uint32_t ofs = 0;
|
|
uint32_t service_len;
|
|
char ctrl;
|
|
while (ofs < sac_size) {
|
|
ctrl = sac[ofs++];
|
|
service_len = (ctrl & 0xF) + 1;
|
|
cur_entry = calloc(1, sizeof(sac_entry_t));
|
|
if (ctrl & 0x80) {
|
|
cur_entry->valid = r_host != NULL ? sac_matches(r_host, cur_entry->service) : 1;
|
|
} else {
|
|
cur_entry->valid = r_host != NULL ? sac_matches(r_accesses, cur_entry->service) : 1;
|
|
}
|
|
strncpy(cur_entry->service, &sac[ofs], service_len);
|
|
if (ctrl & 0x80 && hosts == NULL) {
|
|
hosts = cur_entry;
|
|
} else if (!(ctrl & 0x80) && accesses == NULL) {
|
|
accesses = cur_entry;
|
|
} else {
|
|
if (ctrl & 0x80) {
|
|
temp = hosts;
|
|
} else {
|
|
temp = accesses;
|
|
}
|
|
while (temp->next != NULL) {
|
|
temp = temp->next;
|
|
}
|
|
temp->next = cur_entry;
|
|
}
|
|
cur_entry = NULL;
|
|
ofs += service_len;
|
|
}
|
|
*out_hosts = hosts;
|
|
*out_accesses = accesses;
|
|
}
|
|
|
|
static void sac_print(char *acid_sac, uint32_t acid_size, char *aci0_sac, uint32_t aci0_size) {
|
|
/* Parse the ACID sac. */
|
|
sac_entry_t *acid_accesses = NULL;
|
|
sac_entry_t *acid_hosts = NULL;
|
|
sac_entry_t *temp = NULL;
|
|
sac_parse(acid_sac, acid_size, NULL, NULL, &acid_hosts, &acid_accesses);
|
|
|
|
/* The ACID sac restricts the ACI0 sac... */
|
|
sac_entry_t *aci0_accesses = NULL;
|
|
sac_entry_t *aci0_hosts = NULL;
|
|
sac_parse(aci0_sac, aci0_size, acid_hosts, acid_accesses, &aci0_hosts, &aci0_accesses);
|
|
|
|
int first = 1;
|
|
while (aci0_hosts != NULL) {
|
|
printf(first ? " Hosts: %-16s%s\n" : " %-16s%s\n", aci0_hosts->service, aci0_hosts->valid ? "" : "(Invalid)");
|
|
temp = aci0_hosts;
|
|
aci0_hosts = aci0_hosts->next;
|
|
free(temp);
|
|
first = 0;
|
|
}
|
|
|
|
first = 1;
|
|
while (aci0_accesses != NULL) {
|
|
printf(first ? " Accesses: %-16s%s\n" : " %-16s%s\n", aci0_accesses->service, aci0_accesses->valid ? "" : "(Invalid)");
|
|
temp = aci0_accesses;
|
|
aci0_accesses = aci0_accesses->next;
|
|
free(temp);
|
|
first = 0;
|
|
}
|
|
|
|
while (acid_hosts != NULL) {
|
|
temp = acid_hosts;
|
|
acid_hosts = acid_hosts->next;
|
|
free(temp);
|
|
}
|
|
|
|
while (acid_accesses != NULL) {
|
|
temp = acid_accesses;
|
|
acid_accesses = acid_accesses->next;
|
|
free(temp);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
static void fac_print(fac_t *fac, fah_t *fah) {
|
|
if (fac->version == fah->version) {
|
|
printf(" Version: %"PRId32"\n", fac->version);
|
|
} else {
|
|
printf(" Control Version: %"PRId32"\n", fac->version);
|
|
printf(" Header Version: %"PRId32"\n", fah->version);
|
|
}
|
|
uint64_t perms = fac->perms & fah->perms;
|
|
printf(" Raw Permissions: 0x%016"PRIx64"\n", perms);
|
|
printf(" RW Permissions: ");
|
|
for (unsigned int i = 0; i < MAX_FS_PERM_RW; i++) {
|
|
if (fs_permissions_rw[i].mask & perms) {
|
|
if (fs_permissions_rw[i].mask & (perms & FS_PERM_MASK_NODEBUG)) {
|
|
printf("%s\n ", fs_permissions_rw[i].name);
|
|
} else {
|
|
printf("%-32s [DEBUG ONLY]\n ", fs_permissions_rw[i].name);
|
|
}
|
|
}
|
|
}
|
|
printf("\n");
|
|
printf(" Boolean Permissions: ");
|
|
for (unsigned int i = 0; i < MAX_FS_PERM_BOOL; i++) {
|
|
if (fs_permissions_bool[i].mask & perms) {
|
|
if (fs_permissions_bool[i].mask & (perms & FS_PERM_MASK_NODEBUG)) {
|
|
printf("%s\n ", fs_permissions_bool[i].name);
|
|
} else {
|
|
printf("%-32s [DEBUG ONLY]\n ", fs_permissions_bool[i].name);
|
|
}
|
|
}
|
|
}
|
|
printf("\n");
|
|
}
|
|
|
|
void npdm_process(npdm_t *npdm, hactool_ctx_t *tool_ctx) {
|
|
if (tool_ctx->action & ACTION_INFO) {
|
|
npdm_print(npdm, tool_ctx);
|
|
}
|
|
|
|
if (tool_ctx->action & ACTION_EXTRACT) {
|
|
npdm_save(npdm, tool_ctx);
|
|
}
|
|
}
|
|
|
|
void npdm_print(npdm_t *npdm, hactool_ctx_t *tool_ctx) {
|
|
printf("NPDM:\n");
|
|
print_magic(" Magic: ", npdm->magic);
|
|
printf(" MMU Flags: %"PRIx8"\n", npdm->mmu_flags);
|
|
printf(" Main Thread Priority: %"PRId8"\n", npdm->main_thread_prio);
|
|
printf(" Default CPU ID: %"PRIx8"\n", npdm->default_cpuid);
|
|
printf(" Version: %"PRIu32".%"PRIu32".%"PRIu32"-%"PRIu32" (%"PRIu32")\n", (npdm->version >> 26) & 0x3F, (npdm->version >> 20) & 0x3F, (npdm->version >> 16) & 0xF, (npdm->version >> 0) & 0xFFFF, npdm->version);
|
|
printf(" Main Thread Stack Size: 0x%"PRIx32"\n", npdm->main_stack_size);
|
|
printf(" Title Name: %s\n", npdm->title_name);
|
|
npdm_acid_t *acid = npdm_get_acid(npdm);
|
|
npdm_aci0_t *aci0 = npdm_get_aci0(npdm);
|
|
printf(" ACID:\n");
|
|
print_magic(" Magic: ", acid->magic);
|
|
if (tool_ctx->action & ACTION_VERIFY) {
|
|
if (npdm->acid_sign_key_index < sizeof(tool_ctx->settings.keyset.acid_fixed_key_moduli) / sizeof(tool_ctx->settings.keyset.acid_fixed_key_moduli[0])) {
|
|
printf(" Signature Key (GOOD): %"PRIu32"\n", npdm->acid_sign_key_index);
|
|
if (rsa2048_pss_verify(acid->modulus, acid->size, acid->signature, tool_ctx->settings.keyset.acid_fixed_key_moduli[npdm->acid_sign_key_index])) {
|
|
memdump(stdout, " Signature (GOOD): ", &acid->signature, 0x100);
|
|
} else {
|
|
memdump(stdout, " Signature (FAIL): ", &acid->signature, 0x100);
|
|
}
|
|
} else {
|
|
printf(" Signature Key (FAIL): %"PRIu32"\n", npdm->acid_sign_key_index);
|
|
memdump(stdout, " Signature (FAIL): ", &acid->signature, 0x100);
|
|
}
|
|
|
|
} else {
|
|
printf(" Signature Key: %"PRIu32"\n", npdm->acid_sign_key_index);
|
|
memdump(stdout, " Signature: ", &acid->signature, 0x100);
|
|
}
|
|
memdump(stdout, " Header Modulus: ", &acid->modulus, 0x100);
|
|
printf(" Is Retail: %"PRId32"\n", acid->flags & 1);
|
|
printf(" Pool Partition: %"PRId32"\n", (acid->flags >> 2) & 3);
|
|
printf(" Title ID Range: %016"PRIx64"-%016"PRIx64"\n", acid->title_id_range_min, acid->title_id_range_max);
|
|
printf(" ACI0:\n");
|
|
print_magic(" Magic: ", aci0->magic);
|
|
printf(" Title ID: %016"PRIx64"\n", aci0->title_id);
|
|
|
|
/* Kernel access control. */
|
|
uint32_t *acid_kac = (uint32_t *)((char *)acid + acid->kac_offset);
|
|
uint32_t *aci0_kac = (uint32_t *)((char *)aci0 + aci0->kac_offset);
|
|
if (acid->kac_size == aci0->kac_size && memcmp(acid_kac, aci0_kac, acid->kac_size) == 0) {
|
|
/* Shared KAC. */
|
|
printf(" Kernel Access Control:\n");
|
|
kac_print(acid_kac, acid->kac_size/sizeof(uint32_t));
|
|
} else {
|
|
/* Different KAC. */
|
|
printf(" ACID Kernel Access Control:\n");
|
|
kac_print(acid_kac, acid->kac_size/sizeof(uint32_t));
|
|
printf(" ACI0 Kernel Access Control:\n");
|
|
kac_print(aci0_kac, aci0->kac_size/sizeof(uint32_t));
|
|
}
|
|
|
|
/* Service access control. */
|
|
char *acid_sac = ((char *)acid + acid->sac_offset);
|
|
char *aci0_sac = ((char *)aci0 + aci0->sac_offset);
|
|
printf(" Service Access Control:\n");
|
|
sac_print(acid_sac, acid->sac_size, aci0_sac, aci0->sac_size);
|
|
|
|
/* FS access control. */
|
|
fac_t *fac = (fac_t *)((char *)acid + acid->fac_offset);
|
|
fah_t *fah = (fah_t *)((char *)aci0 + aci0->fah_offset);
|
|
printf(" Filesystem Access Control:\n");
|
|
fac_print(fac, fah);
|
|
}
|
|
|
|
|
|
void npdm_save(npdm_t *npdm, hactool_ctx_t *tool_ctx) {
|
|
filepath_t *json_path = &tool_ctx->settings.npdm_json_path;
|
|
if (json_path->valid != VALIDITY_VALID) {
|
|
return;
|
|
}
|
|
|
|
FILE *f_json = os_fopen(json_path->os_path, OS_MODE_WRITE);
|
|
if (f_json == NULL) {
|
|
fprintf(stderr, "Failed to open %s!\n", json_path->char_path);
|
|
return;
|
|
}
|
|
|
|
char *json = npdm_get_json(npdm);
|
|
if (fwrite(json, 1, strlen(json), f_json) != strlen(json)) {
|
|
fprintf(stderr, "Failed to write JSON file!\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
cJSON_free(json);
|
|
fclose(f_json);
|
|
}
|
|
|
|
static cJSON *kac_create_obj(const char *type, cJSON *val) {
|
|
cJSON *tempobj = NULL;
|
|
|
|
tempobj = cJSON_CreateObject();
|
|
cJSON_AddStringToObject(tempobj, "type", type);
|
|
cJSON_AddItemToObject(tempobj, "value", val);
|
|
return tempobj;
|
|
}
|
|
|
|
void cJSON_AddU16ToKacArray(cJSON *obj, const char *name, uint16_t val) {
|
|
char buf[0x20] = {0};
|
|
snprintf(buf, sizeof(buf), "0x%04"PRIx16, val);
|
|
cJSON_AddItemToArray(obj, kac_create_obj(name, cJSON_CreateString(buf)));
|
|
}
|
|
|
|
void cJSON_AddU32ToKacArray(cJSON *obj, const char *name, uint32_t val) {
|
|
char buf[0x20] = {0};
|
|
snprintf(buf, sizeof(buf), "0x%08"PRIx32, val);
|
|
cJSON_AddItemToArray(obj, kac_create_obj(name, cJSON_CreateString(buf)));
|
|
}
|
|
|
|
void cJSON_AddU8ToObject(cJSON *obj, const char *name, uint8_t val) {
|
|
char buf[0x20] = {0};
|
|
snprintf(buf, sizeof(buf), "0x%02"PRIx8, val);
|
|
cJSON_AddStringToObject(obj, name, buf);
|
|
}
|
|
|
|
void cJSON_AddU16ToObject(cJSON *obj, const char *name, uint16_t val) {
|
|
char buf[0x20] = {0};
|
|
snprintf(buf, sizeof(buf), "0x%04"PRIx16, val);
|
|
cJSON_AddStringToObject(obj, name, buf);
|
|
}
|
|
|
|
void cJSON_AddU32ToObject(cJSON *obj, const char *name, uint32_t val) {
|
|
char buf[0x20] = {0};
|
|
snprintf(buf, sizeof(buf), "0x%08"PRIx32, val);
|
|
cJSON_AddStringToObject(obj, name, buf);
|
|
}
|
|
|
|
void cJSON_AddU64ToObject(cJSON *obj, const char *name, uint64_t val) {
|
|
char buf[0x20] = {0};
|
|
snprintf(buf, sizeof(buf), "0x%016"PRIx64, val);
|
|
cJSON_AddStringToObject(obj, name, buf);
|
|
}
|
|
|
|
static cJSON *sac_access_get_json(char *sac, uint32_t sac_size) {
|
|
cJSON *sac_json = cJSON_CreateArray();
|
|
char service[9] = {0};
|
|
uint32_t ofs = 0;
|
|
uint32_t service_len;
|
|
char ctrl;
|
|
while (ofs < sac_size) {
|
|
ctrl = sac[ofs++];
|
|
service_len = (ctrl & 0x7) + 1;
|
|
if (!(ctrl & 0x80)) {
|
|
memset(service, 0, sizeof(service));
|
|
memcpy(service, &sac[ofs], service_len);
|
|
cJSON_AddItemToArray(sac_json, cJSON_CreateString(service));
|
|
}
|
|
ofs += service_len;
|
|
}
|
|
|
|
return sac_json;
|
|
}
|
|
|
|
static cJSON *sac_host_get_json(char *sac, uint32_t sac_size) {
|
|
cJSON *sac_json = cJSON_CreateArray();
|
|
char service[9] = {0};
|
|
uint32_t ofs = 0;
|
|
uint32_t service_len;
|
|
char ctrl;
|
|
while (ofs < sac_size) {
|
|
ctrl = sac[ofs++];
|
|
service_len = (ctrl & 0x7) + 1;
|
|
if (ctrl & 0x80) {
|
|
memset(service, 0, sizeof(service));
|
|
memcpy(service, &sac[ofs], service_len);
|
|
cJSON_AddItemToArray(sac_json, cJSON_CreateString(service));
|
|
}
|
|
ofs += service_len;
|
|
}
|
|
|
|
return sac_json;
|
|
}
|
|
|
|
cJSON *kac_get_json(const uint32_t *descriptors, uint32_t num_descriptors) {
|
|
cJSON *kac_json = cJSON_CreateArray();
|
|
cJSON *syscall_memory = NULL;
|
|
cJSON *temp = NULL;
|
|
unsigned int syscall_base;
|
|
for (uint32_t i = 0; i < num_descriptors; i++) {
|
|
uint32_t desc = descriptors[i];
|
|
if (desc == 0xFFFFFFFF) {
|
|
continue;
|
|
}
|
|
unsigned int low_bits = 0;
|
|
while (desc & 1) {
|
|
desc >>= 1;
|
|
low_bits++;
|
|
}
|
|
desc >>= 1;
|
|
switch (low_bits) {
|
|
case 3: /* Kernel flags. */
|
|
temp = cJSON_CreateObject();
|
|
cJSON_AddNumberToObject(temp, "highest_thread_priority", desc & 0x3F);
|
|
desc >>= 6;
|
|
cJSON_AddNumberToObject(temp, "lowest_thread_priority", desc & 0x3F);
|
|
desc >>= 6;
|
|
cJSON_AddNumberToObject(temp, "lowest_cpu_id", desc & 0xFF);
|
|
desc >>= 8;
|
|
cJSON_AddNumberToObject(temp, "highest_cpu_id", desc & 0xFF);
|
|
cJSON_AddItemToArray(kac_json, kac_create_obj("kernel_flags", temp));
|
|
break;
|
|
case 4: /* Syscall mask. */
|
|
if (syscall_memory == NULL) {
|
|
temp = cJSON_CreateObject();
|
|
cJSON_AddItemToArray(kac_json, kac_create_obj("syscalls", temp));
|
|
syscall_memory = temp;
|
|
} else {
|
|
temp = syscall_memory;
|
|
}
|
|
syscall_base = (desc >> 24) * 0x18;
|
|
for (unsigned int sc = 0; sc < 0x18 && syscall_base + sc < 0x80; sc++) {
|
|
if (desc & 1) {
|
|
cJSON_AddU8ToObject(temp, strdup(svc_names[sc + syscall_base]), sc + syscall_base);
|
|
}
|
|
desc >>= 1;
|
|
}
|
|
break;
|
|
case 6: /* Map IO/Normal. */
|
|
temp = cJSON_CreateObject();
|
|
cJSON_AddU32ToObject(temp, "address", (desc & 0xFFFFFF) << 12);
|
|
cJSON_AddBoolToObject(temp, "is_ro", (desc >> 24) & 1);
|
|
if (i == num_descriptors - 1) {
|
|
fprintf(stderr, "Error: Invalid Kernel Access Control Descriptors!\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
desc = descriptors[++i];
|
|
if ((desc & 0x7F) != 0x3F) {
|
|
fprintf(stderr, "Error: Invalid Kernel Access Control Descriptors!\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
desc >>= 7;
|
|
cJSON_AddU32ToObject(temp, "size", (desc & 0xFFFFFF) << 12);
|
|
cJSON_AddBoolToObject(temp, "is_io", ((desc >> 24) & 1) == 0);
|
|
cJSON_AddItemToArray(kac_json, kac_create_obj("map", temp));
|
|
break;
|
|
case 7: /* Map Normal Page. */
|
|
cJSON_AddU32ToKacArray(kac_json, "map_page", desc << 12);
|
|
break;
|
|
case 11: /* IRQ Pair. */
|
|
temp = cJSON_CreateArray();
|
|
if ((desc & 0x3FF) == 0x3FF) {
|
|
cJSON_AddItemToArray(temp, cJSON_CreateNull());
|
|
} else {
|
|
cJSON_AddItemToArray(temp, cJSON_CreateNumber(desc & 0x3FF));
|
|
}
|
|
desc >>= 10;
|
|
if ((desc & 0x3FF) == 0x3FF) {
|
|
cJSON_AddItemToArray(temp, cJSON_CreateNull());
|
|
} else {
|
|
cJSON_AddItemToArray(temp, cJSON_CreateNumber(desc & 0x3FF));
|
|
}
|
|
cJSON_AddItemToArray(kac_json, kac_create_obj("irq_pair", temp));
|
|
break;
|
|
case 13: /* App Type. */
|
|
cJSON_AddItemToArray(kac_json, kac_create_obj("application_type", cJSON_CreateNumber(desc & 7)));
|
|
break;
|
|
case 14: /* Kernel Release Version. */
|
|
cJSON_AddU16ToKacArray(kac_json, "min_kernel_version", desc & 0xFFFF);
|
|
break;
|
|
case 15: /* Handle Table Size. */
|
|
cJSON_AddItemToArray(kac_json, kac_create_obj("handle_table_size", cJSON_CreateNumber(desc)));
|
|
break;
|
|
case 16: /* Debug Flags. */
|
|
temp = cJSON_CreateObject();
|
|
cJSON_AddBoolToObject(temp, "allow_debug", (desc >> 0) & 1);
|
|
cJSON_AddBoolToObject(temp, "force_debug", (desc >> 1) & 1);
|
|
cJSON_AddItemToArray(kac_json, kac_create_obj("debug_flags", temp));
|
|
|
|
// kac.has_debug_flags = 1;
|
|
// kac.allow_debug = desc & 1;
|
|
// kac.force_debug = (desc >> 1) & 1;
|
|
break;
|
|
}
|
|
temp = NULL;
|
|
}
|
|
return kac_json;
|
|
}
|
|
|
|
char *npdm_get_json(npdm_t *npdm) {
|
|
npdm_acid_t *acid = npdm_get_acid(npdm);
|
|
npdm_aci0_t *aci0 = npdm_get_aci0(npdm);
|
|
cJSON *npdm_json = cJSON_CreateObject();
|
|
char *output_str = NULL;
|
|
char work_buffer[0x300] = {0};
|
|
|
|
/* Add NPDM header fields. */
|
|
strcpy(work_buffer, npdm->title_name);
|
|
cJSON_AddStringToObject(npdm_json, "name", work_buffer);
|
|
cJSON_AddU64ToObject(npdm_json, "title_id", aci0->title_id);
|
|
cJSON_AddU64ToObject(npdm_json, "title_id_range_min", acid->title_id_range_min);
|
|
cJSON_AddU64ToObject(npdm_json, "title_id_range_max", acid->title_id_range_max);
|
|
cJSON_AddU32ToObject(npdm_json, "main_thread_stack_size", npdm->main_stack_size);
|
|
cJSON_AddNumberToObject(npdm_json, "main_thread_priority", npdm->main_thread_prio);
|
|
cJSON_AddNumberToObject(npdm_json, "default_cpu_id", npdm->default_cpuid);
|
|
cJSON_AddU32ToObject(npdm_json, "version", npdm->version);
|
|
cJSON_AddBoolToObject(npdm_json, "is_retail", acid->flags & 1);
|
|
cJSON_AddNumberToObject(npdm_json, "pool_partition", (acid->flags >> 2) & 3);
|
|
cJSON_AddBoolToObject(npdm_json, "is_64_bit", npdm->mmu_flags & 1);
|
|
cJSON_AddNumberToObject(npdm_json, "address_space_type", (npdm->mmu_flags >> 1) & 7);
|
|
|
|
/* Add FAC. */
|
|
fac_t *fac = (fac_t *)((char *)acid + acid->fac_offset);
|
|
fah_t *fah = (fah_t *)((char *)aci0 + aci0->fah_offset);
|
|
cJSON *fac_json = cJSON_CreateObject();
|
|
cJSON_AddU64ToObject(fac_json, "permissions", fac->perms & fah->perms);
|
|
cJSON_AddItemToObject(npdm_json, "filesystem_access", fac_json);
|
|
|
|
/* Add SAC. */
|
|
cJSON *sac_access_json = sac_access_get_json((char *)aci0 + aci0->sac_offset, aci0->sac_size);
|
|
cJSON *sac_host_json = sac_host_get_json((char *)aci0 + aci0->sac_offset, aci0->sac_size);
|
|
cJSON_AddItemToObject(npdm_json, "service_access", sac_access_json);
|
|
cJSON_AddItemToObject(npdm_json, "service_host", sac_host_json);
|
|
|
|
/* Add KAC. */
|
|
cJSON *kac_json = kac_get_json((uint32_t *)((char *)aci0 + aci0->kac_offset), aci0->kac_size / sizeof(uint32_t));
|
|
cJSON_AddItemToObject(npdm_json, "kernel_capabilities", kac_json);
|
|
|
|
output_str = cJSON_Print(npdm_json);
|
|
|
|
cJSON_Delete(npdm_json);
|
|
return output_str;
|
|
}
|