Hooks are an extremely powerful extension to the scanning engine of the Profiler. They allow the user to do customize scans and do all sorts of things. Because there’s basically no limit to the applications, I’ll just try to give a brief introduction in this post. In the following post I’ll demonstrate their use with a real-world case.
Just like key providers introduced in the previous post, hooks have their INI configuration file as well (hooks.cfg). This can contain a minimal hook entry:
[Test Hook] file = test_hooks.py scanned = scanned
And the python code:
def scanned(sp, ud): print(sp.getObjectFormat())
scanned gets called after every file scan and prints out the format of the object. This function is not being called from the main thread, so it’s not possible to call UI functions. However, print is thread-safe and when doing a batch scan will just output to stdout.
Now let’s open the Profiler and go to the new Extensions view.
You’ll notice that the box next to the name of the hook we just created is unchecked. This means that it’s disabled and hence won’t be called. We can enable it manually or we may even specify from the INI file to enable our extension by default:
[Test Hook] file = test_hooks.py scanned = scanned enable = yes
We also specify the scan mode we are interested in:
; not specifying a mode equals to: mode = single|batch mode = batch
Now the extension will be notified only when doing batch scans. To be even more selective, it’s possible to specify the file format(s) we are interested in:
; this is an optional field, you can omit it and the hook will be notified for any format formats = PE|SWF
Ok, now let’s create a small sample which actually does something. Let’s say we want to perform a search among the disassembled code of Java Class files and include in the resulting report only those files which contain a particular string.
The configuration entry:
[Search Java Class] file = test_hooks.py scanned = searchJavaClass mode = batch formats = Class
And the code:
def searchJavaClass(sp, ud): from Pro.Core import NTTextBuffer cl = sp.getObject() out = NTTextBuffer() cl.Disassemble(out) # search string ret = out.buffer.find("HelloWorld") != -1 sp.include(ret)
Let’s activate the extension by checking its box and then perform a custom scan only on files identified as Java Classes.
The result will be:
The method ScanProvider::include(bool b) is what tells the Profiler which files have to be included in the final report (its counterpart is ScanProvider::exclude(bool b)). Of course, there could be more than one hook active during a scan and a file can be both excluded and included. The logic is that include has priority over exclude and once a file has been included by a hook it can’t be excluded by another one.
Although the few lines above already have a purpose, it’s not quite handy having to change the code in order to perform different searches. Thus, hooks can optionally implement two more callbacks: init and end. Both these callbacks are called from the main UI thread (so that it’s safe to call UI functions). The first one is called before any scan operation is performed, while the latter after all of them have finished.
The syntax for for these callbacks is the following:
def init(): print("init") return print # returns what the other callbacks will get as their 'ud' argument def end(ud): ud("end")
Instead of using ugly global variables, init can optionally return the user data passed on to the other callbacks. end is useful to perform cleanup operations. But in our sample above we don’t really need to clean up anything, we just need an input box to ask the user for a string to be searched. So we just need to add an init callback.
[Search Java Class] file = test_hooks.py init = initSearchJavaClass scanned = searchJavaClass mode = batch formats = Class
And add the new logic to the code:
def initSearchJavaClass(): from Pro.UI import ProInput return ProInput.askText("Insert string:") def searchJavaClass(sp, ud): if ud == None: return from Pro.Core import NTTextBuffer cl = sp.getObject() out = NTTextBuffer() cl.Disassemble(out) # search string ret = out.buffer.find(ud) != -1 sp.include(ret)
Of course, this sample could be improved endlessly by adding options, regular expressions, support for more file formats etc. But that is beyond the scope of this post which was just briefly introduce hooks.
The upcoming version of the Profiler which includes all the improvements of the previous weeks is almost ready. Stay tuned!