In memory of Ben “bushing” Byer, who passed away on Monday, February 8th, 2016.

Changes

Jump to navigation Jump to search
←Created page with 'Here is a revision that handles content decryption. prng-seed is specific to the wii that created that content.bin, and needs to be extracted via xyzzy. <source lang="c"> //...'
Here is a revision that handles content decryption. prng-seed is specific to the wii that created that content.bin, and needs to be extracted via xyzzy.

<source lang="c">
// parse-channel.c
// Compile with:
// gcc -g -DLARGE_FILES -D_FILE_OFFSET_BITS=64 -Wall -W -O2 -c -o parse-channel.o parse-channel.c
// gcc -g -lcrypto parse-channel.o tools.o bn.o ec.o -o parse-channel
// The other files are from segher's git repository, created by his Makefile.

// Copyright 2008 Magicus <magicus@gmail.com>
// This file is based on tachtig.c, which is
// Copyright 2007,2008 Segher Boessenkool <segher@kernel.crashing.org>
//
// Licensed under the terms of the GNU GPL, version 2
// http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt

// Version 1.0 Initial release
// Version 1.1 Fixing IV-bug for part B. Adding support for decompressing part B;
// thanks Arcnor <arcnorj@yahoo.es> for the basic LZ77 code!
// Version 1.2 Decrypts contents provided you have prng seed extracted from your wii via xyzzy. (caitsith2)
// for some unknown reason, u64s were being treated like u32s, causing tmd misalignment in my setup.


#include <string.h>
#include <stdlib.h>
#include <stdio.h>

#include "tools.h"

#define ERROR(s) do { fprintf(stderr, s "\n"); exit(1); } while (0)

static u8 sd_key[16];
static u8 sd_iv[16];
static u8 md5_blanker[16];
static u8 prngseed[16];

static FILE *fp;

static char gamename[5];

static size_t partB_size;
static u16 num_contents;
static size_t* content_sizes;

typedef struct {
u32 title_id_01_code;
u32 title_id_02_name;
u32 partB_size;
u8 md5[0x10];
u8 data[0x624];
} partA_header_t;

typedef struct {
u32 imd5_tag; // 0x494D4435 "IMD5";
u32 size; // size of the rest of part B, starting from next field.
u8 zeroes[8];
u8 crypto[16];
u32 lz77_tag; // 0x4C5A3737 "LZ77"
u32 unknown; // padding to 0x40?
} partB_header_t;

typedef struct {
u32 sig_type;
u8 sig[256];
u8 fill1[60];
u8 issuer[64]; // Root-CA%08x-CP%08x
u8 version;
u8 ca_crl_version;
u8 signer_crl_version;
u8 fill2;
u32 sys_version_hi;
u32 sys_version_lo;
u32 title_id_hi;
u32 title_id_lo;
u32 title_type;
u16 group_id; // publisher
u16 zero;
u16 region;
u8 ratings[16];
u8 reserved[42];
u32 access_rights;
u16 title_version;
u16 num_contents;
u16 boot_index;
u16 fill3;
} tmd_t;

typedef struct {
u32 cid; // content id
u16 index; // # number of the file
u16 type;
u32 size_lo;
u32 size_hi;
u8 hash [20]; // SHA1 hash content
} content_record_t;

static void write_part(void* data, size_t size, char* name)
{
FILE *out;
char filename[128];

snprintf(filename, sizeof(filename), "%s_%s.bin", gamename, name);
filename[128] = '\0';
out = fopen(filename, "wb");
fwrite(data, 1, size, out);
fclose(out);
}

static void do_partA_header(void)
{
partA_header_t header;
u8 partA_iv[16];
u8 md5_file[16];
u8 md5_calc[16];

fread(&header, 1, sizeof header, fp);

// Use a private copy of the sd_iv since we need it again
// and it will be overwritten otherwise.
memcpy(partA_iv, sd_iv, 16);
aes_cbc_dec(sd_key, partA_iv, (u8*) &header, sizeof header, (u8*) &header);

memcpy(md5_file, header.md5, 16);
memcpy(header.md5, md5_blanker, 16);
md5((u8*) &header, sizeof header, md5_calc);

if (memcmp(md5_file, md5_calc, 0x10)) {
ERROR("MD5 mismatch");
}

// Get the four-letter code of the game, for file naming purposes.
strncpy(gamename, (char*) &header.title_id_02_name, 4);
gamename[5] = '\0';
printf("Game code is: %s\n", gamename);

partB_size = be32((u8*) &header.partB_size);

write_part(&header, sizeof(header), "01_header");
}

