Difference between revisions of "User talk:Magicus/Magicus's Tools/Parse-channel.c"

From WiiBrew
Jump to navigation Jump to search
Line 21: Line 21:
 
//    for some unknown reason, u64s were being treated like u32s, causing tmd misalignment in my setup.
 
//    for some unknown reason, u64s were being treated like u32s, causing tmd misalignment in my setup.
 
// Version 1.3 Verifies content hashes, and ecc (eliptic curve cryptography) signature.
 
// Version 1.3 Verifies content hashes, and ecc (eliptic curve cryptography) signature.
 +
// Version 1.4 Now uses "Bk" header to determine which contents are present.
 
   
 
   
 
   
 
   
Line 36: Line 37:
 
static u8 prngseed[16];
 
static u8 prngseed[16];
  
 
 
 
static FILE *fp;
 
static FILE *fp;
 
 
static char gamename[5];
 
static char gamename[5];
 
 
static size_t partB_size;
 
static size_t partB_size;
static u16 num_contents;
 
static size_t* content_sizes;
 
static u8* hashes;
 
static u32 total_size;
 
 
   
 
   
 
typedef struct {
 
typedef struct {
Line 64: Line 57:
 
       u32 unknown; // padding to 0x40?
 
       u32 unknown; // padding to 0x40?
 
}  partB_header_t;
 
}  partB_header_t;
 +
 +
 +
typedef struct {
 +
      u32 size; //Always 0x70, however, this struct made 0x80 because of align 64.
 +
      u32 magic; //Always 0x426B0001
 +
      u32 console_id;
 +
      u32 num_files;
 +
      u32 file_size;
 +
      u32 tmd_size;
 +
      u32 payload_size;
 +
      u32 total_size;
 +
      u8 contents[0x40];
 +
      u32 title_id_hi;
 +
      u32 title_id_lo;
 +
      u32 unknown[2];
 +
      u32 align_64[4];
 +
}  partC_header_t;
 
   
 
   
 
typedef struct {
 
typedef struct {
Line 99: Line 109:
 
   u8  hash [20]; //  SHA1 hash content
 
   u8  hash [20]; //  SHA1 hash content
 
} content_record_t;
 
} content_record_t;
 +
 +
static partC_header_t bKheader;
 +
static tmd_t tmd;
 +
static content_record_t *rec;
 
   
 
   
 
static void write_part(void* data, size_t size, char* name)
 
static void write_part(void* data, size_t size, char* name)
Line 231: Line 245:
 
data = malloc(rounded_size);
 
data = malloc(rounded_size);
 
fread(data, 1, rounded_size, fp);
 
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, sd_iv, data, rounded_size, data);
aes_cbc_dec(sd_key, partB_iv, data, rounded_size, data);
 
 
   
 
   
 
header = (partB_header_t*) data;
 
header = (partB_header_t*) data;
Line 256: Line 269:
 
   write_part(data,rounded_size,"02_banner");
 
   write_part(data,rounded_size,"02_banner");
 
write_part(data, sizeof(partB_header_t), "02a_banner_header");
 
write_part(data, sizeof(partB_header_t), "02a_banner_header");
  //write_part(data + sizeof(partB_header_t), partB_size - sizeof(partB_header_t), "02b_banner_compressed");
 
 
   
 
   
 
decompressed_data = decompress_lz77(data + sizeof(partB_header_t), partB_size - sizeof(partB_header_t), &decompressed_size);
 
decompressed_data = decompress_lz77(data + sizeof(partB_header_t), partB_size - sizeof(partB_header_t), &decompressed_size);
Line 267: Line 279:
 
static void do_partC_Bk_header(void)
 
static void do_partC_Bk_header(void)
 
{
 
{
u8 header[0x80];
+
  fread(&bKheader, 1, sizeof bKheader, fp);
 
fread(header, 1, sizeof header, fp);
 
 
   
 
   
if (be32(header + 4) != 0x426b0001)
+
if (be32((u8*) &bKheader.magic) != 0x426b0001)
 
ERROR("no Bk header");
 
ERROR("no Bk header");
if (be32(header) != 0x70)
+
if (be32((u8*) &bKheader.size) != 0x70)
 
ERROR("wrong Bk header size");
 
ERROR("wrong Bk header size");
 
   
 
   
fprintf(stderr, "NG id: %08x\n", be32(header + 8));
+
fprintf(stderr, "NG id: %08x\n", be32((u8*) &bKheader.console_id));
 
 
  total_size = be32(header + 0x1c);
 
 
   
 
   
write_part(header, sizeof(header), "03_bk_header");
+
write_part(&bKheader, sizeof(bKheader), "03_bk_header");
 
}
 
}
 
   
 
   
 
static void do_partD_tmd(void)
 
static void do_partD_tmd(void)
 
