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 have01 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 withFC 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 withFC 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
andcpr_tclCodeTableEntry
- the structs are then used with methods called
cpr_tclCodeTable::iu32SearchIndexOfCode
andcpr_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;
}
USED_COMPRESSION{ '5' }
which could perhaps be something like gzip. Here are a few samples: mediafire.com/file/7qtrkm290xrt0vi/Nissan.rar/filecpr_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.