mirror of
https://github.com/SciresM/hactool
synced 2024-11-10 06:34:14 +00:00
653ed85536
One case was specifying a size smaller than the actual type being printed, while the other was caused due to C's integral promotion rules. Operation on types smaller than int, will be promoted to an int. In this case, the mask was unnecessary anyways, so it can just be removed.
917 lines
33 KiB
C
917 lines
33 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",
|
|
"svcGetFutureThreadInfo",
|
|
"svcGetLastThreadInfo",
|
|
"svcGetResourceLimitLimitValue",
|
|
"svcGetResourceLimitCurrentValue",
|
|
"svcSetThreadActivity",
|
|
"svcGetThreadContext3",
|
|
"svcWaitForAddress",
|
|
"svcSignalToAddress",
|
|
"svcUnknown",
|
|
"svcUnknown",
|
|
"svcUnknown",
|
|
"svcUnknown",
|
|
"svcUnknown",
|
|
"svcUnknown",
|
|
"svcDumpInfo",
|
|
"svcDumpInfoNew",
|
|
"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(" Process Category: %s\n", npdm_get_proc_category(npdm->process_category));
|
|
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 (rsa2048_pss_verify(acid->modulus, acid->size, acid->signature, tool_ctx->settings.keyset.acid_fixed_key_modulus)) {
|
|
memdump(stdout, " Signature (GOOD): ", &acid->signature, 0x100);
|
|
} else {
|
|
memdump(stdout, " Signature (FAIL): ", &acid->signature, 0x100);
|
|
}
|
|
} else {
|
|
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_AddNumberToObject(npdm_json, "process_category", npdm->process_category);
|
|
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;
|
|
}
|