static u8* decompress_lz77(u8 *data, size_t data_size, size_t* decompressed_size)
{
u8 *data_end;
u8 *decompressed_data;
size_t unpacked_size;
u8 *in_ptr;
u8 *out_ptr;
u8 *out_end;

in_ptr = data;
data_end = data + data_size;

// Assume this for now and grow when needed
unpacked_size = data_size;

decompressed_data = malloc(unpacked_size);
out_end = decompressed_data + unpacked_size;

out_ptr = decompressed_data;

while (in_ptr < data_end) {
int bit;
u8 bitmask = *in_ptr;

in_ptr++;
for (bit = 0x80; bit != 0; bit >>= 1) {
if (bitmask & bit) {
// Next section is compressed
u8 rep_length;
u16 rep_offset;

rep_length = (*in_ptr >> 4) + 3;
rep_offset = *in_ptr & 0x0f;
in_ptr++;
rep_offset = *in_ptr | (rep_offset << 8);
in_ptr++;
if (out_ptr-decompressed_data < rep_offset) {
ERROR("Inconsistency in LZ77 encoding");
}

for ( ; rep_length > 0; rep_length--) {
*out_ptr = out_ptr[-rep_offset-1];
out_ptr++;
if (out_ptr >= out_end) {
// Need to grow buffer
decompressed_data = realloc(decompressed_data, unpacked_size*2);
out_ptr = decompressed_data + unpacked_size;
unpacked_size *= 2;
out_end = decompressed_data + unpacked_size;
}
}
} else {
// Just copy byte
*out_ptr = *in_ptr;
out_ptr++;
if (out_ptr >= out_end) {
// Need to grow buffer
decompressed_data = realloc(decompressed_data, unpacked_size*2);
out_ptr = decompressed_data + unpacked_size;
unpacked_size *= 2;
out_end = decompressed_data + unpacked_size;
}
in_ptr++;
}
}
}

*decompressed_size = (out_ptr - decompressed_data);
return decompressed_data;
}

static void do_partB_gameinfo(void)
{
u8 *data;
u8 *decompressed_data;
size_t rounded_size;
size_t decompressed_size;
partB_header_t* header;
u32 tag;
u32 unknown;
u32 size;
u8 partB_iv[16];

rounded_size = (partB_size + 63) & ~63;

data = malloc(rounded_size);
fread(data, 1, rounded_size, fp);

memcpy(partB_iv, sd_iv, 16); //And we need the sd-iv for content decryption as well.
aes_cbc_dec(sd_key, partB_iv, data, rounded_size, data);

header = (partB_header_t*) data;

tag = be32((u8*) &header->imd5_tag);
if (tag != 0x494D4435) {
ERROR("No IMD5 tag");
}
size = be32((u8*) &header->size);
if (size != partB_size - 32) {
ERROR("Size mismatch");
}

tag = be32((u8*) &header->lz77_tag);
if (tag != 0x4C5A3737) {
ERROR("No LZ77 tag");
}

unknown = be32((u8*) &header->unknown);
printf("Part B unknown field: %x\n", unknown);

write_part(data, sizeof(partB_header_t), "02a_banner_header");

decompressed_data = decompress_lz77(data + sizeof(partB_header_t), partB_size - sizeof(partB_header_t), &decompressed_size);
write_part(decompressed_data, decompressed_size, "02b_banner_decompressed");

free(data);
// free(decompressed_data);
}

static void do_partC_Bk_header(void)
{
u8 header[0x80];

fread(header, 1, sizeof header, fp);

if (be32(header + 4) != 0x426b0001)
ERROR("no Bk header");
if (be32(header) != 0x70)
ERROR("wrong Bk header size");

fprintf(stderr, "NG id: %08x\n", be32(header + 8));

write_part(header, sizeof(header), "03_bk_header");
}

static void do_partD_tmd(void)
{
tmd_t tmd;
u8* data;
size_t tmd_size;
int i;
content_record_t* rec;

fread(&tmd, 1, sizeof tmd, fp);

num_contents = be16((u8*) &tmd.num_contents);
printf("Number of content files: %d\n", num_contents);

// Now we can read the rest of the tmd.
tmd_size = sizeof(tmd) + num_contents*sizeof(content_record_t);
tmd_size = (tmd_size + 63) & ~63;

data = malloc(tmd_size);
memcpy(data, &tmd, sizeof(tmd));
fread(&data[sizeof(tmd)], 1, tmd_size-sizeof(tmd), fp);

write_part(data, tmd_size, "04_tmd");

content_sizes = calloc(1, sizeof (size_t) * num_contents);

rec = (content_record_t*) &data[sizeof(tmd)];
for (i = 0; i < num_contents; i++, rec++) {
u16 type = be16((u8*) &rec->type);

if (!(type & 0x8000)) {
content_sizes[i] = (size_t)be32((u8*) &rec->size_hi);
}
}
}

static void do_partE_contents(void)
{
int i;
u8 partE_iv[16];

for (i=0; i < num_contents; i++) {
if (content_sizes[i] != 0) {
char name[128];
u8 *data;
size_t rounded_size = (content_sizes[i] + 63) & ~63;

data = malloc(rounded_size);
fread(data, 1, rounded_size, fp);

memset(partE_iv,0,16);
aes_cbc_dec(prngseed, partE_iv, data, rounded_size, data);

snprintf(name, 128, "05_content_%02d", i);
printf("Writing included content index %d of size: %x\n", i, content_sizes[i]);

write_part(data, rounded_size, name);

free (data);
}
}
}

static void do_partF_cert(void)
{
u8 cert[0x340];

fread(cert, 1, sizeof cert, fp);

write_part(cert, sizeof(cert), "06_cert");
}


int main(int argc, char **argv)
{
if (argc != 2) {
ERROR("Usage: parse-channel <file>");
}

get_key("sd-key", sd_key, 16);
get_key("sd-iv", sd_iv, 16);
get_key("md5-blanker", md5_blanker, 16);
get_key("prngseed", prngseed, 16);

fp = fopen(argv[1], "rb");

do_partA_header();
do_partB_gameinfo();
do_partC_Bk_header();
do_partD_tmd();
do_partE_contents();
do_partF_cert();

fclose(fp);

return 0;
}
</source>
40

edits

Navigation menu