{
 
{
tmd_t tmd;
 
 
u8* data;
 
u8* data;
 
size_t tmd_size;
 
size_t tmd_size;
int i;
+
 
content_record_t* rec;
+
  fread(&tmd, 1, sizeof tmd, fp);
+
 
fread(&tmd, 1, sizeof tmd, fp);
+
   printf("Number of content files: %d\n", be16((u8*) &tmd.num_contents));
 
  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.
 
   // Now we can read the rest of the tmd.
   tmd_size = sizeof(tmd) + num_contents*sizeof(content_record_t);
+
   tmd_size = sizeof(tmd) + be16((u8*) &tmd.num_contents)*sizeof(content_record_t);
 
   tmd_size = (tmd_size + 63) & ~63;
 
   tmd_size = (tmd_size + 63) & ~63;
 +
  tmd_size = tmd_size - sizeof(tmd);
 
   
 
   
   data = malloc(tmd_size);
+
  rec = malloc(tmd_size);
 +
  fread(rec, 1, tmd_size, fp);
 +
 
 +
   data = malloc(tmd_size+sizeof(tmd));
 
   memcpy(data, &tmd, sizeof(tmd));
 
   memcpy(data, &tmd, sizeof(tmd));
   fread(&data[sizeof(tmd)], 1, tmd_size-sizeof(tmd), fp);
+
   memcpy(&data[sizeof(tmd)], rec, tmd_size);
 +
 
 
   
 
   
   write_part(data, tmd_size, "04_tmd");
+
   write_part(data, tmd_size+sizeof(tmd), "04_tmd");
 
   
 
   
  content_sizes = calloc(1, sizeof (size_t) *  num_contents);
 
  hashes = calloc(20, num_contents);
 
 
  rec = (content_record_t*) &data[sizeof(tmd)];
 
  for (i = 0; i < num_contents; i++, rec++) {
 
    u16 type = be16((u8*) &rec->type);
 
    memcpy(hashes + (20*i), rec->hash, 20);
 
   
 
    if (!(type & 0x8000)) {
 
      content_sizes[i] = (size_t)be32((u8*) &rec->size_hi);
 
    }
 
  }
 
 
}
 
}
 
   
 
   
Line 326: Line 323:
 
   u8 hash[0x14];
 
   u8 hash[0x14];
 
   
 
   
   for (i=0; i < num_contents; i++) {
+
   for (i=0; i < be16((u8*) &tmd.num_contents); i++) {
 
     printf("Content index %d: ",i);
 
     printf("Content index %d: ",i);
  if (content_sizes[i] != 0) {
+
    if(bKheader.contents[i/8] & (1 << (i % 8))) {
 
       char name[128];
 
       char name[128];
 
       u8 *data;
 
       u8 *data;
       size_t rounded_size = (content_sizes[i] + 63) & ~63;
+
       size_t rounded_size = (be32((u8*) &rec[i].size_hi) + 63) & ~63;
+
     
 
       data = malloc(rounded_size);
 
       data = malloc(rounded_size);
 
       fread(data, 1, rounded_size, fp);
 
       fread(data, 1, rounded_size, fp);
Line 339: Line 336:
 
       wbe16(partE_iv,i);
 
       wbe16(partE_iv,i);
 
       aes_cbc_dec(prngseed, partE_iv, data, rounded_size, data);
 
       aes_cbc_dec(prngseed, partE_iv, data, rounded_size, data);
+
     
 
       snprintf(name, 128, "05_content_%02d", i);
 
       snprintf(name, 128, "05_content_%02d", i);
       printf("is included. Written size: %x, ", content_sizes[i]);
+
       printf("is included. Written size: %x, ", be32((u8*) &rec[i].size_hi));
 
        
 
        
       sha(data, content_sizes[i],hash);
+
       sha(data, be32((u8*) &rec[i].size_hi),hash);
       if(memcmp(hash,hashes+(i*20),20))
+
       if(memcmp(hash,&rec[i].hash[0],20))
 
         printf("hash invalid\n");
 
         printf("hash invalid\n");
 
       else
 
       else
 
         printf("hash ok\n");
 
         printf("hash ok\n");
+
     
 
       write_part(data, rounded_size, name);
 
       write_part(data, rounded_size, name);
+
     
 
       free (data);
 
       free (data);
 
     }
 
     }
 
     else
 
     else
 
     {
 
     {
       printf("is not included (shared)\n");
+
       printf("is not included\n");
 
     }
 
     }
 
   }
 
   }
Line 381: Line 378:
 
   rounded_size = (partB_size + 63) & ~63;
 
   rounded_size = (partB_size + 63) & ~63;
 
   rounded_size += 0x640;
 
   rounded_size += 0x640;
   data_size = total_size - 0x340;
+
   data_size = be32((u8*) &bKheader.total_size) - 0x340;
  
 
data = malloc(data_size);
 
data = malloc(data_size);

Revision as of 00:21, 7 April 2009

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.

