diff --git a/MSET9_installer_script/mset9.py b/MSET9_installer_script/mset9.py index ff181db..b975156 100644 --- a/MSET9_installer_script/mset9.py +++ b/MSET9_installer_script/mset9.py @@ -1,146 +1,155 @@ #!/usr/bin/python3 import os,sys,platform,time,shutil,binascii -VERSION="v1.0" -p=platform.system() -if p == 'Windows': #0-win, 1-lin, 2-mac, x-win lol go with the market leader i guess - OPSYS=0 -elif p == 'Linux': - OPSYS=1 -elif p == 'Darwin': - OPSYS=2 -else: - OPSYS=0 -cwd = os.path.dirname(os.path.abspath(__file__)) -print(cwd) -try: - os.chdir(cwd) -except Exception: - print("Failed to set cwd: " + cwd) - exit(1) - -if OPSYS == 0: #windows - _ = os.system('cls') -else: #linux or mac - _ = os.system('clear') - -print("MSET9 %s SETUP by zoogie" % VERSION) -print("What is your console model and version?") -print("Old 3DS has two shoulder buttons (L and R)") -print("New 3DS has four shoulder buttons (L, R, ZL, ZR)") -print("\n-- Please type in a number then hit return --\n") -print("1. Old 3DS, 11.8.0 to 11.17.0") -print("2. New 3DS, 11.8.0 to 11.17.0") -print("3. Old 3DS, 11.4.0 to 11.7.0") -print("4. New 3DS, 11.4.0 to 11.7.0") - -while 1: - try: - command = int(input('>>>')) - except: - command = 42 - if command == 1: - MODE=0 - break - elif command == 2: - MODE=1 - break - elif command == 3: - MODE=2 - break - elif command == 4: - MODE=3 - break - else: - print("Invalid input, try again.") - -trigger="002F003A.txt" #all 3ds ":/" - -#old3ds 11.8-11.17 -if MODE == 0: - id1_haxstr="FFFFFFFA119907488546696508A10122054B984768465946C0AA171C4346034CA047B84700900A0871A0050899CE0408730064006D00630000900A0862003900" - model_str="OLD3DS" - firmrange_str="11.8-11.17" -elif MODE == 1: - id1_haxstr="FFFFFFFA119907488546696508A10122054B984768465946C0AA171C4346034CA047B84700900A0871A005085DCE0408730064006D00630000900A0862003900" - model_str="NEW3DS" - firmrange_str="11.8-11.17" -elif MODE == 2: - id1_haxstr="FFFFFFFA119907488546696508A10122054B984768465946C0AA171C4346034CA047B84700900A08499E050899CC0408730064006D00630000900A0862003900" - model_str="OLD3DS" - firmrange_str="11.4-11.7" -elif MODE == 3: - id1_haxstr="FFFFFFFA119907488546696508A10122054B984768465946C0AA171C4346034CA047B84700900A08459E050881CC0408730064006D00630000900A0862003900" - model_str="NEW3DS" - firmrange_str="11.4-11.7" -else: - print("What is wrong with the elf?") - sys.exit(0) - -haxid1=bytes.fromhex(id1_haxstr) #ID1 - arm injected payload in readable format -haxid1=haxid1.decode("utf-16le") -haxid1_path="" +############################################## +# Global vars # +############################################## +VERSION="v2.0" # less of these please +trigger="002F003A.txt" # decodes to ":/", will be copied to stack by 3DS where haxID1 can complete its sdmc:/b9 payload (because PC OS doesn't like these inside dirnames) +trigger_path="" +trigger_exists=0 +cwd="" # current working directory. needs to be sdmc:/ but tries to be everywhere in practice +OPSYS=99 # 0=windows, 1=macOS, 2=linux, 99=no OS detected +ext_root="" +oldtag="_user-id1" +mode=0 # 0 setup state, 1 hax state +finish_remove=0 id1="" id1_root="" id1_path="" - -ext_root="" -oldtag="_user-id1" -mode=0 #0 setup state, 1 hax state -id0_count=0 -id0_list=[] -finish_remove=0 - -home_menu=[0x8f,0x98,0x82,0xA1,0xA9,0xB1] #us,eu,jp,ch,kr,tw -mii_maker=[0x217,0x227,0x207,0x267,0x277,0x287] #us,eu,jp,ch,kr,tw - -if not os.path.exists("Nintendo 3DS/"): - print("Are you sure you're running this script from the root of your SD card (right next to 'Nintendo 3DS')? You need to!") - print("Current dir: %s" % cwd) - time.sleep(10) - sys.exit(0) - - -for root, dirs, files in os.walk("Nintendo 3DS/", topdown=True): - for name in files: - pass - for name in dirs: - if "sdmc" not in name and len(name[:32]) == 32: - try: - temp=int(name[:32],16) - except: - continue - if type(temp) is int: - if os.path.exists(os.path.join(root, name)+"/extdata"): - id1=name - id1_root=root - id1_path=os.path.join(root, name) - if oldtag in name: - mode=1 - else: - id0_count+=1 - id0_list.append(os.path.join(root, name)) - if "sdmc" in name and len(name) == 32: - if haxid1 != name: - print("Yikes, don't change modes in the middle of MSET9!") - print("Make sure to run option 4, Remove MSET9 before you change modes!") - time.sleep(2) - print("Removing mismatched haxid1 ...") - shutil.rmtree(os.path.join(root, name)) - print("done.") - time.sleep(3) - finish_remove=1 - +haxid1="" +haxid1_root="" +haxid1_path="" +id1_haxstr_list=[ +# one of these hex string payloads will be encoded in utf-16le and copied to the 3DS sd card as an ID1 directory. which one will be determined by getModelFirm() +# once activated by the trigger file on sd card, they will load sd:/b9 to arm9 mem and execute it +# the only difference in these payloads is the address of process9's fopen and fread, which often changes upon process9 revisions +"FFFFFFFA119907488546696508A10122054B984768465946C0AA171C4346034CA047B84700900A0871A0050899CE0408730064006D00630000900A0862003900", +"FFFFFFFA119907488546696508A10122054B984768465946C0AA171C4346034CA047B84700900A0871A005085DCE0408730064006D00630000900A0862003900", +"FFFFFFFA119907488546696508A10122054B984768465946C0AA171C4346034CA047B84700900A08499E050899CC0408730064006D00630000900A0862003900", +"FFFFFFFA119907488546696508A10122054B984768465946C0AA171C4346034CA047B84700900A08459E050881CC0408730064006D00630000900A0862003900", +] -def setup(): - global mode, id1_path, id1_root, id1 - menu_ok=0 - mii_ok=0 - print("Setting up...", end='') - if mode: - print("Already setup!") - return +############################################## +# Function defs # +############################################## +def error(code, description): + print("ERROR %03d: %s" % (code,description)) + print("Please consult troubleshooting if you don't understand:") + print("https://3ds.hacks.guide/troubleshooting#installing-boot9strap-mset9") + time.sleep(5) # just in case window wants to disappear fast + sys.exit(code) + +def getPlatform(): + global OPSYS + p=platform.system() + if p == 'Windows': #0-win, 1-lin, 2-mac, x-win lol go with the market leader i guess + OPSYS=0 + elif p == 'Linux': + OPSYS=1 + elif p == 'Darwin': + OPSYS=2 + else: + error(1, "Platform OS not recognized") + +def cls(): + global OPSYS + if OPSYS == 0: #windows + _ = os.system('cls') + else: #linux or mac + _ = os.system('clear') + +def searchKeyPaths(): + global id1, id1_root, id1_path, haxid1, oldtag, trigger_exists, trigger_path, mode + id0_count=0 + id0_list=[] + for root, dirs, files in os.walk("Nintendo 3DS/", topdown=True): + for name in files: + if name == trigger: + trigger_exists=1 + for name in dirs: + if "sdmc" not in name and len(name[:32]) == 32: + try: + temp=int(name[:32],16) + except: + continue + if type(temp) is int: + if os.path.exists(os.path.join(root, name)+"/extdata"): + id1=name + id1_root=root + id1_path=os.path.join(root, name) + if oldtag in name: + mode=1 + else: + id0_count+=1 + id0_list.append(os.path.join(root, name)) + if "sdmc" in name and "b9" in name and len(name) == 32: + if haxid1 != name: + print("Yikes, don't change modes in the middle of MSET9!") + print("Make sure to run option 4, Remove MSET9 before you change modes!") + time.sleep(2) + print("Removing mismatched haxid1 ...") + shutil.rmtree(os.path.join(root, name)) + print("done.") + time.sleep(3) + finish_remove=1 + + if id0_count == 0: + error(4,"You're supposed to be running this on the 3DS SD card!\nNOT %s" % cwd) + + print("Detected ID0(s):") + for i in id0_list: + print(i) + print("") + if id0_count != 1: + error(5, "You don't have 1 ID0 in your Nintendo 3DS folder, you have %d!" % id0_count) + trigger_path=id1_root+"/"+haxid1+"/extdata/"+trigger + + +def getModelFirm(): # get 3ds model and firmware to determine payload. i'd love to blend this into the main menu + global model_str, firmrange_str, id1_haxstr, haxid1 + command=99 + print("MSET9 %s SETUP by zoogie" % VERSION) + print("What is your console model and version?") + print("Old 3DS has two shoulder buttons (L and R)") + print("New 3DS has four shoulder buttons (L, R, ZL, ZR)") + print("\n-- Please type in a number then hit return --\n") + print("1. Old 3DS, 11.8.0 to 11.17.0") + print("2. New 3DS, 11.8.0 to 11.17.0") + print("3. Old 3DS, 11.4.0 to 11.7.0") + print("4. New 3DS, 11.4.0 to 11.7.0") + + while 1: + try: + command = int(input('>>>')) + except: + command = 42 + if command == 1: + model_str="OLD3DS" + firmrange_str="11.8-11.17" + break + elif command == 2: + model_str="NEW3DS" + firmrange_str="11.8-11.17" + break + elif command == 3: + model_str="OLD3DS" + firmrange_str="11.4-11.7" + break + elif command == 4: + model_str="NEW3DS" + firmrange_str="11.4-11.7" + break + else: + print("Invalid input, try again.") + + id1_haxstr=id1_haxstr_list[command-1] + haxid1=bytes.fromhex(id1_haxstr) #ID1 - arm injected payload in readable format + haxid1=haxid1.decode("utf-16le") + +def titleDB_CheckRestore(): + global id1_path softv = softcheck(id1_path+"/dbs/title.db", 0x31e400, 0, 1) softv +=softcheck(id1_path+"/dbs/import.db", 0x31e400, 0, 2) if softv > 0: @@ -161,6 +170,19 @@ def setup(): sys.exit(0) print("Invalid database,\nplease reset it in settings -> data management -> nintendo 3ds -> software first before coming back") sys.exit(0) + +def setup(): + global mode, id1_path, id1_root, id1, haxid1_path + home_menu=[0x8f,0x98,0x82,0xA1,0xA9,0xB1] #us,eu,jp,ch,kr,tw + mii_maker=[0x217,0x227,0x207,0x267,0x277,0x287] #us,eu,jp,ch,kr,tw + menu_ok=0 + mii_ok=0 + print("Setting up...", end='') + if mode: + print("Already setup!") + return + + titleDB_CheckRestore() if os.path.exists(id1_path+"/extdata/"+trigger): os.remove(id1_path+"/extdata/"+trigger) @@ -177,16 +199,17 @@ def setup(): for i in home_menu: temp=ext_root+"/%08X" % i if os.path.exists(temp): - #print(temp,haxid1_path+"/extdata/00000000/%08X" % i) shutil.copytree(temp,haxid1_path+"/extdata/00000000/%08X" % i) menu_ok+=1 - assert(menu_ok==1) + if menu_ok != 1: + error(2, "Home extdata not found") for i in mii_maker: temp=ext_root+"/%08X" % i if os.path.exists(temp): shutil.copytree(temp,haxid1_path+"/extdata/00000000/%08X" % i) mii_ok+=1 - assert(mii_ok==1) + if mii_ok != 1: + error(3, "Mii extdata not found") if os.path.exists(id1_path): os.rename(id1_path, id1_path+oldtag) @@ -196,11 +219,11 @@ def setup(): print(" done.") def inject(): + global trigger_path if mode==0: print("Run setup first!") return print("Injecting...", end='') - trigger_path=id1_root+"/"+haxid1+"/extdata/"+trigger if not os.path.exists(trigger_path): with open(trigger_path,"w") as f: f.write("plz be haxxed mister arm9, thx") @@ -208,11 +231,11 @@ def inject(): print(" done.") def delete(): + global trigger_path if mode==0: print("Run setup first!") return print("Deleting...", end='') - trigger_path=id1_root+"/"+haxid1+"/extdata/"+trigger if os.path.exists(trigger_path): os.remove(trigger_path) print(" done.") @@ -225,7 +248,6 @@ def remove(): return if os.path.exists(id1_path) and oldtag in id1_path: os.rename(id1_path, id1_root+"/"+id1[:32]) - #print(id1_path, id1_root+"/"+id1[:32]) if os.path.exists(id1_root+"/"+haxid1): shutil.rmtree(id1_root+"/"+haxid1) id1=id1[:32] @@ -269,6 +291,7 @@ def check(keyfile, size, crc32): sys.exit(0) def reapply_cwd(): + global cwd try: os.chdir(cwd) return True @@ -276,33 +299,36 @@ def reapply_cwd(): print("Couldn't reapply cwd, is sdcard reinserted?") return False -if finish_remove: - remove() -check("boot9strap/boot9strap.firm", 0, 0x08129c1f) -#check("Nintendo 3DS/Private/00020400/phtcache.bin", 0x7f53c, 0) -check("boot.firm", 0, 0) -check("boot.3dsx", 0, 0) -check("b9", 0, 0) -if id0_count == 0: - print("\nYou're supposed to be running this on the 3DS SD card!") - print("NOT \n%s" % cwd) - time.sleep(10) - sys.exit(0) +def init(): + global cwd + cwd = os.path.dirname(os.path.abspath(__file__)) + try: + os.chdir(cwd) + except: + error(7, "Failed to set cwd: " + cwd) + getPlatform() -print("Detected ID0(s):") -for i in id0_list: - print(i) -print("") -if id0_count != 1: - print("You don't have 1 ID0 in your Nintendo 3DS folder, you have %d!" % id0_count) - print("Consult:\nhttps://3ds.hacks.guide/troubleshooting#installing-boot9strap-mset9\nfor help!") - sys.exit(0) + check("boot9strap/boot9strap.firm", 0, 0x08129c1f) + check("b9", 0, 0) + #check("Nintendo 3DS/Private/00020400/phtcache.bin", 0x7f53c, 0) # what could have been ... + #check("boot.firm", 0, 0) # will be covered by 'finalizing setup' from now on + #check("boot.3dsx", 0, 0) # '' + + if finish_remove: #todo - get rid of this + remove() + if not os.path.exists("Nintendo 3DS/"): + print("Current dir: %s" % cwd) + error(6,"Are you sure you're running this script from the root of your SD card (right next to 'Nintendo 3DS')?") -if OPSYS == 0: #windows - _ = os.system('cls') -else: #linux or mac - _ = os.system('clear') +############################################## # this is not the pythonic way to do main() but the pythonic way makes my eyes twitch +# Main # +############################################## +init() +getModelFirm() +searchKeyPaths() + +cls() print("MSET9 %s SETUP by zoogie" % VERSION) print("%s %s" % (model_str,firmrange_str)) @@ -336,4 +362,4 @@ while 1: else: print("Invalid input, try again.") -time.sleep(2) +time.sleep(2) \ No newline at end of file diff --git a/README.md b/README.md index 66aeb21..186425a 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ ## Thanks - Luigoalma for some asm help in ID1gen! - Aspargas2 (the Black Cat) for asm help in ID1gen as well! -- TuxSH for usr2arm9ldr and some good advice! +- TuxSH for [usr2arm9ldr](https://github.com/TuxSH/usr2arm9ldr) and some good advice! [MSET9 in action](https://zoogie.github.io/web/m9/(%20%CD%A1%C2%B0%20%CD%9C%CA%96%20%CD%A1%C2%B0).webm) @@ -11,9 +11,12 @@ This is an ARM9 primary exploit for 3DS that can be launched with only filename data added to the inserted SD card. ## How does it work -When FS_EnumerateExtData is called by MSET (System Settings) to parse 3DS extdata IDs for Data Managment, a file that starts with 8 hex digits can crash ARM9 if placed directly inside the extdata directory. It can crash in various ways based on subtleties in the way the user triggers the crash event.
+In the implementation for FSPXI:EnumerateExtSaveData (called by MSET to parse 3DS extdata IDs for Data Management), the return value of the P9 internal function call to open a directory (when enumerating contents of the extdata directory) was not checked. Therefore, if the call fails, an uninitialised pointer on stack will be used for a vtable call.
-While mostly leading to null derefs, in one specific context, ARM9 jumps directly to the ID1 string being held nearby in ARM9 memory. By chance, the 3DS doesn't discern what characters are used for the ID1 directory name on the SD, only requiring exactly 32 chars. This allows an attacker to insert arm instructions into the unicode ID1 dirname and take control of ARM9, and thus, full control of the 3DS. +As such, a file that starts with 8 hex digits can crash process9 if placed directly inside the extdata directory. It can crash in various ways based on subtle differences in the way the user triggers the crash event.
+ +While mostly leading to null derefs, in one specific context, process9 jumps directly to an ID1 string being held in ARM9 memory. Surprisingly, the 3DS doesn't discern what characters are used for the ID1 directory name on the SD, only requiring exactly 32 chars. This allows the attacker to insert arm instructions into the unicode ID1 dirname and take control of the ARM9, and thus, full control of the 3DS.
+[3Dbrew](https://www.3dbrew.org/wiki/3DS_System_Flaws#Process9) ## Can I do it? -- You need a 3ds 11.4-11.17, any region (probably, haven't tested them all)
@@ -21,11 +24,11 @@ While mostly leading to null derefs, in one specific context, ARM9 jumps directl -- Windows/Linux PC (this might be expanded to MAC and/or Android at some point, if possible)
## Directions -In release archive. It may seem long and complex but it really isn't that bad (the above demo video is just under 90 seconds). People who have trouble following directions will struggle though.
+In release archive or, preferably, [3DS Hacks Guide- MSET9](https://3ds.hacks.guide/installing-boot9strap-(mset9).html).
## Troubleshooting - [mset9.py shows error ".../title.db doesn't exist on sd card"?] Inside sdmc:/Nintendo 3DS/ID0/ID1/dbs, create empty files title.db and import.db. You need to create the dbs folder first. Now go to System Settings -> Data Management -> Nintendo 3DS -> Software and say yes to the prompts to build your database files. Now redo everything from the start. -- [Swirling System Settings loop] This is just a general crash of arm9. Did you follow the instructions.txt EXACTLY? Are you running an old3ds 11.8-11.17? +- [Swirling System Settings loop] This is just a general crash of arm9. Did you follow the instructions EXACTLY? - [Nothing happens when I reinstert card - just shows mii maker icon] Did you try option 2 on mset9.py on step 6? Go back to step - [Still can't get it to work] In some stubborn cases, it might be better to just start fresh with a spare blank SD card. For that, follow these steps: 1. Format the spare SD card with SD formatter.
@@ -41,21 +44,21 @@ sdmc:/Nintendo 3DS/aaaabbbbccccdddd1111222233334444/aaaabbbbccccdddd111122223333 ## FAQ Q: This installs boot9strap and writes to NAND?
- A: Yes! What else ya gonna do with ARM9 control, a9lh? pastaCFW? sketchy tetris clones" :p
+ A: Yes! What else ya gonna do with ARM9 control, a9lh? pastaCFW? sketchy tetris clones" :p

Q: That sounds dangerous, Zoogie!
- A: Yeah, it kinda is but the scene's been doing this dangerous stuff for years. Just sit out the beta phase if concerned.
+ A: Yeah, it kinda is but the scene's been doing this dangerous stuff for years. Just sit out the beta phase if concerned.

Q: What happens if I fail to uninstall the exploit when I'm done?
- A: You'll have trouble launching previously installed titles, in addition to random crashes in FBI and System Settings. So make sure to clean up the exploit! (option 4 in the mset9.py menu does this)
+ A: You'll have trouble launching previously installed titles, in addition to random crashes in FBI and System Settings. So make sure to clean up the exploit! (option 4 in the mset9.py menu does this)

-(the rest of this is more FYI than anything important)
+(the rest of this is more FYI than anything important)

Q: That file that triggers the exploit (002F003A.txt) ... it kinda looks like ... some virtual address, huh?
- A: It's the characters ":/", something we can't display in a typical file/folder name. A convenient fact of that file (besides triggering the overall crash) is that the first 8 chars of that hex filename are converted to a u32 that happens to exist 0x44 past SP, so I can use it to plug in the missing chars in the payload filepath "sdmc??b9", and keep the PC's OS happy.
+ A: It's the characters ":/", something we can't display in a typical file/folder name. A convenient fact of that file (besides triggering the overall crash) is that the first 8 chars of that hex filename are converted to a u32 that happens to exist 0x44 past SP, so I can use it to plug in the missing chars in the payload filepath "sdmc??b9", and keep the PC's OS happy.

Q: You suggested in the hack explanation above that FS_EnumerateExtData is the responsible function for allowing the crash in MSET/ARM9, could this be called in userland homebrew to take over ARM9?
A: Maybe? I briefly played around with this very idea, but was unable to find a crash context that I could control, unlike the pre-userland method MSET9 is. Maybe this could be an exercise for the dedicated user to explore and flesh out this potential variant of MSET9! It could be useful down the line.
- Fun fact: The 8 digit hex file, if left in extdata, will also crash FBI when selecting the "Ext Save Data" option in its main menu. It's the only homebrew I know that calls FS_EnumerateExtData.
-Q: You shortened SafeB9SInstaller.bin to SafeB9S.bin, why? - A: Keeps FAT's 8.3 filename standard which avoids Long File Names, and thus enables significant space savings in the FatFs library. "B9" is also used for the same reason albeit not FatFs related. Small code footprint is of paramount importance everywhere in this exploit.
+ Fun fact: The 8 digit hex file, if left in extdata, will also crash FBI when selecting the "Ext Save Data" option in its main menu. It's the only homebrew I know that calls FS_EnumerateExtData.

+Q: You shortened SafeB9SInstaller.bin to SafeB9S.bin, why?
+ A: Keeps FAT's 8.3 filename standard which avoids Long File Names, and thus enables significant space savings in the FatFs library. "B9" is also used for the same reason albeit not FatFs related. Small code footprint is of paramount importance everywhere in this exploit.

Q: Why doesn't this work on MAC?
A: Because it refuses to render the following unicode craziness: ￿﫿餑䠇䚅敩ꄈ∁䬅䞘䙨䙙꫿ᰗ䙃䰃䞠䞸退ࠊꁱࠅ캙ࠄsdmc退ࠊb9
( ͡° ͜ʖ ͡°)