diff --git a/sysmodules/loader/source/hbldr.c b/sysmodules/loader/source/hbldr.c index 5529236..fcc8cec 100644 --- a/sysmodules/loader/source/hbldr.c +++ b/sysmodules/loader/source/hbldr.c @@ -27,10 +27,13 @@ #include <3ds.h> #include #include +#include "paslr.h" #include "util.h" #include "hbldr.h" #include "3dsx.h" +extern bool isN3DS; + static const char serviceList[32][8] = { "APT:U", @@ -225,8 +228,6 @@ void hbldrPatchExHeaderInfo(ExHeader_Info *exhi) u64 lastdep = sizeof(dependencyListNativeFirm)/8; exhi->sci.dependencies[lastdep++] = 0x0004013000004002ULL; // nfc strncpy((char*)&localcaps0->service_access[0x20], "nfc:u", 8); - s64 dummy = 0; - bool isN3DS = svcGetSystemInfo(&dummy, 0x10001, 0) == 0; if (isN3DS) { exhi->sci.dependencies[lastdep++] = 0x0004013020004102ULL; // mvd @@ -275,7 +276,7 @@ Result hbldrLoadProcess(Handle *outProcessHandle, const ExHeader_Info *exhi) u32 tmp = 0; u32 addr = 0x10000000; - res = svcControlMemory(&tmp, addr, 0, totalSize, MEMOP_ALLOC | region, MEMPERM_READ | MEMPERM_WRITE); + res = allocateProgramMemory(exhi, addr, totalSize); if (R_FAILED(res)) { IFile_Close(&file); diff --git a/sysmodules/loader/source/hbldr.h b/sysmodules/loader/source/hbldr.h index f6fe40f..8a4e44c 100644 --- a/sysmodules/loader/source/hbldr.h +++ b/sysmodules/loader/source/hbldr.h @@ -35,8 +35,10 @@ void hbldrHandleCommands(void *ctx); static inline bool hbldrIs3dsxTitle(u64 tid) { - return Luma_SharedConfig->use_hbldr && tid == Luma_SharedConfig->hbldr_3dsx_tid; -} + if (!Luma_SharedConfig->use_hbldr) + return false; + u64 hbldrTid = Luma_SharedConfig->hbldr_3dsx_tid; -void HBLDR_RestartHbApplication(void *p); -void HBLDR_HandleCommands(void *ctx); + // Just like p9 clears them, ignore platform/N3DS bits + return ((tid ^ hbldrTid) & ~0xF0000000ull) == 0; +} diff --git a/sysmodules/loader/source/loader.c b/sysmodules/loader/source/loader.c index 1a9eae5..8b09fb5 100644 --- a/sysmodules/loader/source/loader.c +++ b/sysmodules/loader/source/loader.c @@ -1,6 +1,7 @@ #include <3ds.h> #include "memory.h" #include "patcher.h" +#include "paslr.h" #include "ifile.h" #include "util.h" #include "hbldr.h" @@ -9,8 +10,8 @@ extern u32 config, multiConfig, bootConfig; extern bool isN3DS, isSdMode; -static u8 g_ret_buf[sizeof(ExHeader_Info)]; static u64 g_cached_programHandle; +static u64 g_cached_hbldr3dsxTid; static ExHeader_Info g_exheaderInfo; typedef struct ContentPath { @@ -20,7 +21,7 @@ typedef struct ContentPath { static const ContentPath codeContentPath = { .contentType = 1, // ExeFS (with code) - .fileName = ".code", // last 3 bytes have to be 0, but this is guaranteed here.s + .fileName = ".code", // last 3 bytes have to be 0, but this is guaranteed here. }; typedef struct prog_addrs_t @@ -100,18 +101,16 @@ static int lzss_decompress(u8 *end) return ret; } -static Result allocateSharedMem(prog_addrs_t *shared, const prog_addrs_t *vaddr, int flags) +static Result allocateProgramMemoryWrapper(prog_addrs_t *mapped, const ExHeader_Info *exhi, const prog_addrs_t *vaddr) { - u32 dummy; - - memcpy(shared, vaddr, sizeof(prog_addrs_t)); - shared->text_addr = 0x10000000; - shared->ro_addr = shared->text_addr + (shared->text_size << 12); - shared->data_addr = shared->ro_addr + (shared->ro_size << 12); - return svcControlMemory(&dummy, shared->text_addr, 0, shared->total_size << 12, (flags & 0xF00) | MEMOP_ALLOC, MEMPERM_READ | MEMPERM_WRITE); + memcpy(mapped, vaddr, sizeof(prog_addrs_t)); + mapped->text_addr = 0x10000000; + mapped->ro_addr = mapped->text_addr + (mapped->text_size << 12); + mapped->data_addr = mapped->ro_addr + (mapped->ro_size << 12); + return allocateProgramMemory(exhi, mapped->text_addr, mapped->total_size << 12); } -static Result loadCode(const ExHeader_Info *exhi, u64 programHandle, const prog_addrs_t *shared) +static Result loadCode(const ExHeader_Info *exhi, u64 programHandle, const prog_addrs_t *mapped) { IFile file; FS_Path archivePath; @@ -123,7 +122,7 @@ static Result loadCode(const ExHeader_Info *exhi, u64 programHandle, const prog_ const ExHeader_CodeSetInfo *csi = &exhi->sci.codeset_info; bool isCompressed = csi->flags.compress_exefs_code; - if(!CONFIG(PATCHGAMES) || !loadTitleCodeSection(titleId, (u8 *)shared->text_addr, (u64)shared->total_size << 12)) + if(!CONFIG(PATCHGAMES) || !loadTitleCodeSection(titleId, (u8 *)mapped->text_addr, (u64)mapped->total_size << 12)) { archivePath.type = PATH_BINARY; archivePath.data = &programHandle; @@ -132,30 +131,27 @@ static Result loadCode(const ExHeader_Info *exhi, u64 programHandle, const prog_ filePath.type = PATH_BINARY; filePath.data = &codeContentPath; filePath.size = sizeof(codeContentPath); - Result res; - if (R_FAILED(res = IFile_Open(&file, ARCHIVE_SAVEDATA_AND_CONTENT2, archivePath, filePath, FS_OPEN_READ))) - *(u64 *)0x1238 = programHandle;//panic(programHandle);//svcBreak(USERBREAK_ASSERT); - // get file size + assertSuccess(IFile_Open(&file, ARCHIVE_SAVEDATA_AND_CONTENT2, archivePath, filePath, FS_OPEN_READ)); assertSuccess(IFile_GetSize(&file, &size)); // check size - if (size > (u64)shared->total_size << 12) + if (size > (u64)mapped->total_size << 12) { IFile_Close(&file); return 0xC900464F; } // read code - assertSuccess(IFile_Read(&file, &total, (void *)shared->text_addr, size)); + assertSuccess(IFile_Read(&file, &total, (void *)mapped->text_addr, size)); IFile_Close(&file); // done reading // decompress if (isCompressed) - lzss_decompress((u8 *)shared->text_addr + size); + lzss_decompress((u8 *)mapped->text_addr + size); } - patchCode(titleId, csi->flags.remaster_version, (u8 *)shared->text_addr, shared->total_size << 12, csi->text.size, csi->rodata.size, csi->data.size, csi->rodata.address, csi->data.address); + patchCode(titleId, csi->flags.remaster_version, (u8 *)mapped->text_addr, mapped->total_size << 12, csi->text.size, csi->rodata.size, csi->data.size, csi->rodata.address, csi->data.address); return 0; } @@ -171,7 +167,7 @@ static inline bool IsHioId(u64 id) return R_LEVEL(FSREG_CheckHostLoadId(id)) == RL_SUCCESS; // check if this is an alias to an HIO-loaded title } -static Result GetProgramInfo(ExHeader_Info *exheaderInfo, u64 programHandle) +static Result GetProgramInfoImpl(ExHeader_Info *exheaderInfo, u64 programHandle) { Result res; TRY(IsHioId(programHandle) ? FSREG_GetProgramInfo(exheaderInfo, 1, programHandle) : PXIPM_GetProgramInfo(exheaderInfo, programHandle)); @@ -191,13 +187,31 @@ static Result GetProgramInfo(ExHeader_Info *exheaderInfo, u64 programHandle) return res; } +static Result GetProgramInfo(u64 programHandle) +{ + Result res = 0; + + u64 cachedTid = g_exheaderInfo.aci.local_caps.title_id; + u64 hbldr3dsxTid = Luma_SharedConfig->hbldr_3dsx_tid; + bool hbTitleChanged = hbldr3dsxTid != g_cached_hbldr3dsxTid; + g_cached_hbldr3dsxTid = Luma_SharedConfig->hbldr_3dsx_tid; + + if (programHandle != g_cached_programHandle || (hbldrIs3dsxTitle(cachedTid) && hbTitleChanged)) + { + res = GetProgramInfoImpl(&g_exheaderInfo, programHandle); + g_cached_programHandle = R_SUCCEEDED(res) ? programHandle : 0; + } + + return res; +} + static Result LoadProcessImpl(Handle *outProcessHandle, const ExHeader_Info *exhi, u64 programHandle) { const ExHeader_CodeSetInfo *csi = &exhi->sci.codeset_info; Result res = 0; u32 dummy; - prog_addrs_t sharedAddr; + prog_addrs_t mapped; prog_addrs_t vaddr; Handle codeset; CodeSetInfo codesetinfo; @@ -223,11 +237,11 @@ static Result LoadProcessImpl(Handle *outProcessHandle, const ExHeader_Info *exh vaddr.data_size = (csi->data.size + 4095) >> 12; dataMemSize = (csi->data.size + csi->bss_size + 4095) >> 12; vaddr.total_size = vaddr.text_size + vaddr.ro_size + vaddr.data_size; - TRY(allocateSharedMem(&sharedAddr, &vaddr, region)); + TRY(allocateProgramMemoryWrapper(&mapped, exhi, &vaddr)); // load code u64 titleId = exhi->aci.local_caps.title_id; - if (R_SUCCEEDED(res = loadCode(exhi, programHandle, &sharedAddr))) + if (R_SUCCEEDED(res = loadCode(exhi, programHandle, &mapped))) { memcpy(&codesetinfo.name, csi->name, 8); codesetinfo.program_id = titleId; @@ -240,7 +254,7 @@ static Result LoadProcessImpl(Handle *outProcessHandle, const ExHeader_Info *exh codesetinfo.rw_addr = vaddr.data_addr; codesetinfo.rw_size = vaddr.data_size; codesetinfo.rw_size_total = dataMemSize; - res = svcCreateCodeSet(&codeset, &codesetinfo, (void *)sharedAddr.text_addr, (void *)sharedAddr.ro_addr, (void *)sharedAddr.data_addr); + res = svcCreateCodeSet(&codeset, &codesetinfo, (void *)mapped.text_addr, (void *)mapped.ro_addr, (void *)mapped.data_addr); if (R_SUCCEEDED(res)) { // There are always 28 descriptors @@ -250,25 +264,14 @@ static Result LoadProcessImpl(Handle *outProcessHandle, const ExHeader_Info *exh } } - svcControlMemory(&dummy, sharedAddr.text_addr, 0, sharedAddr.total_size << 12, MEMOP_FREE, 0); + svcControlMemory(&dummy, mapped.text_addr, 0, mapped.total_size << 12, MEMOP_FREE, 0); return res; } static Result LoadProcess(Handle *process, u64 programHandle) { - Result res; - - // make sure the cached info corresponds to the current programHandle - if (g_cached_programHandle != programHandle || hbldrIs3dsxTitle(g_exheaderInfo.aci.local_caps.title_id)) - { - res = GetProgramInfo(&g_exheaderInfo, programHandle); - g_cached_programHandle = programHandle; - if (R_FAILED(res)) - { - g_cached_programHandle = 0; - return res; - } - } + Result res = 0; + TRY(GetProgramInfo(programHandle)); if (hbldrIs3dsxTitle(g_exheaderInfo.aci.local_caps.title_id)) return hbldrLoadProcess(process, &g_exheaderInfo); @@ -348,16 +351,11 @@ void loaderHandleCommands(void *ctx) break; case 4: // GetProgramInfo memcpy(&programHandle, &cmdbuf[1], 8); - if (programHandle != g_cached_programHandle || hbldrIs3dsxTitle(g_exheaderInfo.aci.local_caps.title_id)) - { - res = GetProgramInfo(&g_exheaderInfo, programHandle); - g_cached_programHandle = R_SUCCEEDED(res) ? programHandle : 0; - } - memcpy(&g_ret_buf, &g_exheaderInfo, sizeof(ExHeader_Info)); + res = GetProgramInfo(programHandle); cmdbuf[0] = IPC_MakeHeader(4, 1, 2); cmdbuf[1] = res; cmdbuf[2] = IPC_Desc_StaticBuffer(sizeof(ExHeader_Info), 0); //0x1000002; - cmdbuf[3] = (u32)&g_ret_buf; + cmdbuf[3] = (u32)&g_exheaderInfo; // official Loader makes a copy here, but this is isn't necessary break; default: // error cmdbuf[0] = IPC_MakeHeader(0, 1, 0); diff --git a/sysmodules/loader/source/paslr.c b/sysmodules/loader/source/paslr.c new file mode 100644 index 0000000..43de7b5 --- /dev/null +++ b/sysmodules/loader/source/paslr.c @@ -0,0 +1,167 @@ +#include <3ds.h> +#include "paslr.h" +#include "util.h" +#include "hbldr.h" + +static const u32 titleUidListForPaslr[] = { + // JPN/USA/EUR/KOR/TWN or GLOBAL + 0x0209, 0x0219, 0x0229, 0x0269, 0x0289, // Nintendo eShop + 0x0334, 0x0335, 0x0336, // The Legend of Zelda: Ocarina of Time 3D + 0x0343, 0x0465, 0x04B3, // Cubic Ninja + 0x0913, 0x09FA, 0x0933, // Freakyforms Deluxe + 0x07FD, 0x0961, // VVVVVV + 0x0A5D, 0x0A5E, 0x0A6F, 0x0C61, 0x0C81, // Paper Mario: Sticker Star + 0x0D7C, 0x0D7D, 0x0D7E, // Steel Diver: Sub Wars + 0x11C4, // Pokemon Omega Ruby (GLOBAL) + 0x11C5, // Pokemon Alpha Sapphire (GLOBAL) + 0x149B, 0x1746, 0x1744, // Pokemon Super Mystery Dungeon + 0x1773, 0x12C1, 0x136E, // Citizens of Earth + 0x17C1, // Pokemon Picross (GLOBAL) +}; + +static u32 getRandomInt(u32 maxNum) +{ + // As reverse-engineered from latest Loader + static u64 state = 0; + s64 tick = (s64)svcGetSystemTick(); + + u64 tmp = (state >> 7) - (tick >> 2); + state = (tmp ^ state) & 0x49CC9BA7FC61CA67ull; + + tmp = 0x5D588B656C078965ull * state + 0x29EC3; + return (u32)(((tmp >> 32) * maxNum) >> 32); +} + +static bool needsPaslr(u32 *outRegion, const ExHeader_Info *exhi) +{ + u32 region = 0; + u16 minKernelVer = 0; + for (u32 i = 0; i < 28; i++) + { + u32 desc = exhi->aci.kernel_caps.descriptors[i]; + if (desc >> 23 == 0x1FE) + region = desc & 0xF00; + else if (desc >> 25 == 0x7E) + minKernelVer = (u16)desc; + } + + *outRegion = region; + +#ifdef LOADER_ENABLE_PASLR + // Only applications and system applets (HM, Internet Browser...) are eligible for PASLR + if (region != MEMOP_REGION_APP && region != MEMOP_REGION_SYSTEM) + return false; + else if (region == MEMOP_REGION_SYSTEM && exhi->aci.local_caps.reslimit_category == RESLIMIT_CATEGORY_LIB_APPLET) + return false; + + // All titles requiring 11.2+ kernel get PASLR + if (minKernelVer >= (SYSTEM_VERSION(2, 57, 0) >> 16)) + return true; + + // Otherwise, only games with known exploits and eShop get it + u64 titleId = exhi->aci.local_caps.title_id; + + // Check if this is indeed a CTR title ID (high u32 = 00040xxx) + if (titleId >> 46 != 0x10) + return false; + + for (u32 i = 0; i < sizeof(titleUidListForPaslr)/sizeof(titleUidListForPaslr[0]); i++) + { + if (((titleId >> 8) & 0xFFFFF) == titleUidListForPaslr[i]) + return true; + } +#else + (void)minKernelVer; + (void)titleUidListForPaslr; +#endif + return false; +} + +Result allocateProgramMemory(const ExHeader_Info *exhi, u32 vaddr, u32 size) +{ + Result res = 0; + u32 region = 0; + bool doPaslr = needsPaslr(®ion, exhi); + u32 tmp = 0; + + if (region == 0) + return MAKERESULT(RL_PERMANENT, RS_INVALIDARG, 1, 2); + + u32 allocFlags = MEMOP_ALLOC | region; + if (!doPaslr) + return svcControlMemory(&tmp, vaddr, 0, size, allocFlags, MEMPERM_READWRITE); + + // Divide the virtual address space into up to 7 chunks, each a random multiple (between 1 and 16) of 64KB. + // 64KB corresponds to a "large page" for the Armv6 MMU, hence the optimization choice. + // An additional 8th chunk gets the rest (if anything). + u32 totalNumPages = size >> 12; + u32 curPage = 0; + u32 numChunks; + u32 chunkStartAddrs[8]; + u32 chunkSizes[8]; + for (numChunks = 0; numChunks < 7; numChunks++) + { + u32 maxUnits = (totalNumPages - curPage) / 16; + if (maxUnits == 0) + break; + else if (maxUnits > 15) + maxUnits = 15; + + u32 numPages = 16 * (1 + getRandomInt(maxUnits)); + chunkStartAddrs[numChunks] = vaddr + (curPage << 12); + chunkSizes[numChunks] = numPages << 12; + curPage += numPages; + } + if (curPage < totalNumPages) + { + u32 numPages = totalNumPages - curPage; + chunkStartAddrs[numChunks] = vaddr + (curPage << 12); + chunkSizes[numChunks] = numPages << 12; + curPage += numPages; + ++numChunks; + } + + // Randomize the order the VA chunks are allocated in physical memory, from last to first + u32 chunkOrder[8] = {0, 1, 2, 3, 4, 5, 6, 7}; + u32 numChunksToRandomize = numChunks; + if (totalNumPages % 16 != 0) + { + // For MMU optimization reasons, we only randomize chunks that are multiples of 64KB (large page) + // in size. This means that if the process image (.text, .rodata, .data) is not a muliple of 64KB + // in size, then its last few pages are not randomized at all. + // In pratice, under normal circumstances, this means that the last few pages of applications are + // guaranteed to be located in - (image_size & ~0xFFFF). + --numChunksToRandomize; + } + + for (s32 i = numChunksToRandomize - 1; i >= 0; i--) + { + u32 j = getRandomInt(i + 1); + tmp = chunkOrder[i]; + chunkOrder[i] = chunkOrder[j]; + chunkOrder[j] = tmp; + } + + // Allocate the memory + u32 i; + for (i = 0; i < numChunks; i++) + { + u32 idx = chunkOrder[i]; + res = svcControlMemory(&tmp, chunkStartAddrs[idx], 0, chunkSizes[idx], allocFlags, MEMPERM_READWRITE); + if (R_FAILED(res)) + break; + } + + // Success + if (i == numChunks) + return res; + + // Clean up on failure + for (u32 j = 0; j < i; j++) + { + u32 idx = chunkOrder[i]; + svcControlMemory(&tmp, chunkStartAddrs[idx], 0, chunkSizes[idx], MEMOP_FREE, MEMPERM_DONTCARE); + } + + return res; +} diff --git a/sysmodules/loader/source/paslr.h b/sysmodules/loader/source/paslr.h new file mode 100644 index 0000000..56fd7aa --- /dev/null +++ b/sysmodules/loader/source/paslr.h @@ -0,0 +1,6 @@ +#pragma once + +#include <3ds/types.h> +#include <3ds/exheader.h> + +Result allocateProgramMemory(const ExHeader_Info *exhi, u32 vaddr, u32 size);