2

I've come across some files which are used in a car's satellite navigation system. Looking at the files in a hex editor shows the ‰PNG signature, but the chunks do not follow the format spec - for example:

Offset(h) 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F

00000000  05 00 10 00 43 50 52 4E 41 56 5F 32 36 02 00 00  ....CPRNAV_26...
00000010  03 00 01 00 1C 00 00 00 08 01 00 00 4B 00 CA 3D  ............K.Ê=
00000020  FC 06 35 54 38 25 56 C8 1F 50 E9 06 04 58 85 5E  ü.5T8%VÈ.Pé..X…^
00000030  8C 91 E1 AD 84 DF 60 49 FC 01 61 FF 47 A9 D5 6A  Œ‘á.„ß`Iü.aÿG©Õj
00000040  B5 B2 06 48 65 0D D0 FA BE C3 D3 0E A1 EF 3B BC  µ².He.Ðú¾ÃÓ.¡ï;¼
00000050  EF F0 BE C3 73 0E A1 EF 3B BC EF F0 BE C3 73 0E  ïð¾Ãs.¡ï;¼ïð¾Ãs.
00000060  A1 DB E1 0F 58 E4 3E 89 50 4E 47 0D 0A 1A 0A 00  ¡Ûá.Xä>‰PNG.....  // PNG begins here
00000070  00 00 0D 49 48 44 52 F0 E0 08 02 B2 E1 7C 5E 00  ...IHDRðà..²á|^.  // IHDR chunk indicated as 13 (0x0D) bytes, yet this is clearly not the case
00000080  01 73 52 47 42 00 AE CE 1C E9 06 62 4B 47 44 00  .sRGB.®Î.é.bKGD.
00000090  FF A0 BD A7 93 09 70 48 59 73 0B 11 01 7F 64 5F  ÿ ½§“.pHYs....d_
000000A0  91 07 74 49 4D 45 07 E0 02 18 07 12 13 CC F8 BA  ‘.tIME.à.....Ìøº
000000B0  BA B6 49 44 41 54 78 DA ED D2 C1 0D 04 00 31 C4  º¶IDATxÚíÒÁ...1Ä
000000C0  FE 2B 33 83 AF B4 23 5C 2E A7 27 E0 8B 92 00 43  þ+3ƒ¯´#\.§'à‹’.C
000000D0  83 A1 C1 D0 60 68 0C 0D 86 06 43 83 A1 C1 D0 18  ƒ¡ÁÐ`h..†.Cƒ¡ÁÐ.
000000E0  1A 0C 0D 86 06 31 34 18 1A 63 68 30 34 C6 30 8D  ...†.14..ch04Æ0.
000000F0  60 86 AB 05 8F 4F 03 C2 58 19 85 B6 00 49 45 4E  `†«..O.ÂX.…¶.IEN
00000100  44 AE 42 60 82 00 00 00                          D®B`‚...

As you can see, several standard PNG chunk names are present (sRGB, bKGD, IDAT, etc.) however the data between them appears to be compressed/encrypted.

My observations so far:

  • bytes 0x14-17 seem to be a DWORD indicating the custom header size (0x1C or 28 bytes)
  • bytes 0x1C-1D seem to be a WORD indicating the size of the data between themselves and where the actual PNG header begins (0x4B00 or 75 bytes, including the 2 size bytes) - this might be the compressed data table/dictionary
  • files are aligned to 4 byte blocks with 0x00
  • of the 2,196 compressed PNG files, exactly half have the same IHDR data: F0 E0 08 02 B2 E1 7C 5E 00 01; the other half have 01 90 88 08 02 E0 58 21 6D
  • similarly, half of the files' compressed data/dictionaries begin with the same 22 bytes: FC 06 31 46 85 53 68 85 84 F9 95 6E 40 80 55 E8 C5 18 19 DE 4A F8; the other half begin with FC 06 35 54 38 25 56 C8 1F 50 E9 06 04 58 85 5E 8C 91 E1 AD 84 - note how every file begins with FC 03 3x xx

