Skip to main content

Firmware validation (WIP)

The logic to validate the firmware bundle begins at: libupg_validate_firmware_mem.

This is called from libupg_upgrade_mem that is subsequently called from the command upgrade in the psbl terminal.

This can be seen from the upgrade command handler:

int upgrade(int argc,char **argv)
{
  int iVar1;
  undefined4 *param2;
  
  if ((argc == 2 || argc == 4) && ((argc != 4 || (iVar1 = strcmp(argv[1],"-i"), iVar1 == 0)))) {
    *argv = "upgrade";
    argv[argc] = "/dev/ram";
    param2 = (undefined4 *)tftp(argc + 1,argv); // [1]
    if (0 < (int)param2) {
      iVar1 = libupg_upgrade_mem((astruct *)0xb4500000,param2); // [2]
      return iVar1;
    }
  }
  else {
    upgrade_usage();
  }
  return -1;
}

A couple of things to note from this snippet:

  • [1] Shows the data is being obtained through tftp (wtf)
  • We can see the address [2] 0xb4500000 being passed into libupg_upgrade_mem. This will be the location that the firmware is either downloaded to or where we will begin flashing.

libupg_validate_firmware_mem

The function starts by taking two arguments:

int libupg_validate_firmware_mem(byte *param1,uint param2)
[...]
  • param1 is the pointer into RAM discussed above (0xb4500000)
  • param2 is the result from tftp (Looks like it is being used as a data length)

The function begins by validating the firmware's header.

Header validation

The header validation can be found in function validate_firmware_header.

The function starts by taking a structure (we're going to call it firmware_header_struct).

The function contains two stages:

  • Digest validation
  • Randseq validation

If we search for "Randseq" - as it seems custom - we come across a handy Github repo (https://github.com/BigNerd95/paytonImageEditor). This has a tonne of information and the findings from this can be found in the Appendix. The rest of this section wiill use terminology sourced from it.

Digest validation

To calculate the digest you perform the following:

  1. Zero out the Signature
  2. Zero out the Digest
  3. MD5 hash of the firmware and module headers (?)

This can be seen in the following tidy Ghidra decompilation:

int gen_fmhdr_digest(void *md5_struct_out,firmware_header_struct *fm_hdr,size_t size) {
  byte *pDigest;
  byte *pSignature;
  astruct_1 MD5Buffer [3];
  undefined lSignature [32];
  undefined lDigest [16];
  
  // Save signature and digest
  pSignature = fm_hdr->Signature;
  memcpy(lSignature,pSignature,0x20);
  
  pDigest = fm_hdr->Digest;
  memcpy(lDigest,pDigest,0x10);
  
  // Zero signature and digest
  memset(pSignature,0,0x20);
  memset(pDigest,0,0x10);
  
  // MD5 time
  MD5Init(MD5Buffer);
  MD5Update(MD5Buffer,fm_hdr,size);
  MD5Final(md5_struct_out,MD5Buffer);
  
  // Restore signature and digest
  memcpy(pSignature,lSignature,0x20);
  memcpy(pDigest,lDigest,0x10);
  return 0;
}

Randseq validation

TODO

Appendix

Firmware Header

Size (byte) Type Name Description
16 Byte array Magic Eg: SkOsMo5 fIrMwArE
32 Byte array Signature Not used
16 Byte array Digest MD5 of Firmware Header + Modules Headers
16 Byte array Randseq Not so random
4 Unsigned BE Int Firmware Header Size Eg: 136
4 Unsigned BE Int Module Header Size Eg: 128
4 Unsigned BE Int Firmware size Entire firmware image size
32 Byte array Version Eg: 1.4.1
4 Unsigned BE Int Modules number Number of modules present in this firmware image

Comparing to our firmware header:

We can extract from this the Digest and Randseq

  • Digest: 05B910ECA58C1B6275E78185F3FAC6FC
  • Randseq: 68974CEF8CE0F0318587EC50083155C0

Alongside:

  • Firmware header size: 0x80
  • Module (?) header size: 0x40
  • Entire firmware size: 0x43162c (4.19 MB)

  • Version: 7.6.2g

  • Number of modules: 0x4F