HackMii

Notes from inside your Wii

HackMii header image 1

Putting the genie back into bottle? (MIOS)

June 22nd, 2008 by bushing · 63 Comments

The June 16th (“3.3”) Wii System Update did more than bring the death of the Twilight Hack (sort of) and a patched version of IOS30.  It also brought new versions of BC and MIOS — which actually may be the more interesting of the changes in the update.

BC and MIOS are titles 1-100 and 1-101, respectively.  MIOS is the Gamecube compatibility layer — a fairly small amount of ARM code that runs instead of IOS when Gamecube games are running, and then the 157k Gamecube IPL — the PPC code that actually reads the Gamecube disc and boots it.   BC is still an unknown; in many ways, it resembles boot1.  Both are about the same size, and both contain code to load boot2 from flash and execute it.  When the Wii System Menu detects a Gamecube disc, it launches title 1-100 — BC, not MIOS.   It’s possible that BC launches boot2 which launches MIOS — we’re still not sure.   Let’s come back to that.

MIOS contains the GC IPL that was discovered about a year ago.  The ARM code serves mainly to load the GC IPL into memory and start the PPC running it; the rest of the code seems to just be a stripped down version of IOS (and therefore resembles everything else.)  However … some highly suspicious code has been added!

I’d imagine it looks something like this:

int do_hash_comparison(void) {
//	using the memory mapper, this should be aliased to
//	0x0D408000.  This resides in the on-chip SRAM.

	u8 *buffer  = (u8 *)0xFFFF8000;
	u8 *MEM2_ptr = (u8 *)0x10000000;  // = 0x90000000
	u32 hash1[5];
	u32 hash2[5]={0x4F00A54E,0x57E1E2C4,0x78634365,
                        0xF56BA5D3,0xF7DECA52};
	int i;

	memset(buffer, 0xCAFEBABE, 0x8000);
	memset(buffer + 0x8000, 0xCAFEBABE, 0x8000);
	aes_set_key(0x2B7E1516, 0x28AED2A6, 0xABF71588, 0x09CF4F3C);
	sha_init();

	for (i=0; i<1024; i++) {
		aes_set_iv(i, i, i, i);
		aes_encrypt(MEM2_ptr, buffer, 0x10000);
		sha_update(MEM2_ptr, 0x10000);
	}

	sha_finalize(hash1);
	if (hash1[0] == hash2[0] && hash1[1] == hash2[1] && hash1[2] == hash2[2] &&
		hash1[3] == hash2[3] && hash1[4] == hash2[4]) {
			do_log_message("%s shaHash: %x %x %x %x %x [%u ticks]\n",
				NULL, hash1[0], hash1[1], hash1[2], hash1[3], hash1[4], 0);
			return 0;
	}

	do_log_message("Hash comparison failed. Halting boot!\n");
	return -1;
}

This is actually pretty clever — far more so than anything else in this update. I don’t think there’s anything special about any of those “magic numbers” — what this does is overwrite all 64MB of MEM2 with (pseudo-) random garbage, verify that it was actually written, and only then reboot into GC mode.

This not only prevents the tweezer attack we used to get the common key last year — which was the primary piece of information that has made everything else possible, but it prevents what tmbinc called the “anti-tweezer attack” where we short some address lines while it’s trying to clear the memory to keep it from actually clearing the memory.  That won’t work, because it verifies the hash to make sure the garbage was actually written.

Why?

I’m not really sure — getting the common key was a one-time hack.  The only thing I can think of is that they are trying to reclaim the platform.  Maybe they intend to generate a new common key for use on new Wiis — Wiis that will not be able to read current games — and current Wiis will not be able to read any game that will work on those Wiis.  Does this make any sense?

Maybe.  Nuke mentioned on his forum that new Korean discs can’t be decrypted using the normal USA/JAP/PAL common key.  I can’t confirm this — the only disc image I’ve seen seems to have been corrupted — but it’s certainly possible, and it would explain this MIOS change.

In other news, the new BC has a fixed signature check.  This might prevent MIOS or boot2 from being modified (if you’re using the new BC) — but more likely, it means that new Wiis coming out of the factory have a fixed boot1 without the signature-checking bug.  I’m surprised it’s taken this long, but I guess we won’t know until someone dumps their NAND flash.

 

→ 63 CommentsTags:

Twilight Hack v0.1beta1

June 21st, 2008 by marcan · 188 Comments

This new version of the Twilight Hack is compatible with version 3.3 of the Wii System Menu, using the workarounds explained in previous posts. Thanks to everyone on IRC who contributed to the search for the bugs, and to tmbinc and tehpola for finding the two bugs that make this possible.

Changelog for this release:

  • The Twilight Hack is now compatible with version 3.3 of the Wii System Menu.
  • Improvements in video configuration. The entire console should now be visible in all video modes, and scrolling has been improved.
  • For the USA version, the two variants of the hack have been packed into one save file. Just select the save slot that corresponds to your version of Twilight Pricess when you start the game.
  • New savegame icons by drmr. The new icons now show which region that version of the hack is for.
  • This version now tries to load boot.dol, and falls back to boot.elf if boot.dol is not found.
  • ???????
  • Many, many bug fixes.

Download it here. Head over to Wiibrew for more information and updates. See the included README for installation and usage instructions.

Enjoy!

→ 188 CommentsTags:

Wii menu TP-hack-killer analysis

June 17th, 2008 by bushing · 172 Comments

Okay, I’ve spent a little bit of time trying to reconstruct the C code used to build the channel from my disassembler.  The full IDA Pro output for those funcs is here: http://static.hackmii.com/verifyzelda.html

Below, you’ll find my C version.  I’ve tried to make it function exactly like the one in the new system menu.  Hopefully I did a good job, because I’d like to see people try to find bug in this that could lead to an exploit.   There are at least two here, which we used in combination; can you find any more?

