atjboottool: gzipped fw files, option for big-endian fw, clarify ECIES in fwu

Added fw modifications required to unpack real world player dumps.


Documented more fwu header fields, magic numbers and finite field arithmetics (extended Euclidean for inverse, long division for reducing modulo field_poly).

v3 encryption used is standard RC4 with the key additionally ciphered by the Elliptic Curve Integrated Encryption Scheme.

Either sect233k1 (NIST K-233) or sect163r2 (NIST B-163) curves can be used, with the former overwhelmingly prevailing, being hardwired in SDK's maker.exe. Using a private/public key scheme is superfluous because both are stored in the firmware, with the added level of complexity likely serving the purpose of obfuscation. The private key is generated at random with each invokation.

None of KDF or MAC from ECIES are used, RC4 key is directly xored with the shared secret. The random number r used to calculate rG isn't stored, but that's unimportant since only krG == rkG is actually used in the encryption.

Change-Id: Ieacf8cc744bc90c7c5582dd724b2c10a41bfc191
This commit is contained in:
Nikita Burnashev 2023-04-16 12:46:55 +03:00 committed by Solomon Peachy
parent 72c0e49b41
commit e232f69214
7 changed files with 499 additions and 317 deletions

View file

@ -2,7 +2,7 @@ DEFINES=
CC=gcc
LD=gcc
CFLAGS=-g -std=c99 -W -Wall $(DEFINES)
LDFLAGS=
LDFLAGS=-lz
BINS=atjboottool
all: $(BINS)

View file

@ -20,7 +20,7 @@
****************************************************************************/
#include <stdint.h>
uint8_t g_check_block_A_table[1024] =
uint8_t g_decode_A_table[1024] =
{
0x16, 0x2b, 0x01, 0xe4, 0x0e, 0x3d, 0xc1, 0xdf, 0x0f, 0x35, 0x8f, 0xf5, 0xe2,
0x48, 0xa0, 0x2e, 0x1c, 0x6a, 0x57, 0xea, 0x6d, 0x9a, 0xe2, 0x03, 0xec, 0xe8,
@ -109,45 +109,45 @@ uint8_t g_decode_B_table[20] =
0xf8, 0xb4, 0x36, 0x41, 0xc5, 0x51, 0xaf
};
uint32_t g_crypto_table[8] =
uint32_t g_sect233k1_G_x[8] =
{
0xefad6126, 0x0a4c9d6e, 0x19c26bf5, 0x149563a4, 0x29f22ff4, 0x7e731af1,
0x32ba853a, 0x00000172
};
uint32_t g_crypto_table2[8] =
uint32_t g_sect233k1_G_y[8] =
{
0x56fae6a3, 0x56e0c110, 0xf18aeb9b, 0x27a8cd9b, 0x555a67c4, 0x19b7f70f,
0x537dece8, 0x000001db
};
uint32_t g_crypto_key6[8] =
uint32_t g_sect233k1_b[8] =
{
0x00000001, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000
};
uint32_t g_crypto_key3[6] =
uint32_t g_sect163r2_G_x[6] =
{
0xe8343e36, 0xd4994637, 0xa0991168, 0x86a2d57e, 0xf0eba162, 0x00000003
};
uint32_t g_crypto_key4[6] =
uint32_t g_sect163r2_G_y[6] =
{
0x797324f1, 0xb11c5c0c, 0xa2cdd545, 0x71a0094f, 0xd51fbc6c, 0x00000000
};
uint32_t g_atj_ec163_a[6] =
uint32_t g_sect163r2_a[6] =
{
0x00000001, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000
};
uint32_t g_crypto_key5[6] =
uint32_t g_sect163r2_b[6] =
{
0x4a3205fd, 0x512f7874, 0x1481eb10, 0xb8c953ca, 0x0a601907, 0x00000002
};
uint32_t g_atj_ec233_a[8] =
uint32_t g_sect233k1_a[8] =
{
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
};

View file

