Skip to content

Instantly share code, notes, and snippets.

@marcelrv
Last active November 25, 2023 12:12
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save marcelrv/4edcad9b63b34e1c30ac5758cc88ba9c to your computer and use it in GitHub Desktop.
Save marcelrv/4edcad9b63b34e1c30ac5758cc88ba9c to your computer and use it in GitHub Desktop.
Onkyo Firmware analysis / hacking
/*
* Onkyo firmware decryptor v3 (c) 2014 - vZ@divideoverflow.com
*
*
* Version 3
* Updated to run on 64bit & fixed fatal errors during compile
*
* version 2:
* re-written for more sophisticated parsing, fixing bug with some blocks being missed
*
* version 1.0:
* initial release
*
* Thanks to Turmio for the only page found on the web dedicated to ONKYO reversing
* (https://jkry.org/ouluhack/HackingOnkyo%20TR-NX509)
* and to Na Na for providing libupdater.so.
*
*
*
* gcc -o onkyo-dec onkyo-decryptor.c
*
*/
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <glob.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <errno.h>
#include <stdlib.h>
// these keys were found statically entered in libupdater.so
// however, they aren't usable for every block, hence we'll calculate most of them on demand
// using known-plaintext attack.
unsigned char keyA[8] = "\xda\x57\x68\x0d\x44\x21\x30\x7a";
unsigned char keyB[8] = "\xae\xb7\x31\x74\x47\xe4\xfb\x5d";
unsigned char cryptKey[8] = { 0 };
char plaintext[] = "ONKYO Encryption";
unsigned int Magic1 = 0x57cb4295;
FILE *fp;
char path[4000] = { 0 };
char outname[4000] = { 0 };
char outdir[4000] = { 0 };
unsigned int blocksize = 0x1000;
unsigned char lastkey = 0;
unsigned int counter = 0;
unsigned char dst[0x1000] = { 0 };
int ofnum = 0;
unsigned int
calc_crc (unsigned char *src, unsigned int size)
{
unsigned int x = 0, y = 0;
int i = 0;
unsigned char b1, b2, b3, b4;
size--;
do
{
b1 = src[i++];
x = y + b1;
if (i > size)
break;
b2 = src[i++];
x += b2 << 8;
if (i > size)
break;
b3 = src[i++];
x += b3 << 16;
if (i > size)
break;
b4 = src[i++];
x += b4 << 24;
y = (x << 11) + (x >> 21);
if (i > size)
break;
}
while (1);
return x;
}
void
calc_key (unsigned char *src, unsigned char *cryptKey)
{
unsigned char key[8] = { 0 };
int n, j, b;
unsigned char lk = 0;
lk = plaintext[0] ^ src[0];
key[0] = lk;
for (j = 1; j < 8; j++)
{
n = lk >> 7;
b = n;
lk = lk & 0x7f;
n = n | (lk << 1);
lk = plaintext[j] ^ src[j];
key[j] = lk + 0x100 * b - n;
}
memcpy (cryptKey, key, 8);
}
int
match_crc (unsigned char *src, unsigned int size, unsigned int crc)
{
return (crc == calc_crc (src, size));
}
void
decrypt_block (unsigned char *src, unsigned char *dst, int size,
unsigned char *xorkey, unsigned int *c, unsigned char *lk)
{
int n, i = 0, j = 0;
int k = 0;
if (size == 0)
return;
if (*c == 0)
*lk = xorkey[0];
j = *c % 7;
do
{
dst[i] = src[i] ^ *lk;
n = (*lk >> 7);
j++;
k = xorkey[j];
*lk = *lk & 0x7f;
n = n | (*lk << 1);
k = k + n;
*c = *c + 1;
k = k + (*c >> 6);
*lk = k & 0xff;
if (j == 7)
j = 0;
i++;
size--;
}
while (size > 0);
return;
}
void
make_target_dir ()
{
struct stat sb;
int e;
strcpy (outdir, path);
strcat (outdir, "/extracted");
e = stat (outdir, &sb);
if (e == 0)
{
if (!(sb.st_mode & S_IFDIR))
{
fprintf (stdout, "Target '%s' must be a directory!\n", outdir);
}
}
else
{
if (errno = ENOENT)
{
e = mkdir (outdir, S_IRWXU);
if (e != 0)
perror ("mkdir failed\n");
}
}
}
int
parse_header (unsigned char *src)
{
typedef struct header
{
char sig[0x10];
unsigned int dataofs;
unsigned int crc;
unsigned int pname;
unsigned int ptree;
unsigned int precords;
unsigned char unk1[12];
char name[0x20];
char subname[4];
unsigned char unpackedfiles;
unsigned char packedfiles;
unsigned char ofnum;
unsigned char fileshere;
unsigned char unk2[0x1a8];
} t_header;
t_header hdr;
typedef struct block
{
char filename[8];
unsigned int offset;
unsigned int size;
unsigned int crc;
} t_block;
t_block blk[20];
unsigned char *blkptr;
int result = -1;
int tb = 0;
int i, f;
unsigned int counter = 0;
decrypt_block (src, dst, sizeof (hdr), keyA, &counter, &lastkey);
if (memcmp (dst, plaintext, 0x10) != 0)
return -1;
memcpy (&hdr, dst, sizeof (hdr));
if (match_crc
((unsigned char *) (&hdr) + 0x18, hdr.dataofs - 0x18, hdr.crc))
{
sprintf (outname, "%s/of%i.%s.hdr", outdir, ofnum, hdr.name);
fp = fopen (outname, "w");
if (fp)
{
fwrite (&hdr, 1, hdr.dataofs, fp);
fclose (fp);
fprintf (stdout, "Header block decrypted and saved to %s\n",
outname);
result = 1;
}
int prec = hdr.precords;
int frec = hdr.ptree;
while (prec < hdr.dataofs)
{
if (*(unsigned int *) ((unsigned char *) &hdr + prec) != 0
&& *(unsigned int *) ((unsigned char *) &hdr + prec + 4))
{
blk[tb].size =
*(unsigned int *) ((unsigned char *) &hdr + prec);
blk[tb].offset =
*(unsigned int *) ((unsigned char *) &hdr + prec + 4);
blk[tb].crc =
*(unsigned int *) ((unsigned char *) &hdr + prec + 8);
strncpy (blk[tb].filename, (char *) ((char *) &hdr + frec + 1),
7);
tb++;
}
prec += 0x10;
frec += 8;
}
for (i = 0; i < tb; i++)
{
blkptr = src + blk[i].offset;
counter = 0;
blocksize = 0x1000;
if (calc_crc (blkptr, blk[i].size) != blk[i].crc)
{
fprintf (stdout, "Error: CRC mismatch! Skipping block..\n");
continue;
}
if (*(unsigned int *) (blkptr) == Magic1)
{
// process header
if (parse_header (blkptr) < 0)
fprintf (stdout, "Error parsing header block!\n");
}
else
{
// calculate decryption key
calc_key (blkptr, cryptKey);
sprintf (outname, "%s/of%i.%s.%s", outdir, ofnum, hdr.name,
blk[i].filename);
fprintf (stdout,
"Writing block from 0x%.8ix of size %iu to %s\n",
blk[i].offset, blk[i].size, outname);
f = 0;
do
{
decrypt_block (blkptr, dst, blocksize, cryptKey, &counter,
&lastkey);
if (counter == blocksize)
{
// verify we got it right
if (memcmp (dst, plaintext, 0x10) != 0)
{
fprintf (stdout,
"Error: Invalid decryption key/signature .. skipping this block.\n\n");
break;
}
f = 1;
fp = fopen (outname, "w");
fwrite (dst + 0x10, 1, blocksize - 0x10, fp);
}
else
{
fwrite (dst, 1, blocksize, fp);
}
blkptr += blocksize;
if (counter == blk[i].size)
break;
if (blk[i].size - counter < blocksize)
blocksize = blk[i].size - counter;
}
while (1);
if (f)
{
fclose (fp);
fprintf (stdout,
"Block successfully decrypted and saved.\n");
result = 1;
}
}
}
}
else
{
fprintf (stdout, "Error: Header CRC mismatch.. skipping.\n");
}
return result;
}
int
main (int argc, char *argv[])
{
int fd;
glob_t globbuf;
struct stat sb;
unsigned char *p;
char searchpath[0x4000] = { 0 };
int buflen;
int j;
fprintf (stdout,
"Decrypt Onkyo firmware, (c) 2014, <vZ@divideoverflow.com>\n\n");
if (argc > 1)
{
strcpy (path, argv[1]);
}
else
{
strcpy (path, ".");
}
fprintf (stdout, "Searching for firmware '.of' files in '%s' .. ", path);
strcpy (searchpath, path);
strcat (searchpath, "/*.of?");
glob (searchpath, 0, NULL, &globbuf);
if (globbuf.gl_pathc > 0)
{
fprintf (stdout, "%li files found.\n", globbuf.gl_pathc);
}
else
{
fprintf (stdout, "no files found.\n");
return 0;
}
make_target_dir ();
for (j = 0; j < globbuf.gl_pathc; j++)
{
fprintf (stdout, "\nProcessing %s..\n", globbuf.gl_pathv[j]);
ofnum = atoi (globbuf.gl_pathv[j] + strlen (globbuf.gl_pathv[j]) - 1);
fd = open (globbuf.gl_pathv[j], O_RDWR);
if (fd == -1)
{
perror ("open");
return 1;
}
if (fstat (fd, &sb) == -1)
{
perror ("fstat");
return 1;
}
if (!S_ISREG (sb.st_mode))
{
fprintf (stdout, "%s is not a file\n", globbuf.gl_pathv[j]);
return 1;
}
buflen = sb.st_size;
p = mmap (0, buflen, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (p == MAP_FAILED)
{
perror ("mmap");
return 1;
}
if (close (fd) == -1)
{
perror ("close");
return 1;
}
if (buflen < blocksize)
blocksize = buflen;
if (*(unsigned int *) p != Magic1)
{
// of0 special case. the file is useless but just for the sake of completness
if ((*(unsigned int *) &p[0x10] == Magic1 && blocksize < 512))
{
decrypt_block (p + 0x10, dst, blocksize, keyA, &counter,
&lastkey);
sprintf (outname, "%s/of%i", outdir, ofnum);
fp = fopen (outname, "w");
fwrite (&dst, 1, blocksize - 0x10, fp);
fclose (fp);
fprintf (stdout, ".of0 file decrypted as %s\n", outname);
}
else
{
perror ("Invalid file format");
}
}
else
{
if (parse_header (p) < 0)
fprintf (stdout, "Error parsing header block!\n");
}
_done:
if (munmap (p, buflen) == -1)
{
perror ("munmap");
return 1;
}
}
globfree (&globbuf);
fprintf (stdout, "\nDone!\n");
return 0;
}

See https://zonnigbreda.blogspot.com/2021/05/onkyo-onkyo-tx-nr656-hacking-firmware.html for a better write down of these steps

Decrypt the firmware simply execute ./onkyo-dec in the firmware folder. It creates an extracted folder with the output

Get the decoded content and see what's there

$ file *
of0:                                 empty
of1.ONKAVR001F_E70000EAEAEOEO.hdr:   data
of2.ONKAVR001F_E70000EAEAEOEO.EA107: data
of2.ONKAVR001F_E70000EAEAEOEO.EA109: data
of2.ONKAVR001F_E70000EAEAEOEO.hdr:   data
of3.AM335XEO_010203040506.03296:     data
of3.AM335XEO_010203040506.04296:     u-boot legacy uImage, Linux-3.19.0, Linux/ARM, OS Kernel Image (Not compressed), 4625424 bytes, Tue Feb 12 03:24:24 2019, Load Address: 0x80008000, Entry Point: 0x80008000, Header CRC: 0x01BD6550, Data CRC: 0x8D747944
of3.AM335XEO_010203040506.05296:     Linux Compressed ROM File System data, little endian size 61440 version #2 sorted_dirs CRC 0x61c58604, edition 0, 58 blocks, 8 files
of3.AM335XEO_010203040506.07296:     UBI image, version 1
of3.AM335XEO_010203040506.hdr:       data
of3.ONKAVR001F_E70000EAEAEOEO.hdr:   data
of4.ONKAVR001F_E70000EAEAEOEO.EO211: data
of4.ONKAVR001F_E70000EAEAEOEO.hdr:   data

Seems the file system is in of3.AM335XEO_010203040506.07296

$ blkid of3.AM335XEO_010203040506.07296
of3.AM335XEO_010203040506.07296: UUID="152348150" TYPE="ubi"

Now we need to mount this see also https://www.coresecurity.com/core-labs/articles/next-generation-ubi-and-ubifs

see the structure

$ hexdump of3.AM335XEO_010203040506.07296 -C | head -n 30
00000000  55 42 49 23 01 00 00 00  00 00 00 00 00 00 00 00  |UBI#............|
00000010  00 00 08 00 00 00 10 00  09 14 a5 f6 00 00 00 00  |................|
00000020  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000030  00 00 00 00 00 00 00 00  00 00 00 00 5b 78 84 d4  |............[x..|
00000040  ff ff ff ff ff ff ff ff  ff ff ff ff ff ff ff ff  |................|
*
00000800  55 42 49 21 01 01 00 05  7f ff ef ff 00 00 00 00  |UBI!............|
00000810  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
00000830  00 00 00 00 00 00 00 00  00 00 00 00 b8 25 64 a8  |.............%d.|
00000840  ff ff ff ff ff ff ff ff  ff ff ff ff ff ff ff ff  |................|
*
00001000  00 00 03 95 00 00 00 01  00 00 00 00 01 00 00 06  |................|
00001010  72 6f 6f 74 66 73 00 00  00 00 00 00 00 00 00 00  |rootfs..........|
00001020  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
00001090  01 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
000010a0  00 00 00 00 00 00 00 00  69 d9 4a a6 00 00 00 00  |........i.J.....|
000010b0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|

Mount the file

$ modprobe nandsim modprobe nandsim first_id_byte=0x01 second_id_byte=0xf1 third_id_byte=0x80 fourth_id_byte=0x1d

$ ubiformat -O 2048 -f of3.AM335XEO_010203040506.07296 /dev/mtd0
ubiformat: mtd0 (nand), size 134217728 bytes (128.0 MiB), 1024 eraseblocks of 131072 bytes (128.0 KiB), min. I/O size 2048 bytes
libscan: scanning eraseblock 1023 -- 100 % complete
ubiformat: 1024 eraseblocks are supposedly empty
ubiformat: flashing eraseblock 715 -- 100 % complete
ubiformat: formatting eraseblock 1023 -- 100 % complete

$ modprobe ubi

$ ubiattach -O 2048 -p /dev/ubi0
UBI device number 0, total 1024 LEBs (130023424 bytes, 124.0 MiB), available 0 LEBs (0 bytes), LEB size 126976 bytes (124.0 KiB)

$ ubinfo /dev/ubi0
ubi0
Volumes count:                           1
Logical eraseblock size:                 126976 bytes, 124.0 KiB
Total amount of logical eraseblocks:     1024 (130023424 bytes, 124.0 MiB)
Amount of available logical eraseblocks: 0 (0 bytes)
Maximum count of volumes                 128
Count of bad physical eraseblocks:       0
Count of reserved physical eraseblocks:  20
Current maximum erase counter value:     1
Minimum input/output unit size:          2048 bytes
Character device major/minor:            247:0
Present volumes:                         0
 
$ mkdir onkyofs
$ mount -t ubifs /dev/ubi0_0 ./onkyofs

Now you can browse the file system from ./onkyofs

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment