This post will be about key providers, which are the first kind of extension to the scan engine we’re going to see. Key providers are nothing else than a convenient way to provide keys through scripting to files which require a decryption key (e.g. an encrypted PDF).
Let’s take for instance an encrypted Zip file. If we’re not doing a batch scan, the Profiler will ask the user with its dialog to enter a decryption key. While this dialog already has the ability to accept multiple keys and also remember them, there are things it can’t do. For example it is not suitable for trying out key dictionaries (copy and pasting them is inefficient) or to generate a key based on environmental factors (like the name of the file requiring the decryption).
This may sound all a bit complicated, but don’t worry. One of the main objectives of the Profiler is to allow users to do things in the simplest way possible. Thus, showing a practical sample is the best way to demonstrate how it all works.
You’ll notice that the upcoming version of the Profiler contains a keyp.cfg_sample in its config directory. This file can be used as template to create our first provider, just rename it to keyp.cfg. As all configuration files, this is an INI as well. This is what an entry for a key provider looks like:
[KeyProvider Test] file = key_provider.py callback = keyProvider ; this is an optional field, you can omit it and the provider will be used for any format formats = Zip|PDF
Which is pretty much self explaining. It tells the Profiler where our callback is located (the relative path defaults to the plugins/python directory) and it can also optionally specify the formats which may be used in conjunction with this provider. The Python code can be as simple as:
from Pro.Core import * def keyProvider(obj, index): if index != 0: return None l = NTVariantList() l.append("password") return l
The provider returns a single key (‘password’). This means that when one of the specified file formats is encrypted, all registered key providers will be asked to provide decryption keys. If one key works, the file is automatically decrypted.
The returned list can contain even thousands of keys, it is up to the user to decide the amount returned. The index argument can be used to decide which bulk of keys must be returned, it starts at 0 and is incremented by l.size(). The key provider will be called until a match is found or it doesn’t return any more keys. Thus, be careful not to always return a key without checking the index, otherwise it’ll result in an endless loop.
When a string is appended to the list, then it will be converted internally by the conversion handlers to bytes (this means that a single string could, for instance, first be converted to UTF8 then to Ascii in order to obtain a match). Sometimes you want to return the exact bytes to be matched. In that case just append a bytearray object to the list.
The same sample could be transformed into a key generation based on variables:
from Pro.Core import * def keyProvider(obj, index): if index != 0: return None name = obj.GetStream().name() # do some operations involving the file name variable_part = ... l = NTVariantList() l.append("static_part" + variable_part) return l
And this comes handy when we want to avoid typing in passwords for certain Zip archives which have a fixed decryption key schema.
So, to sum up key providers are powerful and easy-to-use extensions which allow us to test out key dictionaries on various file formats (those for which the Profiler supports decryption) and to avoid the all too frequent hassle of having to type common passwords.
Another part will follow this one and in my opinion it will be even more interesting than the previous posts. After that yet another post will follow which is going to show a real-world case for demonstration purposes. Then it shouldn’t take long for the new version to be released. Stay tuned!