diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a5e74c7..79ca5fe 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -9,9 +9,7 @@ jobs: - name: Checkout uses: actions/checkout@v3 - name: Compile (GCC) - run: make CC=gcc - - name: Compile (Clang) - run: make CC=clang + run: make - name: Upload uses: actions/upload-artifact@v4 with: @@ -23,7 +21,7 @@ jobs: steps: - name: Checkout uses: actions/checkout@v3 - - name: Compile + - name: Compile (Clang) run: make - name: Upload uses: actions/upload-artifact@v4 @@ -43,11 +41,9 @@ jobs: uses: msys2/setup-msys2@v2 with: msystem: mingw64 - install: make mingw-w64-x86_64-gcc mingw-w64-x86_64-clang + install: make mingw-w64-x86_64-gcc - name: Compile (GCC) run: make CC=gcc - - name: Compile (Clang) - run: make CC=clang - name: Upload uses: actions/upload-artifact@v4 with: diff --git a/README.md b/README.md index a1533e0..8bd0a65 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,7 @@ Licensed to you under the GNU General Public License version 2. See LICENSE for ## Features so far +* Decompressing the CE/5BL base kernel. * Extracting and updating the kernel stages. (CE/SE + CG/SE) ## Usage diff --git a/include/cb-handler.h b/include/cb-handler.h new file mode 100644 index 0000000..002e7c7 --- /dev/null +++ b/include/cb-handler.h @@ -0,0 +1,9 @@ +#include +#include + +bool cb_is_decrypted(uint8_t *cb_data); +void cb_print_info(uint8_t *cb_data); +void cb_decrypt(uint8_t *cb_data, uint8_t *cba_or_1bl_key); +void cb_b_decrypt(uint8_t *cb_b_data, uint8_t *cb_a_data, uint8_t *cpu_key); +void cb_calculate_rotsum(uint8_t *cb_data, uint8_t *sha_out); +bool cb_verify_signature(uint8_t *cb_data); diff --git a/include/cd-handler.h b/include/cd-handler.h new file mode 100644 index 0000000..3236e0b --- /dev/null +++ b/include/cd-handler.h @@ -0,0 +1,7 @@ +#include +#include + +bool cd_is_decrypted(uint8_t *cd_data); +void cd_print_info(uint8_t *cd_data); +void cd_decrypt(uint8_t *cd_data, uint8_t *cbb_key, uint8_t *cpu_key); +void cd_calculate_rotsum(uint8_t *cd_data, uint8_t *sha_out); diff --git a/include/cf-handler.h b/include/cf-handler.h index a4640dd..756ee6c 100644 --- a/include/cf-handler.h +++ b/include/cf-handler.h @@ -3,6 +3,6 @@ bool cf_is_decrypted(uint8_t *cf_data); void cf_print_info(uint8_t *cf_data); -void cf_decrypt(uint8_t *cf_data, uint8_t *cpu_or_1bl_key); +void cf_decrypt(uint8_t *cf_data, uint8_t *onebl_key); void cf_calculate_rotsum(uint8_t *cf_data, uint8_t *sha_out); bool cf_verify_signature(uint8_t *cf_data); diff --git a/include/commands.h b/include/commands.h new file mode 100644 index 0000000..97a3e6e --- /dev/null +++ b/include/commands.h @@ -0,0 +1,3 @@ +int do_decompress_command(int argc, char **argv); +int do_nand_extract_command(int argc, char **argv); +int do_xboxupd_command(int argc, char **argv); diff --git a/include/nand-ecc.h b/include/nand-ecc.h new file mode 100644 index 0000000..eadab67 --- /dev/null +++ b/include/nand-ecc.h @@ -0,0 +1,91 @@ +// Reference: https://free60.org/System-Software/NAND_File_System/ +// these all need checking before i want to rely on using them for anything +#include + +// ECC spare data used on 1st gen NAND controllers +typedef struct _ecc_data_v1 { + // uint16_t block_id : 12; + uint8_t block_id_1; + uint8_t block_id_0 : 4; + + uint8_t fs_unused : 4; + uint8_t fs_sequence_0; + uint8_t fs_sequence_1; + uint8_t fs_sequence_2; + uint8_t bad_block; + uint8_t fs_sequence_3; + + // uint16_t fs_size; + uint8_t fs_size_1; + uint8_t fs_size_0; + + uint8_t fs_page_count; + uint8_t fs_unused_2[2]; + uint8_t fs_block_type : 6; + + // 14-bit ECC data + uint8_t ecc3 : 2; + uint8_t ecc2; + uint8_t ecc1; + uint8_t ecc0; +} ecc_data_v1; + +// ECC spare data used on 2nd gen NAND controllers (16/64MB) +typedef struct _ecc_data_v2_sb { + uint8_t fs_sequence_0; + + // uint16_t block_id : 12; + uint8_t block_id_1; + uint8_t block_id_0 : 4; + + uint8_t fs_unused : 4; + uint8_t fs_sequence_1; + uint8_t fs_sequence_2; + uint8_t bad_block; + uint8_t fs_sequence_3; + + // uint16_t fs_size; + uint8_t fs_size_1; + uint8_t fs_size_0; + + uint8_t fs_page_count; + uint8_t fs_unused_2[2]; + uint8_t fs_block_type : 6; + + // 14-bit ECC data + uint8_t ecc3 : 2; + uint8_t ecc2; + uint8_t ecc1; + uint8_t ecc0; +} ecc_data_v2_sb; + +// ECC spare data used on 2nd gen NAND controllers (256/512MB) +typedef struct _ecc_data_v2_bb { + uint8_t bad_block; + + // uint16_t block_id : 12; + uint8_t block_id_1; + uint8_t block_id_0 : 4; + + uint8_t fs_unused : 4; + uint8_t fs_sequence_2; + uint8_t fs_sequence_1; + uint8_t fs_sequence_0; + uint8_t fs_unused_2; + + // uint16_t fs_size; + uint8_t fs_size_1; + uint8_t fs_size_0; + + uint8_t fs_page_count; + uint8_t fs_unused_3[2]; + uint8_t fs_block_type : 6; + + // 14-bit ECC data + uint8_t ecc3 : 2; + uint8_t ecc2; + uint8_t ecc1; + uint8_t ecc0; +} ecc_data_v2_bb; + +void calculate_nand_ecc(uint8_t page[0x200], uint8_t spare[0x10], uint8_t ecc[0x4]); diff --git a/include/nand.h b/include/nand.h index 3a0545e..3c4dd5b 100644 --- a/include/nand.h +++ b/include/nand.h @@ -1,6 +1,14 @@ #include +#include +#include #include "xenon-bootloader.h" +#define NAND_PAGE_SIZE 0x200 +#define NAND_META_SIZE 0x10 +#define NAND_PAGE_FULL (NAND_PAGE_SIZE + NAND_META_SIZE) + +#define NAND_KV_VERSION 0x0712 + typedef struct _xenon_nand_header { bootloader_header header; // entrypoint leads to CB char copyright[0x40]; @@ -15,3 +23,49 @@ typedef struct _xenon_nand_header { uint32_t smc_boot_size; uint32_t smc_boot_offset; } xenon_nand_header; + +typedef enum _nand_type { + NAND_TYPE_SMALLBLOCK, // Small 16MB/64MB NANDs on pre-Jasper + NAND_TYPE_SMALLBLOCK_V2, // Small 16MB/64MB NANDs on Jasper and later + NAND_TYPE_BIGBLOCK, // Large 256MB/512MB NANDs on Jasper + NAND_TYPE_UNECC, // NAND image lacks ECC data, and isn't Corona + NAND_TYPE_CORONA, // NAND image is Corona. lolhynix + + NAND_TYPE_UNKNOWN = -1 +} nand_type; + +typedef enum _storage_size { + STORAGE_SIZE_16MB, + STORAGE_SIZE_64MB, + STORAGE_SIZE_256MB, + STORAGE_SIZE_512MB, + STORAGE_SIZE_4GB +} storage_size; + +typedef enum _console_type { + CONSOLE_XENON, + CONSOLE_ELPIS, // runs 7xxx series bootloaders + CONSOLE_FALCON, + CONSOLE_JASPER, + CONSOLE_TRINITY, + CONSOLE_CORONA_16MB, + CONSOLE_CORONA_4GB, + CONSOLE_WINCHESTER, + + CONSOLE_UNKNOWN = -1 +} console_type; + +typedef struct _nand_file { + FILE *fp; + int type; + int size; + int console; + bool close_file; +} nand_file; + +nand_file *open_nand_file(FILE *fp, bool create); +nand_file *open_nand_file_name(char *name, bool create); + +void read_nand_data(nand_file *file, uint32_t offset, uint8_t *data, size_t len); + +void close_nand_file(nand_file *file); diff --git a/include/utility.h b/include/utility.h index 5008d0e..c3f771f 100644 --- a/include/utility.h +++ b/include/utility.h @@ -1,4 +1,6 @@ #include +#include +#include #ifndef SHOULD_BE_BE #define BE16(i) ((((i) & 0xFF) << 8 | ((i) >> 8) & 0xFF) & 0xFFFF) @@ -10,4 +12,17 @@ #define BE64(i) i #endif +#ifdef SHOULD_BE_BE +#define LE16(i) ((((i) & 0xFF) << 8 | ((i) >> 8) & 0xFF) & 0xFFFF) +#define LE(i) (((i) & 0xff) << 24 | ((i) & 0xff00) << 8 | ((i) & 0xff0000) >> 8 | ((i) >> 24) & 0xff) +#define LE64(i) (BE((i) & 0xFFFFFFFF) << 32 | BE(((i) >> 32) & 0xFFFFFFFF)) +#else +#define LE16(i) i +#define LE(i) i +#define LE64(i) i +#endif + void hexdump(uint8_t *data, uint32_t size); +bool parse_hex_str(char *str, uint8_t *out_buf, size_t buf_size); +void dump_to_file(char *filename, uint8_t *buf, size_t size); +size_t get_file_size(FILE *fp); diff --git a/include/xenon-bootloader.h b/include/xenon-bootloader.h index c9859ce..913f7be 100644 --- a/include/xenon-bootloader.h +++ b/include/xenon-bootloader.h @@ -2,6 +2,7 @@ #define XENON_BOOTLOADER_H_ #include +#include #include "excrypt.h" typedef enum _xenon_bl_type { @@ -37,8 +38,8 @@ typedef struct _onebl_globals { typedef struct _bootloader_header { uint16_t magic; uint16_t version; - uint16_t pairing; - uint16_t flags; + uint16_t pairing; // 0x8000 = devkit? + uint16_t flags; // 0x0001 = mfg, 0x0800 = cba? uint32_t entrypoint; uint32_t size; } bootloader_header; @@ -62,17 +63,22 @@ typedef struct _bootloader_cb_header { uint8_t key[0x10]; uint64_t padding_or_args[4]; EXCRYPT_SIG signature; - uint8_t globals[0x25C]; // find out whats in here... + uint8_t globals[0x128]; // find out whats in here... + EXCRYPT_RSAPUB_2048 devkit_pubkey; + uint8_t sc_key[0x10]; + char sc_salt[10]; + char sd_salt[10]; uint8_t cd_cbb_hash[0x14]; // CB_A has CB_B hash, CB_B has CD hash - uint8_t more_globals[0x10]; // and here as well... + uint8_t more_globals[0x10]; // more_globals[1] has LDV } bootloader_cb_header; typedef struct _bootloader_cd_header { bootloader_header header; uint8_t key[0x10]; - uint8_t idk_yet[0x220]; // possibly unused? + EXCRYPT_SIG signature; // only valid on devkits + uint8_t idk_yet[0x120]; // unused? char cf_salt[10]; - uint16_t unused; + uint16_t unused2; uint8_t ce_hash[0x14]; } bootloader_cd_header; @@ -87,9 +93,9 @@ typedef struct _bootloader_ce_header { typedef struct _bootloader_cf_header { bootloader_header header; uint16_t base_ver; - uint16_t base_flags; // unsure + uint16_t base_flags; // unsure, 0x8000 for devkit uint16_t target_ver; - uint16_t target_flags; // unsure + uint16_t target_flags; // unsure, 0x8000 for devkit uint32_t unknown; // think this is unused uint32_t cg_size; uint8_t key[0x10]; @@ -126,6 +132,17 @@ typedef struct _bootloader_update_inner { uint8_t part2_sha[0x14]; } bootloader_update_inner; +typedef struct _bootloader_update_tab_entry { + uint16_t cpu_pvr; + uint16_t xenos_id; + uint16_t unk1; // differentiates elpis and corona - possibly PCI bridge revision ID? + uint16_t unk2; // also differentiates elpis/corona but also new GPU revs? + uint16_t from_cb; // seems to be used exclusively on Xenon + uint16_t from_cd; // never used, just an assumption? + uint16_t to_cb; // the CB (and CB_B in v2) version to update to + uint16_t to_cd; // the CD version to update to (matches CB in v2) +} bootloader_update_tab_entry; + typedef struct _bootloader_compression_header { uint32_t window_size; uint32_t block_size; @@ -145,4 +162,10 @@ typedef struct _bootloader_delta_block { uint16_t compressed_size; } bootloader_delta_block; +int get_bootloader_type(bootloader_header *bl); +bool is_bootloader_devkit(bootloader_header *bl); +void xke_sc_calculate_rotsum(uint8_t *bl_data, uint8_t *sha_out); +bool xke_sc_verify_signature(uint8_t *bl_data, char *salt, uint8_t *pubkey); +void xke_sc_decrypt(uint8_t *bl_data, uint8_t *dec_key); + #endif diff --git a/source/bltool-cli.c b/source/bltool-cli.c index 09b216b..ca31538 100644 --- a/source/bltool-cli.c +++ b/source/bltool-cli.c @@ -19,294 +19,9 @@ #include #include #include +#include #include -#include "1bl-keys.h" -#include "ce-handler.h" -#include "cg-handler.h" -#include "cf-handler.h" -#include "xenon-bootloader.h" - -void hexdump(uint8_t *data, uint32_t size) { - for (uint32_t i = 0; i < size; i++) { - printf("%02x ", data[i]); - } - printf("\n"); -} - -// shitty hack to get a file size -// idk how to do it properly -size_t get_file_size(FILE *fp) { - fseek(fp, 0, SEEK_END); - size_t fs = ftell(fp); - fseek(fp, 0, SEEK_SET); - return fs; -} - -int do_decompress_command(int argc, char **argv) { - if (argc < 2) { - printf("not enough arguments!\n"); - return -1; - } - - char *base_path = argv[1]; - char *output_path = argv[2]; - - // open, read and close our base file - FILE *base_file = fopen(base_path, "rb"); - if (base_file == NULL) { - printf("couldn't open the input file!\n"); - return -1; - } - size_t base_size = get_file_size(base_file); - void *base_buf = malloc(base_size); - if (base_buf == NULL) { - printf("failed to allocate buffer for input file.\n"); - return -1; - } - fread(base_buf, 1, base_size, base_file); - fclose(base_file); - - // detect the type of the base kernel file - uint8_t *base_kernel = (uint8_t *)base_buf; - uint32_t base_kernel_size = base_size; - bootloader_header *hdr = (bootloader_header *)base_buf; - if ((BE16(hdr->magic) & 0xFFF) != 0x345) { - printf("input file isn't CE/5BL.\n"); - free(base_buf); - return -1; - } - - bootloader_ce_header *ce_hdr = (bootloader_ce_header *)hdr; - ce_print_info(base_buf); - if (!ce_is_decrypted(base_buf)) { - printf("input CE/5BL file is encrypted.\n"); - free(base_buf); - return -1; - } - - // we have to decompress the base kernel - uint8_t *decompressed_kernel_buf = malloc(BE(ce_hdr->uncompressed_size)); - if (decompressed_kernel_buf == NULL) { - printf("failed to allocate buffer for decompressed kernel.\n"); - free(base_buf); - return -1; - } - - if (!ce_decompress(base_buf, decompressed_kernel_buf)) { - printf("decompression failed.\n"); - free(base_buf); - free (decompressed_kernel_buf); - return -1; - } - - base_kernel_size = BE(ce_hdr->uncompressed_size); - base_kernel = decompressed_kernel_buf; - hdr = (bootloader_header *)decompressed_kernel_buf; - - // free the original basefile buffer, we don't need the CE anymore - free(base_buf); - - // save the file - printf("writing decompressed kernel to '%s'...\n", output_path); - FILE *out_file = fopen(output_path, "wb+"); - if (out_file == NULL) { - printf("failed to open output file!\n"); - free(decompressed_kernel_buf); - return -1; - } - fwrite(decompressed_kernel_buf, 1, BE(ce_hdr->uncompressed_size), out_file); - fclose(out_file); - free(decompressed_kernel_buf); - - return 0; -} - -int do_xboxupd_command(int argc, char **argv) { - if (argc < 3) { - printf("not enough arguments!\n"); - return -1; - } - - char *xboxupd_path = argv[1]; - char *base_path = argv[2]; - char *output_path = argv[3]; - - // open, read and close our base file - FILE *base_file = fopen(base_path, "rb"); - if (base_file == NULL) { - printf("couldn't open the base file!\n"); - return -1; - } - size_t base_size = get_file_size(base_file); - void *base_buf = malloc(base_size); - if (base_buf == NULL) { - printf("failed to allocate buffer for basefile.\n"); - return -1; - } - fread(base_buf, 1, base_size, base_file); - fclose(base_file); - - // detect the type of the base kernel file - uint8_t *base_kernel = (uint8_t *)base_buf; - uint32_t base_kernel_size = base_size; - bootloader_header *hdr = (bootloader_header *)base_buf; - if ((BE16(hdr->magic) & 0xFFF) == 0xE4E) { - printf("base file is raw HV %i with size 0x%x\n", BE16(hdr->version), base_kernel_size); - } else if ((BE16(hdr->magic) & 0xFFF) == 0x345) { - bootloader_ce_header *ce_hdr = (bootloader_ce_header *)hdr; - ce_print_info(base_buf); - if (!ce_is_decrypted(base_buf)) { - printf("base CE/5BL file is encrypted.\n"); - free(base_buf); - return -1; - } - - // we have to decompress the base kernel - uint8_t *decompressed_kernel_buf = malloc(BE(ce_hdr->uncompressed_size)); - if (decompressed_kernel_buf == NULL) { - printf("failed to allocate buffer for decompressed kernel.\n"); - free(base_buf); - return -1; - } - - if (!ce_decompress(base_buf, decompressed_kernel_buf)) { - printf("decompression failed.\n"); - free(base_buf); - free (decompressed_kernel_buf); - return -1; - } - - base_kernel_size = BE(ce_hdr->uncompressed_size); - base_kernel = decompressed_kernel_buf; - hdr = (bootloader_header *)decompressed_kernel_buf; - - // free the original basefile buffer, we don't need the CE anymore - free(base_buf); - - printf("base file is decompressed HV %i\n", BE16(hdr->version)); - } else { - printf("unknown base file format. (%04x)\n", BE16(hdr->magic)); - free(base_buf); - return -1; - } - - // load the xboxupd.bin file - FILE *xboxupd_file = fopen(xboxupd_path, "rb"); - if (xboxupd_file == NULL) { - printf("couldn't open the xboxupd.bin file!\n"); - free(base_kernel); - return -1; - } - size_t xboxupd_size = get_file_size(xboxupd_file); - uint8_t *xboxupd_buf = malloc(xboxupd_size); - if (xboxupd_buf == NULL) { - printf("failed to allocate buffer for xboxupd.bin.\n"); - free(base_kernel); - return -1; - } - fread(xboxupd_buf, 1, xboxupd_size, xboxupd_file); - fclose(xboxupd_file); - - // TODO(Emma): allow just a decrypted CG (or CG with key) without CF - - // make sure we have both the CF and CG stages in the file - bootloader_cf_header *cf_hdr = (bootloader_cf_header *)xboxupd_buf; - if ((BE16(cf_hdr->header.magic) & 0xFFF) != 0x346) { - printf("CF header not found. invalid xboxupd.bin?\n"); - free(base_kernel); - free(xboxupd_buf); - return -1; - } - // some basic size sanity checking - if (xboxupd_size < ( BE(cf_hdr->header.size) + BE(cf_hdr->cg_size) )) { - printf("input xboxupd.bin file too small for CF+CG. invalid file?\n"); - free(base_kernel); - free(xboxupd_buf); - return -1; - } - // and try to decrypt the CF - if (!cf_is_decrypted((uint8_t *)cf_hdr)) { - cf_decrypt((uint8_t *)cf_hdr, key_1bl); - } - if (!cf_is_decrypted((uint8_t *)cf_hdr)) { - printf("failed to decrypt CF\n"); - free(base_kernel); - free(xboxupd_buf); - return -1; - } - cf_print_info((uint8_t *)cf_hdr); - - if (BE16(cf_hdr->base_ver) != BE16(hdr->version)) { - printf("mismatching base kernel version %i (CF expects %i)\n", BE16(hdr->version), BE16(cf_hdr->base_ver)); - free(base_kernel); - free(xboxupd_buf); - return -1; - } - - // load the CG as well - bootloader_cg_header *cg_hdr = (bootloader_cg_header *)(xboxupd_buf + BE(cf_hdr->header.size)); - if ((BE16(cg_hdr->header.magic) & 0xFFF) != 0x347) { - printf("CG header not found. invalid xboxupd.bin?\n"); - free(base_kernel); - free(xboxupd_buf); - return -1; - } - // and try to decrypt that, too - // maybe cg_is_decrypted should be replaced with a rotsum check against CF? - if (!cg_is_decrypted((uint8_t *)cg_hdr)) { - cg_decrypt((uint8_t *)cg_hdr, cf_hdr->cg_hmac); - } - if (!cg_is_decrypted((uint8_t *)cg_hdr)) { - printf("failed to decrypt CG\n"); - free(base_kernel); - free(xboxupd_buf); - return -1; - } - - uint8_t cg_rotsum[0x14]; - cg_calculate_rotsum((uint8_t *)cg_hdr, cg_rotsum); - if (memcmp(cg_rotsum, cf_hdr->cg_hash, 0x14) != 0) { - printf("CG checksum does not match CF header\n"); - free(base_kernel); - free(xboxupd_buf); - return -1; - } - cg_print_info((uint8_t *)cg_hdr); - - // patch the kernel - uint8_t *patched_kernel = malloc(BE(cg_hdr->new_size)); - if (patched_kernel == NULL) { - printf("failed to allocate memory for new kernel.\n"); - free(base_kernel); - free(xboxupd_buf); - return -1; - } - if (!cg_apply_patch((uint8_t *)cg_hdr, base_kernel, patched_kernel)) { - printf("failed to apply the kernel patch.\n"); - free(base_kernel); - free(xboxupd_buf); - free(patched_kernel); - return -1; - } - - // free these now since we won't need them later - free(base_kernel); - free(xboxupd_buf); - - // save the file - printf("writing patched kernel to '%s'...\n", output_path); - FILE *out_file = fopen(output_path, "wb+"); - if (out_file == NULL) { - printf("failed to open output file!\n"); - free(patched_kernel); - return -1; - } - fwrite(patched_kernel, 1, BE(cg_hdr->new_size), out_file); - fclose(out_file); - free(patched_kernel); - - return 0; -} +#include "commands.h" typedef int(*command_handler_t)(int argc, char **argv); typedef struct _command_verb { @@ -320,8 +35,9 @@ typedef struct _command_verb { static const command_verb verbs[] = { { "decompress", "Decompresses a CE/SE (5BL) bootloader.", 2, "[path to CE] [output path]", do_decompress_command }, { "xboxupd", "Applies an xboxupd.bin (CF+CG) patch to a base kernel or CE.", 3, "[path to xboxupd.bin] [path to CE/base] [output_path]", do_xboxupd_command }, + { "nand_extract", "Extracts bootloader stages from a NAND image.", 2, "[path to nand.bin] [cpu key]", do_nand_extract_command }, }; -static const int total_verbs = 2; +int total_verbs = sizeof(verbs) / sizeof(verbs[0]); int main(int argc, char **argv) { printf("xenon-bltool - https://github.com/InvoxiPlayGames/xenon-bltool\n\n"); diff --git a/source/bootloader.c b/source/bootloader.c new file mode 100644 index 0000000..7d0606e --- /dev/null +++ b/source/bootloader.c @@ -0,0 +1,93 @@ +/* + bootloader.c - General utility functions for all 360 bootloader types. + Copyright 2024 Emma https://ipg.gay/ + + This file is part of xenon-bltool. + + xenon-bltool is free software: you can redistribute it and/or modify it under the terms of + the GNU General Public License as published by the Free Software Foundation, version 2 of + the License. + + xenon-bltool is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with xenon-bltool. + If not, see . +*/ + +#include +#include +#include "utility.h" +#include "xenon-bootloader.h" + +int get_bootloader_type(bootloader_header *bl) +{ + uint16_t bl_magic = BE16(bl->magic); + + // these bootloader types all use first nibble as retail/devkit designation + switch (bl_magic & 0xFFF) { + // boot stages + case 0x341: + return XENON_BOOTLOADER_1BL; + case 0x342: + return XENON_BOOTLOADER_CB; + case 0x343: + return XENON_BOOTLOADER_SC; + case 0x344: + return XENON_BOOTLOADER_CD; + case 0x345: + return XENON_BOOTLOADER_CE; + case 0x346: + return XENON_BOOTLOADER_CF; + case 0x347: + return XENON_BOOTLOADER_CG; + // other bl-esque payloads + case 0xD4D: + return XENON_BOOTLOADER_XKE; + case 0xE4E: + return XENON_BOOTLOADER_HV; + } + + // fixed magic regardless of devkit or retail + if (bl_magic == 0xEC4C) + return XENON_BOOTLOADER_BLUPD; + + // no idea + return XENON_BOOTLOADER_INVALID; +} + +bool is_bootloader_devkit(bootloader_header *bl) +{ + uint16_t bl_magic = BE16(bl->magic); + return ((bl_magic & 0x1000) == 0x1000); +} + +void xke_sc_calculate_rotsum(uint8_t *bl_data, uint8_t *sha_out) { + bootloader_generic_header *hdr = (bootloader_generic_header *)bl_data; + uint32_t size_aligned = BE(hdr->header.size) + 0xF & 0xFFFFFFF0; + + ExCryptRotSumSha((uint8_t *)hdr, 0x10, (uint8_t *)(hdr + 1), size_aligned - sizeof(bootloader_generic_header), sha_out, 0x14); +} + +bool xke_sc_verify_signature(uint8_t *bl_data, char *salt, uint8_t *pubkey) { + bootloader_generic_header *hdr = (bootloader_generic_header *)bl_data; + uint8_t bl_hash[0x14]; + xke_sc_calculate_rotsum(bl_data, bl_hash); + + BOOL result = ExCryptBnQwBeSigVerify(&hdr->signature, bl_hash, salt, (EXCRYPT_RSA *)pubkey); + return (result == 1); +} + +void xke_sc_decrypt(uint8_t *bl_data, uint8_t *dec_key) { + bootloader_generic_header *hdr = (bootloader_generic_header *)bl_data; + EXCRYPT_RC4_STATE rc4 = {0}; + uint32_t size_aligned = BE(hdr->header.size) + 0xF & 0xFFFFFFF0; + + // dec_key is either the header from SB for SC or the 1BL key for XKE payloads + ExCryptHmacSha(dec_key, 0x10, hdr->key, sizeof(hdr->key), NULL, 0, NULL, 0, hdr->key, sizeof(hdr->key)); + ExCryptRc4Key(&rc4, hdr->key, sizeof(hdr->key)); + + // 0x20 = sizeof(bootloader_header), sizeof(hdr->key) - all content after &padding_or_args is encrypted + ExCryptRc4Ecb(&rc4, (uint8_t *)(hdr + 1), size_aligned - sizeof(bootloader_generic_header)); +} diff --git a/source/cb-handler.c b/source/cb-handler.c index 70b786d..10d7fc1 100644 --- a/source/cb-handler.c +++ b/source/cb-handler.c @@ -1 +1,107 @@ -// TODO +/* + cb-handler.c - Handling for Xbox 360 CB/2BL bootloader stages. + Copyright 2024 Emma https://ipg.gay/ + + This file is part of xenon-bltool. + + xenon-bltool is free software: you can redistribute it and/or modify it under the terms of + the GNU General Public License as published by the Free Software Foundation, version 2 of + the License. + + xenon-bltool is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with xenon-bltool. + If not, see . +*/ + +#include +#include +#include +#include +#include "1bl-keys.h" +#include "utility.h" +#include "excrypt.h" +#include "xenon-bootloader.h" + +bool cb_is_decrypted(uint8_t *cb_data) { + bootloader_cb_header *hdr = (bootloader_cb_header *)cb_data; + // this global is the start of a 64-bit address that ignores hrmor + return (hdr->globals[0x110] == 0x80); +} + +void cb_calculate_rotsum(uint8_t *cb_data, uint8_t *sha_out) { + bootloader_cb_header *hdr = (bootloader_cb_header *)cb_data; + uint32_t size_aligned = BE(hdr->header.size) + 0xF & 0xFFFFFFF0; + + ExCryptRotSumSha((uint8_t *)hdr, 0x10, (uint8_t *)&hdr->globals, size_aligned - 0x140, sha_out, 0x14); +} + +bool cb_verify_signature(uint8_t *cb_data) { + bootloader_cb_header *hdr = (bootloader_cb_header *)cb_data; + uint8_t cb_hash[0x14]; + cb_calculate_rotsum(cb_data, cb_hash); + + BOOL result = ExCryptBnQwBeSigVerify(&hdr->signature, cb_hash, (const uint8_t *)"XBOX_ROM_B", (EXCRYPT_RSA *)rsa_1bl); + return (result == 1); +} + +void cb_print_info(uint8_t *cb_data) { + bootloader_cb_header *hdr = (bootloader_cb_header *)cb_data; + char *indicator = ((BE16(hdr->header.magic) & 0xF000) == 0x5000) ? "SB" : "CB"; + // hacky detection for CBA but works, there is no SB_A + if ((BE16(hdr->header.flags) & 0x800) == 0x800) + indicator = "CB_A"; + if (hdr->signature.padding[0] == 0) + indicator = "CB_B"; + printf("%s version: %i\n", indicator, BE16(hdr->header.version)); + printf("%s size: 0x%x\n", indicator, BE(hdr->header.size)); + printf("%s entrypoint: 0x%x\n", indicator, BE(hdr->header.entrypoint)); + if (cb_is_decrypted(cb_data)) { + printf("%s LDV: %i\n", indicator, hdr->more_globals[1]); + printf("%s next hash: ", indicator); + hexdump(hdr->cd_cbb_hash, sizeof(hdr->cd_cbb_hash)); + // detect if we actually have a signature - CB_B doesn't + if (hdr->signature.padding[0] != 0) + printf("%s signature: %s\n", indicator, cb_verify_signature(cb_data) ? "signed" : "invalid"); + } else { + printf("%s is encrypted\n", indicator); + } +} + +void cb_decrypt(uint8_t *cb_data, uint8_t *onebl_key) { + bootloader_cb_header *hdr = (bootloader_cb_header *)cb_data; + EXCRYPT_RC4_STATE rc4 = {0}; + uint32_t size_aligned = BE(hdr->header.size) + 0xF & 0xFFFFFFF0; + + // the key is the key used to decrypt CBA + ExCryptHmacSha(onebl_key, 0x10, hdr->key, sizeof(hdr->key), NULL, 0, NULL, 0, hdr->key, sizeof(hdr->key)); + ExCryptRc4Key(&rc4, hdr->key, sizeof(hdr->key)); + + // 0x20 = sizeof(bootloader_header), sizeof(hdr->key) - all content after &padding_or_args is encrypted + ExCryptRc4Ecb(&rc4, (uint8_t *)&hdr->padding_or_args, size_aligned - 0x20); +} + +void cb_b_decrypt(uint8_t *cb_b_data, uint8_t *cb_a_data, uint8_t *cpu_key) { + bootloader_cb_header *cb_a_hdr = (bootloader_cb_header *)cb_a_data; + bootloader_cb_header *cb_b_hdr = (bootloader_cb_header *)cb_b_data; + EXCRYPT_RC4_STATE rc4 = {0}; + uint32_t size_aligned = BE(cb_b_hdr->header.size) + 0xF & 0xFFFFFFF0; + + // no idea which CB_A started doing this? + // 9188 doesn't, 6754 and 13182 do + bootloader_header cb_a_hdr_copy; + memcpy(&cb_a_hdr_copy, cb_a_data, sizeof(bootloader_header)); + cb_a_hdr_copy.flags = 0; + + // the key is the key used to decrypt CBA + ExCryptHmacSha(cb_a_hdr->key, 0x10, cb_b_hdr->key, sizeof(cb_b_hdr->key), cpu_key, 0x10, (uint8_t *)&cb_a_hdr_copy, 0x10, cb_b_hdr->key, sizeof(cb_b_hdr->key)); + ExCryptRc4Key(&rc4, cb_b_hdr->key, sizeof(cb_b_hdr->key)); + + // 0x20 = sizeof(bootloader_header), sizeof(hdr->key) - all content after &padding_or_args is encrypted + ExCryptRc4Ecb(&rc4, (uint8_t *)&cb_b_hdr->padding_or_args, size_aligned - 0x20); + + // we don't memset the key to 0, since it's used to decrypt CB_B/CD + // we do that later +} diff --git a/source/cd-handler.c b/source/cd-handler.c index 70b786d..5751213 100644 --- a/source/cd-handler.c +++ b/source/cd-handler.c @@ -1 +1,80 @@ -// TODO +/* + cd-handler.c - Handling for Xbox 360 CD/4BL bootloader stages. + Copyright 2024 Emma https://ipg.gay/ + + This file is part of xenon-bltool. + + xenon-bltool is free software: you can redistribute it and/or modify it under the terms of + the GNU General Public License as published by the Free Software Foundation, version 2 of + the License. + + xenon-bltool is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with xenon-bltool. + If not, see . +*/ + +#include +#include +#include +#include +#include "1bl-keys.h" +#include "utility.h" +#include "excrypt.h" +#include "xenon-bootloader.h" + +bool cd_is_decrypted(uint8_t *cd_data) { + bootloader_cd_header *hdr = (bootloader_cd_header *)cd_data; + // always seen this as 0, no idea what it is + return (hdr->idk_yet[0] == 0x00); +} + +void cd_calculate_rotsum(uint8_t *cd_data, uint8_t *sha_out) { + bootloader_cd_header *hdr = (bootloader_cd_header *)cd_data; + uint32_t size_aligned = BE(hdr->header.size) + 0xF & 0xFFFFFFF0; + + ExCryptRotSumSha((uint8_t *)hdr, 0x10, (uint8_t *)&hdr->idk_yet, size_aligned - 0x120, sha_out, 0x14); +} + +void cd_print_info(uint8_t *cd_data) { + bootloader_cd_header *hdr = (bootloader_cd_header *)cd_data; + char *indicator = ((BE16(hdr->header.magic) & 0xF000) == 0x5000) ? "SD" : "CD"; + printf("%s version: %i\n", indicator, BE16(hdr->header.version)); + printf("%s size: 0x%x\n", indicator, BE(hdr->header.size)); + printf("%s entrypoint: 0x%x\n", indicator, BE(hdr->header.entrypoint)); + printf("%s cfsalt: %.10s\n", indicator, hdr->cf_salt); + if (cd_is_decrypted(cd_data)) { + printf("%s-E hash: ", indicator); + hexdump(hdr->ce_hash, sizeof(hdr->ce_hash)); + } else { + printf("%s is encrypted\n", indicator); + } +} + +void cd_decrypt(uint8_t *cd_data, uint8_t *cbb_key, uint8_t *cpu_key) { + bootloader_cd_header *hdr = (bootloader_cd_header *)cd_data; + EXCRYPT_RC4_STATE rc4 = {0}; + uint32_t size_aligned = BE(hdr->header.size) + 0xF & 0xFFFFFFF0; + + // the key is the key used to decrypt CBB + ExCryptHmacSha(cbb_key, 0x10, hdr->key, sizeof(hdr->key), NULL, 0, NULL, 0, hdr->key, sizeof(hdr->key)); + + if (cpu_key != NULL) + ExCryptHmacSha(cpu_key, 0x10, hdr->key, sizeof(hdr->key), NULL, 0, NULL, 0, hdr->key, sizeof(hdr->key)); + + ExCryptRc4Key(&rc4, hdr->key, sizeof(hdr->key)); + + // 0x20 = sizeof(bootloader_header), sizeof(hdr->key) - all content after &signature is encrypted + ExCryptRc4Ecb(&rc4, (uint8_t *)&hdr->signature, size_aligned - 0x20); +} + +bool cd_verify_signature_devkit(uint8_t *cd_data, uint8_t *pubkey) { + bootloader_cd_header *hdr = (bootloader_cd_header *)cd_data; + uint8_t cd_hash[0x14]; + cd_calculate_rotsum(cd_data, cd_hash); + + BOOL result = ExCryptBnQwBeSigVerify(&hdr->signature, cd_hash, (const uint8_t *)"XBOX_ROM_4", (EXCRYPT_RSA *)pubkey); + return (result == 1); +} diff --git a/source/ce-handler.c b/source/ce-handler.c index 068c937..195e289 100644 --- a/source/ce-handler.c +++ b/source/ce-handler.c @@ -57,9 +57,6 @@ void ce_decrypt(uint8_t *ce_data, uint8_t *cd_key) { // 0x20 = sizeof(bootloader_header), sizeof(hdr->key) - all content after &target_address is encrypted ExCryptRc4Ecb(&rc4, (uint8_t *)&hdr->target_address, size_aligned - 0x20); - - // set the key to all 00s so we know it's not encrypted - memset(hdr->key, 0, sizeof(hdr->key)); } void ce_calculate_rotsum(uint8_t *ce_data, uint8_t *sha_out) { diff --git a/source/cf-handler.c b/source/cf-handler.c index b72caae..2e4fb98 100644 --- a/source/cf-handler.c +++ b/source/cf-handler.c @@ -67,19 +67,16 @@ void cf_print_info(uint8_t *cf_data) { } } -void cf_decrypt(uint8_t *cf_data, uint8_t *cpu_or_1bl_key) { +void cf_decrypt(uint8_t *cf_data, uint8_t *onebl_key) { bootloader_cf_header *hdr = (bootloader_cf_header *)cf_data; uint8_t cf_key[0x10]; EXCRYPT_RC4_STATE rc4 = {0}; uint32_t size_aligned = BE(hdr->header.size) + 0xF & 0xFFFFFFF0; - // the key is the CPU key for decrypting from CD or 1bl key for decrypting from HV - ExCryptHmacSha(cpu_or_1bl_key, 0x10, hdr->key, sizeof(hdr->key), NULL, 0, NULL, 0, cf_key, sizeof(cf_key)); + // the key is the 1bl key + ExCryptHmacSha(onebl_key, 0x10, hdr->key, sizeof(hdr->key), NULL, 0, NULL, 0, cf_key, sizeof(cf_key)); ExCryptRc4Key(&rc4, cf_key, sizeof(cf_key)); // 0x30 = sizeof(bootloader_header), sizeof(hdr->key), and the version info - all content after &pairing is encrypted ExCryptRc4Ecb(&rc4, (uint8_t *)&hdr->pairing, size_aligned - 0x30); - - // set the key to all 00s so we know it's not encrypted - memset(hdr->key, 0, sizeof(hdr->key)); } diff --git a/source/cg-handler.c b/source/cg-handler.c index 0064b72..6a8f0f9 100644 --- a/source/cg-handler.c +++ b/source/cg-handler.c @@ -60,9 +60,6 @@ void cg_decrypt(uint8_t *cg_data, uint8_t *cg_hmac) { // 0x20 = sizeof(bootloader_header), sizeof(hdr->key) - all content after &original_size is encrypted ExCryptRc4Ecb(&rc4, (uint8_t *)&hdr->original_size, size_aligned - 0x20); - - // set the key to all 00s so we know it's not encrypted - memset(hdr->key, 0, sizeof(hdr->key)); } void cg_calculate_rotsum(uint8_t *cg_data, uint8_t *sha_out) { diff --git a/source/command-decompress.c b/source/command-decompress.c new file mode 100644 index 0000000..8970193 --- /dev/null +++ b/source/command-decompress.c @@ -0,0 +1,104 @@ +/* + command-decompress.c - Xbox 360 CE decompression + Copyright 2024 Emma https://ipg.gay/ + + This file is part of xenon-bltool. + + xenon-bltool is free software: you can redistribute it and/or modify it under the terms of + the GNU General Public License as published by the Free Software Foundation, version 2 of + the License. + + xenon-bltool is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with xenon-bltool. + If not, see . +*/ + +#include +#include +#include +#include +#include "xenon-bootloader.h" +#include "ce-handler.h" +#include "utility.h" + +int do_decompress_command(int argc, char **argv) { + if (argc < 2) { + printf("not enough arguments!\n"); + return -1; + } + + char *base_path = argv[1]; + char *output_path = argv[2]; + + // open, read and close our base file + FILE *base_file = fopen(base_path, "rb"); + if (base_file == NULL) { + printf("couldn't open the input file!\n"); + return -1; + } + size_t base_size = get_file_size(base_file); + void *base_buf = malloc(base_size); + if (base_buf == NULL) { + printf("failed to allocate buffer for input file.\n"); + return -1; + } + fread(base_buf, 1, base_size, base_file); + fclose(base_file); + + // detect the type of the base kernel file + uint8_t *base_kernel = (uint8_t *)base_buf; + uint32_t base_kernel_size = base_size; + bootloader_header *hdr = (bootloader_header *)base_buf; + if ((BE16(hdr->magic) & 0xFFF) != 0x345) { + printf("input file isn't CE/5BL.\n"); + free(base_buf); + return -1; + } + + bootloader_ce_header *ce_hdr = (bootloader_ce_header *)hdr; + ce_print_info(base_buf); + if (!ce_is_decrypted(base_buf)) { + printf("input CE/5BL file is encrypted.\n"); + free(base_buf); + return -1; + } + + // we have to decompress the base kernel + uint8_t *decompressed_kernel_buf = malloc(BE(ce_hdr->uncompressed_size)); + if (decompressed_kernel_buf == NULL) { + printf("failed to allocate buffer for decompressed kernel.\n"); + free(base_buf); + return -1; + } + + if (!ce_decompress(base_buf, decompressed_kernel_buf)) { + printf("decompression failed.\n"); + free(base_buf); + free (decompressed_kernel_buf); + return -1; + } + + base_kernel_size = BE(ce_hdr->uncompressed_size); + base_kernel = decompressed_kernel_buf; + hdr = (bootloader_header *)decompressed_kernel_buf; + + // free the original basefile buffer, we don't need the CE anymore + free(base_buf); + + // save the file + printf("writing decompressed kernel to '%s'...\n", output_path); + FILE *out_file = fopen(output_path, "wb+"); + if (out_file == NULL) { + printf("failed to open output file!\n"); + free(decompressed_kernel_buf); + return -1; + } + fwrite(decompressed_kernel_buf, 1, BE(ce_hdr->uncompressed_size), out_file); + fclose(out_file); + free(decompressed_kernel_buf); + + return 0; +} \ No newline at end of file diff --git a/source/command-nand_extract.c b/source/command-nand_extract.c new file mode 100644 index 0000000..36c9ff6 --- /dev/null +++ b/source/command-nand_extract.c @@ -0,0 +1,168 @@ +/* + command-nand_extract.c - Extracting bootloader stages from Xbox 360 NAND images. + Copyright 2024 Emma https://ipg.gay/ + + This file is part of xenon-bltool. + + xenon-bltool is free software: you can redistribute it and/or modify it under the terms of + the GNU General Public License as published by the Free Software Foundation, version 2 of + the License. + + xenon-bltool is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with xenon-bltool. + If not, see . +*/ + +/* + Note for 18th January 2024: This is unfinished code! It's not accessible in + bltool-cli. It'll only read one of a certain type of image, because the way + CB_B/CD is decrypted can vary in certain versions, and devkit / zeropair / + RGH3 put some more spanners in the works. It'll need a full rewrite with all + that in mind for it to truly "work". + + Sorry if this ever ends up remaining unfinished forever, just putting it + out there unfinished so it doesn't go to waste! -Emma +*/ + +#include +#include +#include +#include +#include "1bl-keys.h" +#include "xenon-bootloader.h" +#include "nand.h" +#include "utility.h" +#include "cb-handler.h" +#include "cd-handler.h" +#include "ce-handler.h" +#include "cf-handler.h" +#include "cg-handler.h" + +int do_nand_extract_command(int argc, char **argv) { + if (argc < 1) { + printf("not enough arguments!\n"); + return -1; + } + + char *nand_path = argv[1]; + nand_file *nand = open_nand_file_name(nand_path, false); + if (nand == NULL) { + printf("couldn't open the nand file!\n"); + return -1; + } + + uint8_t cpu_key[0x10] = {0}; + bool has_cpu_key = false; + if (argc < 2) { + printf("no cpu key provided! stages will not be decrypted\n"); + } else { + if (!parse_hex_str(argv[2], cpu_key, sizeof(cpu_key))) + printf("failed to parse CPU key!\n"); + else { + has_cpu_key = true; + printf("cpu key: "); + hexdump(cpu_key, sizeof(cpu_key)); + } + } + + // read from header + xenon_nand_header nand_header = {0}; + bootloader_header bl_hdr = {0}; // scratch buffer for next stages + read_nand_data(nand, 0x0, (uint8_t *)&nand_header, sizeof(xenon_nand_header)); + + printf("CB offset: 0x%x\n", BE(nand_header.header.entrypoint)); + printf("CF offset: 0x%x\n", BE(nand_header.cf_offset)); + printf("\n"); + + // get the size of CB and load it all + uint32_t cb_start = BE(nand_header.header.entrypoint); + read_nand_data(nand, cb_start, (uint8_t *)&bl_hdr, sizeof(bootloader_header)); + if (BE(bl_hdr.size) > 0x10000) { + printf("CB size is too big to be sane\n"); + return -1; + } + uint8_t *cb_buf = malloc(BE(bl_hdr.size)); + if (cb_buf == NULL) { + printf("failed to allocate buffer for CB\n"); + return -1; + } + read_nand_data(nand, cb_start, cb_buf, BE(bl_hdr.size)); + + bootloader_cb_header *cb_hdr = (bootloader_cb_header *)cb_buf; + if (!cb_is_decrypted(cb_buf)) + cb_decrypt(cb_buf, key_1bl); + cb_print_info(cb_buf); + + // the next stage starts right after the first - repeats until 5BL/CE + uint32_t next_stage_start = cb_start + (BE(bl_hdr.size) + 0xF & 0xFFFFFFF0); + + // detect CB_A and handle next stage as CB_B + if ((BE16(bl_hdr.flags) & 0x800) == 0x800) { + // read the header to get the size + read_nand_data(nand, next_stage_start, (uint8_t *)&bl_hdr, sizeof(bootloader_header)); + + uint8_t *cb_b_buf = malloc(BE(bl_hdr.size)); + if (cb_b_buf == NULL) { + printf("failed to allocate buffer for CB_B\n"); + return -1; + } + read_nand_data(nand, next_stage_start, cb_b_buf, BE(bl_hdr.size)); + + if (!cb_is_decrypted(cb_b_buf) && has_cpu_key) + cb_b_decrypt(cb_b_buf, cb_buf, cpu_key); + + cb_print_info(cb_b_buf); + + // validate the rotsumsha + uint8_t cb_b_sha[0x14]; + cb_calculate_rotsum(cb_b_buf, cb_b_sha); + if (memcmp(cb_b_sha, cb_hdr->cd_cbb_hash, 0x14) != 0) + printf("! CB_B does not match RotSumSha in CB_A !\n"); + + // the CB_B's buffer is what we'll be caring about in the future + cb_hdr = (bootloader_cb_header *)cb_b_buf; + next_stage_start += (BE(bl_hdr.size) + 0xF & 0xFFFFFFF0); + } + + // read the header to get the size + read_nand_data(nand, next_stage_start, (uint8_t *)&bl_hdr, sizeof(bootloader_header)); + + if ((BE16(bl_hdr.magic)) == 0x5343) { + printf("skipping SC...\n"); + next_stage_start += (BE(bl_hdr.size) + 0xF & 0xFFFFFFF0); + read_nand_data(nand, next_stage_start, (uint8_t *)&bl_hdr, sizeof(bootloader_header)); + } + + uint8_t *cd_buf = malloc(BE(bl_hdr.size)); + if (cd_buf == NULL) { + printf("failed to allocate buffer for CD\n"); + return -1; + } + memset(cd_buf, 0, BE(bl_hdr.size)); + read_nand_data(nand, next_stage_start, cd_buf, BE(bl_hdr.size)); + + if (!cd_is_decrypted(cd_buf) && has_cpu_key) + cd_decrypt(cd_buf, cb_hdr->key, cpu_key); + + cd_print_info(cd_buf); + + // validate the rotsumsha + uint8_t cd_sha[0x14]; + cd_calculate_rotsum(cd_buf, cd_sha); + if (memcmp(cd_sha, cb_hdr->cd_cbb_hash, 0x14) != 0) + printf("! CD does not match RotSumSha in CB !\n"); + + dump_to_file("cd.bin", cd_buf, BE(bl_hdr.size)); + + bootloader_cd_header *cd_hdr = (bootloader_cd_header *)cd_buf; + next_stage_start += (BE(bl_hdr.size) + 0xF & 0xFFFFFFF0); + + free(cb_buf); + free(cd_buf); + + close_nand_file(nand); + return 0; +} diff --git a/source/command-xboxupd.c b/source/command-xboxupd.c new file mode 100644 index 0000000..f2a3583 --- /dev/null +++ b/source/command-xboxupd.c @@ -0,0 +1,216 @@ +/* + command-xboxupd.c - Xbox 360 CE->CG delta patching + Copyright 2024 Emma https://ipg.gay/ + + This file is part of xenon-bltool. + + xenon-bltool is free software: you can redistribute it and/or modify it under the terms of + the GNU General Public License as published by the Free Software Foundation, version 2 of + the License. + + xenon-bltool is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with xenon-bltool. + If not, see . +*/ + +#include +#include +#include +#include +#include "1bl-keys.h" +#include "ce-handler.h" +#include "cf-handler.h" +#include "cg-handler.h" +#include "compression-handler.h" +#include "lzx-delta.h" +#include "utility.h" + +int do_xboxupd_command(int argc, char **argv) { + if (argc < 3) { + printf("not enough arguments!\n"); + return -1; + } + + char *xboxupd_path = argv[1]; + char *base_path = argv[2]; + char *output_path = argv[3]; + + // open, read and close our base file + FILE *base_file = fopen(base_path, "rb"); + if (base_file == NULL) { + printf("couldn't open the base file!\n"); + return -1; + } + size_t base_size = get_file_size(base_file); + void *base_buf = malloc(base_size); + if (base_buf == NULL) { + printf("failed to allocate buffer for basefile.\n"); + return -1; + } + fread(base_buf, 1, base_size, base_file); + fclose(base_file); + + // detect the type of the base kernel file + uint8_t *base_kernel = (uint8_t *)base_buf; + uint32_t base_kernel_size = base_size; + bootloader_header *hdr = (bootloader_header *)base_buf; + if ((BE16(hdr->magic) & 0xFFF) == 0xE4E) { + printf("base file is raw HV %i with size 0x%x\n", BE16(hdr->version), base_kernel_size); + } else if ((BE16(hdr->magic) & 0xFFF) == 0x345) { + bootloader_ce_header *ce_hdr = (bootloader_ce_header *)hdr; + ce_print_info(base_buf); + if (!ce_is_decrypted(base_buf)) { + printf("base CE/5BL file is encrypted.\n"); + free(base_buf); + return -1; + } + + // we have to decompress the base kernel + uint8_t *decompressed_kernel_buf = malloc(BE(ce_hdr->uncompressed_size)); + if (decompressed_kernel_buf == NULL) { + printf("failed to allocate buffer for decompressed kernel.\n"); + free(base_buf); + return -1; + } + + if (!ce_decompress(base_buf, decompressed_kernel_buf)) { + printf("decompression failed.\n"); + free(base_buf); + free (decompressed_kernel_buf); + return -1; + } + + base_kernel_size = BE(ce_hdr->uncompressed_size); + base_kernel = decompressed_kernel_buf; + hdr = (bootloader_header *)decompressed_kernel_buf; + + // free the original basefile buffer, we don't need the CE anymore + free(base_buf); + + printf("base file is decompressed HV %i\n", BE16(hdr->version)); + } else { + printf("unknown base file format. (%04x)\n", BE16(hdr->magic)); + free(base_buf); + return -1; + } + + // load the xboxupd.bin file + FILE *xboxupd_file = fopen(xboxupd_path, "rb"); + if (xboxupd_file == NULL) { + printf("couldn't open the xboxupd.bin file!\n"); + free(base_kernel); + return -1; + } + size_t xboxupd_size = get_file_size(xboxupd_file); + uint8_t *xboxupd_buf = malloc(xboxupd_size); + if (xboxupd_buf == NULL) { + printf("failed to allocate buffer for xboxupd.bin.\n"); + free(base_kernel); + return -1; + } + fread(xboxupd_buf, 1, xboxupd_size, xboxupd_file); + fclose(xboxupd_file); + + // TODO(Emma): allow just a decrypted CG (or CG with key) without CF + + // make sure we have both the CF and CG stages in the file + bootloader_cf_header *cf_hdr = (bootloader_cf_header *)xboxupd_buf; + if ((BE16(cf_hdr->header.magic) & 0xFFF) != 0x346) { + printf("CF header not found. invalid xboxupd.bin?\n"); + free(base_kernel); + free(xboxupd_buf); + return -1; + } + // some basic size sanity checking + if (xboxupd_size < ( BE(cf_hdr->header.size) + BE(cf_hdr->cg_size) )) { + printf("input xboxupd.bin file too small for CF+CG. invalid file?\n"); + free(base_kernel); + free(xboxupd_buf); + return -1; + } + // and try to decrypt the CF + if (!cf_is_decrypted((uint8_t *)cf_hdr)) { + cf_decrypt((uint8_t *)cf_hdr, key_1bl); + } + if (!cf_is_decrypted((uint8_t *)cf_hdr)) { + printf("failed to decrypt CF\n"); + free(base_kernel); + free(xboxupd_buf); + return -1; + } + cf_print_info((uint8_t *)cf_hdr); + + if (BE16(cf_hdr->base_ver) != BE16(hdr->version)) { + printf("mismatching base kernel version %i (CF expects %i)\n", BE16(hdr->version), BE16(cf_hdr->base_ver)); + free(base_kernel); + free(xboxupd_buf); + return -1; + } + + // load the CG as well + bootloader_cg_header *cg_hdr = (bootloader_cg_header *)(xboxupd_buf + BE(cf_hdr->header.size)); + if ((BE16(cg_hdr->header.magic) & 0xFFF) != 0x347) { + printf("CG header not found. invalid xboxupd.bin?\n"); + free(base_kernel); + free(xboxupd_buf); + return -1; + } + // and try to decrypt that, too + // maybe cg_is_decrypted should be replaced with a rotsum check against CF? + if (!cg_is_decrypted((uint8_t *)cg_hdr)) { + cg_decrypt((uint8_t *)cg_hdr, cf_hdr->cg_hmac); + } + if (!cg_is_decrypted((uint8_t *)cg_hdr)) { + printf("failed to decrypt CG\n"); + free(base_kernel); + free(xboxupd_buf); + return -1; + } + + uint8_t cg_rotsum[0x14]; + cg_calculate_rotsum((uint8_t *)cg_hdr, cg_rotsum); + if (memcmp(cg_rotsum, cf_hdr->cg_hash, 0x14) != 0) { + printf("CG checksum does not match CF header\n"); + free(base_kernel); + free(xboxupd_buf); + return -1; + } + cg_print_info((uint8_t *)cg_hdr); + + // patch the kernel + uint8_t *patched_kernel = malloc(BE(cg_hdr->new_size)); + if (patched_kernel == NULL) { + printf("failed to allocate memory for new kernel.\n"); + free(base_kernel); + free(xboxupd_buf); + return -1; + } + if (!cg_apply_patch((uint8_t *)cg_hdr, base_kernel, patched_kernel)) { + printf("failed to apply the kernel patch.\n"); + free(base_kernel); + free(xboxupd_buf); + free(patched_kernel); + return -1; + } + + // free these now since we won't need them later + free(base_kernel); + free(xboxupd_buf); + + // save the file + printf("writing patched kernel to '%s'...\n", output_path); + FILE *out_file = fopen(output_path, "wb+"); + if (out_file == NULL) { + printf("failed to open output file!\n"); + free(patched_kernel); + return -1; + } + fwrite(patched_kernel, 1, BE(cg_hdr->new_size), out_file); + fclose(out_file); + free(patched_kernel); + + return 0; +} \ No newline at end of file diff --git a/source/nand-ecc.c b/source/nand-ecc.c new file mode 100644 index 0000000..ef62508 --- /dev/null +++ b/source/nand-ecc.c @@ -0,0 +1,30 @@ +#include +#include "utility.h" + +// Reference: +// - https://github.com/Free60Project/libxenon/blob/master/libxenon/drivers/xenon_nand/xenon_sfcx.c +// - https://free60.org/System-Software/NAND_File_System/ + +void calculate_nand_ecc(uint8_t page[0x200], uint8_t spare[0x10], uint8_t ecc[0x4]) { + uint32_t v = 0, val = 0; + uint32_t *data = (uint32_t *)page; + + for (int i = 0; i < 0x1066; i++) { + if (i == 0x1000) + data = (uint32_t *)spare; + if (!(i & 31)) + v = ~LE(*data++); + + val ^= v & 1; + v>>=1; + if (val & 1) + val ^= 0x6954559; + val >>= 1; + } + val = ~val; + + ecc[0] = (val << 6) & 0xC0; + ecc[1] = (val >> 2) & 0xFF; + ecc[2] = (val >> 10) & 0xFF; + ecc[3] = (val >> 18) & 0xFF; +} diff --git a/source/nand.c b/source/nand.c new file mode 100644 index 0000000..8f79a77 --- /dev/null +++ b/source/nand.c @@ -0,0 +1,145 @@ +/* + nand.c - Helper functions for interfacing with NAND images. + Copyright 2024 Emma https://ipg.gay/ + + This file is part of xenon-bltool. + + xenon-bltool is free software: you can redistribute it and/or modify it under the terms of + the GNU General Public License as published by the Free Software Foundation, version 2 of + the License. + + xenon-bltool is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with xenon-bltool. + If not, see . +*/ + +#include +#include +#include +#include +#include "nand-ecc.h" +#include "nand.h" +#include "utility.h" + +#define MAX(x, y) (((x) > (y)) ? (x) : (y)) +#define MIN(x, y) (((x) < (y)) ? (x) : (y)) + +// TODO: if create is set don't do checks +nand_file *open_nand_file(FILE *fp, bool create) { + // basic sanity check + if (fp == NULL) + return NULL; + + // detect the filesize + fseek(fp, 0, SEEK_END); + int file_size = ftell(fp); + + // some sort of "sanity check". + // might get rid of this soon depending on + if (file_size < 0x1000000) { + printf("input nand file too small!\n"); + return NULL; + } + + // read the header to make sure it's a valid image, and try to figure some things out + uint8_t page_and_ecc[NAND_PAGE_FULL]; + uint8_t calc_ecc[0x4]; + fseek(fp, 0, SEEK_SET); + fread(page_and_ecc, 1, NAND_PAGE_FULL, fp); + + xenon_nand_header *hdr = (xenon_nand_header *)page_and_ecc; + if (BE16(hdr->header.magic) != 0xFF4F) { + printf("invalid file magic!\n"); + return NULL; + } + + // as far as i can tell there's no good way to detect + // this is entirely temporary and a "for the future" thing + // TODO! + int nand_type = NAND_TYPE_UNECC; + if (page_and_ecc[NAND_PAGE_SIZE] == 0xFF) + nand_type = NAND_TYPE_BIGBLOCK; + else if (page_and_ecc[NAND_PAGE_SIZE + 0x5] == 0xFF) + nand_type = NAND_TYPE_SMALLBLOCK; + + calculate_nand_ecc(page_and_ecc, &(page_and_ecc[NAND_PAGE_SIZE]), calc_ecc); + // calc_ecc should match ecc[0xC] + + nand_file *file_obj = malloc(sizeof(nand_file)); + file_obj->type = nand_type; + file_obj->fp = fp; + // both of these are TODOs + file_obj->console = CONSOLE_UNKNOWN; + file_obj->size = STORAGE_SIZE_16MB; + + return file_obj; +} + +nand_file *open_nand_file_name(char *name, bool create) { + FILE *fp = fopen(name, "rb"); // TODO: should be wb, and wb+ for create + if (fp == NULL) { + printf("failed to open file!\n"); + return NULL; + } + nand_file *opened = open_nand_file(fp, create); + if (opened != NULL) + opened->close_file = true; + return opened; +} + +void read_nand_data(nand_file *file, uint32_t offset, uint8_t *data, size_t len) { + if (file == NULL || file->fp == NULL) + return; + + // for these types, we can just read directly from the file at the offset given + if (file->type == NAND_TYPE_UNECC || + file->type == NAND_TYPE_CORONA) { + fseek(file->fp, offset, SEEK_SET); + fread(data, 1, len, file->fp); + return; + } + + // otherwise, we have to read each page individually and extract the data + // TODO: do we want to verify ECC data for the pages read and warn if they're bad? + // also bad block handling would be a requirement in the future + // the goal is to match what the NAND controller at 0xC8000000 is doing + + // seek to the page we're trying to read + int page_to_read = (offset / NAND_PAGE_SIZE); + uint32_t page_file_offset = page_to_read * NAND_PAGE_FULL; + fseek(file->fp, page_file_offset, SEEK_SET); + + // read whole pages, but only copy the bytes we need + int num_pages = (len / NAND_PAGE_SIZE) + 1; + uint32_t bytes_remaining = len; + uint32_t offset_in_page = (offset % NAND_PAGE_SIZE); + if (offset_in_page > 0) + num_pages++; + + for (int i = 0; i < num_pages; i++) { + // read the whole NAND page including ECC data + uint8_t page_read[NAND_PAGE_FULL]; + if (fread(page_read, NAND_PAGE_FULL, 1, file->fp) != 1) + break; + + // copy the remaining number of bytes to the output buffer + int copybytes = MIN(bytes_remaining, NAND_PAGE_SIZE - offset_in_page); + memcpy(data, page_read + offset_in_page, copybytes); + bytes_remaining -= copybytes; + data += copybytes; + + // offset_in_page is solely for the first page read when unaligned + offset_in_page = 0; + } +} + +void close_nand_file(nand_file *file) { + if (file == NULL) + return; + if (file->close_file) + fclose(file->fp); + free(file); +} diff --git a/source/utility.c b/source/utility.c new file mode 100644 index 0000000..bd5db20 --- /dev/null +++ b/source/utility.c @@ -0,0 +1,72 @@ +/* + utility.c - Utility and helper functions for I/O and such. + Copyright 2024 Emma https://ipg.gay/ + + This file is part of xenon-bltool. + + xenon-bltool is free software: you can redistribute it and/or modify it under the terms of + the GNU General Public License as published by the Free Software Foundation, version 2 of + the License. + + xenon-bltool is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with xenon-bltool. + If not, see . +*/ + +#include +#include +#include +#include +#include + +void hexdump(uint8_t *data, uint32_t size) { + for (uint32_t i = 0; i < size; i++) { + printf("%02x ", data[i]); + } + printf("\n"); +} + +void dump_to_file(char *filename, uint8_t *buf, size_t size) { + FILE *fp = fopen(filename, "wb+"); + if (fp == NULL) + return; + fwrite(buf, 1, size, fp); + fclose(fp); +} + +bool parse_hex_str(char *str, uint8_t *out_buf, size_t buf_size) { + // length check - make sure our input is the same size as our buffer is + if (strlen(str) != (buf_size * 2)) + return false; + for (int i = 0; i < (buf_size * 2); i += 2) { + uint8_t nibble_1 = toupper(str[i]); + uint8_t nibble_2 = toupper(str[i+1]); + // this feels wrong, do it for the first nibble + if (nibble_1 >= '0' && nibble_1 <= '9') + nibble_1 = nibble_1 - '0'; + else if (nibble_1 >= 'A' && nibble_1 <= 'F') + nibble_1 = nibble_1 - 'A' + 0xA; + else return false; + // and then the next... + if (nibble_2 >= '0' && nibble_2 <= '9') + nibble_2 = nibble_2 - '0'; + else if (nibble_2 >= 'A' && nibble_2 <= 'F') + nibble_2 = nibble_2 - 'A' + 0xA; + else return false; + // then OR them together + out_buf[i/2] = (nibble_1 << 4) | nibble_2; + } + return true; +} + +// shitty hack to get a file size +// idk how to do it properly +size_t get_file_size(FILE *fp) { + fseek(fp, 0, SEEK_END); + size_t fs = ftell(fp); + fseek(fp, 0, SEEK_SET); + return fs; +}