Researching BLASTPASS: Analysing the Apple & Google WebP POC file - Part 2

· 695 words · 4 minute read

More than 14 weeks pasted since Apple Product Security team reported the issue affecting WebP open source project to Google, in follow up to the BLASTPASS iOS exploit that was discovered in the wild by CitizenLab and discussed in September. This means that the email chain is now public as of December 14, 2023.

We also learn that that Brotli compression algorithm almost got impacted by the same issue (c.f. BrotliBuildHuffmanTable) but the shape of Huffman tree is checked before actual lookup table is built so it was not vulnerable.

One of the nice thing about this email chain is that a webp poc is shared by Apple Product Security Team to Google, which I also added into ELEGANTBOUNCER repository and unit tests.

The code length counts used by NSO, as recovered by mistymntncop from the Apple POC, are the following:

   static CodeLenCountsArr code_lengths_counts = {
        //  1  2  3  4  5  6  7  8  9    10   11  12 13 14 15
        {0, 1, 0, 0, 0, 0, 0, 0, 0, 177, 154, 7,  1, 1, 1, 2}, //size = 716
        {0, 1, 0, 1, 1, 1, 0, 0, 0, 81,  85,  81, 1, 1, 1, 2}, //size = 628
        {0, 1, 0, 1, 1, 1, 0, 0, 0, 81,  85,  81, 1, 1, 1, 2}, //size = 628
        {0, 1, 0, 1, 1, 1, 0, 0, 0, 81,  85,  81, 1, 1, 1, 2}, //size = 628
        {0, 0, 0, 0, 0, 0, 3, 2, 2, 3,   12,  2,  2, 2, 0, 12} //size = 526!!!
    };

compared to the ones we used in the sample we re-created in the September 2023:

    static CodeLenCountsArr code_lengths_counts = {
        //  1  2  3  4  5  6  7  8  9  10   11  12 13 14 15
        {0, 1, 1, 0, 0, 0, 0, 0, 0, 3, 229, 41,  1, 1, 1, 2},   //size = 654
        {0, 1, 1, 0, 0, 0, 0, 0, 0, 7, 241,  1,  1, 1, 1, 2},   //size = 630
        {0, 1, 1, 0, 0, 0, 0, 0, 0, 7, 241,  1,  1, 1, 1, 2},   //size = 630
        {0, 1, 1, 0, 0, 0, 0, 0, 0, 7, 241,  1,  1, 1, 1, 2},   //size = 630
        {0, 1, 1, 1, 1, 1, 0, 0, 0, 11, 5,   1, 10, 4, 2, 2},   //size = 414!!!
    };

The main different comes down to the fact that color_cache_bits is used which changes the size of the huffman tables.

Upon reading the code lengths, a specific prefix code is generated for each symbol type (A, R, G, B, distance) based on their respective alphabet sizes which depends of the color cache bits.

  • G Channel: Calculated as 256 + 24 + color_cache_size
  • Other Literals (A,R,B): Constant at 256
  • Distance Code: Constant at 40

For color_cache_bits == 6, the size fo the Green channel huffman table buffer is 718 instead of 654 (for color_cache_bits == 0), while the other ones (A, R, B) and the distance code remain constant:

static const uint16_t kTableSize[12] = {
  FIXED_TABLE_SIZE + 654, // For color_cache_bits (0)
  FIXED_TABLE_SIZE + 656, // (...)
  FIXED_TABLE_SIZE + 658, // (...)
  FIXED_TABLE_SIZE + 662, // (...)
  FIXED_TABLE_SIZE + 670, // (...)
  FIXED_TABLE_SIZE + 686, // (...)
  FIXED_TABLE_SIZE + 718, // For color_cache_bits (6)
  FIXED_TABLE_SIZE + 782, // (...)
  FIXED_TABLE_SIZE + 912,
  FIXED_TABLE_SIZE + 1168,
  FIXED_TABLE_SIZE + 1680,
  FIXED_TABLE_SIZE + 2704
};

This gives us a total size of 3018 elements instead of 2954 elements to overflow. This potential change of size was already accounted within the ELEGANTBOUNCER detection code implemented in September:

    let mut alphabet_size = K_ALPHABET_SIZE[j];
    if j == 0 && color_cache_bits > 0 {
        alphabet_size += 1 << color_cache_bits;
    }

    let max_table_size = match j {
        0 => K_TABLE_SIZE[color_cache_bits as usize] - FIXED_TABLE_SIZE,
        1 | 2 | 3 => MAX_RBA_TABLE_SIZE,
        4 => MAX_DISTANCE_TABLE_SIZE,
        _ => panic!("Unhandled idx value: {}", j),
    };

Conclusion 🔗

Thanks again to mistymntncop for pointing out to me that the email chain was now public.

Learn more about ELEGANTBOUNCER on GitHub.

Happy Holidays!

References 🔗