libextractor

GNU libextractor
Log | Files | Refs | Submodules | README | LICENSE

plist_extractor.c (38019B)


      1 /*
      2      This file is part of libextractor.
      3      Copyright (C) 2026 Vidyut Samanta and Christian Grothoff
      4 
      5      libextractor is free software; you can redistribute it and/or modify
      6      it under the terms of the GNU General Public License as published
      7      by the Free Software Foundation; either version 3, or (at your
      8      option) any later version.
      9 
     10      libextractor is distributed in the hope that it will be useful, but
     11      WITHOUT ANY WARRANTY; without even the implied warranty of
     12      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
     13      General Public License for more details.
     14 
     15      You should have received a copy of the GNU General Public License
     16      along with libextractor; see the file COPYING.  If not, write to the
     17      Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
     18      Boston, MA 02110-1301, USA.
     19  */
     20 /**
     21  * @file plugins/plist_extractor.c
     22  * @brief plugin to support Apple property lists, binary and XML
     23  * @author Christian Grothoff
     24  *
     25  * Property lists are the configuration and manifest format of macOS
     26  * and iOS; the forensically interesting ones are the `Info.plist' of
     27  * an application bundle (which names the vendor, the SDK and the tool
     28  * chain that built it) and the `Info.plist' / `Manifest.plist' of an
     29  * iOS backup (which names the device, its serial number and when it
     30  * was last backed up).
     31  *
     32  * Two on-disk representations exist.  The binary one is documented by
     33  * Apple's CoreFoundation sources (CFBinaryPlist.c): a `bplist00'
     34  * signature, a body of tagged objects, an offset table and a 32-byte
     35  * trailer at the very end of the file.  The XML one is a DTD-bound
     36  * document of alternating <key> and value elements.
     37  *
     38  * This is a first-pass identifier, not a plist library.  We decode the
     39  * top-level dictionary one level deep and stop; we never build an
     40  * object graph, never follow arrays or nested dictionaries, and never
     41  * read more than a bounded slice of the file.
     42  */
     43 #include "platform.h"
     44 #include "extractor.h"
     45 #include "forensics.h"
     46 
     47 #include <math.h>
     48 
     49 
     50 /**
     51  * Upper bound on the object count we accept from the trailer.  A
     52  * plist with more objects than this is either not a plist or is not
     53  * something a first pass should be walking.
     54  */
     55 #define PLIST_MAX_OBJECTS 100000
     56 
     57 /**
     58  * How many key/value pairs of the top-level dictionary we decode.
     59  */
     60 #define PLIST_MAX_PAIRS 64
     61 
     62 /**
     63  * How many pairs whose key we do not recognise we report as
     64  * #EXTRACTOR_METATYPE_UNKNOWN.  The point is a characterisation, not
     65  * a dump of the file.
     66  */
     67 #define PLIST_MAX_UNKNOWN 12
     68 
     69 /**
     70  * Longest unrecognised value we are willing to report in the
     71  * "key: value" form.
     72  */
     73 #define PLIST_MAX_UNKNOWN_VALUE 80
     74 
     75 /**
     76  * Bytes we look at to decide whether an XML file is a plist.
     77  */
     78 #define PLIST_SNIFF_SIZE 1024
     79 
     80 /**
     81  * Bytes of an XML plist we scan for key/value pairs.
     82  */
     83 #define PLIST_XML_SCAN_SIZE (64 * 1024)
     84 
     85 /**
     86  * Size of the buffers holding one decoded key or value, in bytes.
     87  * Keys and values in these files are identifiers, versions and dates.
     88  */
     89 #define PLIST_MAX_TEXT 256
     90 
     91 /**
     92  * Seconds between the Unix epoch and the Core Foundation epoch of
     93  * 2001-01-01.  Binary plist dates count from the latter.
     94  */
     95 #define PLIST_EPOCH_DELTA 978307200LL
     96 
     97 /**
     98  * Name we report our meta data under.
     99  */
    100 #define PLIST_PLUGIN "plist"
    101 
    102 /**
    103  * MIME type of both representations.  `application/xml' would also be
    104  * true of the XML form, but it is useless for identification.
    105  */
    106 #define PLIST_MIME "application/x-plist"
    107 
    108 
    109 /**
    110  * A well-known property list key and the meta data type it maps to.
    111  */
    112 struct PlistKey
    113 {
    114   /**
    115    * Key as it appears in the file.
    116    */
    117   const char *key;
    118 
    119   /**
    120    * Type to report the associated value as.
    121    */
    122   enum EXTRACTOR_MetaType type;
    123 };
    124 
    125 
    126 /**
    127  * Keys worth reporting as something better than "unknown".  The
    128  * CFBundle* and DT* families come from application bundles, the rest
    129  * from iOS backup manifests.
    130  */
    131 static const struct PlistKey plist_keys[] = {
    132   { "CFBundleIdentifier", EXTRACTOR_METATYPE_APPLICATION_ID },
    133   { "CFBundleName", EXTRACTOR_METATYPE_PACKAGE_NAME },
    134   { "CFBundleDisplayName", EXTRACTOR_METATYPE_PACKAGE_NAME },
    135   { "CFBundleShortVersionString", EXTRACTOR_METATYPE_SOFTWARE_VERSION },
    136   { "CFBundleVersion", EXTRACTOR_METATYPE_PACKAGE_VERSION },
    137   { "CFBundleExecutable", EXTRACTOR_METATYPE_FILENAME },
    138   { "CFBundlePackageType", EXTRACTOR_METATYPE_RESOURCE_TYPE },
    139   { "MinimumOSVersion", EXTRACTOR_METATYPE_MINIMUM_OS_VERSION },
    140   { "LSMinimumSystemVersion", EXTRACTOR_METATYPE_MINIMUM_OS_VERSION },
    141   { "NSHumanReadableCopyright", EXTRACTOR_METATYPE_COPYRIGHT },
    142   { "DTPlatformName", EXTRACTOR_METATYPE_TARGET_PLATFORM },
    143   { "DTSDKName", EXTRACTOR_METATYPE_TARGET_PLATFORM },
    144   { "UIDeviceFamily", EXTRACTOR_METATYPE_TARGET_PLATFORM },
    145   { "DTXcode", EXTRACTOR_METATYPE_TOOLCHAIN },
    146   { "DTCompiler", EXTRACTOR_METATYPE_TOOLCHAIN },
    147   { "BuildMachineOSBuild", EXTRACTOR_METATYPE_AUTHORING_OS },
    148   { "DeviceName", EXTRACTOR_METATYPE_DEVICE_MODEL },
    149   { "ProductType", EXTRACTOR_METATYPE_DEVICE_MODEL },
    150   { "ProductVersion", EXTRACTOR_METATYPE_DEVICE_MODEL },
    151   { "SerialNumber", EXTRACTOR_METATYPE_SERIAL },
    152   { "UniqueDeviceID", EXTRACTOR_METATYPE_SERIAL },
    153   { "LastBackupDate", EXTRACTOR_METATYPE_MODIFICATION_DATE },
    154   { NULL, EXTRACTOR_METATYPE_RESERVED }
    155 };
    156 
    157 
    158 /**
    159  * State of the binary plist we are decoding: everything the trailer
    160  * told us, already validated against the file size.
    161  */
    162 struct BPlist
    163 {
    164   /**
    165    * Number of objects, from the trailer.
    166    */
    167   uint64_t num_objects;
    168 
    169   /**
    170    * Absolute offset of the offset table.
    171    */
    172   uint64_t offset_table;
    173 
    174   /**
    175    * Index of the root object.
    176    */
    177   uint64_t top_object;
    178 
    179   /**
    180    * Width in bytes of an entry in the offset table, 1 to 8.
    181    */
    182   unsigned int offset_int_size;
    183 
    184   /**
    185    * Width in bytes of an object reference, 1 to 8.
    186    */
    187   unsigned int object_ref_size;
    188 };
    189 
    190 
    191 /**
    192  * Look up @a key in #plist_keys.
    193  *
    194  * @param key NUL-terminated key from the file
    195  * @param[out] type meta data type to use for the value
    196  * @return 1 if the key is well-known, 0 if not
    197  */
    198 static int
    199 lookup_key (const char *key,
    200             enum EXTRACTOR_MetaType *type)
    201 {
    202   for (unsigned int i = 0; NULL != plist_keys[i].key; i++)
    203     if (0 == strcmp (key,
    204                      plist_keys[i].key))
    205     {
    206       *type = plist_keys[i].type;
    207       return 1;
    208     }
    209   return 0;
    210 }
    211 
    212 
    213 /**
    214  * Read a big-endian unsigned integer of @a len bytes.
    215  *
    216  * @param p the bytes
    217  * @param len number of bytes, 1 to 8
    218  * @return the value
    219  */
    220 static uint64_t
    221 be_n (const unsigned char *p,
    222       unsigned int len)
    223 {
    224   uint64_t v = 0;
    225 
    226   for (unsigned int i = 0; i < len; i++)
    227     v = (v << 8) | (uint64_t) p[i];
    228   return v;
    229 }
    230 
    231 
    232 /**
    233  * Convert a UTF-16BE string to UTF-8.  Binary plists store their
    234  * non-ASCII strings this way; #EXTRACTOR_forensic_emit_utf16le_()
    235  * cannot be used because it reads the other byte order.
    236  *
    237  * @param in the UTF-16BE bytes
    238  * @param bytes number of bytes (not code units) in @a in
    239  * @param[out] out where to write the NUL-terminated UTF-8
    240  * @param out_size size of @a out in bytes
    241  * @return 1 on success, 0 if the input is malformed or does not fit
    242  */
    243 static int
    244 utf16be_to_utf8 (const unsigned char *in,
    245                  size_t bytes,
    246                  char *out,
    247                  size_t out_size)
    248 {
    249   size_t o = 0;
    250   size_t i = 0;
    251 
    252   while (i + 1 < bytes)
    253   {
    254     uint32_t cp = EXTRACTOR_forensic_be16_ (&in[i]);
    255 
    256     i += 2;
    257     if (0 == cp)
    258       break;   /* NUL terminator */
    259     if ( (0xD800 <= cp) && (cp <= 0xDBFF) )
    260     {
    261       uint32_t lo;
    262 
    263       if (i + 1 >= bytes)
    264         return 0;   /* truncated surrogate pair */
    265       lo = EXTRACTOR_forensic_be16_ (&in[i]);
    266       if ( (lo < 0xDC00) || (lo > 0xDFFF) )
    267         return 0;   /* unpaired high surrogate */
    268       i += 2;
    269       cp = 0x10000 + ((cp - 0xD800) << 10) + (lo - 0xDC00);
    270     }
    271     else if ( (0xDC00 <= cp) && (cp <= 0xDFFF) )
    272     {
    273       return 0;   /* stray low surrogate */
    274     }
    275     if (o + 4 >= out_size)
    276       return 0;   /* does not fit */
    277     if (cp < 0x80)
    278     {
    279       out[o++] = (char) cp;
    280     }
    281     else if (cp < 0x800)
    282     {
    283       out[o++] = (char) (0xC0 | (cp >> 6));
    284       out[o++] = (char) (0x80 | (cp & 0x3F));
    285     }
    286     else if (cp < 0x10000)
    287     {
    288       out[o++] = (char) (0xE0 | (cp >> 12));
    289       out[o++] = (char) (0x80 | ((cp >> 6) & 0x3F));
    290       out[o++] = (char) (0x80 | (cp & 0x3F));
    291     }
    292     else
    293     {
    294       out[o++] = (char) (0xF0 | (cp >> 18));
    295       out[o++] = (char) (0x80 | ((cp >> 12) & 0x3F));
    296       out[o++] = (char) (0x80 | ((cp >> 6) & 0x3F));
    297       out[o++] = (char) (0x80 | (cp & 0x3F));
    298     }
    299   }
    300   out[o] = '\0';
    301   return 1;
    302 }
    303 
    304 
    305 /**
    306  * Format @a unix_time as ISO 8601 in UTC, the spelling
    307  * #EXTRACTOR_forensic_emit_unix_time_() uses.
    308  *
    309  * @param unix_time seconds since 1970-01-01
    310  * @param[out] out where to write the NUL-terminated result
    311  * @param out_size size of @a out in bytes, at least 21
    312  * @return 1 on success, 0 if the value is implausible
    313  */
    314 static int
    315 format_time (int64_t unix_time,
    316              char *out,
    317              size_t out_size)
    318 {
    319   struct tm tm;
    320   time_t t = (time_t) unix_time;
    321 
    322   if ( (unix_time < 315532800LL) ||     /* 1980-01-01 */
    323        (unix_time > 4102444800LL) )     /* 2100-01-01 */
    324     return 0;
    325   if (NULL == gmtime_r (&t,
    326                         &tm))
    327     return 0;
    328   return (0 != strftime (out,
    329                          out_size,
    330                          "%Y-%m-%dT%H:%M:%SZ",
    331                          &tm)) ? 1 : 0;
    332 }
    333 
    334 
    335 /**
    336  * Resolve an object reference to its absolute offset in the file.
    337  *
    338  * The offset table is read one entry at a time rather than in one
    339  * block: at #PLIST_MAX_PAIRS pairs we need at most 129 entries, and
    340  * eight-byte offsets times #PLIST_MAX_OBJECTS would otherwise be an
    341  * 800 KB read on a file we may well reject a moment later.
    342  *
    343  * @param ec extraction context
    344  * @param st validated trailer data
    345  * @param ref object index
    346  * @param[out] off absolute offset of the object
    347  * @return 1 on success, 0 if @a ref is out of range or the offset it
    348  *         names does not point into the object area
    349  */
    350 static int
    351 object_offset (struct EXTRACTOR_ExtractContext *ec,
    352                const struct BPlist *st,
    353                uint64_t ref,
    354                uint64_t *off)
    355 {
    356   unsigned char raw[8];
    357   uint64_t v;
    358 
    359   if (ref >= st->num_objects)
    360     return 0;
    361   /* no overflow: num_objects <= PLIST_MAX_OBJECTS, offset_int_size <= 8
    362      and offset_table + num_objects * offset_int_size was checked to be
    363      within the file when the trailer was parsed */
    364   if (! EXTRACTOR_forensic_read_ (ec,
    365                                   (int64_t) (st->offset_table
    366                                              + ref * st->offset_int_size),
    367                                   raw,
    368                                   st->offset_int_size))
    369     return 0;
    370   v = be_n (raw,
    371             st->offset_int_size);
    372   /* objects live between the 8-byte signature and the offset table */
    373   if ( (v < 8) ||
    374        (v >= st->offset_table) )
    375     return 0;
    376   *off = v;
    377   return 1;
    378 }
    379 
    380 
    381 /**
    382  * Read the object reference stored at @a pos.
    383  *
    384  * @param ec extraction context
    385  * @param st validated trailer data
    386  * @param pos absolute offset of the reference
    387  * @param[out] ref where to store the object index
    388  * @return 1 on success, 0 on error
    389  */
    390 static int
    391 read_ref (struct EXTRACTOR_ExtractContext *ec,
    392           const struct BPlist *st,
    393           uint64_t pos,
    394           uint64_t *ref)
    395 {
    396   unsigned char raw[8];
    397 
    398   if (! EXTRACTOR_forensic_read_ (ec,
    399                                   (int64_t) pos,
    400                                   raw,
    401                                   st->object_ref_size))
    402     return 0;
    403   *ref = be_n (raw,
    404                st->object_ref_size);
    405   return 1;
    406 }
    407 
    408 
    409 /**
    410  * Decode the integer object at @a pos.
    411  *
    412  * @param ec extraction context
    413  * @param st validated trailer data
    414  * @param pos absolute offset of the object
    415  * @param[out] value the value; eight-byte integers are signed, the
    416  *        shorter widths unsigned, as CFBinaryPlist writes them
    417  * @param[out] next offset just past the object, may be NULL
    418  * @return 1 on success, 0 if there is no integer object at @a pos
    419  */
    420 static int
    421 read_integer (struct EXTRACTOR_ExtractContext *ec,
    422               const struct BPlist *st,
    423               uint64_t pos,
    424               int64_t *value,
    425               uint64_t *next)
    426 {
    427   unsigned char marker;
    428   unsigned char raw[8];
    429   unsigned int nbytes;
    430 
    431   if (pos >= st->offset_table)
    432     return 0;
    433   if (! EXTRACTOR_forensic_read_ (ec,
    434                                   (int64_t) pos,
    435                                   &marker,
    436                                   1))
    437     return 0;
    438   if (0x10 != (marker & 0xF0))
    439     return 0;
    440   if (3 < (marker & 0x0F))
    441     return 0;   /* 128-bit integers exist; we do not need them */
    442   nbytes = 1u << (marker & 0x0F);
    443   if ( (pos + 1 + nbytes) > st->offset_table)
    444     return 0;
    445   if (! EXTRACTOR_forensic_read_ (ec,
    446                                   (int64_t) (pos + 1),
    447                                   raw,
    448                                   nbytes))
    449     return 0;
    450   /* one, two and four byte integers are unsigned and always fit; the
    451      eight byte form is signed, which is exactly what the cast does */
    452   *value = (int64_t) be_n (raw,
    453                            nbytes);
    454   if (NULL != next)
    455     *next = pos + 1 + nbytes;
    456   return 1;
    457 }
    458 
    459 
    460 /**
    461  * Decode the marker byte at @a pos and the collection/string length
    462  * that goes with it.  A low nibble of 0x0F means the length follows as
    463  * an integer object rather than being the nibble itself.
    464  *
    465  * @param ec extraction context
    466  * @param st validated trailer data
    467  * @param pos absolute offset of the object
    468  * @param[out] marker the marker byte
    469  * @param[out] len the decoded length
    470  * @param[out] body offset of the first byte after marker and length
    471  * @return 1 on success, 0 on error
    472  */
    473 static int
    474 read_marker (struct EXTRACTOR_ExtractContext *ec,
    475              const struct BPlist *st,
    476              uint64_t pos,
    477              unsigned char *marker,
    478              uint64_t *len,
    479              uint64_t *body)
    480 {
    481   if (pos >= st->offset_table)
    482     return 0;
    483   if (! EXTRACTOR_forensic_read_ (ec,
    484                                   (int64_t) pos,
    485                                   marker,
    486                                   1))
    487     return 0;
    488   if (0x0F != (*marker & 0x0F))
    489   {
    490     *len = *marker & 0x0F;
    491     *body = pos + 1;
    492     return 1;
    493   }
    494   {
    495     int64_t v;
    496 
    497     if (! read_integer (ec,
    498                         st,
    499                         pos + 1,
    500                         &v,
    501                         body))
    502       return 0;
    503     if ( (v < 0) ||
    504          ((uint64_t) v > st->offset_table) )
    505       return 0;
    506     *len = (uint64_t) v;
    507   }
    508   return 1;
    509 }
    510 
    511 
    512 /**
    513  * Render the object @a ref as a string, if it is one of the scalar
    514  * kinds we care about.  Anything else -- arrays, sets, dictionaries,
    515  * UIDs, nulls -- is deliberately not decoded.
    516  *
    517  * @param ec extraction context
    518  * @param st validated trailer data
    519  * @param ref object index
    520  * @param[out] out where to write the NUL-terminated UTF-8 rendering
    521  * @param out_size size of @a out in bytes
    522  * @return 1 on success, 0 if the object is of another kind or is
    523  *         malformed
    524  */
    525 static int
    526 render_value (struct EXTRACTOR_ExtractContext *ec,
    527               const struct BPlist *st,
    528               uint64_t ref,
    529               char *out,
    530               size_t out_size)
    531 {
    532   uint64_t pos;
    533   uint64_t len;
    534   uint64_t body;
    535   unsigned char marker;
    536 
    537   if (! object_offset (ec,
    538                        st,
    539                        ref,
    540                        &pos))
    541     return 0;
    542   if (! read_marker (ec,
    543                      st,
    544                      pos,
    545                      &marker,
    546                      &len,
    547                      &body))
    548     return 0;
    549   switch (marker & 0xF0)
    550   {
    551   case 0x00:
    552     if (0x08 == marker)
    553     {
    554       strcpy (out,
    555               "false");
    556       return 1;
    557     }
    558     if (0x09 == marker)
    559     {
    560       strcpy (out,
    561               "true");
    562       return 1;
    563     }
    564     return 0;
    565   case 0x10:
    566     {
    567       int64_t v;
    568 
    569       if (! read_integer (ec,
    570                           st,
    571                           pos,
    572                           &v,
    573                           NULL))
    574         return 0;
    575       snprintf (out,
    576                 out_size,
    577                 "%lld",
    578                 (long long) v);
    579       return 1;
    580     }
    581   case 0x20:
    582     {
    583       unsigned char raw[8];
    584       unsigned int nbytes = 1u << (marker & 0x0F);
    585       double d;
    586 
    587       if ( (4 != nbytes) &&
    588            (8 != nbytes) )
    589         return 0;
    590       if ( (pos + 1 + nbytes) > st->offset_table)
    591         return 0;
    592       if (! EXTRACTOR_forensic_read_ (ec,
    593                                       (int64_t) (pos + 1),
    594                                       raw,
    595                                       nbytes))
    596         return 0;
    597       if (4 == nbytes)
    598       {
    599         uint32_t bits = EXTRACTOR_forensic_be32_ (raw);
    600         float f;
    601 
    602         memcpy (&f,
    603                 &bits,
    604                 sizeof (f));
    605         d = (double) f;
    606       }
    607       else
    608       {
    609         uint64_t bits = EXTRACTOR_forensic_be64_ (raw);
    610 
    611         memcpy (&d,
    612                 &bits,
    613                 sizeof (d));
    614       }
    615       if (! isfinite (d))
    616         return 0;
    617       snprintf (out,
    618                 out_size,
    619                 "%.10g",
    620                 d);
    621       return 1;
    622     }
    623   case 0x30:
    624     {
    625       unsigned char raw[8];
    626       uint64_t bits;
    627       double d;
    628 
    629       if (0x33 != marker)
    630         return 0;
    631       if ( (pos + 9) > st->offset_table)
    632         return 0;
    633       if (! EXTRACTOR_forensic_read_ (ec,
    634                                       (int64_t) (pos + 1),
    635                                       raw,
    636                                       8))
    637         return 0;
    638       bits = EXTRACTOR_forensic_be64_ (raw);
    639       memcpy (&d,
    640               &bits,
    641               sizeof (d));
    642       /* seconds since 2001-01-01; reject anything that cannot be a
    643          date before converting, so the cast below is defined */
    644       if ( (! isfinite (d)) ||
    645            (d < -1.0e12) ||
    646            (d > 1.0e12) )
    647         return 0;
    648       return format_time ((int64_t) d + PLIST_EPOCH_DELTA,
    649                           out,
    650                           out_size);
    651     }
    652   case 0x40:
    653     /* the payload of a data object is not text; its size is still
    654        worth knowing */
    655     if (body + len < body)
    656       return 0;   /* overflow */
    657     if (body + len > st->offset_table)
    658       return 0;
    659     snprintf (out,
    660               out_size,
    661               "%llu bytes of data",
    662               (unsigned long long) len);
    663     return 1;
    664   case 0x50:
    665     {
    666       unsigned char raw[PLIST_MAX_TEXT];
    667 
    668       if (0 == len)
    669         return 0;
    670       if (len > sizeof (raw))
    671         len = sizeof (raw);
    672       if (body + len > st->offset_table)
    673         return 0;
    674       if (! EXTRACTOR_forensic_read_ (ec,
    675                                       (int64_t) body,
    676                                       raw,
    677                                       (size_t) len))
    678         return 0;
    679       /* an "ASCII" string with the high bit set is not one; refuse it
    680          rather than guess at a code page */
    681       for (uint64_t i = 0; i < len; i++)
    682         if (0 != (raw[i] & 0x80))
    683           return 0;
    684       if (len >= out_size)
    685         len = out_size - 1;
    686       memcpy (out,
    687               raw,
    688               (size_t) len);
    689       out[len] = '\0';
    690       return 1;
    691     }
    692   case 0x60:
    693     {
    694       unsigned char raw[2 * PLIST_MAX_TEXT];
    695       uint64_t bytes;
    696 
    697       if (0 == len)
    698         return 0;
    699       if (len > sizeof (raw) / 2)
    700         len = sizeof (raw) / 2;
    701       bytes = 2 * len;
    702       if (body + bytes > st->offset_table)
    703         return 0;
    704       if (! EXTRACTOR_forensic_read_ (ec,
    705                                       (int64_t) body,
    706                                       raw,
    707                                       (size_t) bytes))
    708         return 0;
    709       return utf16be_to_utf8 (raw,
    710                               (size_t) bytes,
    711                               out,
    712                               out_size);
    713     }
    714   default:
    715     return 0;
    716   }
    717 }
    718 
    719 
    720 /**
    721  * Decode the top-level dictionary of a binary property list and report
    722  * the keys we recognise.
    723  *
    724  * @param ec extraction context
    725  * @param st validated trailer data
    726  * @return 1 if the caller should stop extracting, 0 to continue
    727  */
    728 static int
    729 extract_binary_dict (struct EXTRACTOR_ExtractContext *ec,
    730                      const struct BPlist *st)
    731 {
    732   uint64_t pos;
    733   uint64_t count;
    734   uint64_t body;
    735   uint64_t pairs;
    736   unsigned char marker;
    737   unsigned int unknown = 0;
    738 
    739   if (! object_offset (ec,
    740                        st,
    741                        st->top_object,
    742                        &pos))
    743     return 0;
    744   if (! read_marker (ec,
    745                      st,
    746                      pos,
    747                      &marker,
    748                      &count,
    749                      &body))
    750     return 0;
    751   if (0xD0 != (marker & 0xF0))
    752     return 0;   /* the root is not a dictionary; nothing for us here */
    753   if ( (0 == count) ||
    754        (count > PLIST_MAX_OBJECTS) )
    755     return 0;
    756   /* the reference block holds count keys followed by count values */
    757   if (count > (UINT64_MAX / (2 * (uint64_t) st->object_ref_size)))
    758     return 0;
    759   if (body + 2 * count * st->object_ref_size > st->offset_table)
    760     return 0;
    761   pairs = (count < PLIST_MAX_PAIRS) ? count : PLIST_MAX_PAIRS;
    762   for (uint64_t i = 0; i < pairs; i++)
    763   {
    764     char key[PLIST_MAX_TEXT];
    765     char value[PLIST_MAX_TEXT];
    766     uint64_t kref;
    767     uint64_t vref;
    768     enum EXTRACTOR_MetaType type;
    769 
    770     if (! read_ref (ec,
    771                     st,
    772                     body + i * st->object_ref_size,
    773                     &kref))
    774       return 0;
    775     if (! read_ref (ec,
    776                     st,
    777                     body + (count + i) * st->object_ref_size,
    778                     &vref))
    779       return 0;
    780     if (! render_value (ec,
    781                         st,
    782                         kref,
    783                         key,
    784                         sizeof (key)))
    785       continue;   /* key is not a string; skip the pair */
    786     if (! render_value (ec,
    787                         st,
    788                         vref,
    789                         value,
    790                         sizeof (value)))
    791       continue;   /* value is a collection or something we do not decode */
    792     if (lookup_key (key,
    793                     &type))
    794     {
    795       if (EXTRACTOR_forensic_emit_text_ (ec,
    796                                          PLIST_PLUGIN,
    797                                          type,
    798                                          value,
    799                                          strlen (value)))
    800         return 1;
    801       continue;
    802     }
    803     if (unknown >= PLIST_MAX_UNKNOWN)
    804       continue;
    805     if (strlen (value) > PLIST_MAX_UNKNOWN_VALUE)
    806       continue;
    807     unknown++;
    808     if (EXTRACTOR_forensic_emit_ (ec,
    809                                   PLIST_PLUGIN,
    810                                   EXTRACTOR_METATYPE_UNKNOWN,
    811                                   "%s: %s",
    812                                   key,
    813                                   value))
    814       return 1;
    815   }
    816   return 0;
    817 }
    818 
    819 
    820 /**
    821  * Handle a file that starts with the `bplist' signature.
    822  *
    823  * @param ec extraction context
    824  * @param size size of the file
    825  * @param header the first eight bytes of the file
    826  */
    827 static void
    828 extract_binary (struct EXTRACTOR_ExtractContext *ec,
    829                 uint64_t size,
    830                 const unsigned char *header)
    831 {
    832   unsigned char trailer[32];
    833   struct BPlist st;
    834 
    835   if (! EXTRACTOR_forensic_read_ (ec,
    836                                   (int64_t) (size - 32),
    837                                   trailer,
    838                                   sizeof (trailer)))
    839     return;
    840   /* CFBinaryPlistTrailer: five unused bytes, sortVersion,
    841      offsetIntSize, objectRefSize, then three big-endian 64-bit
    842      fields.  (Note that the leading run of unused bytes is five, not
    843      six: the sixth byte is _sortVersion.) */
    844   st.offset_int_size = trailer[6];
    845   st.object_ref_size = trailer[7];
    846   st.num_objects = EXTRACTOR_forensic_be64_ (&trailer[8]);
    847   st.top_object = EXTRACTOR_forensic_be64_ (&trailer[16]);
    848   st.offset_table = EXTRACTOR_forensic_be64_ (&trailer[24]);
    849   if ( (st.offset_int_size < 1) ||
    850        (st.offset_int_size > 8) ||
    851        (st.object_ref_size < 1) ||
    852        (st.object_ref_size > 8) )
    853     return;
    854   if ( (0 == st.num_objects) ||
    855        (st.num_objects > PLIST_MAX_OBJECTS) )
    856     return;
    857   if (st.top_object >= st.num_objects)
    858     return;
    859   if ( (st.offset_table < 8) ||
    860        (st.offset_table > size - 32) )
    861     return;
    862   /* no overflow: num_objects <= 100000 and offset_int_size <= 8 */
    863   if (st.num_objects * st.offset_int_size > size - 32 - st.offset_table)
    864     return;
    865   if (EXTRACTOR_forensic_emit_text_ (ec,
    866                                      PLIST_PLUGIN,
    867                                      EXTRACTOR_METATYPE_MIMETYPE,
    868                                      PLIST_MIME,
    869                                      strlen (PLIST_MIME)))
    870     return;
    871   if (EXTRACTOR_forensic_emit_text_ (ec,
    872                                      PLIST_PLUGIN,
    873                                      EXTRACTOR_METATYPE_FORMAT,
    874                                      "Binary property list",
    875                                      strlen ("Binary property list")))
    876     return;
    877   if (EXTRACTOR_forensic_emit_text_ (ec,
    878                                      PLIST_PLUGIN,
    879                                      EXTRACTOR_METATYPE_FORMAT_VERSION,
    880                                      (const char *) &header[6],
    881                                      2))
    882     return;
    883   if (EXTRACTOR_forensic_emit_size_ (ec,
    884                                      PLIST_PLUGIN,
    885                                      EXTRACTOR_METATYPE_ENTRY_COUNT,
    886                                      st.num_objects))
    887     return;
    888   (void) extract_binary_dict (ec,
    889                               &st);
    890 }
    891 
    892 
    893 /**
    894  * Find @a needle in @a haystack.  Written out rather than using
    895  * memmem(), which is a GNU extension.
    896  *
    897  * @param haystack where to search
    898  * @param hlen number of bytes in @a haystack
    899  * @param needle what to search for
    900  * @param nlen number of bytes in @a needle
    901  * @return pointer to the first occurrence, NULL if there is none
    902  */
    903 static const char *
    904 find_bytes (const char *haystack,
    905             size_t hlen,
    906             const char *needle,
    907             size_t nlen)
    908 {
    909   if ( (0 == nlen) ||
    910        (hlen < nlen) )
    911     return NULL;
    912   for (size_t i = 0; i + nlen <= hlen; i++)
    913     if (0 == memcmp (&haystack[i],
    914                      needle,
    915                      nlen))
    916       return &haystack[i];
    917   return NULL;
    918 }
    919 
    920 
    921 /**
    922  * Copy XML character data, resolving the five predefined entities and
    923  * numeric character references.
    924  *
    925  * @param in the text between the tags
    926  * @param len number of bytes in @a in
    927  * @param[out] out where to write the NUL-terminated result
    928  * @param out_size size of @a out in bytes
    929  * @return 1 on success, 0 if the text does not fit
    930  */
    931 static int
    932 xml_unescape (const char *in,
    933               size_t len,
    934               char *out,
    935               size_t out_size)
    936 {
    937   size_t o = 0;
    938   size_t i = 0;
    939 
    940   while (i < len)
    941   {
    942     const char *semi;
    943     size_t elen;
    944 
    945     if ('&' != in[i])
    946     {
    947       if (o + 1 >= out_size)
    948         return 0;
    949       out[o++] = in[i++];
    950       continue;
    951     }
    952     semi = find_bytes (&in[i],
    953                        len - i,
    954                        ";",
    955                        1);
    956     if ( (NULL == semi) ||
    957          ((size_t) (semi - &in[i]) > 10) )
    958     {
    959       if (o + 1 >= out_size)
    960         return 0;
    961       out[o++] = in[i++];   /* a bare ampersand; keep it */
    962       continue;
    963     }
    964     elen = (size_t) (semi - &in[i]) + 1;
    965     if (o + 4 >= out_size)
    966       return 0;
    967     if ( (5 == elen) &&
    968          (0 == memcmp (&in[i], "&amp;", 5)) )
    969       out[o++] = '&';
    970     else if ( (4 == elen) &&
    971               (0 == memcmp (&in[i], "&lt;", 4)) )
    972       out[o++] = '<';
    973     else if ( (4 == elen) &&
    974               (0 == memcmp (&in[i], "&gt;", 4)) )
    975       out[o++] = '>';
    976     else if ( (6 == elen) &&
    977               (0 == memcmp (&in[i], "&quot;", 6)) )
    978       out[o++] = '"';
    979     else if ( (6 == elen) &&
    980               (0 == memcmp (&in[i], "&apos;", 6)) )
    981       out[o++] = '\'';
    982     else if ( (2 < elen) &&
    983               ('#' == in[i + 1]) )
    984     {
    985       unsigned long cp;
    986       char num[12];
    987       size_t nlen = elen - 3;
    988 
    989       if (nlen >= sizeof (num))
    990         return 0;
    991       memcpy (num,
    992               &in[i + 2],
    993               nlen);
    994       num[nlen] = '\0';
    995       cp = strtoul (('x' == num[0]) || ('X' == num[0])
    996                     ? &num[1] : num,
    997                     NULL,
    998                     (('x' == num[0]) || ('X' == num[0])) ? 16 : 10);
    999       if ( (0 == cp) ||
   1000            (cp > 0x10FFFF) ||
   1001            ( (0xD800 <= cp) && (cp <= 0xDFFF) ) )
   1002         return 0;
   1003       if (cp < 0x80)
   1004       {
   1005         out[o++] = (char) cp;
   1006       }
   1007       else if (cp < 0x800)
   1008       {
   1009         out[o++] = (char) (0xC0 | (cp >> 6));
   1010         out[o++] = (char) (0x80 | (cp & 0x3F));
   1011       }
   1012       else if (cp < 0x10000)
   1013       {
   1014         out[o++] = (char) (0xE0 | (cp >> 12));
   1015         out[o++] = (char) (0x80 | ((cp >> 6) & 0x3F));
   1016         out[o++] = (char) (0x80 | (cp & 0x3F));
   1017       }
   1018       else
   1019       {
   1020         out[o++] = (char) (0xF0 | (cp >> 18));
   1021         out[o++] = (char) (0x80 | ((cp >> 12) & 0x3F));
   1022         out[o++] = (char) (0x80 | ((cp >> 6) & 0x3F));
   1023         out[o++] = (char) (0x80 | (cp & 0x3F));
   1024       }
   1025     }
   1026     else
   1027     {
   1028       out[o++] = '&';   /* entity we do not know; keep the text */
   1029       i++;
   1030       continue;
   1031     }
   1032     i += elen;
   1033   }
   1034   out[o] = '\0';
   1035   return 1;
   1036 }
   1037 
   1038 
   1039 /**
   1040  * One value element we are prepared to read out of an XML plist.
   1041  */
   1042 struct XmlValueTag
   1043 {
   1044   /**
   1045    * Opening tag, including the angle brackets.
   1046    */
   1047   const char *open;
   1048 
   1049   /**
   1050    * Matching closing tag, NULL for an empty element such as <true/>.
   1051    */
   1052   const char *close;
   1053 
   1054   /**
   1055    * Text to report for an empty element, NULL otherwise.
   1056    */
   1057   const char *literal;
   1058 };
   1059 
   1060 
   1061 /**
   1062  * Value elements of the plist DTD that carry a scalar.  Collections
   1063  * (<dict>, <array>) are absent on purpose: we do not descend.
   1064  */
   1065 static const struct XmlValueTag xml_value_tags[] = {
   1066   { "<string>", "</string>", NULL },
   1067   { "<integer>", "</integer>", NULL },
   1068   { "<real>", "</real>", NULL },
   1069   { "<date>", "</date>", NULL },
   1070   { "<true/>", NULL, "true" },
   1071   { "<false/>", NULL, "false" },
   1072   { NULL, NULL, NULL }
   1073 };
   1074 
   1075 
   1076 /**
   1077  * Scan an XML property list for <key>/value pairs.
   1078  *
   1079  * This is a bounded byte scan and not an XML parser: it will be
   1080  * confused by a <key> inside a comment or a CDATA section, and it does
   1081  * not track nesting, so a key of the same name inside a nested
   1082  * dictionary is treated like a top-level one.  For a first pass whose
   1083  * job is to say "this bundle is org.example.app, built with that SDK"
   1084  * that is an acceptable trade; adding a real XML parser would mean a
   1085  * dependency and a great deal more attack surface.  What it must never
   1086  * do -- and does not -- is read outside @a buf.
   1087  *
   1088  * @param ec extraction context
   1089  * @param buf the first bytes of the file
   1090  * @param len number of bytes in @a buf
   1091  * @return 1 if the caller should stop extracting, 0 to continue
   1092  */
   1093 static int
   1094 scan_xml (struct EXTRACTOR_ExtractContext *ec,
   1095           const char *buf,
   1096           size_t len)
   1097 {
   1098   const char *p = buf;
   1099   const char *end = buf + len;
   1100   unsigned int pairs = 0;
   1101   unsigned int unknown = 0;
   1102 
   1103   while ( (p < end) &&
   1104           (pairs < PLIST_MAX_PAIRS) )
   1105   {
   1106     const char *k;
   1107     const char *ke;
   1108     const char *q;
   1109     char key[PLIST_MAX_TEXT];
   1110     char value[PLIST_MAX_TEXT];
   1111     enum EXTRACTOR_MetaType type;
   1112     unsigned int i;
   1113 
   1114     k = find_bytes (p,
   1115                     (size_t) (end - p),
   1116                     "<key>",
   1117                     5);
   1118     if (NULL == k)
   1119       break;
   1120     k += 5;
   1121     ke = find_bytes (k,
   1122                      (size_t) (end - k),
   1123                      "</key>",
   1124                      6);
   1125     if (NULL == ke)
   1126       break;
   1127     p = ke + 6;
   1128     pairs++;
   1129     if (! xml_unescape (k,
   1130                         (size_t) (ke - k),
   1131                         key,
   1132                         sizeof (key)))
   1133       continue;
   1134     q = p;
   1135     while ( (q < end) &&
   1136             ( (' ' == *q) || ('\t' == *q) ||
   1137               ('\r' == *q) || ('\n' == *q) ) )
   1138       q++;
   1139     if ( (q >= end) ||
   1140          ('<' != *q) )
   1141       continue;
   1142     for (i = 0; NULL != xml_value_tags[i].open; i++)
   1143     {
   1144       size_t olen = strlen (xml_value_tags[i].open);
   1145 
   1146       if ((size_t) (end - q) < olen)
   1147         continue;
   1148       if (0 != memcmp (q,
   1149                        xml_value_tags[i].open,
   1150                        olen))
   1151         continue;
   1152       if (NULL == xml_value_tags[i].close)
   1153       {
   1154         strcpy (value,
   1155                 xml_value_tags[i].literal);
   1156         p = q + olen;
   1157       }
   1158       else
   1159       {
   1160         const char *vs = q + olen;
   1161         const char *ve;
   1162         size_t clen = strlen (xml_value_tags[i].close);
   1163 
   1164         ve = find_bytes (vs,
   1165                          (size_t) (end - vs),
   1166                          xml_value_tags[i].close,
   1167                          clen);
   1168         if (NULL == ve)
   1169           return 0;   /* truncated; nothing more to be had */
   1170         p = ve + clen;
   1171         if (! xml_unescape (vs,
   1172                             (size_t) (ve - vs),
   1173                             value,
   1174                             sizeof (value)))
   1175           value[0] = '\0';
   1176       }
   1177       break;
   1178     }
   1179     if (NULL == xml_value_tags[i].open)
   1180       continue;   /* <dict>, <array>, <data> or an unknown element */
   1181     if ('\0' == value[0])
   1182       continue;
   1183     if (lookup_key (key,
   1184                     &type))
   1185     {
   1186       if (EXTRACTOR_forensic_emit_text_ (ec,
   1187                                          PLIST_PLUGIN,
   1188                                          type,
   1189                                          value,
   1190                                          strlen (value)))
   1191         return 1;
   1192       continue;
   1193     }
   1194     if (unknown >= PLIST_MAX_UNKNOWN)
   1195       continue;
   1196     if (strlen (value) > PLIST_MAX_UNKNOWN_VALUE)
   1197       continue;
   1198     unknown++;
   1199     if (EXTRACTOR_forensic_emit_ (ec,
   1200                                   PLIST_PLUGIN,
   1201                                   EXTRACTOR_METATYPE_UNKNOWN,
   1202                                   "%s: %s",
   1203                                   key,
   1204                                   value))
   1205       return 1;
   1206   }
   1207   return 0;
   1208 }
   1209 
   1210 
   1211 /**
   1212  * Handle a file that looks like an XML property list.
   1213  *
   1214  * @param ec extraction context
   1215  * @param size size of the file
   1216  */
   1217 static void
   1218 extract_xml (struct EXTRACTOR_ExtractContext *ec,
   1219              uint64_t size)
   1220 {
   1221   char *buf;
   1222   size_t len;
   1223 
   1224   len = (size < PLIST_XML_SCAN_SIZE)
   1225         ? (size_t) size
   1226         : PLIST_XML_SCAN_SIZE;
   1227   buf = malloc (len);
   1228   if (NULL == buf)
   1229     return;
   1230   if (! EXTRACTOR_forensic_read_ (ec,
   1231                                   0,
   1232                                   buf,
   1233                                   len))
   1234   {
   1235     free (buf);
   1236     return;
   1237   }
   1238   if (EXTRACTOR_forensic_emit_text_ (ec,
   1239                                      PLIST_PLUGIN,
   1240                                      EXTRACTOR_METATYPE_MIMETYPE,
   1241                                      PLIST_MIME,
   1242                                      strlen (PLIST_MIME)))
   1243   {
   1244     free (buf);
   1245     return;
   1246   }
   1247   if (EXTRACTOR_forensic_emit_text_ (ec,
   1248                                      PLIST_PLUGIN,
   1249                                      EXTRACTOR_METATYPE_FORMAT,
   1250                                      "XML property list",
   1251                                      strlen ("XML property list")))
   1252   {
   1253     free (buf);
   1254     return;
   1255   }
   1256   (void) scan_xml (ec,
   1257                    buf,
   1258                    len);
   1259   free (buf);
   1260 }
   1261 
   1262 
   1263 /**
   1264  * Decide whether @a buf is the beginning of an XML property list.
   1265  *
   1266  * Both conditions matter.  The `<plist' is what keeps us from
   1267  * claiming every XML document on the volume; the leading `<' is what
   1268  * keeps us from claiming a file that merely mentions a plist in its
   1269  * first kilobyte.
   1270  *
   1271  * @param buf the first bytes of the file
   1272  * @param len number of bytes in @a buf
   1273  * @return 1 if this is an XML plist, 0 if not
   1274  */
   1275 static int
   1276 looks_like_xml_plist (const char *buf,
   1277                       size_t len)
   1278 {
   1279   size_t i = 0;
   1280 
   1281   if ( (len >= 3) &&
   1282        (0xEF == (unsigned char) buf[0]) &&
   1283        (0xBB == (unsigned char) buf[1]) &&
   1284        (0xBF == (unsigned char) buf[2]) )
   1285     i = 3;   /* UTF-8 byte order mark */
   1286   while ( (i < len) &&
   1287           ( (' ' == buf[i]) || ('\t' == buf[i]) ||
   1288             ('\r' == buf[i]) || ('\n' == buf[i]) ) )
   1289     i++;
   1290   if ( (i >= len) ||
   1291        ('<' != buf[i]) )
   1292     return 0;
   1293   if (NULL != find_bytes (buf,
   1294                           len,
   1295                           "<plist",
   1296                           6))
   1297     return 1;
   1298   if (NULL != find_bytes (buf,
   1299                           len,
   1300                           "<!DOCTYPE plist",
   1301                           15))
   1302     return 1;
   1303   return 0;
   1304 }
   1305 
   1306 
   1307 /**
   1308  * Main entry method for the plist extraction plugin.
   1309  *
   1310  * @param ec extraction context provided to the plugin
   1311  */
   1312 void
   1313 EXTRACTOR_plist_extract_method (struct EXTRACTOR_ExtractContext *ec);
   1314 
   1315 void
   1316 EXTRACTOR_plist_extract_method (struct EXTRACTOR_ExtractContext *ec)
   1317 {
   1318   unsigned char head[PLIST_SNIFF_SIZE];
   1319   uint64_t size;
   1320   size_t hlen;
   1321 
   1322   size = ec->get_size (ec->cls);
   1323   if ( (UINT64_MAX == size) ||
   1324        (size < 16) )
   1325     return;
   1326   hlen = (size < sizeof (head)) ? (size_t) size : sizeof (head);
   1327   if (! EXTRACTOR_forensic_read_ (ec,
   1328                                   0,
   1329                                   head,
   1330                                   hlen))
   1331     return;
   1332   if ( (hlen >= 8) &&
   1333        (0 == memcmp (head,
   1334                      "bplist",
   1335                      6)) &&
   1336        (isdigit ((unsigned char) head[6])) &&
   1337        (isdigit ((unsigned char) head[7])) )
   1338   {
   1339     if (size < 40)
   1340       return;   /* eight-byte signature plus a 32-byte trailer at least */
   1341     extract_binary (ec,
   1342                     size,
   1343                     head);
   1344     return;
   1345   }
   1346   if (looks_like_xml_plist ((const char *) head,
   1347                             hlen))
   1348     extract_xml (ec,
   1349                  size);
   1350 }
   1351 
   1352 
   1353 /* end of plist_extractor.c */