Don’t worry, I’ll give the answers if nobody gets them 🙂

// this helper function gets called during the NAND check
// for the TP hack files

int ipl::utility::ESMisc::DeleteSavedata(u32 titleid_h, u32 titleid_l) {
    char pathname[0x100];
    int deleted_files = 0;
    
    sprintf(pathname, "/title/%08x/%08x/data/", titleid_hi, titleid_lo);

    int num_dir_entries = 0;
    int retval = nandReadDir(pathname, 0, &num_dir_entries);
    if (retval != 0 || num_dir_entries == 0) {
        OSReportError("iplESMisc.cpp::DeleteSavedata: "
             "Could not read1 %s: %d\n", pathname);
        goto done;
    }
    
    dirent_t *direntries=malloc(sizeof dirent_t * num_dir_entries);
    if (direntries == NULL) {
        OSReportError("iplESMisc.cpp::DeleteSavedata:"
        "Could not alloc: %d\n");
        goto done;
    }
    
    retval = nandReadDir(pathname, num_dir_entries, direntries);
    if (retval != 0) {
        OSReportError("iplESMisc.cpp::DeleteSavedata: "
        "Could not read2 %s: %d\n", pathname);
        goto done;
    }
    
    int i;
    for (i=0; i < num_dir_entries; i++) {
        char buf[0x100];
        strcpy(buf, pathname);
        strcat(buf, direntries[i].filename);
        retval = NANDPrivateDelete(buf);
        if (retval != 0) {
            OSReportError("iplESMisc.cpp::DeleteSavedata: Failed to delete %s: %d\n", buf);
            goto done;
        }
        deleted_files = 1;
    }
done:
    if (direntries != NULL) free(direntries);
    return deleted_files;
}

// this function is called upon boot or something
ipl::utility::ESMisc::VerifySavedataZD(u32 titleid_hi, u32 titleid_lo) {
    int savegame_bad = 1;
    char pathname[0x100];

    sprintf(pathname, "/title/%08x/%08x/data/%s", titleid_hi, titleid_lo, "zeldaTp.dat");

    if(ipl::utility::ESMisc::ChangeUid(titleid_hi, titleid_lo)==0) goto done;
    
    int retval = nandPrivateOpen(pathname, &fd, O_RDWR);
    if (retval == -ENOENT) {
        OSReportError("iplESMisc.cpp::VerifySavedataZD: Does not exist %s: %d\n", pathname);
        goto done;
    }

    if (retval == 0) {
        OSReportError("iplESMisc.cpp::VerifySavedataZD:Open save data file failed: %d\n");
        goto done;
    }

    u32 file_length;
    retval = NANDGetLength(fd, &file_length);
    if (retval != 0) {
        OSReportError("iplESMisc.cpp::VerifySavedataZD:Get file length failed: %d\n");
        goto done;
    }

    char *buf = malloc(file_length);
    if (buf == NULL) {
        OSReportError("iplESMisc.cpp::VerifySavedataZD: Alloc failed: %d\n");
        goto done;
    }
    
    int bytes_read;
    bytes_read = NANDRead(fd, buf, _align_size(file_length, 32));
    
    if (bytes_read != _align_size(file_length, 32)) {
        OSReportError("iplESMisc.cpp::VerifySavedataZD: Read file failed: %d\n");
        goto done;
    }

    if (WADCheckSavedataZD(buf) == 0) {
        OSReport("iplESMisc.cpp::VerifySavedataZD: Verify failed for %016llx\n", 
            titleid_hi << 32 | titleid_lo);
        NAND_Close(fd);
        fd = 0;
        ipl::utility::ESMisc::DeleteSavedata(titleid_h, titleid_l);
    }
    savegame_bad = 0;

done:
    if (buf) free(buf);
    if (fd) NANDClose(fd);
    ipl::utility::ESMisc::ChangeUid(1,2);

    return savegame_bad;
}

int _align_size(int value, int alignment) {
    // round up value to next highest multiple of alignment
    // e.g align_size(40, 32) = 64
    return value + (alignment-1) & ~alignment;
}
    
int _check_strlen(char *string, int max) {
    int i;
    for (i=0; i< max; i++) if (string[i]=='\0') return 1;
    return 0;
}

int _check_save(char *buf) {
    if (!_check_strlen(buf + 0x56, 8)) return 0; // random strings
    if (!_check_strlen(buf + 0x60, 8)) return 0; // inside savegame
    if (!_check_strlen(buf + 0x7A, 8)) return 0;
    if (!_check_strlen(buf + 0x96, 8)) return 0;
    if (!_check_strlen(buf + 0x1BC, 17)) return 0; // player name
    if (!_check_strlen(buf + 0x1CD, 17)) return 0; // horse name
    return 1;
}

int WADCheckSavedataZD(char *buf) {
    int i;
    // check 3 primary saveslots
    for (i=0; i<3; i++) if (!_check_save(buf + i*0xA94)) return 0;
    // check 3 backup saveslots
    for (i=0; i< 3; i++) if (!_check_save(buf + 0x2008 + i*0xA94)) return 0;
    return 1;
}

// this function is called when any savegame WAD is being 
// installed (copied from SD)
int _wad_check_for_twilight_hack(WAD *wadfile) {
    int i;
    for (i=0; i <  wadfile.numfiles; i++) {
        // skip any leading directory names
        char *p = strrchr(wadfile.filename[i], '/');
        if (p == NULL) p = wadfile.filename[i];
        else p++;
        if (strcmp(wadfile.filename[i], "zeldaTp.dat")==0) {
            return WADCheckSavedataZD(wadfile.filedata[i]);
        }
    }
}

 

 

→ 172 CommentsTags: