mirror of
https://github.com/Rockbox/rockbox.git
synced 2026-10-10 08:03:04 -04:00
Registers the Nano 3G (platform ipodnano3g, model number 117, "nn3g"
header) so mks5lboot can build DFU installers and uninstallers for it,
adds its original bootloader to the dualboot code, and lists the
platform in the usage text and the README.
The target has to be named ipodnano3g rather than nano3g: the dualboot
Makefile derives the source directory and the piezo driver's file name
from it.
The per-target OF hash table is the substantive change. identify_fw()
decrypts the IM3 header's data_sign with the hardware UKEY and looks it
up in of_sha[], and anything not listed is taken to be a Rockbox
bootloader. The table held only iPod Classic firmware, so on a Nano 3G
the installer took Apple's own bootloader for a Rockbox one and gave up,
and the uninstaller would have refused to restore it. Both bail out
before writing, so nothing is damaged, but neither can work. The table
is now per target, and lists the bootloader of every Nano 3G firmware
release, 1.0.1 to 1.1.3.
The decrypted data_sign is the first 16 bytes of the SHA-1 of the
plaintext bootloader, so it is the same on every unit. Each release's
updater image (aupd, GID-encrypted, in the ipsw) carries that bootloader,
0x1f800 bytes, at the start of a NOR image. The aupd of each release was
decrypted on a Nano 3G with the hardware GID key and hashed on the host.
For 1.1.3 the result matches the data_sign read from a 4GB unit (model
MA978) whose NOR had never been written to, and the bootloader in its
aupd is byte-identical to the decrypted copy the installer relocated on
that unit. 1.1.2 and 1.1.3 ship the same bootloader.
dualboot.c is generated, and only the Nano 3G arrays are added. The iPod
Classic arrays are left byte-for-byte as they were: rebuilding them with
a different compiler changes their bytes, which would ship an untested
installer to Classic users for no reason.
The dualboot Makefile did not build from the current tree for any
target, which the committed blobs, older than both problems, had hidden.
config.h needs autoconf.h, which tools/configure generates per target,
so each target now takes a CONFIGDIR_<target> pointing at a configured
bootloader build for it, e.g.
make CONFIGDIR_ipod6g=../../../build-ipod6g-bl \
CONFIGDIR_ipodnano3g=../../../build-nano3g-bl
And the linker script is preprocessed with __ASSEMBLER__ defined, under
which config.h now emits the ldmpc/ldrpc assembler macros that ld
rejects; the sed that cleans it now also drops .macro, .endm and .syntax
lines and the macro bodies. Before these fixes the iPod Classic build
failed first on the missing autoconf.h and then with a linker syntax
error; with them it builds both blobs.
Tested on that unit, with s5l8702pwnage delivering the images through
Apple's DFU, and again with mks5lboot's own --bl-inst and --bl-uninst:
the installer put Rockbox in NOR, the unit then booted
Rockbox, and holding MENU booted Apple's firmware from the relocated
original bootloader; the uninstaller restored it and the unit booted
Apple's firmware again. Those images carried a table holding only the
1.1.3 entry. The Nano 3G blobs in dualboot.c were then rebuilt with the
Makefile for the full table - with the one-entry table the rebuild was
byte-identical to what the tested images carried - and mks5lboot
--bl-inst with them installed Rockbox on the same unit, which booted
Rockbox and, holding MENU, Apple's firmware. The uninstaller built with
the full table has not been run, and no firmware other than 1.1.3 has
been installed to or uninstalled from on hardware. For the iPod Classic,
the uninstaller DFU this builds is byte-identical to the one built
before this change.
AI provenance: developed with Claude Opus 5 (Anthropic), used through
Claude Code. The model wrote most of the code and this message under
Andrew Rice's direction. Any hardware testing described above was
carried out by Andrew Rice, who is responsible for this change.
Change-Id: I4b2fa692ac4110192ccbde0f1790b0ae2d1c73f5
321 lines
11 KiB
C
321 lines
11 KiB
C
/***************************************************************************
|
|
* __________ __ ___.
|
|
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
|
|
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
|
|
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
|
|
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
|
|
* \/ \/ \/ \/ \/
|
|
*
|
|
* Copyright (C) 2015 by Cástor Muñoz
|
|
*
|
|
* This program 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; either version 2
|
|
* of the License, or (at your option) any later version.
|
|
*
|
|
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
|
|
* KIND, either express or implied.
|
|
*
|
|
****************************************************************************/
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <stdint.h>
|
|
#include <stdbool.h>
|
|
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <fcntl.h>
|
|
#include <unistd.h>
|
|
#include <string.h>
|
|
|
|
#include "mks5lboot.h"
|
|
|
|
/* Header for ARM code binaries */
|
|
#include "dualboot.h"
|
|
|
|
/* Win32 compatibility */
|
|
#ifndef O_BINARY
|
|
#define O_BINARY 0
|
|
#endif
|
|
|
|
/*
|
|
* This code is based on emCORE, adds a couple of improvements thanks to
|
|
* some extra features in Apple's ROM:
|
|
* - Generic Im3Info header valid for all payloads. It is done by moving
|
|
* the certificate to a fixed position (before data), and using a 'magic'
|
|
* value (0x300) for signature offset.
|
|
* - Some integer overflows in ROM make it possible to use the free space
|
|
* available in Im3Hdr, resulting a maximum payload size of:
|
|
* 128 KiB - 0x50 bytes (IM3INFO_SZ) - 700 bytes (CERT_SIZE).
|
|
*/
|
|
|
|
const struct ipod_models ipod_identity[] =
|
|
{
|
|
[MODEL_IPOD6G] = {
|
|
"Classic 6G", "ipod6g", "ip6g", 71,
|
|
dualboot_install_ipod6g, sizeof(dualboot_install_ipod6g),
|
|
dualboot_uninstall_ipod6g, sizeof(dualboot_uninstall_ipod6g) },
|
|
[MODEL_IPODNANO3G] = {
|
|
"Nano 3G", "ipodnano3g", "nn3g", 117,
|
|
dualboot_install_ipodnano3g, sizeof(dualboot_install_ipodnano3g),
|
|
dualboot_uninstall_ipodnano3g, sizeof(dualboot_uninstall_ipodnano3g) },
|
|
};
|
|
|
|
struct Im3Info s5l8702hdr =
|
|
{
|
|
.ident = IM3_IDENT,
|
|
.version = IM3_VERSION,
|
|
.enc_type = 3,
|
|
.u.enc34 = {
|
|
.sign_off = "\x00\x03\x00\x00",
|
|
.cert_off = "\x50\xF8\xFF\xFF", /* -0x800 + CERT_OFFSET */
|
|
.cert_sz = "\xBA\x02\x00\x00", /* CERT_SIZE */
|
|
},
|
|
.info_sign = "\xC2\x54\x51\x31\xDC\xC0\x84\xA4"
|
|
"\x7F\xD1\x45\x08\xE8\xFF\xE8\x1D",
|
|
};
|
|
|
|
unsigned char s5l8702pwnage[/*CERT_SIZE*/] =
|
|
{
|
|
"\x30\x82\x00\x7A\x30\x66\x02\x00\x30\x0D\x06\x09\x2A\x86\x48\x86"
|
|
"\xF7\x0D\x01\x01\x05\x05\x00\x30\x0B\x31\x09\x30\x07\x06\x03\x55"
|
|
"\x04\x03\x13\x00\x30\x1E\x17\x0D\x00\x00\x00\x00\x00\x00\x00\x00"
|
|
"\x00\x00\x00\x00\x00\x17\x0D\x00\x00\x00\x00\x00\x00\x00\x00\x00"
|
|
"\x00\x00\x00\x00\x30\x0B\x31\x09\x30\x07\x06\x03\x55\x04\x03\x13"
|
|
"\x00\x30\x19\x30\x0D\x06\x09\x2A\x86\x48\x86\xF7\x0D\x01\x01\x01"
|
|
"\x05\x00\x03\x08\x00\x30\x05\x02\x01\x00\x02\x00\x30\x0D\x06\x09"
|
|
"\x2A\x86\x48\x86\xF7\x0D\x01\x01\x05\x05\x00\x03\x01\x00\x30\x82"
|
|
"\x00\x7A\x30\x66\x02\x00\x30\x0D\x06\x09\x2A\x86\x48\x86\xF7\x0D"
|
|
"\x01\x01\x05\x05\x00\x30\x0B\x31\x09\x30\x07\x06\x03\x55\x04\x03"
|
|
"\x13\x00\x30\x1E\x17\x0D\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
|
|
"\x00\x00\x00\x17\x0D\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
|
|
"\x00\x00\x30\x0B\x31\x09\x30\x07\x06\x03\x55\x04\x03\x13\x00\x30"
|
|
"\x19\x30\x0D\x06\x09\x2A\x86\x48\x86\xF7\x0D\x01\x01\x01\x05\x00"
|
|
"\x03\x08\x00\x30\x05\x02\x01\x00\x02\x00\x30\x0D\x06\x09\x2A\x86"
|
|
"\x48\x86\xF7\x0D\x01\x01\x05\x05\x00\x03\x01\x00\x30\x82\x01\xBA"
|
|
"\x30\x50\x02\x00\x30\x0D\x06\x09\x2A\x86\x48\x86\xF7\x0D\x01\x01"
|
|
"\x05\x05\x00\x30\x00\x30\x1E\x17\x0D\x00\x00\x00\x00\x00\x00\x00"
|
|
"\x00\x00\x00\x00\x00\x00\x17\x0D\x00\x00\x00\x00\x00\x00\x00\x00"
|
|
"\x00\x00\x00\x00\x00\x30\x00\x30\x19\x30\x0D\x06\x09\x2A\x86\x48"
|
|
"\x86\xF7\x0D\x01\x01\x01\x05\x00\x03\x08\x00\x30\x05\x02\x01\x00"
|
|
"\x02\x00\x30\x0D\x06\x09\x2A\x86\x48\x86\xF7\x0D\x01\x01\x05\x05"
|
|
"\x00\x03\x82\x01\x55"
|
|
};
|
|
|
|
static uint32_t get_uint32le(unsigned char* p)
|
|
{
|
|
return p[0] | (p[1] << 8) | (p[2] << 16) | (p[3] << 24);
|
|
}
|
|
|
|
static uint32_t get_uint32be(unsigned char* p)
|
|
{
|
|
return (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3];
|
|
}
|
|
|
|
static void put_uint32le(unsigned char* p, uint32_t x)
|
|
{
|
|
p[0] = x & 0xff;
|
|
p[1] = (x >> 8) & 0xff;
|
|
p[2] = (x >> 16) & 0xff;
|
|
p[3] = (x >> 24) & 0xff;
|
|
}
|
|
|
|
#define _ERR(format, ...) \
|
|
do { \
|
|
snprintf(errstr, errstrsize, "[ERR] "format, __VA_ARGS__); \
|
|
goto error; \
|
|
} while(0)
|
|
|
|
static unsigned char *load_file(char *filename, int *bufsize,
|
|
const struct ipod_models** model,
|
|
char* errstr, int errstrsize)
|
|
{
|
|
int fd, i;
|
|
struct stat s;
|
|
unsigned char header[8];
|
|
unsigned char *buf = NULL;
|
|
bool is_rbbl = (model);
|
|
|
|
fd = open(filename, O_RDONLY|O_BINARY);
|
|
if (fd < 0)
|
|
_ERR("Could not open %s for reading", filename);
|
|
|
|
if (fstat(fd, &s) < 0)
|
|
_ERR("Checking filesize of input file %s", filename);
|
|
*bufsize = s.st_size;
|
|
|
|
if (is_rbbl) {
|
|
/* Read Rockbox header */
|
|
if (read(fd, header, sizeof(header)) != sizeof(header))
|
|
_ERR("Could not read file %s", filename);
|
|
*bufsize -= sizeof(header);
|
|
|
|
for (i = 0; i < NUM_MODELS; i++)
|
|
if (memcmp(ipod_identity[i].rb_model_name, header + 4, 4) == 0)
|
|
break;
|
|
|
|
if (i == NUM_MODELS)
|
|
_ERR("Model name \"%4.4s\" unknown. "
|
|
"Is this really a rockbox bootloader?", header + 4);
|
|
|
|
*model = &ipod_identity[i];
|
|
}
|
|
|
|
buf = malloc(*bufsize);
|
|
if (buf == NULL)
|
|
_ERR("Could not allocate memory for %s", filename);
|
|
|
|
if (read(fd, buf, *bufsize) != *bufsize)
|
|
_ERR("Could not read file %s", filename);
|
|
|
|
if (is_rbbl) {
|
|
/* Check checksum */
|
|
uint32_t sum = (*model)->rb_model_num;
|
|
for (i = 0; i < *bufsize; i++) {
|
|
/* add 8 unsigned bits but keep a 32 bit sum */
|
|
sum += buf[i];
|
|
}
|
|
if (sum != get_uint32be(header))
|
|
_ERR("Checksum mismatch in %s", filename);
|
|
}
|
|
|
|
close(fd);
|
|
return buf;
|
|
|
|
error:
|
|
if (fd >= 0)
|
|
close(fd);
|
|
if (buf)
|
|
free(buf);
|
|
return NULL;
|
|
}
|
|
|
|
unsigned char *mkdfu(int dfu_type, char *dfu_arg, int* dfu_size,
|
|
char* errstr, int errstrsize)
|
|
{
|
|
const struct ipod_models *model = NULL;
|
|
unsigned char *dfu_buf = NULL;
|
|
unsigned char *f_buf = NULL;
|
|
int f_size;
|
|
uint32_t padded_bl_size = 0;
|
|
uint32_t cert_off, cert_sz;
|
|
off_t cur_off;
|
|
char *dfu_desc;
|
|
int i;
|
|
|
|
switch (dfu_type)
|
|
{
|
|
case DFU_INST:
|
|
case DFU_INST_SINGLE:
|
|
f_buf = load_file(dfu_arg, &f_size, &model, errstr, errstrsize);
|
|
if (f_buf == NULL)
|
|
goto error;
|
|
/* IM3 data size should be padded to 16 */
|
|
padded_bl_size = ((f_size + 0xf) & ~0xf);
|
|
*dfu_size = BIN_OFFSET + (model->dualboot_install_size +
|
|
(IM3HDR_SZ - (int)IM3INFO_SZ) + (int)padded_bl_size);
|
|
dfu_desc = (dfu_type == DFU_INST_SINGLE) ? "BL installer (single)"
|
|
: "BL installer";
|
|
break;
|
|
|
|
case DFU_UNINST:
|
|
for (i = 0; i < NUM_MODELS; i++) {
|
|
if (!strcmp(ipod_identity[i].platform_name, dfu_arg)) {
|
|
model = &ipod_identity[i];
|
|
break;
|
|
}
|
|
}
|
|
if (!model)
|
|
_ERR("Platform name \"%s\" unknown", dfu_arg);
|
|
|
|
*dfu_size = BIN_OFFSET + model->dualboot_uninstall_size;
|
|
dfu_desc = "BL uninstaller";
|
|
break;
|
|
|
|
case DFU_RAW:
|
|
default:
|
|
f_buf = load_file(dfu_arg, &f_size, NULL, errstr, errstrsize);
|
|
if (f_buf == NULL)
|
|
goto error;
|
|
/* IM3 data size should be padded to 16 */
|
|
*dfu_size = BIN_OFFSET + ((f_size + 0xf) & ~0xf);
|
|
dfu_desc = "raw binary";
|
|
break;
|
|
}
|
|
|
|
printf("[INFO] Building DFU:\n");
|
|
printf("[INFO] type: %s\n", dfu_desc);
|
|
if ((dfu_type == DFU_INST) || (dfu_type == DFU_INST_SINGLE))
|
|
printf("[INFO] BL size: %d\n", f_size);
|
|
if (dfu_type == DFU_RAW)
|
|
printf("[INFO] BIN size: %d\n", f_size);
|
|
printf("[INFO] DFU size: %d\n", *dfu_size);
|
|
if (model) {
|
|
printf("[INFO] model name: %s\n", model->model_name);
|
|
printf("[INFO] platform: %s\n", model->platform_name);
|
|
printf("[INFO] RB name: %s\n", model->rb_model_name);
|
|
printf("[INFO] RB num: %d\n", model->rb_model_num);
|
|
}
|
|
|
|
if (*dfu_size > DFU_MAXSIZE)
|
|
_ERR("DFU image (%d bytes) too big", *dfu_size);
|
|
|
|
dfu_buf = calloc(*dfu_size, 1);
|
|
if (!dfu_buf)
|
|
_ERR("Could not allocate %d bytes for DFU image", *dfu_size);
|
|
|
|
cert_off = get_uint32le(s5l8702hdr.u.enc34.cert_off);
|
|
cert_sz = get_uint32le(s5l8702hdr.u.enc34.cert_sz);
|
|
|
|
memcpy(dfu_buf, &s5l8702hdr, sizeof(s5l8702hdr));
|
|
|
|
cur_off = IM3HDR_SZ + cert_off;
|
|
|
|
memcpy(dfu_buf + cur_off, s5l8702pwnage, sizeof(s5l8702pwnage));
|
|
|
|
/* set entry point */
|
|
cur_off += cert_sz - 4;
|
|
put_uint32le(dfu_buf + cur_off, DFU_LOADADDR + BIN_OFFSET);
|
|
|
|
cur_off = BIN_OFFSET;
|
|
|
|
switch (dfu_type)
|
|
{
|
|
case DFU_INST:
|
|
case DFU_INST_SINGLE:
|
|
/* copy the dualboot installer binary */
|
|
memcpy(dfu_buf + cur_off, model->dualboot_install,
|
|
model->dualboot_install_size);
|
|
/* point to the start of the included IM3 header info */
|
|
cur_off += model->dualboot_install_size - IM3INFO_SZ;
|
|
/* set bootloader data size */
|
|
struct Im3Info *bl_info = (struct Im3Info*)(dfu_buf + cur_off);
|
|
put_uint32le(bl_info->data_sz, padded_bl_size);
|
|
/* use info_sign to pass params to the dualboot installer */
|
|
if (dfu_type == DFU_INST_SINGLE)
|
|
bl_info->info_sign[0] = 0x1;
|
|
/* add bootloader binary */
|
|
cur_off += IM3HDR_SZ;
|
|
memcpy(dfu_buf + cur_off, f_buf, f_size);
|
|
break;
|
|
|
|
case DFU_UNINST:
|
|
/* copy the dualboot uninstaller binary */
|
|
memcpy(dfu_buf + cur_off, model->dualboot_uninstall,
|
|
model->dualboot_uninstall_size);
|
|
break;
|
|
|
|
case DFU_RAW:
|
|
default:
|
|
/* add raw binary */
|
|
memcpy(dfu_buf + cur_off, f_buf, f_size);
|
|
break;
|
|
}
|
|
|
|
return dfu_buf;
|
|
|
|
error:
|
|
if (dfu_buf)
|
|
free(dfu_buf);
|
|
return NULL;
|
|
}
|