Saturday, December 29, 2018

Making NSKeyedArchives human readable

If you've been doing macOS analysis, you are definitely familiar with the (now not so new) serialized plist format also known as an NSKeyedArchive. There are parsers available to extract data from this format, such as the ccl_bplist from Alex Caithness (some more sophisticated ones listed at the bottom of this post). I've been using this library in mac_apt and other projects too. That is all old news.

Need for a human readable version

I always like to manually verify the results of my code by looking at the raw plist values to make sure my analysis programs parsed the right values as well as if they missed something. That isn't possible with NSKeyedArchives.

The ccl_bplist parser is great if you want to explore the structure of a plist interactively with python or programatically. What it does not do is auto-generate and save the deserialized version of that plist as a new human readable plist file. So that is what I set out to do yesterday. 

Long story short, the code is available on github here

Below are screenshots showing an NSKeyedArchive sfl2 file (Figure 1) and its deserialized human readable form (Figure 2).
Figure 1 - NSKeyedArchive


Figure 2 - Deserialized form of NSKeyedArchive


Using ccl_bplist and biplist, this should have been a 2-5 line program, ccl_bplist to deserialize and biplist to write out the new plist. However it turned out to be a few lines more than that as I had to write a short recursion function to process the plist data because the ccl_bplist also includes $class information which needed to be stripped out. Also as I found out, this will only work with Python 3 because Python 2 does not distinguish between the types str and bytes. In Python 2, all these (below) are the same:
 '\x12\x34'
 b'\x12\x34'
 bytes('\x12\x34') 

This creates a problem as biplist cannot distinguish between binary blobs and strings as they all appear to be strings and it fails when it encounters a byte that cannot be encoded as a string. No problems with python 3 as these are different distinct types there.

The code is here.

Alternatives

Since writing this, I did find another library (bpylist2) that has similar functions, reading/writing binary plists as well as creating/reading keyed archives. I haven't tested it yet.

(Update) Our good friends at Aon also maintain a library for plist parsing which seems pretty solid and worth checking out here.

2 comments:

  1. Cool work. FYI we have an NSKeyedArchiveParser available in plistutils (https://github.com/strozfriedberg/plistutils/blob/master/plistutils/nskeyedarchiver.py) that supports more classes than ccl_bplist, and I like our organization and extensability a little better. You can also find plistutils on PyPI (https://pypi.org/project/plistutils/). I hadn't seen bpylist2 before, but I think we support more NS classes than it, as well. Of course, you'd still need to write the parsed archive out to a plist yourself with plistutils.

    I'd be interested in feedback if you test it out.

    -Geoff

    ReplyDelete
  2. Hmm, this does look pretty fantastic. For some reason your code does not show up in a google search (at least on the first page), so I missed it! I will likely be using your lib in mac_apt as I clean up the sfl parsing and add slf2 parsing to it. Thanks for letting me know.

    ReplyDelete