@ -21,15 +21,15 @@
#ifndef __ATJ_TABLES__
#define __ATJ_TABLES__
uint8_t g_check_block_A_table[1024];
uint8_t g_decode_B_table[20];
uint32_t g_crypto_table[8];
uint32_t g_crypto_table2[8];
uint32_t g_crypto_key6[8];
uint32_t g_crypto_key3[6];
uint32_t g_crypto_key4[6];
uint32_t g_crypto_key5[6];
uint32_t g_atj_ec233_a[8];
uint32_t g_atj_ec163_a[6];
extern uint8_t g_decode_A_table[1024];
extern uint8_t g_decode_B_table[20];
extern uint32_t g_sect233k1_G_x[8];
extern uint32_t g_sect233k1_G_y[8];
extern uint32_t g_sect233k1_b[8];
extern uint32_t g_sect163r2_G_x[6];
extern uint32_t g_sect163r2_G_y[6];
extern uint32_t g_sect163r2_a[6];
extern uint32_t g_sect163r2_b[6];
extern uint32_t g_sect233k1_a[8];
#endif // __ATJ_TABLES__

View file

@ -27,6 +27,7 @@
#include <stdarg.h>
#include <ctype.h>
#include <sys/stat.h>
#include <zlib.h>
#include "misc.h"
#include "fwu.h"
#include "afi.h"
@ -100,7 +101,26 @@ static int unpack_afi_fw_cb(const char *filename, uint8_t *buf, size_t size)
FILE *f = fopen(name, "wb");
if(f)
{
fwrite(buf, size, 1, f);
if (0 != memcmp(buf, "\x1f\x8b\x8\0\0\0\0\0\0\xb", 10))
fwrite(buf, size, 1, f);
else
{
uint8_t buf_out[8192];
z_stream zs;
int err = Z_OK;
cprintf(GREEN, "inflating... ");
memset(&zs, 0, sizeof(zs));
zs.next_in = buf + 10;
zs.avail_in = size - 10;
inflateInit2(&zs, -MAX_WBITS); /* raw */
while (err == Z_OK)
{
zs.next_out = buf_out;
zs.avail_out = sizeof(buf_out);
err = inflate(&zs, Z_NO_FLUSH);
fwrite(buf_out, 1, sizeof(buf_out) - zs.avail_out, f);
}
}
fclose(f);
cprintf(RED, "Ok\n");
return 0;
@ -119,10 +139,10 @@ static int do_afi(uint8_t *buf, size_t size)
return afi_unpack(buf, size, &unpack_afi_fw_cb);
}
static int do_fw(uint8_t *buf, size_t size)
static int do_fw(uint8_t *buf, size_t size, bool big_endian)
{
build_out_prefix(".unpack", "", true);
return fw_unpack(buf, size, &unpack_afi_fw_cb);
return fw_unpack(buf, size, &unpack_afi_fw_cb, big_endian);
}
static void usage(void)
{
@ -135,6 +155,7 @@ static void usage(void)
printf(" --fwu Unpack a FWU firmware file\n");
printf(" --afi Unpack a AFI archive file\n");
printf(" --fw Unpack a FW archive file\n");
printf(" --fw251 Big-endian FW archive used on Flip80251\n");
printf(" --atj2127 Force ATJ2127 decryption mode\n");
printf("The default is to try to guess the format.\n");
printf("If several formats are specified, all are tried.\n");
@ -147,6 +168,7 @@ int main(int argc, char **argv)
bool try_fwu = false;
bool try_afi = false;
bool try_fw = false;
bool big_endian = false;
enum fwu_mode_t fwu_mode = FWU_AUTO;
while(1)
@ -159,11 +181,12 @@ int main(int argc, char **argv)
{"fwu", no_argument, 0, 'u'},
{"afi", no_argument, 0, 'a'},
{"fw", no_argument, 0, 'w'},
{"fw251", no_argument, 0, 'b'},
{"atj2127", no_argument, 0, '2'},
{0, 0, 0, 0}
};
int c = getopt_long(argc, argv, "hdco:a2", long_options, NULL);
int c = getopt_long(argc, argv, "hdco:a2b", long_options, NULL);
if(c == -1)
break;
switch(c)
@ -192,6 +215,10 @@ int main(int argc, char **argv)
case 'w':
try_fw = true;
break;
case 'b':
try_fw = true;
big_endian = true;
break;
case '2':
fwu_mode = FWU_ATJ2127;
break;
@ -238,7 +265,7 @@ int main(int argc, char **argv)
else if(try_afi || afi_check(buf, size))
ret = do_afi(buf, size);
else if(try_fw || fw_check(buf, size))
ret = do_fw(buf, size);
ret = do_fw(buf, size, big_endian);
else
{
cprintf(GREY, "No valid format found\n");

View file

@ -38,7 +38,7 @@ struct fw_entry_t
uint16_t version;
uint32_t block_offset; // offset shift by 9
uint32_t size;
uint32_t unk;
uint32_t bytes;
uint32_t checksum;
} __attribute__((packed));
@ -78,7 +78,8 @@ struct fw_hdr_f0_t
uint8_t sig[FW_SIG_SIZE];
uint8_t res[12];
uint32_t checksum;
uint8_t res2[492];
uint8_t res2[490];
uint16_t header_checksum;
struct fw_entry_t entry[FW_ENTRIES];
} __attribute__((packed));
@ -97,14 +98,56 @@ static void build_filename_fw(char buf[16], struct fw_entry_t *ent)
{
int pos = 0;
for(int i = 0; i < 8 && ent->name[i] != ' '; i++)
buf[pos++] = ent->name[i];
buf[pos++] = tolower(ent->name[i]);
buf[pos++] = '.';
for(int i = 0; i < 3 && ent->ext[i] != ' '; i++)
buf[pos++] = ent->ext[i];
buf[pos++] = tolower(ent->ext[i]);
buf[pos] = 0;
}
int fw_unpack(uint8_t *buf, size_t size, fw_extract_callback_t unpack_cb)
static inline uint32_t u32_endian_swap(uint32_t u32)
{
return ((u32 & 0xff000000u) >> 24) |
((u32 & 0x00ff0000u) >> 8) |
((u32 & 0x0000ff00u) << 8) |
((u32 & 0x000000ffu) << 24);
}
static uint32_t big_endian_checksum(void *ptr, size_t size)
{
uint32_t crc = 0;
uint32_t *cp = ptr;
for(; size >= 4; size -= 4)
crc += u32_endian_swap(*cp++);
/* FIXME all observed sizes divisible by 4, unclear how to add remainder */
return crc;
}
static inline uint16_t u16_endian_swap(uint16_t u16)
{
return ((u16 & 0xff00u) >> 8) |
((u16 & 0x00ffu) << 8);
}
static uint16_t lfi_header_checksum(void *ptr, size_t size, bool big_endian)
{
uint16_t crc = 0;
uint16_t *cp = ptr;
if (big_endian)
{
for(; size >= 2; size -= 2)
crc += u16_endian_swap(*cp++);
return u16_endian_swap(crc); /* to make comparable with the stored one */
}
else
{
for(; size >= 2; size -= 2)
crc += *cp++;
return crc;
}
}
int fw_unpack(uint8_t *buf, size_t size, fw_extract_callback_t unpack_cb, bool big_endian)
{
struct fw_hdr_t *hdr = (void *)buf;
@ -165,8 +208,32 @@ int fw_unpack(uint8_t *buf, size_t size, fw_extract_callback_t unpack_cb)
}
else
{
/* struct fw_hdr_f0_t *hdr_f0 = (void *)hdr; */
cprintf(GREEN, " Header not dumped because format is unclear.\n");
struct fw_hdr_f0_t *hdr_f0 = (void *)hdr;
uint32_t chk;
if (big_endian)
chk = u32_endian_swap(big_endian_checksum(buf + 0x200, 0x1e00));
else
chk = afi_checksum(buf + 0x200, 0x1e00);
cprintf_field(" Directory checksum: ", "0x%x ", hdr_f0->checksum);
if(chk != hdr_f0->checksum)
{
cprintf(RED, "Mismatch, 0x%x expected\n", chk);
return 1;
}
else
cprintf(RED, "Ok\n");
uint16_t header_chk = lfi_header_checksum(buf, 510, big_endian);
cprintf_field(" Header checksum: ", "0x%x ", hdr_f0->header_checksum);
if(header_chk != hdr_f0->header_checksum)
{
cprintf(RED, "Mismatch, 0x%x expected\n", header_chk);
return 1;
}
else
cprintf(RED, "Ok\n");
cprintf(GREEN, " Rest of header not dumped because format is unclear.\n");
}
cprintf(BLUE, "Entries\n");
@ -175,15 +242,29 @@ int fw_unpack(uint8_t *buf, size_t size, fw_extract_callback_t unpack_cb)
if(hdr->entry[i].name[0] == 0)
continue;
struct fw_entry_t *entry = &hdr->entry[i];
if (big_endian)
{
/* must be in-place for correct load checksum later */
entry->block_offset = u32_endian_swap(entry->block_offset);
entry->size = u32_endian_swap(entry->size);
entry->checksum = u32_endian_swap(entry->checksum);
}
char filename[16];
build_filename_fw(filename, entry);
cprintf(RED, " %s\n", filename);
cprintf_field(" Attr: ", "%02x\n", entry->attr);
cprintf_field(" Offset: ", "0x%x\n", entry->block_offset << 9);
cprintf_field(" Size: ", "0x%x\n", entry->size);
cprintf_field(" Unknown: ", "%x\n", entry->unk);
cprintf_field(" Bytes: ", "0x%x\n", entry->bytes);
cprintf_field(" Checksum: ", "0x%x ", entry->checksum);
uint32_t chk = afi_checksum(buf + (entry->block_offset << 9), entry->size);
if (entry->bytes == 0)
entry->bytes = entry->size;
memset(buf + (entry->block_offset << 9) + entry->bytes, 0, entry->size - entry->bytes);
uint32_t chk;
if (big_endian)
chk = big_endian_checksum(buf + (entry->block_offset << 9), entry->size);
else
chk = afi_checksum(buf + (entry->block_offset << 9), entry->size);
if(chk != entry->checksum)
{
cprintf(RED, "Mismatch\n");
@ -191,11 +272,25 @@ int fw_unpack(uint8_t *buf, size_t size, fw_extract_callback_t unpack_cb)
}
else
cprintf(RED, "Ok\n");
int ret = unpack_cb(filename, buf + (entry->block_offset << 9), entry->size);
int ret = unpack_cb(filename, buf + (entry->block_offset << 9), entry->bytes);
if(ret != 0)
return ret;
}
if (big_endian)
{
uint32_t load_checksum = *(uint32_t *)(buf + size - 4);
uint32_t load_chk = big_endian_checksum(buf, size - 512);
cprintf_field(" Load checksum: ", "0x%x ", load_checksum);
if(load_chk != load_checksum)
{
cprintf(RED, "Mismatch, 0x%x expected\n", load_chk);
return 1;
}
else
cprintf(RED, "Ok\n");
}
return 0;
}

View file

@ -27,7 +27,7 @@
* its name and content. If the callback returns a nonzero value, the function will stop and return
* that value. Returns 0 on success */
typedef int (*fw_extract_callback_t)(const char *name, uint8_t *buf, size_t size);
int fw_unpack(uint8_t *buf, size_t size, fw_extract_callback_t cb);
int fw_unpack(uint8_t *buf, size_t size, fw_extract_callback_t cb, bool big_endian);
/* Check if a file looks like an AFI file */
bool fw_check(uint8_t *buf, size_t size);

File diff suppressed because it is too large Load diff