Update 2: I've created a GitHub repository with my progress so far, for anyone who's willing to take a look.

Update 1: After digging around, I've managed to find a copy of the firmware with the symbols included (available here: https://www.mediafire.com/file/a0u7k2m87aj1jp6/Files.zip/file) and I'm pretty sure I've located the correct function for decompressing the files: cpr_tclDecompressAlgorithm::bDecompressData.

Unfortunately, I'm not able to make a great deal of sense of it, but what I can see from the symbols is that:

  • a couple of custom structs are used in the decompression method, cpr_tclCodeTable and cpr_tclCodeTableEntry
  • the structs are then used with methods called cpr_tclCodeTable::iu32SearchIndexOfCode and cpr_tclCodeTable::corfoGetCodeEntry
  • decompression begins by calling cpr_tclDecompressAlgorithm::u32GetNextBits to read 32 bits / 4 bytes
  • the return value from u32GetNextBits is used with & 3 and right shifted 2 bits, which indicates the first two bits are used as some kind of flag

I will post IDA's pseudocode of the function below, but I'm not expecting anyone to spend a great deal of time on this - I just wondered if anyone can recognise what's happening here, e.g. Huffman, LZSS, etc.

int __fastcall cpr_tclDecompressAlgorithm::bDecompressData(int a1, int a2, int a3, size_t a4, unsigned int a5, int a6)
{
  int v7; // r8
  unsigned int v10; // r5
  int v11; // r9
  void *v12; // r0
  unsigned int v13; // r8
  unsigned int v14; // r0
  const cpr_tclCodeTable *CodeTable; // r11
  unsigned int v16; // r6
  unsigned int v17; // r7
  unsigned int v18; // r0
  int v19; // r0
  int v20; // r2
  int v21; // r3
  int v22; // r5
  int v23; // r0
  int v24; // r7
  unsigned int v25; // r1
  int v26; // r3
  _BYTE *v27; // r0
  size_t v28; // r6
  __int16 v29; // r12
  int v30; // r2
  int v31; // r5
  int v32; // r0
  unsigned int v33; // r10
  int v34; // r3
  _BYTE *v35; // r1
  int v36; // r0
  unsigned int v37; // r12
  int v38; // r3
  void *v39; // r0
  size_t v40; // r5
  int v41; // r2
  int result; // r0
  int v43; // r3
  int v44; // r2
  _BYTE *v45; // r2
  int v46; // r0
  int v47; // r3
  _BOOL4 v48; // r3
  int v49; // r0

  *(_DWORD *)(a1 + 16) = a4;
  v7 = a2 & 3;
  *(_DWORD *)(a1 + 12) = a3;
  *(_DWORD *)(a1 + 4) = a2;
  *(_DWORD *)(a1 + 8) = a2;
  if ( (a2 & 3) != 0 )
  {
    v49 = trc_szGetFileName(
            "/opt/bosch/DI_SWNAVI_13.2C5P10/di_swnavi/components/dapi/devicemanager/devicehandler/compression/cpr_decompression.cpp");
    trc_tclGlobalTrace::vTraceLevel1(21504, 332, v49, "CPR read access to unaligned adress 0x%x", a2);
    return 0;
  }
  cpr_tclDecompressAlgorithm::vInterpreteHeader((cpr_tclDecompressAlgorithm *)a1, a5);
  v10 = a5 - *(_DWORD *)(a1 + 48);
  if ( a4 < a5 )
  {
    if ( a4 <= v10 )
    {
      *(_DWORD *)(a1 + 48) = v7;
      v10 = a4;
      v11 = 1;
      goto LABEL_7;
    }
    *(_DWORD *)(a1 + 48) = a4 - v10;
  }
  v11 = v7;
LABEL_7:
  v12 = *(void **)(a1 + 12);
  v13 = (unsigned int)v12 + v10;
  if ( !v10 )
    goto LABEL_34;
  if ( a6 == 3 )
  {
    v14 = cpr_tclDecompressAlgorithm::u32GetNextBits((cpr_tclDecompressAlgorithm *)a1, 0x20u);
    CodeTable = (const cpr_tclCodeTable *)(*(_DWORD *)a1 + 44 * (v14 & 3));
    v16 = v14 >> 2;
    v17 = 2;
    cpr_tclDecompressAlgorithm::vInit((cpr_tclDecompressAlgorithm *)a1, CodeTable);
    while ( 1 )
    {
      while ( 1 )
      {
        if ( *(_DWORD *)(a1 + 12) >= v13 )
          goto LABEL_34;
        v18 = cpr_tclCodeTable::iu32SearchIndexOfCode(CodeTable, v16);
        v19 = cpr_tclCodeTable::corfoGetCodeEntry(CodeTable, v18);
        v20 = *(_DWORD *)(v19 + 12);
        v21 = *(unsigned __int8 *)(v19 + 16);
        v22 = v19;
        v17 += v21;
        v16 >>= v21;
        if ( v20 != 3 )
          break;
        v23 = cpr_tclDecompressAlgorithm::u32GetNextBits((cpr_tclDecompressAlgorithm *)a1, v17);
        v24 = *(unsigned __int8 *)(v22 + 17);
        v25 = v23 | v16;
        v26 = (v23 | v16) & ~(-1 << v24);
        v27 = *(_BYTE **)(a1 + 12);
        v28 = (unsigned __int16)(v26 + *(_WORD *)(v22 + 2));
        v29 = *(_WORD *)(v22 + 6);
        v30 = *(_DWORD *)(a1 + 44);
        v31 = *(unsigned __int8 *)(v22 + 18);
        if ( v13 < (unsigned int)&v27[v28] )
        {
          if ( v11 != 1 )
          {
            v32 = trc_szGetFileName(
                    "/opt/bosch/DI_SWNAVI_13.2C5P10/di_swnavi/components/dapi/devicemanager/devicehandler/compression/cpr"
                    "_decompression.cpp");
            trc_tclGlobalTrace::vTraceLevel1(
              21504,
              253,
              v32,
              "CPR Overwrite Copy n=%d wpos=0x%x epos=0x%x",
              v28,
              *(_DWORD *)(a1 + 12),
              v13);
LABEL_24:
            result = 0;
            goto LABEL_35;
          }
          v28 = (unsigned __int16)(v13 - (_WORD)v27);
        }
        v33 = v25 >> v24;
        v34 = -(unsigned __int16)(v29 + (((v25 >> v24) & ~(-1 << v31)) << v30));
        v35 = &v27[-(unsigned __int16)(v29 + (((v25 >> v24) & ~(-1 << v31)) << v30))];
        if ( v28 == 2 )
        {
          *v27 = v27[v34];
          *(_BYTE *)(*(_DWORD *)(a1 + 12) + 1) = v35[1];
        }
        else
        {
          memcpy(v27, v35, v28);
        }
        v17 = v31 + v24;
        *(_DWORD *)(a1 + 12) += v28;
        v16 = v33 >> v31;
      }
      if ( v20 == 2 )
      {
        v36 = cpr_tclDecompressAlgorithm::u32GetNextBits((cpr_tclDecompressAlgorithm *)a1, v17);
        v17 = *(unsigned __int8 *)(v22 + 17);
        v37 = v36 | v16;
        v38 = (v36 | v16) & ~(-1 << v17);
        v39 = *(void **)(a1 + 12);
        v40 = (unsigned __int16)(v38 + *(_WORD *)(v22 + 2));
        if ( v13 < (unsigned int)v39 + v40 )
        {
          if ( v11 != 1 )
          {
            v41 = trc_szGetFileName(
                    "/opt/bosch/DI_SWNAVI_13.2C5P10/di_swnavi/components/dapi/devicemanager/devicehandler/compression/cpr"
                    "_decompression.cpp");
            trc_tclGlobalTrace::vTraceLevel1(
              21504,
              291,
              v41,
              "CPR Overwrite Lit n=%d wpos=0x%x epos=0x%x",
              v40,
              *(_DWORD *)(a1 + 12),
              v13);
            goto LABEL_24;
          }
          v40 = (unsigned __int16)(v13 - (_WORD)v39);
        }
        v16 = v37 >> v17;
        memcpy(v39, *(const void **)(a1 + 8), v40);
        v43 = *(_DWORD *)(a1 + 12) + v40;
        v44 = *(_DWORD *)(a1 + 8) + v40;
      }
      else
      {
        v45 = *(_BYTE **)(a1 + 12);
        if ( (_BYTE *)v13 == v45 )
        {
          v46 = trc_szGetFileName(
                  "/opt/bosch/DI_SWNAVI_13.2C5P10/di_swnavi/components/dapi/devicemanager/devicehandler/compression/cpr_d"
                  "ecompression.cpp");
          trc_tclGlobalTrace::vTraceLevel1(
            21504,
            306,
            v46,
            "CPR Overwrite Lit1 n=1 wpos=0x%x epos=0x%x",
            *(_DWORD *)(a1 + 12),
            v13);
          goto LABEL_24;
        }
        *v45 = **(_BYTE **)(a1 + 8);
        v43 = *(_DWORD *)(a1 + 12) + 1;
        v44 = *(_DWORD *)(a1 + 8) + 1;
      }
      *(_DWORD *)(a1 + 12) = v43;
      *(_DWORD *)(a1 + 8) = v44;
    }
  }
  if ( a6 == 1 )
  {
    memcpy(v12, *(const void **)(a1 + 4), v10);
    result = 1;
    *(_DWORD *)(a1 + 12) += v10;
  }
  else
  {
LABEL_34:
    result = 1;
  }
LABEL_35:
  v47 = *(_DWORD *)(a1 + 48);
  v48 = v47 != 0;
  if ( result != 1 )
    v48 = 0;
  if ( v48 )
  {
    memset(*(void **)(a1 + 12), 0, *(_DWORD *)(a1 + 48));
    result = 1;
  }
  return result;
}
5
  • 1
    Have you got any access to a piece of software doing the reading and/or writing? It's usually easier to reason about file formats (or deviations thereof) with that information. Also, is a sample of what you encountered available for us to inspect?
    – 0xC0000022L
    Commented Apr 29, 2021 at 16:26
  • @0xC0000022L unfortunately I don't have access to the software as it's a touchscreen car sat nav, however it seems it may be possible to root it which may provide some insight. The files are from a "Nissan Connect 3" SD card - one of the few plaintext files mentions USED_COMPRESSION{ '5' } which could perhaps be something like gzip. Here are a few samples: mediafire.com/file/7qtrkm290xrt0vi/Nissan.rar/file Commented Apr 29, 2021 at 17:21
  • 1
    Thanks. Can't make head or tails of it. Perhaps someone else will chime in and can help.
    – 0xC0000022L
    Commented Apr 29, 2021 at 20:14
  • No worries - appreciate the effort :) Commented Apr 29, 2021 at 20:51
  • @0xC0000022L as luck would have it, I've come across a link to the software that decompresses these files: mediafire.com/file/i6wedcfhvpzdzza/DAPIAPP.OUT/file - fortunately the symbols are included in the file, too, and I can see a method cpr_tclDecompressAlgorithm::bDecompressData which is surely responsible for the decompression. After taking a quick glance, however, it looks complex. It'll take me a while to understand this - just thought I'd share what I found. Commented May 2, 2021 at 13:35

1 Answer 1

1

To answer my own question, this appears to be a custom compression format by Bosch, the manufacturer of the car head unit.

I have been successful in decompressing all of the PNG files; my solution can be found on GitHub here.

It would have been impossible to reverse engineer the compression format without disassembling the firmware as the data at the beginning of the file is used to retrieve values from a hardcoded "code table".

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.