// 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.
// Version 1.3 Verifies content hashes, and ecc (eliptic curve cryptography) signature.
// Version 1.4 Now uses "Bk" header to determine which contents are present. 
 
 
#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;
 
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 size; //Always 0x70, however, this struct made 0x80 because of align 64.
      u32 magic; //Always 0x426B0001
      u32 console_id;
      u32 num_files;
      u32 file_size;
      u32 tmd_size;
      u32 payload_size;
      u32 total_size;
      u8 contents[0x40];
      u32 title_id_hi;
      u32 title_id_lo;
      u32 unknown[2];
      u32 align_64[4];
}  partC_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 partC_header_t bKheader;
static tmd_t tmd;
static content_record_t *rec;
 
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);
  
	aes_cbc_dec(sd_key, sd_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,rounded_size,"02_banner");
	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)
{
  fread(&bKheader, 1, sizeof bKheader, fp);
 
	if (be32((u8*) &bKheader.magic) != 0x426b0001)
		ERROR("no Bk header");
	if (be32((u8*) &bKheader.size) != 0x70)
		ERROR("wrong Bk header size");
 
	fprintf(stderr, "NG id: %08x\n", be32((u8*) &bKheader.console_id));
 
	write_part(&bKheader, sizeof(bKheader), "03_bk_header");
}
 
static void do_partD_tmd(void)
{
	u8* data;
	size_t tmd_size;

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

  printf("Number of content files: %d\n", be16((u8*) &tmd.num_contents));
 
  // Now we can read the rest of the tmd.
  tmd_size = sizeof(tmd) + be16((u8*) &tmd.num_contents)*sizeof(content_record_t);
  tmd_size = (tmd_size + 63) & ~63;
  tmd_size = tmd_size - sizeof(tmd);
 
  rec = malloc(tmd_size);
  fread(rec, 1, tmd_size, fp);
  
  data = malloc(tmd_size+sizeof(tmd));
  memcpy(data, &tmd, sizeof(tmd));
  memcpy(&data[sizeof(tmd)], rec, tmd_size);
  
 
  write_part(data, tmd_size+sizeof(tmd), "04_tmd");
 
}
 
static void do_partE_contents(void)
{
  int i;
  u8 partE_iv[16];
  u8 hash[0x14];
 
  for (i=0; i < be16((u8*) &tmd.num_contents); i++) {
    printf("Content index %d: ",i);
    if(bKheader.contents[i/8] & (1 << (i % 8))) {
      char name[128];
      u8 *data;
      size_t rounded_size = (be32((u8*) &rec[i].size_hi) + 63) & ~63;
      
      data = malloc(rounded_size);
      fread(data, 1, rounded_size, fp);
      
      memset(partE_iv,0,16);
      wbe16(partE_iv,i);
      aes_cbc_dec(prngseed, partE_iv, data, rounded_size, data);
      
      snprintf(name, 128, "05_content_%02d", i);
      printf("is included. Written size: %x, ", be32((u8*) &rec[i].size_hi));
      
      sha(data, be32((u8*) &rec[i].size_hi),hash);
      if(memcmp(hash,&rec[i].hash[0],20))
        printf("hash invalid\n");
      else
        printf("hash ok\n");
      
      write_part(data, rounded_size, name);
      
      free (data);
    }
    else
    {
      printf("is not included\n");
    }
  }
}

static void do_sig(void)
{
	u8 sig[0x40];
	u8 ng_cert[0x180];
	u8 ap_cert[0x180];
	u8 hash[0x14];
	u8 *data;
	u32 data_size;
  u32 rounded_size;
	int ok;
	if (fread(sig, sizeof sig, 1, fp) != 1)
		fatal("read signature");
	if (fread(ng_cert, sizeof ng_cert, 1, fp) != 1)
		fatal("read NG cert");
	if (fread(ap_cert, sizeof ap_cert, 1, fp) != 1)
		fatal("read AP cert");
  
  write_part(sig, 0x340, "06_cert");

  rounded_size = (partB_size + 63) & ~63;
  rounded_size += 0x640;
  data_size = be32((u8*) &bKheader.total_size) - 0x340;

	data = malloc(data_size);
	if (!data)
		fatal("malloc");
	fseek(fp, rounded_size, SEEK_SET);
	if (fread(data, data_size, 1, fp) != 1)
		fatal("read data for sig check");
	sha(data, data_size, hash);
	sha(hash, 20, hash);
	free(data);

	ok = check_ec(ng_cert, ap_cert, sig, hash);
	printf("ECC Signature: %s\n", ok?"ok":"invalid");
}
 
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("default/prng-seed", prngseed, 16);
 
	fp = fopen(argv[1], "rb");
 
  do_partA_header();
	do_partB_gameinfo();
	do_partC_Bk_header();
	do_partD_tmd();
	do_partE_contents();
  do_sig();
 
	fclose(fp);
 
	return 0;
}