Luxul XBR-4500: Upgrading OpenWrt when there is no sysupgrade image


I've tried reading around but so far come up empty handed so was wondering if someone can point me in the right direction.

I have OpenWRT 19.07.7 installed on a Luxul XBR-4500 and I'm looking at upgrading 21.02. I'm trying to figure out how go about the upgrade when there's no sysupgrade image at

The image does not include "factory" in the name so does mean it can be used for initial install and upgrade?

If more detail or clarification is needed just let me know.


After more reading and looking at BINs and TRXs for other devices my understanding is now (oddly enough what I had previously read)

To produce a TRX from a BIN (that's for initial install) all you need to do is strip the first 32 (0x20) octets ie. dd bs=1 skip=32 if=/tmp/firmware.bin of=/tmp/firmware.trx

Though, as noted on numerous pages, this approach for generating the upgrade package is not recommended due to the likelihood of bricking the device if the individual isn't an expert.

In the case of Luxul devices there doesn't currently appear to be any packages specifically built for upgrading, so for now the stripping approach appears to be the only option.

Where the manufacturer is Luxul and device is XBR-4500, to provide some certainty to the user that the file they're stripping ought to subsequently produce a valid TRX image I've composed the following oneliner (yet to be tested) I'm interested in peoples feedback if this looks correct. Once confirmed I'll look at adding it to the devices wiki page.

# Produce TRX from BIN
# Confirm the following checks out for BIN image
#  - manufacturer's signature ie. LXL# at offset 0 (0x0)
#  - device's model ie. XBR-4500 at offset 16 (0x10)
#  - TRX begins at expected location ie. HDR0 at offset 32 (0x20)
# also, TRX_IMAGE must not be blank
# if so strip the manufacturer's details so we just have the TRX

BIN_IMAGE=/tmp/firmware.bin; TRX_IMAGE=/tmp/firmware.trx; [ "$(dd bs=1 count=4 if=${BIN_IMAGE} 2> /dev/null)" = "LXL#" ] && [ "$(dd bs=1 skip=16 count=8 if=${BIN_IMAGE} 2> /dev/null)" = "XBR-4500" ] && [ "$(dd bs=1 skip=32 count=4 if=${BIN_IMAGE} 2> /dev/null)" = "HDR0" ] && [ -n "${TRX_IMAGE}" ] && echo "Attempting to strip ${BIN_IMAGE}" && dd bs=1 skip=32 if=${BIN_IMAGE} of=${TRX_IMAGE} && echo "Produced ${TRX_IMAGE} from stripped ${BIN_IMAGE}"

Here's the oneliner for then flashing the device with the TRX

# Flash device with TRX
# Confirm the following checks out for TRX image
#  - TRX image has expected magic number ie. HDR0 at offset 0 (0x0)
# if so apply to device's firmware

TRX_IMAGE=/tmp/firmware.trx; [ "$(dd bs=1 count=4 if=${TRX_IMAGE} 2> /dev/null)" = "HDR0" ] && echo "Attempting to apply ${TRX_IMAGE} to firmware" && mtd -r write "${TRX_IMAGE}" firmware && echo "${TRX_IMAGE} firmware has been applied"

I was also interested to see what I could do to verify the 32-Bit CRC in the TRX header checks out against the associated file content, binwalk presumably checks the CRC but for whatever reason I wrote this anyways

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <stdbool.h>

uint32_t crc32buf(unsigned char *buf, size_t len);
void hexDump(char *desc, void *addr, int len);

int main(int argc, char **argv)
  FILE *in;
  unsigned char *buf;
  size_t n;
  int exitcode;
  bool debug = (argc > 2 && strncmp(argv[2], "debug", 5) == 0);
  long fsize;
  uint32_t crc32;
  unsigned char crc32Str[4];

  if (argc < 2)
    fprintf(stderr, "A file name must be provided\n");
    return 1;

  if (debug) fprintf(stdout, "reading from file: %s\n", argv[1]);

  if (!(in = fopen(argv[1], "rb")))
    fprintf(stderr, "problem opening file: %s\n", argv[1]);
    return 2;

  fseek(in, 0, SEEK_END);
  fsize = ftell(in);
  fseek(in, 0, SEEK_SET);

  if (!(buf = malloc(fsize)))
    fprintf(stderr, "malloc failed\n");
    return 3;

  n = fread(buf, 1, fsize, in);
  if (n != fsize)
    fprintf(stderr, "failed to read entire file: %s\n", argv[1]);
    return 4;

  if (debug) hexDump("contents of file", buf, n);

  if (!(buf[0] == 'H' &&
        buf[1] == 'D' &&
        buf[2] == 'R' &&
        buf[3] == '0'))
    fprintf(stderr, "TRX magic number not found, not proceeding with CRC calculation\n");
    return 5;

  crc32 = crc32buf(buf + 12, n - 12);
  memcpy(crc32Str, &crc32, 4);

  if (debug) hexDump("calculated 32-Bit CRC", crc32Str, 4);

  if (crc32Str[0] == buf[8] &&
      crc32Str[1] == buf[9] &&
      crc32Str[2] == buf[10] &&
      crc32Str[3] == buf[11])
    fprintf(stdout, "Checksums match: %02x%02x%02x%02x\n", buf[8], buf[9], buf[10], buf[11]);
    exitcode = 0;
    fprintf(stdout, "Checksums do not match\n");
    fprintf(stdout, "Calculated checksum: %02x%02x%02x%02x\n", crc32Str[0], crc32Str[1], crc32Str[2], crc32Str[3]);
    fprintf(stdout, "Checksum read from file: %02x%02x%02x%02x\n", buf[8], buf[9], buf[10], buf[11]);
    exitcode = 6;


  return exitcode;

/* Following CRC calculation code sourced from;a=blob;f=tools/firmware-utils/src/trx.c;h=aa1f5be4b65b66ac9a1a48d7304d9bef131080e1;hb=HEAD#l69

static const uint32_t crc_32_tab[] = { /* CRC polynomial 0xedb88320 */
0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f,
0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,
0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2,
0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9,
0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c,
0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423,
0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106,
0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d,
0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950,
0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7,
0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,
0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa,
0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81,
0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a,
0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84,
0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb,
0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc,
0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e,
0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55,
0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28,
0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f,
0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38,
0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242,
0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69,
0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2,
0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc,
0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693,
0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d

#define UPDC32(octet,crc) (crc_32_tab[((crc) ^ (octet)) & 0xff] ^ ((crc) >> 8))

uint32_t crc32buf(unsigned char *buf, size_t len)
  uint32_t crc;

  crc = 0xFFFFFFFF;

  for (; len; --len, ++buf)
    crc = UPDC32(*buf, crc);

  return crc;

/* Following hexDump function sourced from

void hexDump(char *desc, void *addr, int len)
  int i;
  unsigned char buff[17];
  unsigned char *pc = (unsigned char*)addr;

  // Output description if given.
  if (desc != NULL)
    printf("%s:\n", desc);

  // Process every byte in the data.
  for (i = 0; i < len; i++)
    // Multiple of 16 means new line (with line offset).
    if ((i % 16) == 0)
      // Just don't print ASCII for the zeroth line.
      if (i != 0)
        printf("  %s\n", buff);

      // Output the offset.
      printf("  %04x ", i);

    // Now the hex code for the specific character.
    printf(" %02x", pc[i]);

    // And store a printable ASCII character for later.
    if ((pc[i] < 0x20) || (pc[i] > 0x7e))
      buff[i % 16] = '.';
      buff[i % 16] = pc[i];
    buff[(i % 16) + 1] = '\0';

  // Pad out last line if not exactly 16 characters.
  while ((i % 16) != 0)
    printf("   ");

  // And print the final ASCII bit.
  printf("  %s\n", buff);