class TorrentObject(CFFObject):
if b"announce" in d and type(d[b"announce"]) is bytes:
trackers.append(d[b"announce"])
if b"announce-list" in d:
if type(a) is list and len(a) > 0 and a[0] not in dup and type(a[0]) is bytes:
def trackersViewCb(cv, trackers, code, view, data):
tv.setColumnLabels(labels)
tv.setColumnCWidth(0, 70)
tv.setRowCount(len(trackers))
elif code == pvnGetTableRow:
data.setText(0, trackers[data.row].decode("utf-8", errors="ignore"))
class TorrentScanProvider(ScanProvider):
def _formatViewData(self, sdata):
elif sdata.fid == self.FormatItem_Trackers:
sdata.setViews(SCANVIEW_CUSTOM)
sdata.data.setData("<ui><hl margin="0">")
sdata.setCallback(trackersViewCb, self.obj.GetTrackers())
return False<p><a href="/wp-content/uploads/2015/09/torrent/trackers.png"><img decoding="async" class="post-img" src="/wp-content/uploads/2015/09/torrent/trackers.png" alt="Trackers"></a></p><p>When retrieving data from the dictionary, we also make sure that it is in the correct type, so that the code which handles this data won't end up generating an exception when trying to process an unexpected type.</p><p>And now the files:</p><pre lang="python">class TorrentObject(CFFObject):
if b"name" in d and type(d[b"name"]) is bytes:
files.append((d[b"name"], sz if type(sz) is int else 0))
if not type(flist) is list:
if type(pt) is list and len(pt) > 0 and type(pt[0]) is bytes:
sz = fd.get(b"length", 0)
files.append((pt[0], sz if type(sz) is int else 0))
def filesViewCb(cv, files, code, view, data):
tv.setColumnLabels(labels)
tv.setColumnCWidth(0, 70)
tv.setColumnCWidth(1, 35)
tv.setRowCount(len(files))
elif code == pvnGetTableRow:
data.setText(0, files[data.row][0].decode("utf-8", errors="ignore"))
data.setText(1, "%.02f MBs (%d bytes)" % (sz / 0x100000, sz))
class TorrentScanProvider(ScanProvider):
def _formatViewData(self, sdata):
elif sdata.fid == self.FormatItem_Files:
sdata.setViews(SCANVIEW_CUSTOM)
sdata.data.setData("<ui><hl margin="0"></hl></ui></pre><table id="1"> </table>")
sdata.setCallback(filesViewCb, self.obj.GetFiles())
return False<p><a href="/wp-content/uploads/2015/09/torrent/files.png"><img decoding="async" class="post-img" src="/wp-content/uploads/2015/09/torrent/files.png" alt="Files"></a></p><p>And that's it. Now again the whole code for a better overview:</p><pre lang="python">from Pro.Core import *
from Pro.UI import pvnInit, pvnGetTableRow
MAX_TORRENT_SIZE = 10485760 # 10 MBs
# BEGIN OF 3RD PARTY CODE (adapted to work with Python 3)
# The contents of this file are subject to the BitTorrent Open Source License
# Version 1.1 (the License). You may not copy or use this file, in either
# source code or executable form, except in compliance with the License. You
# may obtain a copy of the License at http://www.bittorrent.com/license/.
# Software distributed under the License is distributed on an AS IS basis,
# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
# for the specific language governing rights and limitations under the
elif x[f] == 0x30 and newf != f+1:
colon = x.index(0x3A, f) # :
if x[f] == 0x30 and colon != f+1:
return (x[colon:colon+n], colon+n)
v, f = decode_func[x[f]](x, f)
k, f = decode_string(x, f)
r[k], f = decode_func[x[f]](x, f)
decode_func[0x6C] = decode_list # l
decode_func[0x64] = decode_dict # d
decode_func[0x69] = decode_int # i
decode_func[0x30] = decode_string
decode_func[0x31] = decode_string
decode_func[0x32] = decode_string
decode_func[0x33] = decode_string
decode_func[0x34] = decode_string
decode_func[0x35] = decode_string
decode_func[0x36] = decode_string
decode_func[0x37] = decode_string
decode_func[0x38] = decode_string
decode_func[0x39] = decode_string
r, l = decode_func[x[0]](x, 0)
except (IndexError, KeyError, ValueError):
class TorrentObject(CFFObject):
super(TorrentObject, self).__init__()
self.SetObjectFormatName("TORRENT")
self.SetDefaultEndianness(ENDIANNESS_LITTLE)
size = min(self.GetSize(), MAX_TORRENT_SIZE)
data = self.Read(0, size)
self.tdict = bdecode(bytes(data))
cd = d.get(b"creation date", None)
if cd == None or not type(cd) is int:
return NTDateTime.fromMSecsSinceEpoch(cd * 1000)
if b"announce" in d and type(d[b"announce"]) is bytes:
trackers.append(d[b"announce"])
if b"announce-list" in d:
if type(a) is list and len(a) > 0 and a[0] not in dup and type(a[0]) is bytes:
if b"name" in d and type(d[b"name"]) is bytes:
files.append((d[b"name"], sz if type(sz) is int else 0))
if not type(flist) is list:
if type(pt) is list and len(pt) > 0 and type(pt[0]) is bytes:
sz = fd.get(b"length", 0)
files.append((pt[0], sz if type(sz) is int else 0))
def trackersViewCb(cv, trackers, code, view, data):
tv.setColumnLabels(labels)
tv.setColumnCWidth(0, 70)
tv.setRowCount(len(trackers))
elif code == pvnGetTableRow:
data.setText(0, trackers[data.row].decode("utf-8", errors="ignore"))
def filesViewCb(cv, files, code, view, data):
tv.setColumnLabels(labels)
tv.setColumnCWidth(0, 70)
tv.setColumnCWidth(1, 35)
tv.setRowCount(len(files))
elif code == pvnGetTableRow:
data.setText(0, files[data.row][0].decode("utf-8", errors="ignore"))
data.setText(1, "%.02f MBs (%d bytes)" % (sz / 0x100000, sz))
class TorrentScanProvider(ScanProvider):
super(TorrentScanProvider, self).__init__()
self.meta_keys = [b"created by", b"creation date", b"comment"]
self.FormatItem_Dictionary = 1
self.FormatItem_Trackers = 2
self.FormatItem_Files = 3
self.fi_names = ["Dictionary", "Trackers", "Files"]
self.obj = TorrentObject()
self.obj.Load(self.getStream())
d = self.obj.GetDictionary()
return self.SCAN_RESULT_OK if len(d) != 0 else self.SCAN_RESULT_ERROR
d = self.obj.GetDictionary()
if any(mk in d for mk in self.meta_keys):
if self.obj.GetSize() > MAX_TORRENT_SIZE:
e.type = CT_UnaccountedSpace
return self.SCAN_RESULT_FINISHED
def _scanViewData(self, xml, dnode, sdata):
if sdata.type == CT_MetaData:
d = self.obj.GetDictionary()
for mk in self.meta_keys:
tmk = mk.decode("utf-8", errors="ignore")
if tmk == "creation date":
dt = self.obj.CreationDate()
tmv = dt.toString() if dt.isValid() else "?"
tmv = d[mk].decode("utf-8", errors="ignore")
sdata.setViews(SCANVIEW_TEXT)
sdata.data.setData(out.buffer)
elif sdata.type == CT_UnaccountedSpace:
sdata.setViews(SCANVIEW_TEXT)
sdata.data.setData("The file size exceeds the maximum allowed one of %d bytes!" % (MAX_TORRENT_SIZE,))
fi = ft.appendChild(None, self.FormatItem_Dictionary)
ft.appendChild(fi, self.FormatItem_Trackers)
ft.appendChild(fi, self.FormatItem_Files)
def _formatViewInfo(self, finfo):
if finfo.fid >= 1 or finfo.fid - 1 < len(self.fi_names):
finfo.text = self.fi_names[finfo.fid - 1]
def _formatViewData(self, sdata):
if sdata.fid == self.FormatItem_Dictionary:
sdata.setViews(SCANVIEW_TEXT)
txt = pprint.pformat(self.obj.GetDictionary())
elif sdata.fid == self.FormatItem_Trackers:
sdata.setViews(SCANVIEW_CUSTOM)
sdata.data.setData("<ui><hl margin="0"></hl></ui></pre><table id="1"> </table>")
sdata.setCallback(trackersViewCb, self.obj.GetTrackers())
elif sdata.fid == self.FormatItem_Files:
sdata.setViews(SCANVIEW_CUSTOM)
sdata.data.setData("<ui><hl margin="0"></hl></ui><table id="1"></table>")
sdata.setCallback(filesViewCb, self.obj.GetFiles())
return TorrentScanProvider()<p>We could still extract more information from the torrent file. For instance, we could show the list of hashes and to which portion of which file they belong to. If that's interesting for forensic purposes, we can easily add this view in the future.</p><footer class="entry-footer">
<span class="byline"><img alt="" src="https://secure.gravatar.com/avatar/7a86aa69922858b8d41989621fc1ea364aae1e027546f88a54d94ab1ec2187fc?s=49&d=mm&r=g" srcset="https://secure.gravatar.com/avatar/7a86aa69922858b8d41989621fc1ea364aae1e027546f88a54d94ab1ec2187fc?s=98&d=mm&r=g 2x" class="avatar avatar-49 photo" height="49" width="49" loading="lazy" decoding="async"><span class="screen-reader-text">Author </span><span class="author vcard"><a class="url fn n" href="https://blog.cerbero.io/author/cerbero/">Erik Pistelli</a></span></span><span class="posted-on"><span class="screen-reader-text">Posted on </span><a href="https://blog.cerbero.io/torrent-support/" rel="bookmark"><time class="entry-date published" datetime="2015-09-23T16:50:31+00:00">September 23, 2015</time><time class="updated" datetime="2021-04-01T16:32:00+00:00">April 1, 2021</time></a></span><span class="cat-links"><span class="screen-reader-text">Categories </span><a href="https://blog.cerbero.io/category/forensics/" rel="category tag">Forensics</a>, <a href="https://blog.cerbero.io/category/suite-standard/" rel="category tag">Suite Standard</a></span><span class="tags-links"><span class="screen-reader-text">Tags </span><a href="https://blog.cerbero.io/tag/p2p/" rel="tag">P2P</a>, <a href="https://blog.cerbero.io/tag/torrent/" rel="tag">Torrent</a>, <a href="https://blog.cerbero.io/tag/trackers/" rel="tag">Trackers</a></span><span class="comments-link"><a href="https://blog.cerbero.io/torrent-support/#respond">Leave a comment<span class="screen-reader-text"> on Torrent Support</span></a></span> </footer><article id="post-1551" class="post-1551 post type-post status-publish format-standard hentry category-suite-standard">
<header class="entry-header">
<h2 class="entry-title"><a href="https://blog.cerbero.io/scan-providers/" rel="bookmark">Scan Providers</a></h2> </header>
<div class="entry-content"> <p>Version 2.5.0 is close to being released and comes with the last type of extension exposed to Python: scan providers. Scan providers extensions are not only the most complex type of extensions, but also the most powerful ones as they allow to add support for new file formats entirely from Python! </p> <p>This feature required exposing a lot more of the SDK to Python and can’t be completely discussed in one post. This post is going to introduce the topic, while future posts will show real life examples.</p> <p>Let’s start from the list of Python scan providers under Extensions -> Scan providers:</p> <p><a href="/wp-content/uploads/2015/09/scanp/extlist.png"><img decoding="async" class="post-img" src="/wp-content/uploads/2015/09/scanp/extlist.png" alt="Scan provider extensions"></a></p> <p>This list is retrieved from the configuration file ‘scanp.cfg’. Here’s an example entry:</p> <pre lang="ini">[TEST]
label = Test scan provider
allocator = allocator</pre> <p>The name of the section has two purposes: it specifies the name of the format being supported (in this case ‘TEST’) and also the name of the extension, which automatically is associated to that format (in this case ‘.test’, case insensitive). The hard limit for format names is 9 characters for now, this may change in the future if more are needed. The <strong>label</strong> is the description. The <strong>ext</strong> parameter is optional and specifies additional extensions to be associated to the format. <strong>group</strong> specifies the type of file which is being supported; available groups are: img, video, audio, doc, font, exe, manexe, arch, db, sys, cert, script. <strong>file</strong> specifies the Python source file and <strong>allocator</strong> the function which returns a new instance of the scan provider class.</p> <p>Let’s start with the allocator:</p> <pre lang="python">def allocator():
return TestScanProvider()</pre> <p>It just returns a new instance of <strong>TestScanProvider</strong>, which is a class dervided from <strong>ScanProvider</strong>:</p> <pre lang="python">class TestScanProvider(ScanProvider):
super(TestScanProvider, self).__init__()
self.obj = None</pre> <p>Every scan provider has some mandatory methods it must override, let’s begin with the first ones:</p> <pre lang="python"> def _clear(self):
self.obj.Load(self.getStream())
return self.SCAN_RESULT_OK</pre> <p><strong>_clear</strong> gives a chance to free internal resources when they’re no longer used. In Python this is not usually important as member objects will automatically be freed when their reference count reaches zero.</p> <p><strong>_getObject</strong> must return the internal instance of the object being parsed. This must return an instance of a <strong>CFFObject</strong> derived class.</p> <p><strong>_initObject</strong> creates the object instance and loads the data stream into it. In the sample above we assume it being successful. Otherwise, we would have to return <strong>SCAN_RESULT_ERROR</strong>. This method is not called by the main thread, so that it doesn’t block the UI during long parse operations.</p> <p>Let’s take a look at the <strong>TestObject</strong> class:</p> <pre lang="python">class TestObject(CFFObject):
super(TestObject, self).__init__()
self.SetObjectFormatName("TEST")
self.SetDefaultEndianness(ENDIANNESS_LITTLE)</pre> <p>This is a minimalistic implementation of a <strong>CFFObject</strong> derived class. Usually it should contain at least an override of the <strong>CustomLoad</strong> method, which gives the opportunity to fail when the data stream is first loaded through the <strong>Load</strong> method. <strong>SetDefaultEndianness</strong> wouldn’t even be necessary, as every object defaults to little endian by default. <strong>SetObjectFormatName</strong>, on the other hand, is very important, as it sets the internal format name of the object.</p> <p>Let’s now take a look at how we scan a file:</p> <pre lang="python"> def _startScan(self):
return self.SCAN_RESULT_OK
self.addEntry(e)</pre> <p>The code above will issue a single warning concerning native code. When <strong>_startScan</strong> returns <strong>SCAN_RESULT_OK</strong>, <strong>_threadScan</strong> will be called from a thread other than the main UI one. The logic behind this is that <strong>_startScan</strong> is actually called from the main thread and if the scan of the file doesn’t require complex operations, like in the case above, then the method could return <strong>SCAN_RESULT_FINISHED</strong> and then <strong>_threadScan</strong> won’t be called at all. During a threaded scan, an abort by the user can be detected via the <strong>isAborted</strong> method.</p> <p>From the UI side point of view, when a scan entry is clicked in summary, the scan provider is supposed to return UI information. </p> <pre lang="python"> def _scanViewData(self, xml, dnode, sdata):
if sdata.type == CT_NativeCode:
sdata.setViews(SCANVIEW_TEXT)
sdata.data.setData("Hello, world!")
return False</pre> <p>This will display a text field with a predefined content when the user clicks the scan entry in the summary. This is fairly easy, but what happens when we have several entries of the same type and need to differentiate between them? There’s where the <strong>data</strong> member of <strong>ScanEntryData</strong> plays a role, this is a string which will be included in the report xml and passed again back to <strong>_scanViewData</strong> as an xml node.</p> <p>For instance:</p> <pre lang="python">e.data = "<o>1234</o>"</pre> <p>Becomes this in the final XML report:</p> <pre lang="xml"><d>
</d></pre> <p>The <strong>dnode</strong> argument of <strong>_scanViewData</strong> points to the ‘d’ node and its first child will be the ‘o’ node we passed. the <strong>xml</strong> argument represents an instance of the <strong>NTXml</strong> class, which can be used to retrieve the children of the <strong>dnode</strong>.</p> <p>But this is only half of the story: some of the scan entries may represent embedded files (category <strong>SEC_File</strong>), in which case the <strong>_scanViewData</strong> method must return the data representing the file.</p> <p>Apart from scan entries, we may also want the user to explore the format of the file. To do that we must return a tree representing the structure of our file:</p> <pre lang="python"> def _getFormat(self):
fi = ft.appendChild(None, 1)
return ft</pre> <p>The <strong>enableIDs</strong> method must be called right after creating a new <strong>FormatTree</strong> class. The code above creates a format item with id 1 with a child item with id 2, which results in the following:</p> <p><a href="/wp-content/uploads/2015/09/scanp/format.png"><img decoding="async" class="post-img" src="/wp-content/uploads/2015/09/scanp/format.png" alt="Format tree"></a></p> <p>But of course, we haven’t specified neither labels nor different icons in the function above. This information is retrieved for each item when required through the following method:</p> <pre lang="python"> def _formatViewInfo(self, finfo):
return False</pre> <p>The various items are identified by their id, which was specified during the creation of the tree.</p> <p>The UI data for each item is retrieved through the <strong>_formatViewData</strong> method:</p> <pre lang="python"> def _formatViewData(self, sdata):
sdata.setViews(SCANVIEW_CUSTOM)
sdata.data.setData("<ui><hsplitter csizes="40-*"></hsplitter></ui></pre></div></article><table id="1">
sdata.setCallback(cb, None)
return False <p>This will display a custom view with a table and a hex view separated by a splitter:</p> <p><a href="/wp-content/uploads/2015/09/scanp/cview.png"><img decoding="async" class="post-img" src="/wp-content/uploads/2015/09/scanp/cview.png" alt="Custom view"></a></p> <p>Of course, also have specified the callback for our custom view:</p> <pre lang="python">def cb(cv, ud, code, view, data):
return 0</pre> <p>It is good to remember that format item IDs and IDs used in custom views are used to encode bookmark jumps. So if they change, saved bookmark jumps become invalid.</p> <p>And here again the whole code for a better overview:</p> <pre lang="python">from Pro.Core import *
from Pro.UI import pvnInit, PubIcon_Dir
class TestObject(CFFObject):
super(TestObject, self).__init__()
self.SetObjectFormatName("TEST")
self.SetDefaultEndianness(ENDIANNESS_LITTLE)
def cb(cv, ud, code, view, data):
class TestScanProvider(ScanProvider):
super(TestScanProvider, self).__init__()
self.obj.Load(self.getStream())
return self.SCAN_RESULT_OK
return self.SCAN_RESULT_OK
def _scanViewData(self, xml, dnode, sdata):
if sdata.type == CT_NativeCode:
sdata.setViews(SCANVIEW_TEXT)
sdata.data.setData("Hello, world!")
fi = ft.appendChild(None, 1)
def _formatViewInfo(self, finfo):
def _formatViewData(self, sdata):
sdata.setViews(SCANVIEW_CUSTOM)
sdata.data.setData("<ui><hsplitter csizes="40-*"></hsplitter></ui></pre></hex><table id="1"></table><hex id="2">")
sdata.setCallback(cb, None)
return TestScanProvider() <p>If you have noticed from the screen-shot above, the analysed file is called ‘a.t’ and as such doesn’t automatically associate to our ‘test’ format. So how does it associate anyway?</p> <p>Clearly Profiler doesn’t rely on extensions alone to identify the format of a file. For external scan providers a signature mechanism based on YARA has been introduced. In the <strong>config</strong> directory of the user, you can create a file named ‘yara.plain’ and insert your identification rules in it, e.g.:</p> <pre lang="text">rule test
}</pre> <p>This rule will identify the format as ‘test’ if the first 4 bytes of the file match the string ‘test’: the name of the rule identifies the format.</p> <p>The file ‘yara.plain’ will be compiled to the binary ‘yara.rules’ file at the first run. In order to refresh ‘yara.rules’, you must delete it.</p> <p>One important thing to remember is that a rule isn’t matched against an entire file, but only against the first 512 bytes.</p> <p>Of course, our provider behaves 100% like all other providers and can be used to load embedded files:</p> <p><a href="/wp-content/uploads/2015/09/scanp/embfiles.png"><img decoding="async" class="post-img" src="/wp-content/uploads/2015/09/scanp/embfiles.png" alt="Embedded files"></a></p> <p>Our new provider is used automatically when an embedded file is identified as matching our format.</p><footer class="entry-footer">
<span class="byline"><img alt="" src="https://secure.gravatar.com/avatar/7a86aa69922858b8d41989621fc1ea364aae1e027546f88a54d94ab1ec2187fc?s=49&d=mm&r=g" srcset="https://secure.gravatar.com/avatar/7a86aa69922858b8d41989621fc1ea364aae1e027546f88a54d94ab1ec2187fc?s=98&d=mm&r=g 2x" class="avatar avatar-49 photo" height="49" width="49" loading="lazy" decoding="async"><span class="screen-reader-text">Author </span><span class="author vcard"><a class="url fn n" href="https://blog.cerbero.io/author/cerbero/">Erik Pistelli</a></span></span><span class="posted-on"><span class="screen-reader-text">Posted on </span><a href="https://blog.cerbero.io/scan-providers/" rel="bookmark"><time class="entry-date published" datetime="2015-09-21T22:13:50+00:00">September 21, 2015</time><time class="updated" datetime="2021-04-01T16:32:53+00:00">April 1, 2021</time></a></span><span class="cat-links"><span class="screen-reader-text">Categories </span><a href="https://blog.cerbero.io/category/suite-standard/" rel="category tag">Suite Standard</a></span><span class="comments-link"><a href="https://blog.cerbero.io/scan-providers/#respond">Leave a comment<span class="screen-reader-text"> on Scan Providers</span></a></span> </footer>
<article id="post-1539" class="post-1539 post type-post status-publish format-standard hentry category-suite-standard tag-command-line tag-news">
<header class="entry-header">
<h2 class="entry-title"><a href="https://blog.cerbero.io/profiler-2-4/" rel="bookmark">Profiler 2.4</a></h2> </header>
<div class="entry-content"> <p>Profiler 2.4 is out with the following news:</p> <p>– <a href="/?p=1530">added initial support for PDB files (including export of types)</a><br> – <a href="#wscript">added support for Windows Encoded Scripts (VBE, JSE)</a><br> – introduced fixed xml structures<br> – <a href="#sdec">added automatic string decoding in struct tables</a><br> – <a href="#pyline">added Python string command line execution</a><br> – remember the last selected logic group<br> – fixed missing support for wchar_t in C types<br> – updated Qt to 5.4.1<br> – various bug fixes</p> <p>While the most important newly introduced feature is the support for PDB files, here are some interesting new features:</p> <p><a name="wscript"></a></p> <h2>Support for Windows Encoded Scripts (VBE, JSE)</h2> <p>Windows encoded scripts like VBE and JSE files (the encoded variants of VBS and JS script files) are now supported and automatically decoded.</p> <p><a href="/wp-content/uploads/2015/06/24/wscript.png"><img decoding="async" class="post-img" src="/wp-content/uploads/2015/06/24/wscript.png"></a></p> <p>In the screen-shot you can see the decoded output of an encoded file (showed at the bottom).</p> <p><a name="sdec"></a></p> <h2>Automatic string decoding in struct tables</h2> <p>A very basic feature: byte-arrays in structures are automatically checked for strings and in case decoded.</p> <p><a href="/wp-content/uploads/2015/06/24/sdec.png"><img decoding="async" class="post-img" src="/wp-content/uploads/2015/06/24/sdec.png"></a></p> <p>(notice the section name automatically displayed as ascii string)</p> <p><a name="pyline"></a></p> <h2>Python string command line execution</h2> <p>Apart from <a href="/?p=1464">executing script files passed as command line arguments</a>, now it is also possible to execute Python statements directly passed as argument. </p> <p>For instance:</p> <pre lang="text">cerpro -c -e "from Pro.Core import *;proCoreContext().msgBox(0, \"Hello world!\")"</pre> <p>The optional argument ‘-c’ specifies to not display the UI.</p> <p>Enjoy!</p></div><footer class="entry-footer">
<span class="byline"><img alt="" src="https://secure.gravatar.com/avatar/7a86aa69922858b8d41989621fc1ea364aae1e027546f88a54d94ab1ec2187fc?s=49&d=mm&r=g" srcset="https://secure.gravatar.com/avatar/7a86aa69922858b8d41989621fc1ea364aae1e027546f88a54d94ab1ec2187fc?s=98&d=mm&r=g 2x" class="avatar avatar-49 photo" height="49" width="49" loading="lazy" decoding="async"><span class="screen-reader-text">Author </span><span class="author vcard"><a class="url fn n" href="https://blog.cerbero.io/author/cerbero/">Erik Pistelli</a></span></span><span class="posted-on"><span class="screen-reader-text">Posted on </span><a href="https://blog.cerbero.io/profiler-2-4/" rel="bookmark"><time class="entry-date published" datetime="2015-06-06T16:43:45+00:00">June 6, 2015</time><time class="updated" datetime="2021-04-01T16:33:35+00:00">April 1, 2021</time></a></span><span class="cat-links"><span class="screen-reader-text">Categories </span><a href="https://blog.cerbero.io/category/suite-standard/" rel="category tag">Suite Standard</a></span><span class="tags-links"><span class="screen-reader-text">Tags </span><a href="https://blog.cerbero.io/tag/command-line/" rel="tag">Command-line</a>, <a href="https://blog.cerbero.io/tag/news/" rel="tag">News</a></span><span class="comments-link"><a href="https://blog.cerbero.io/profiler-2-4/#respond">Leave a comment<span class="screen-reader-text"> on Profiler 2.4</span></a></span> </footer>
<article id="post-1530" class="post-1530 post type-post status-publish format-standard hentry category-suite-standard tag-headers tag-pdb tag-python tag-sdk">
<header class="entry-header">
<h2 class="entry-title"><a href="https://blog.cerbero.io/pdb-support-including-export-of-types/" rel="bookmark">PDB support (including export of types)</a></h2> </header>
<div class="entry-content"> <p>The main feature of the upcoming 2.4 version of Profiler is the initial support for the PDB format. Our code doesn’t rely on the Microsoft DIA SDK and thus works also on OS X and Linux.</p> <p>Since the PDB format is undocumented, this task would’ve been extremely difficult without the <a href="http://undocumented.rawol.com/">fantastic work on PDBs</a> of the never too much revered Sven B. Schreiber.</p> <p>Let’s open a PDB file.</p> <p><a href="/wp-content/uploads/2015/06/pdb/streams.png"><img decoding="async" class="post-img" src="/wp-content/uploads/2015/06/pdb/streams.png"></a></p> <p>As you can see the streams in the PDB can be explored. The TPI stream (the one describing types) offers further inspection.</p> <p><a href="/wp-content/uploads/2015/06/pdb/tpi.png"><img decoding="async" class="post-img" src="/wp-content/uploads/2015/06/pdb/tpi.png"></a></p> <p>All the types contained in the PDB can be exported to a Profiler header by pressing Ctrl+R and executing the ‘Dump types to header’ action.</p> <p><img decoding="async" class="post-img" src="/wp-content/uploads/2015/06/pdb/action.png"></p> <p>Now the types can be used from both the hex editor and the Python SDK. </p> <p><a href="/wp-content/uploads/2015/06/pdb/hexed.png"><img decoding="async" class="post-img" src="/wp-content/uploads/2015/06/pdb/hexed.png"></a></p> <p>We can explore the dumped header by using, as usual, the Header Manager tool.</p> <p><a href="/wp-content/uploads/2015/06/pdb/hdrmgr.png"><img decoding="async" class="post-img" src="/wp-content/uploads/2015/06/pdb/hdrmgr.png"></a></p> <p>The type showed above in the hex editor is simple. So let’s look what a more complex PDB type may look like.</p> <pre lang="xml"><r id="CWnd" type="class" size="84">
<b type="CCmdTarget" offset="0" access="public">
<m id="_GetBaseClass" type="CRuntimeClass * ()">
<s id="classCWnd" type="CRuntimeClass const">
<m id="GetThisClass" type="CRuntimeClass * ()">
<m id="GetRuntimeClass" type="CRuntimeClass * ()">
<m id="CreateObject" type="CObject * ()">
<m id="GetCurrentMessage" type="tagMSG const * ()">
<f id="m_hWnd" type="HWND__ *" offset="32">
<m id="operator struct HWND__ *" type="HWND__ * ()">
<m id="operator==" type="int32 (CWnd const *)">
<m id="operator!=" type="int32 (CWnd const *)">
<m id="GetSafeHwnd" type="HWND__ * ()">
<m id="GetStyle" type="unsigned int ()">
<m id="GetExStyle" type="unsigned int ()">
<m id="ModifyStyle" type="int32 (HWND__ *, unsigned int, unsigned int, uint32)">
<m id="ModifyStyle" type="int32 (unsigned int, unsigned int, uint32)">
<m id="ModifyStyleEx" type="int32 (HWND__ *, unsigned int, unsigned int, uint32)">
<m id="ModifyStyleEx" type="int32 (unsigned int, unsigned int, uint32)">
<m id="GetOwner" type="CWnd * ()">
<m id="SetOwner" type="void (CWnd *)">
<m id="GetWindowInfo" type="int32 (tagWINDOWINFO *)">
<m id="GetTitleBarInfo" type="int32 (tagTITLEBARINFO *)">
<m id="CWnd" type="void (CWnd const *)">
<m id="CWnd" type="void (HWND__ *)">
<m id="CWnd" type="void ()">
<m id="FromHandle" type="CWnd * (HWND__ *)">
<m id="FromHandlePermanent" type="CWnd * (HWND__ *)">
<m id="DeleteTempMap" type="void ()">
</m></m></m></m></m></m></m></m></m></m></m></m></m></m></m></m></m></m></m></m></f></m></m></m></m></s></m></b></r></pre><b><s id="classCWnd" type="CRuntimeClass const"> <p>The PDB code is also exposed to the SDK. This is a small snippet of code, which dumps all the types to a text buffer and then displays them in a text view.</p> <pre lang="python">from Pro.Core import *
obj = ctx.currentScanProvider().getObject()
tpi = obj.GetStreamObject(PDB_STREAM_ID_TPI)
tpihdr = obj.TPIHeader(tpi)
tiMin = tpihdr.Num("tiMin")
tiMax = tpihdr.Num("tiMax")
tctx = obj.CreateTypeContext(tpi)
for ti in range(tiMin, tiMax):
view = ctx.createView(ProView.Type_Text, "PDB Test")
showPDBTypes()</pre> <p><a href="/wp-content/uploads/2015/06/pdb/pyresult.png"><img decoding="async" class="post-img" src="/wp-content/uploads/2015/06/pdb/pyresult.png"></a></p> <p>In order to dump all types to a single header, you can use the <strong>DumpAllToHeader</strong> method.</p></s></b></div><footer class="entry-footer"><b><s id="classCWnd" type="CRuntimeClass const">
<span class="byline"><img alt="" src="https://secure.gravatar.com/avatar/7a86aa69922858b8d41989621fc1ea364aae1e027546f88a54d94ab1ec2187fc?s=49&d=mm&r=g" srcset="https://secure.gravatar.com/avatar/7a86aa69922858b8d41989621fc1ea364aae1e027546f88a54d94ab1ec2187fc?s=98&d=mm&r=g 2x" class="avatar avatar-49 photo" height="49" width="49" loading="lazy" decoding="async"><span class="screen-reader-text">Author </span><span class="author vcard"><a class="url fn n" href="https://blog.cerbero.io/author/cerbero/">Erik Pistelli</a></span></span><span class="posted-on"><span class="screen-reader-text">Posted on </span><a href="https://blog.cerbero.io/pdb-support-including-export-of-types/" rel="bookmark"><time class="entry-date published" datetime="2015-06-01T08:09:04+00:00">June 1, 2015</time><time class="updated" datetime="2021-04-01T16:34:10+00:00">April 1, 2021</time></a></span><span class="cat-links"><span class="screen-reader-text">Categories </span><a href="https://blog.cerbero.io/category/suite-standard/" rel="category tag">Suite Standard</a></span><span class="tags-links"><span class="screen-reader-text">Tags </span><a href="https://blog.cerbero.io/tag/headers/" rel="tag">Headers</a>, <a href="https://blog.cerbero.io/tag/pdb/" rel="tag">PDB</a>, <a href="https://blog.cerbero.io/tag/python/" rel="tag">Python</a>, <a href="https://blog.cerbero.io/tag/sdk/" rel="tag">SDK</a></span><span class="comments-link"><a href="https://blog.cerbero.io/pdb-support-including-export-of-types/#respond">Leave a comment<span class="screen-reader-text"> on PDB support (including export of types)</span></a></span> </s></b></footer><b><s id="classCWnd" type="CRuntimeClass const">
</s></b></article><b><s id="classCWnd" type="CRuntimeClass const">
<article id="post-1519" class="post-1519 post type-post status-publish format-standard hentry category-suite-standard tag-news">
<header class="entry-header">
<h2 class="entry-title"><a href="https://blog.cerbero.io/profiler-2-3/" rel="bookmark">Profiler 2.3</a></h2> </header>
<div class="entry-content"> <p>Profiler 2.3 is out with the following news:</p> <p>– <a href="/?p=1506">introduced YARA 3.2 support</a><br> – <a href="#lgroups">added groups for logic providers</a><br> – <a href="#enctxt">added Python action to encode/decode text</a><br> – <a href="#x2t">added Python action to strip XML down to text</a><br> – <a href="#fixfont">added the possibility to choose the fixed font</a><br> – <a href="#colrand">added color randomization for structs and intervals</a><br> – <a href="#repapis">added close report and quit APIs</a><br> – <a href="#repapis">exposed more methods of the Report class (including save)</a><br> – improved indentation handling in the script editor<br> – <a href="#outsync">synchronized main and workspace output views</a><br> – improved output view<br> – updated libmagic to 5.21<br> – updated Capstone to 3.0<br> – many small improvements<br> – fixed libmagic on Linux<br> – removed the tray icon<br> – minor bug fixes</p> <p><a name="lgroups"></a></p> <h2>Logic provider groups</h2> <p>Logic providers can now be grouped in order to avoid clutter in the main window. Adding the following line to an existing logic provider will result in a new group being created:</p> <pre lang="python">group = Extra</pre> <p><a href="/wp-content/uploads/2014/12/23/lgroups.png"><img decoding="async" class="post-img" src="/wp-content/uploads/2014/12/23/lgroups.png"></a></p> <p><a name="enctxt"></a></p> <h2>Encode/decode text action</h2> <p>A handy Python action to convert from hex to text and vice-versa using all of Python’s supported encodings. Place yourself in a hex or text view and run the encoding/decoding action ‘Bytes to text’ or ‘Text to bytes’.</p> <p><a href="/wp-content/uploads/2014/12/23/enctext.png"><img decoding="async" class="post-img" src="/wp-content/uploads/2014/12/23/enctext.png"></a></p> <p>The operation will open a new text or hex view depending if it was an encoding or a decoding. </p> <p><a name="x2t"></a></p> <h2>XML to text action</h2> <p>Strips tags from an XML and displays only the text. The action can be performed both on a hex and text view. </p> <p><a href="/wp-content/uploads/2014/12/23/x2t.png"><img decoding="async" class="post-img" src="/wp-content/uploads/2014/12/23/x2t.png"></a></p> <p>And it will open a new text view. This is useful to view the text of a DOCX or ODT document. In the future the preview for these documents will be made available automatically, but in the meantime this action is helpful.</p> <p><a href="/wp-content/uploads/2014/12/23/docxprev.png"><img decoding="async" class="post-img" src="/wp-content/uploads/2014/12/23/docxprev.png"></a></p> <p><a name="fixfont"></a></p> <h2>Fixed font preferences</h2> <p>The fixed font used in most views can now be chosen from the ‘General’ settings.</p> <p><a name="colrand"></a></p> <h2>Struct/intervals color randomization</h2> <p>When adding a structure or interval to the hex view the chosen color is now being randomized every time the dialog shows up. This behaviour can be disabled from the dialog itself and it’s also possible to randomize again the color by clicking on the specific refresh button.</p> <p><a href="/wp-content/uploads/2014/12/23/colrand.png"><img decoding="async" class="post-img" src="/wp-content/uploads/2014/12/23/colrand.png"></a></p> <p>Manually picking a different color for every interval is time consuming and so this feature should speed up raw data analysis.</p> <p><a name="repapis"></a></p> <h2>Report APIs</h2> <p>Most of the report APIs have been exposed (check out the SDK documentation). This combined with the newly introduced ‘quit’ SDK method can be used to perform custom scans programmatically and save the resulting report.</p> <p>Here’s a small example which can be launched from the command line:</p> <pre lang="python">from Pro.Core import *
ctx.getSystem().addFile(sys.argv[1])
ctx.unregisterLogicProvider("test_logic")
ctx.getReport().saveAs("auto.cpro")
ctx.registerLogicProvider("test_logic", init, None, None, None, rload)
ctx.startScan("test_logic")</pre> <p>The command line syntax to run this script would be:</p> <pre lang="text">cerpro -r scan.py [file to scan]</pre> <p>The UI will show up and close automatically once the ‘quit’ method is called. Running this script in console mode using the ‘-c’ parameter is not yet possible, because of the differences in message handling on different platforms, but it will be in the future.</p> <p><a name="outsync"></a></p> <h2>Synchronized output views</h2> <p>The output view of the main window and of the workspace are now synchronized, thus avoiding missing important log messages being printed in one or the other context.</p> <p>Enjoy!</p></div><footer class="entry-footer">
<span class="byline"><img alt="" src="https://secure.gravatar.com/avatar/7a86aa69922858b8d41989621fc1ea364aae1e027546f88a54d94ab1ec2187fc?s=49&d=mm&r=g" srcset="https://secure.gravatar.com/avatar/7a86aa69922858b8d41989621fc1ea364aae1e027546f88a54d94ab1ec2187fc?s=98&d=mm&r=g 2x" class="avatar avatar-49 photo" height="49" width="49" loading="lazy" decoding="async"><span class="screen-reader-text">Author </span><span class="author vcard"><a class="url fn n" href="https://blog.cerbero.io/author/cerbero/">Erik Pistelli</a></span></span><span class="posted-on"><span class="screen-reader-text">Posted on </span><a href="https://blog.cerbero.io/profiler-2-3/" rel="bookmark"><time class="entry-date published" datetime="2014-12-27T02:16:53+00:00">December 27, 2014</time><time class="updated" datetime="2021-04-01T16:34:49+00:00">April 1, 2021</time></a></span><span class="cat-links"><span class="screen-reader-text">Categories </span><a href="https://blog.cerbero.io/category/suite-standard/" rel="category tag">Suite Standard</a></span><span class="tags-links"><span class="screen-reader-text">Tags </span><a href="https://blog.cerbero.io/tag/news/" rel="tag">News</a></span><span class="comments-link"><a href="https://blog.cerbero.io/profiler-2-3/#respond">Leave a comment<span class="screen-reader-text"> on Profiler 2.3</span></a></span> </footer>
<article id="post-1506" class="post-1506 post type-post status-publish format-standard hentry category-action category-suite-standard tag-signatures tag-yara">
<header class="entry-header">
<h2 class="entry-title"><a href="https://blog.cerbero.io/yara-3-2-0-support/" rel="bookmark">YARA 3.2.0 support</a></h2> </header>
<div class="entry-content"> <p>The upcoming 2.3 version of Profiler includes support for the latest YARA engine. This new release is scheduled for the first week of January and it will include YARA on all supported platforms.</p> <p>One inherent technical advantage of having YARA support in Profiler is that it will be possible to scan for YARA rules inside embedded files/objects, like files in a Zip archive, in a CHM file, in an OLEStream, streams in a PDF, etc.</p> <p>The YARA engine itself has been compiled with all standard modules (except for cuckoo). Even the <strong>magic</strong> module is available, since libmagic is also supported by Profiler.</p> <p>The initial YARA integration comes as a hook extension, an action and Python SDK support. The YARA Python support is the official one and differs from it only in the import statement. You can run existing YARA Python code without modification by using the following import syntax:</p> <pre lang="python">import Pro.yara as yara</pre> <p>So let’s start a YARA scan. To do that, we need to enable the YARA hook extension. On Windows remember to configure Python in case you haven’t yet, since all extensions have been written in it.</p> <p><a href="/wp-content/uploads/2014/12/yara/1.png"><img decoding="async" class="post-img" src="/wp-content/uploads/2014/12/yara/1.png"></a></p> <p>When a scan is started, a YARA settings dialog will show up. </p> <p><a href="/wp-content/uploads/2014/12/yara/2.png"><img decoding="async" class="post-img" src="/wp-content/uploads/2014/12/yara/2.png"></a></p> <p>This dialog lets us choose various settings including the type of rules to load.</p> <p><a href="/wp-content/uploads/2014/12/yara/3.png"><img decoding="async" class="post-img" src="/wp-content/uploads/2014/12/yara/3.png"></a></p> <p>There are four possibilities. A simple text field containing YARA rules, a plain text rules file, a compiled rules file or a custom expression which must <strong>eval</strong> to a valid <strong>Rules</strong> object.</p> <p>The report settings specify how we will be alerted of matches. The ‘only matches’ option makes sure that only files (or their sub-files) with a match will be included in the final report. The ‘add to meta-data” option causes the matches to be visible as meta-data strings of a file. The ‘as threats’ option reports every match as a 100% risk threat. The ‘print to output’ option prints the matches to the output view.</p> <p>Since we had the ‘only matches’ option enabled, we will find only matching files in our final report.</p> <p><a href="/wp-content/uploads/2014/12/yara/4.png"><img decoding="async" class="post-img" src="/wp-content/uploads/2014/12/yara/4.png"></a></p> <p>And since we had also the ‘to meta-data’ option enabled, we will see the matches when opening a file in the workspace.</p> <p><a href="/wp-content/uploads/2014/12/yara/5.png"><img decoding="async" class="post-img" src="/wp-content/uploads/2014/12/yara/5.png"></a></p> <p>The YARA scan functionality comes also as an action when we find ourselves in a hex view. You can either scan the whole hex data or select a range. Then press Ctrl+R to run an action and select ‘YARA scan’.</p> <p><a href="/wp-content/uploads/2014/12/yara/6.png"><img decoding="async" class="post-img" src="/wp-content/uploads/2014/12/yara/6.png"></a></p> <p>In this case we won’t be given report options, since the only thing which can be performed is to print out matches in the output view.</p> <p><a href="/wp-content/uploads/2014/12/yara/7.png"><img decoding="async" class="post-img" src="/wp-content/uploads/2014/12/yara/7.png"></a></p> <p>Like this:</p> <p><a href="/wp-content/uploads/2014/12/yara/8.png"><img decoding="async" class="post-img" src="/wp-content/uploads/2014/12/yara/8.png"></a></p> <p>Of course, all supported platforms come also with the official YARA command line utility.</p> <p><a href="/wp-content/uploads/2014/12/yara/9.png"><img decoding="async" class="post-img" src="/wp-content/uploads/2014/12/yara/9.png"></a></p> <p>Since this has been a customer request for quite some time, I think it will be appreciated by some of our users.</p></div><footer class="entry-footer">
<span class="byline"><img alt="" src="https://secure.gravatar.com/avatar/7a86aa69922858b8d41989621fc1ea364aae1e027546f88a54d94ab1ec2187fc?s=49&d=mm&r=g" srcset="https://secure.gravatar.com/avatar/7a86aa69922858b8d41989621fc1ea364aae1e027546f88a54d94ab1ec2187fc?s=98&d=mm&r=g 2x" class="avatar avatar-49 photo" height="49" width="49" loading="lazy" decoding="async"><span class="screen-reader-text">Author </span><span class="author vcard"><a class="url fn n" href="https://blog.cerbero.io/author/cerbero/">Erik Pistelli</a></span></span><span class="posted-on"><span class="screen-reader-text">Posted on </span><a href="https://blog.cerbero.io/yara-3-2-0-support/" rel="bookmark"><time class="entry-date published" datetime="2014-12-26T02:19:13+00:00">December 26, 2014</time><time class="updated" datetime="2021-04-01T16:35:26+00:00">April 1, 2021</time></a></span><span class="cat-links"><span class="screen-reader-text">Categories </span><a href="https://blog.cerbero.io/category/action/" rel="category tag">Action</a>, <a href="https://blog.cerbero.io/category/suite-standard/" rel="category tag">Suite Standard</a></span><span class="tags-links"><span class="screen-reader-text">Tags </span><a href="https://blog.cerbero.io/tag/signatures/" rel="tag">signatures</a>, <a href="https://blog.cerbero.io/tag/yara/" rel="tag">YARA</a></span><span class="comments-link"><a href="https://blog.cerbero.io/yara-3-2-0-support/#respond">Leave a comment<span class="screen-reader-text"> on YARA 3.2.0 support</span></a></span> </footer>
<nav class="navigation pagination" aria-label="Posts pagination">
<h2 class="screen-reader-text">Posts pagination</h2>
<div class="nav-links"><a class="prev page-numbers" href="https://blog.cerbero.io/page/16/">Previous page</a> <a class="page-numbers" href="https://blog.cerbero.io/"><span class="meta-nav screen-reader-text">Page </span>1</a> <span class="page-numbers dots">…</span> <a class="page-numbers" href="https://blog.cerbero.io/page/16/"><span class="meta-nav screen-reader-text">Page </span>16</a> <span aria-current="page" class="page-numbers current"><span class="meta-nav screen-reader-text">Page </span>17</span> <a class="page-numbers" href="https://blog.cerbero.io/page/18/"><span class="meta-nav screen-reader-text">Page </span>18</a> <span class="page-numbers dots">…</span> <a class="page-numbers" href="https://blog.cerbero.io/page/27/"><span class="meta-nav screen-reader-text">Page </span>27</a> <a class="next page-numbers" href="https://blog.cerbero.io/page/18/">Next page</a></div></nav>
<aside id="secondary" class="sidebar widget-area">
<section id="search-2" class="widget widget_search">
<form role="search" method="get" class="search-form" action="https://blog.cerbero.io/"></form>
<span class="screen-reader-text">
<input type="search" class="search-field" placeholder="Search …" value="" name="s">
<button type="submit" class="search-submit"><span class="screen-reader-text">
<section id="recent-posts-2" class="widget widget_recent_entries">
<h2 class="widget-title">Recent Posts</h2><nav aria-label="Recent Posts">
<li> <a href="https://blog.cerbero.io/wim-format-package/" aria-current="page">WIM Format Package</a> </li>
<li> <a href="https://blog.cerbero.io/hfs-file-system/">HFS+ File System</a> </li>
<li> <a href="https://blog.cerbero.io/ext-file-systems/">EXT File Systems</a> </li>
<li> <a href="https://blog.cerbero.io/ntfs-file-system/">NTFS File System</a> </li>
<li> <a href="https://blog.cerbero.io/exfat-file-system/">ExFAT File System</a> </li>
<li> <a href="https://blog.cerbero.io/disk-format-package/">Disk Format Package</a> </li>
<li> <a href="https://blog.cerbero.io/fat-file-system/">FAT File System</a> </li>
<li> <a href="https://blog.cerbero.io/prototype-memory-services/">Prototype Memory & Services</a> </li>
<li> <a href="https://blog.cerbero.io/iso-format-2-0-package/">ISO Format 2.0 Package</a> </li>
<li> <a href="https://blog.cerbero.io/memory-decompression-pagefiles/">Memory Decompression & Pagefiles</a> </li>
</nav></section><section id="archives-4" class="widget widget_archive"><h2 class="widget-title">Archives</h2> <label class="screen-reader-text" for="archives-dropdown-4">Archives</label>
<select id="archives-dropdown-4" name="archive-dropdown">
<option value="">Select Month</option>
<option value="https://blog.cerbero.io/2025/06/"> June 2025 (1)</option>
<option value="https://blog.cerbero.io/2025/05/"> May 2025 (7)</option>
<option value="https://blog.cerbero.io/2025/04/"> April 2025 (4)</option>
<option value="https://blog.cerbero.io/2025/03/"> March 2025 (2)</option>
<option value="https://blog.cerbero.io/2024/10/"> October 2024 (3)</option>
<option value="https://blog.cerbero.io/2024/09/"> September 2024 (1)</option>
<option value="https://blog.cerbero.io/2024/08/"> August 2024 (3)</option>
<option value="https://blog.cerbero.io/2024/07/"> July 2024 (5)</option>
<option value="https://blog.cerbero.io/2024/06/"> June 2024 (2)</option>
<option value="https://blog.cerbero.io/2024/04/"> April 2024 (4)</option>
<option value="https://blog.cerbero.io/2024/03/"> March 2024 (1)</option>
<option value="https://blog.cerbero.io/2024/02/"> February 2024 (1)</option>
<option value="https://blog.cerbero.io/2024/01/"> January 2024 (4)</option>
<option value="https://blog.cerbero.io/2023/12/"> December 2023 (3)</option>
<option value="https://blog.cerbero.io/2023/11/"> November 2023 (7)</option>
<option value="https://blog.cerbero.io/2023/10/"> October 2023 (3)</option>
<option value="https://blog.cerbero.io/2023/09/"> September 2023 (1)</option>
<option value="https://blog.cerbero.io/2023/07/"> July 2023 (1)</option>
<option value="https://blog.cerbero.io/2023/05/"> May 2023 (11)</option>
<option value="https://blog.cerbero.io/2023/03/"> March 2023 (9)</option>
<option value="https://blog.cerbero.io/2023/02/"> February 2023 (3)</option>
<option value="https://blog.cerbero.io/2023/01/"> January 2023 (1)</option>
<option value="https://blog.cerbero.io/2022/11/"> November 2022 (1)</option>
<option value="https://blog.cerbero.io/2022/09/"> September 2022 (2)</option>
<option value="https://blog.cerbero.io/2022/08/"> August 2022 (2)</option>
<option value="https://blog.cerbero.io/2022/07/"> July 2022 (3)</option>
<option value="https://blog.cerbero.io/2022/06/"> June 2022 (2)</option>
<option value="https://blog.cerbero.io/2022/05/"> May 2022 (5)</option>
<option value="https://blog.cerbero.io/2022/04/"> April 2022 (3)</option>
<option value="https://blog.cerbero.io/2022/03/"> March 2022 (4)</option>
<option value="https://blog.cerbero.io/2022/02/"> February 2022 (6)</option>
<option value="https://blog.cerbero.io/2022/01/"> January 2022 (1)</option>
<option value="https://blog.cerbero.io/2021/11/"> November 2021 (4)</option>
<option value="https://blog.cerbero.io/2021/10/"> October 2021 (5)</option>
<option value="https://blog.cerbero.io/2021/09/"> September 2021 (7)</option>
<option value="https://blog.cerbero.io/2021/06/"> June 2021 (1)</option>
<option value="https://blog.cerbero.io/2021/04/"> April 2021 (1)</option>
<option value="https://blog.cerbero.io/2021/03/"> March 2021 (4)</option>
<option value="https://blog.cerbero.io/2021/02/"> February 2021 (1)</option>
<option value="https://blog.cerbero.io/2020/12/"> December 2020 (1)</option>
<option value="https://blog.cerbero.io/2020/11/"> November 2020 (1)</option>
<option value="https://blog.cerbero.io/2020/10/"> October 2020 (1)</option>
<option value="https://blog.cerbero.io/2020/09/"> September 2020 (2)</option>
<option value="https://blog.cerbero.io/2020/07/"> July 2020 (2)</option>
<option value="https://blog.cerbero.io/2020/01/"> January 2020 (1)</option>
<option value="https://blog.cerbero.io/2019/09/"> September 2019 (1)</option>
<option value="https://blog.cerbero.io/2019/08/"> August 2019 (2)</option>
<option value="https://blog.cerbero.io/2019/07/"> July 2019 (1)</option>
<option value="https://blog.cerbero.io/2019/06/"> June 2019 (1)</option>
<option value="https://blog.cerbero.io/2019/05/"> May 2019 (3)</option>
<option value="https://blog.cerbero.io/2019/04/"> April 2019 (2)</option>
<option value="https://blog.cerbero.io/2018/06/"> June 2018 (1)</option>
<option value="https://blog.cerbero.io/2018/04/"> April 2018 (1)</option>
<option value="https://blog.cerbero.io/2018/03/"> March 2018 (1)</option>
<option value="https://blog.cerbero.io/2018/01/"> January 2018 (1)</option>
<option value="https://blog.cerbero.io/2017/11/"> November 2017 (2)</option>
<option value="https://blog.cerbero.io/2017/03/"> March 2017 (5)</option>
<option value="https://blog.cerbero.io/2016/07/"> July 2016 (2)</option>
<option value="https://blog.cerbero.io/2016/05/"> May 2016 (2)</option>
<option value="https://blog.cerbero.io/2016/04/"> April 2016 (1)</option>
<option value="https://blog.cerbero.io/2015/10/"> October 2015 (2)</option>
<option value="https://blog.cerbero.io/2015/09/"> September 2015 (2)</option>
<option value="https://blog.cerbero.io/2015/06/"> June 2015 (2)</option>
<option value="https://blog.cerbero.io/2014/12/"> December 2014 (2)</option>
<option value="https://blog.cerbero.io/2014/10/"> October 2014 (1)</option>
<option value="https://blog.cerbero.io/2014/09/"> September 2014 (3)</option>
<option value="https://blog.cerbero.io/2014/08/"> August 2014 (1)</option>
<option value="https://blog.cerbero.io/2014/07/"> July 2014 (1)</option>
<option value="https://blog.cerbero.io/2013/12/"> December 2013 (2)</option>
<option value="https://blog.cerbero.io/2013/11/"> November 2013 (5)</option>
<option value="https://blog.cerbero.io/2013/10/"> October 2013 (5)</option>
<option value="https://blog.cerbero.io/2013/09/"> September 2013 (6)</option>
<option value="https://blog.cerbero.io/2013/08/"> August 2013 (6)</option>
<option value="https://blog.cerbero.io/2013/07/"> July 2013 (1)</option>
<option value="https://blog.cerbero.io/2013/06/"> June 2013 (4)</option>
<option value="https://blog.cerbero.io/2013/05/"> May 2013 (7)</option>
<option value="https://blog.cerbero.io/2013/04/"> April 2013 (5)</option>
<option value="https://blog.cerbero.io/2013/03/"> March 2013 (3)</option>
<option value="https://blog.cerbero.io/2013/02/"> February 2013 (4)</option>
<option value="https://blog.cerbero.io/2013/01/"> January 2013 (3)</option>
<option value="https://blog.cerbero.io/2012/12/"> December 2012 (3)</option>
<option value="https://blog.cerbero.io/2012/11/"> November 2012 (5)</option>
<option value="https://blog.cerbero.io/2012/10/"> October 2012 (3)</option>
<option value="https://blog.cerbero.io/2012/09/"> September 2012 (1)</option>
<option value="https://blog.cerbero.io/2012/08/"> August 2012 (2)</option>
<option value="https://blog.cerbero.io/2012/07/"> July 2012 (2)</option>
<option value="https://blog.cerbero.io/2012/06/"> June 2012 (2)</option>
<option value="https://blog.cerbero.io/2012/05/"> May 2012 (2)</option>
<option value="https://blog.cerbero.io/2012/04/"> April 2012 (1)</option>
<option value="https://blog.cerbero.io/2012/03/"> March 2012 (6)</option>
<option value="https://blog.cerbero.io/2012/02/"> February 2012 (5)</option>
<option value="https://blog.cerbero.io/2012/01/"> January 2012 (8)</option>
<option value="https://blog.cerbero.io/2011/11/"> November 2011 (1)</option>
<option value="https://blog.cerbero.io/2011/08/"> August 2011 (1)</option>
var dropdown=document.getElementById("archives-dropdown-4");
function onSelectChange(){
if(dropdown.options[ dropdown.selectedIndex ].value!==''){
document.location.href=this.options[ this.selectedIndex ].value;
dropdown.onchange=onSelectChange;
</section> </aside><footer id="colophon" class="site-footer">
<nav class="main-navigation" aria-label="Footer Primary Menu">
<div class="menu-main-container"><ul id="menu-main-1" class="primary-menu"><li class="menu-item menu-item-type-custom menu-item-object-custom menu-item-1923"><a href="https://cerbero.io">Home</a></li> <li class="menu-item menu-item-type-custom menu-item-object-custom menu-item-has-children menu-item-2790"><a href="#">Products</a> <ul class="sub-menu"> <li class="menu-item menu-item-type-custom menu-item-object-custom menu-item-2181"><a href="https://cerbero.io/suite/">Cerbero Suite</a></li> <li class="menu-item menu-item-type-custom menu-item-object-custom menu-item-2183"><a href="https://cerbero.io/engine/">Cerbero Engine</a></li> </ul> </li> <li class="menu-item menu-item-type-custom menu-item-object-custom menu-item-2567"><a href="https://cerbero.io/packages/">Packages</a></li> <li class="menu-item menu-item-type-custom menu-item-object-custom menu-item-2430"><a href="https://cerbero.io/e-zine/">E-Zine</a></li> <li class="menu-item menu-item-type-custom menu-item-object-custom menu-item-1926"><a href="/">Blog</a></li> <li class="menu-item menu-item-type-custom menu-item-object-custom menu-item-has-children menu-item-2791"><a href="#">Support</a> <ul class="sub-menu"> <li class="menu-item menu-item-type-custom menu-item-object-custom menu-item-3000"><a href="https://cerbero.io/manual/">User Manual</a></li> <li class="menu-item menu-item-type-custom menu-item-object-custom menu-item-2165"><a href="https://sdk.cerbero.io/">SDK Documentation</a></li> <li class="menu-item menu-item-type-custom menu-item-object-custom menu-item-2514"><a href="https://cerbero.io/faq/">FAQ</a></li> <li class="menu-item menu-item-type-custom menu-item-object-custom menu-item-1927"><a href="https://cerbero.io/resources/">Resources</a></li> <li class="menu-item menu-item-type-custom menu-item-object-custom menu-item-1930"><a href="https://cerbero.io/contact/">Contact</a></li> </ul> </li> <li class="menu-item menu-item-type-custom menu-item-object-custom menu-item-has-children menu-item-2792"><a href="https://cerbero.io/shop/">Shop</a> <ul class="sub-menu"> <li class="menu-item menu-item-type-custom menu-item-object-custom menu-item-1928"><a href="https://cerbero.io/my-account/">My account</a></li> <li class="menu-item menu-item-type-custom menu-item-object-custom menu-item-1929"><a href="https://cerbero.io/cart/">Cart</a></li> </ul> </li> </ul></div></nav>
<div class="site-info"> <span class="site-title"><a href="https://blog.cerbero.io/" rel="home">Cerbero Blog</a></span> <a href="https://wordpress.org/" class="imprint"> Proudly powered by WordPress </a></div></footer><script type="speculationrules">{"prefetch":[{"source":"document","where":{"and":[{"href_matches":"\/*"},{"not":{"href_matches":["\/wp-*.php","\/wp-admin\/*","\/wp-content\/uploads\/*","\/wp-content\/*","\/wp-content\/plugins\/*","\/wp-content\/themes\/twentysixteen-child\/*","\/wp-content\/themes\/twentysixteen\/*","\/*\\?(.+)"]}},{"not":{"selector_matches":"a[rel~=\"nofollow\"]"}},{"not":{"selector_matches":".no-prefetch, .no-prefetch a"}}]},"eagerness":"conservative"}]}</script>
<script src="//blog.cerbero.io/wp-content/cache/wpfc-minified/e5g10mzi/a6zsp.js"></script>
<script id="enlighterjs-js-after">!function(e,n){if("undefined"!=typeof EnlighterJS){var o={"selectors":{"block":"pre","inline":"code"},"options":{"indent":4,"ampersandCleanup":true,"linehover":false,"rawcodeDbclick":false,"textOverflow":"scroll","linenumbers":false,"theme":"enlighter","language":"generic","retainCssClasses":false,"collapse":false,"toolbarOuter":"","toolbarTop":"{BTN_RAW}{BTN_COPY}{BTN_WINDOW}{BTN_WEBSITE}","toolbarBottom":""}};(e.EnlighterJSINIT=function(){EnlighterJS.init(o.selectors.block,o.selectors.inline,o.options)})()}else{(n&&(n.error||n.log)||function(){})("Error: EnlighterJS resources not loaded yet!")}}(window,console);</script></s></b></hex><table id="1"></table></hl></ui>
class TorrentObject(CFFObject):
# ...
def GetTrackers(self):
d = self.GetDictionary()
trackers = []
dup = set()
if b"announce" in d and type(d[b"announce"]) is bytes:
trackers.append(d[b"announce"])
dup.add(trackers[0])
if b"announce-list" in d:
al = d[b"announce-list"]
for a in al:
if type(a) is list and len(a) > 0 and a[0] not in dup and type(a[0]) is bytes:
trackers.append(a[0])
dup.add(a[0])
return trackers
def trackersViewCb(cv, trackers, code, view, data):
if code == pvnInit:
tv = cv.getView(1)
tv.setColumnCount(1)
labels = NTStringList()
labels.append("Tracker")
tv.setColumnLabels(labels)
tv.setColumnCWidth(0, 70)
tv.setRowCount(len(trackers))
return 1
elif code == pvnGetTableRow:
if view.id() == 1:
data.setText(0, trackers[data.row].decode("utf-8", errors="ignore"))
return 0
class TorrentScanProvider(ScanProvider):
# ...
def _formatViewData(self, sdata):
# ...
elif sdata.fid == self.FormatItem_Trackers:
sdata.setViews(SCANVIEW_CUSTOM)
sdata.data.setData("<ui><hl margin="0">")
sdata.setCallback(trackersViewCb, self.obj.GetTrackers())
return True
return False<p><a href="/wp-content/uploads/2015/09/torrent/trackers.png"><img decoding="async" class="post-img" src="/wp-content/uploads/2015/09/torrent/trackers.png" alt="Trackers"></a></p><p>When retrieving data from the dictionary, we also make sure that it is in the correct type, so that the code which handles this data won't end up generating an exception when trying to process an unexpected type.</p><p>And now the files:</p><pre lang="python">class TorrentObject(CFFObject):
# ...
def GetFiles(self):
d = self.GetDictionary()
if not b"info" in d:
return []
d = d[b"info"]
if not type(d) is dict:
return []
files = []
if not b"files" in d:
if b"name" in d and type(d[b"name"]) is bytes:
sz = d.get(b"length", 0)
files.append((d[b"name"], sz if type(sz) is int else 0))
else:
flist = d[b"files"]
if not type(flist) is list:
return []
for fd in flist:
if type(fd) is dict:
if b"path" in fd:
pt = fd[b"path"]
if type(pt) is list and len(pt) > 0 and type(pt[0]) is bytes:
sz = fd.get(b"length", 0)
files.append((pt[0], sz if type(sz) is int else 0))
return files
def filesViewCb(cv, files, code, view, data):
if code == pvnInit:
tv = cv.getView(1)
tv.setColumnCount(2)
labels = NTStringList()
labels.append("Name")
labels.append("Size")
tv.setColumnLabels(labels)
tv.setColumnCWidth(0, 70)
tv.setColumnCWidth(1, 35)
tv.setRowCount(len(files))
return 1
elif code == pvnGetTableRow:
if view.id() == 1:
data.setText(0, files[data.row][0].decode("utf-8", errors="ignore"))
sz = files[data.row][1]
data.setText(1, "%.02f MBs (%d bytes)" % (sz / 0x100000, sz))
return 0
class TorrentScanProvider(ScanProvider):
# ...
def _formatViewData(self, sdata):
# ...
elif sdata.fid == self.FormatItem_Files:
sdata.setViews(SCANVIEW_CUSTOM)
sdata.data.setData("<ui><hl margin="0"></hl></ui></pre><table id="1"> </table>")
sdata.setCallback(filesViewCb, self.obj.GetFiles())
return True
return False<p><a href="/wp-content/uploads/2015/09/torrent/files.png"><img decoding="async" class="post-img" src="/wp-content/uploads/2015/09/torrent/files.png" alt="Files"></a></p><p>And that's it. Now again the whole code for a better overview:</p><pre lang="python">from Pro.Core import *
from Pro.UI import pvnInit, pvnGetTableRow
import pprint
MAX_TORRENT_SIZE = 10485760 # 10 MBs
#
# BEGIN OF 3RD PARTY CODE (adapted to work with Python 3)
#
# The contents of this file are subject to the BitTorrent Open Source License
# Version 1.1 (the License). You may not copy or use this file, in either
# source code or executable form, except in compliance with the License. You
# may obtain a copy of the License at http://www.bittorrent.com/license/.
#
# Software distributed under the License is distributed on an AS IS basis,
# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
# for the specific language governing rights and limitations under the
# License.
# Written by Petru Paler
def decode_int(x, f):
f += 1
newf = x.index(0x65, f)
n = int(x[f:newf])
if x[f] == 0x2D: # -
if x[f + 1] == 0x30:
raise ValueError
elif x[f] == 0x30 and newf != f+1:
raise ValueError
return (n, newf+1)
def decode_string(x, f):
colon = x.index(0x3A, f) # :
n = int(x[f:colon])
if x[f] == 0x30 and colon != f+1:
raise ValueError
colon += 1
return (x[colon:colon+n], colon+n)
def decode_list(x, f):
r, f = [], f+1
while x[f] != 0x65: # e
v, f = decode_func[x[f]](x, f)
r.append(v)
return (r, f + 1)
def decode_dict(x, f):
r, f = {}, f+1
while x[f] != 0x65: # e
k, f = decode_string(x, f)
r[k], f = decode_func[x[f]](x, f)
return (r, f + 1)
decode_func = {}
decode_func[0x6C] = decode_list # l
decode_func[0x64] = decode_dict # d
decode_func[0x69] = decode_int # i
decode_func[0x30] = decode_string
decode_func[0x31] = decode_string
decode_func[0x32] = decode_string
decode_func[0x33] = decode_string
decode_func[0x34] = decode_string
decode_func[0x35] = decode_string
decode_func[0x36] = decode_string
decode_func[0x37] = decode_string
decode_func[0x38] = decode_string
decode_func[0x39] = decode_string
def bdecode(x):
try:
r, l = decode_func[x[0]](x, 0)
except (IndexError, KeyError, ValueError):
return {}
if l != len(x):
return {}
return r
#
# END OF 3RD PARTY CODE
#
class TorrentObject(CFFObject):
def __init__(self):
super(TorrentObject, self).__init__()
self.SetObjectFormatName("TORRENT")
self.SetDefaultEndianness(ENDIANNESS_LITTLE)
self.tdict = None
def GetDictionary(self):
if self.tdict == None:
size = min(self.GetSize(), MAX_TORRENT_SIZE)
data = self.Read(0, size)
self.tdict = bdecode(bytes(data))
return self.tdict
def CreationDate(self):
d = self.GetDictionary()
cd = d.get(b"creation date", None)
if cd == None or not type(cd) is int:
return NTDateTime()
return NTDateTime.fromMSecsSinceEpoch(cd * 1000)
def GetTrackers(self):
d = self.GetDictionary()
trackers = []
dup = set()
if b"announce" in d and type(d[b"announce"]) is bytes:
trackers.append(d[b"announce"])
dup.add(trackers[0])
if b"announce-list" in d:
al = d[b"announce-list"]
for a in al:
if type(a) is list and len(a) > 0 and a[0] not in dup and type(a[0]) is bytes:
trackers.append(a[0])
dup.add(a[0])
return trackers
def GetFiles(self):
d = self.GetDictionary()
if not b"info" in d:
return []
d = d[b"info"]
if not type(d) is dict:
return []
files = []
if not b"files" in d:
if b"name" in d and type(d[b"name"]) is bytes:
sz = d.get(b"length", 0)
files.append((d[b"name"], sz if type(sz) is int else 0))
else:
flist = d[b"files"]
if not type(flist) is list:
return []
for fd in flist:
if type(fd) is dict:
if b"path" in fd:
pt = fd[b"path"]
if type(pt) is list and len(pt) > 0 and type(pt[0]) is bytes:
sz = fd.get(b"length", 0)
files.append((pt[0], sz if type(sz) is int else 0))
return files
def trackersViewCb(cv, trackers, code, view, data):
if code == pvnInit:
tv = cv.getView(1)
tv.setColumnCount(1)
labels = NTStringList()
labels.append("Tracker")
tv.setColumnLabels(labels)
tv.setColumnCWidth(0, 70)
tv.setRowCount(len(trackers))
return 1
elif code == pvnGetTableRow:
if view.id() == 1:
data.setText(0, trackers[data.row].decode("utf-8", errors="ignore"))
return 0
def filesViewCb(cv, files, code, view, data):
if code == pvnInit:
tv = cv.getView(1)
tv.setColumnCount(2)
labels = NTStringList()
labels.append("Name")
labels.append("Size")
tv.setColumnLabels(labels)
tv.setColumnCWidth(0, 70)
tv.setColumnCWidth(1, 35)
tv.setRowCount(len(files))
return 1
elif code == pvnGetTableRow:
if view.id() == 1:
data.setText(0, files[data.row][0].decode("utf-8", errors="ignore"))
sz = files[data.row][1]
data.setText(1, "%.02f MBs (%d bytes)" % (sz / 0x100000, sz))
return 0
class TorrentScanProvider(ScanProvider):
def __init__(self):
super(TorrentScanProvider, self).__init__()
self.obj = None
self.meta_keys = [b"created by", b"creation date", b"comment"]
# format item IDs
self.FormatItem_Dictionary = 1
self.FormatItem_Trackers = 2
self.FormatItem_Files = 3
# format item names
self.fi_names = ["Dictionary", "Trackers", "Files"]
def _clear(self):
self.obj = None
def _getObject(self):
return self.obj
def _initObject(self):
self.obj = TorrentObject()
self.obj.Load(self.getStream())
d = self.obj.GetDictionary()
return self.SCAN_RESULT_OK if len(d) != 0 else self.SCAN_RESULT_ERROR
def _startScan(self):
d = self.obj.GetDictionary()
if any(mk in d for mk in self.meta_keys):
e = ScanEntryData()
e.category = SEC_Privacy
e.type = CT_MetaData
self.addEntry(e)
if self.obj.GetSize() > MAX_TORRENT_SIZE:
e = ScanEntryData()
e.category = SEC_Warn
e.type = CT_UnaccountedSpace
self.addEntry(e)
return self.SCAN_RESULT_FINISHED
def _scanViewData(self, xml, dnode, sdata):
if sdata.type == CT_MetaData:
d = self.obj.GetDictionary()
out = proTextStream()
for mk in self.meta_keys:
if mk in d:
tmk = mk.decode("utf-8", errors="ignore")
if tmk == "creation date":
dt = self.obj.CreationDate()
tmv = dt.toString() if dt.isValid() else "?"
else:
tmv = d[mk].decode("utf-8", errors="ignore")
out._print(tmk)
out._print(": ")
out._print(tmv)
out.nl()
sdata.setViews(SCANVIEW_TEXT)
sdata.data.setData(out.buffer)
return True
elif sdata.type == CT_UnaccountedSpace:
sdata.setViews(SCANVIEW_TEXT)
sdata.data.setData("The file size exceeds the maximum allowed one of %d bytes!" % (MAX_TORRENT_SIZE,))
return True
return False
def _getFormat(self):
ft = FormatTree()
ft.enableIDs(True)
fi = ft.appendChild(None, self.FormatItem_Dictionary)
ft.appendChild(fi, self.FormatItem_Trackers)
ft.appendChild(fi, self.FormatItem_Files)
return ft
def _formatViewInfo(self, finfo):
if finfo.fid >= 1 or finfo.fid - 1 < len(self.fi_names):
finfo.text = self.fi_names[finfo.fid - 1]
return True
return False
def _formatViewData(self, sdata):
if sdata.fid == self.FormatItem_Dictionary:
sdata.setViews(SCANVIEW_TEXT)
txt = pprint.pformat(self.obj.GetDictionary())
sdata.data.setData(txt)
return True
elif sdata.fid == self.FormatItem_Trackers:
sdata.setViews(SCANVIEW_CUSTOM)
sdata.data.setData("<ui><hl margin="0"></hl></ui></pre><table id="1"> </table>")
sdata.setCallback(trackersViewCb, self.obj.GetTrackers())
return True
elif sdata.fid == self.FormatItem_Files:
sdata.setViews(SCANVIEW_CUSTOM)
sdata.data.setData("<ui><hl margin="0"></hl></ui><table id="1"></table>")
sdata.setCallback(filesViewCb, self.obj.GetFiles())
return True
return False
def torrentAllocator():
return TorrentScanProvider()<p>We could still extract more information from the torrent file. For instance, we could show the list of hashes and to which portion of which file they belong to. If that's interesting for forensic purposes, we can easily add this view in the future.</p><footer class="entry-footer">
<span class="byline"><img alt="" src="https://secure.gravatar.com/avatar/7a86aa69922858b8d41989621fc1ea364aae1e027546f88a54d94ab1ec2187fc?s=49&d=mm&r=g" srcset="https://secure.gravatar.com/avatar/7a86aa69922858b8d41989621fc1ea364aae1e027546f88a54d94ab1ec2187fc?s=98&d=mm&r=g 2x" class="avatar avatar-49 photo" height="49" width="49" loading="lazy" decoding="async"><span class="screen-reader-text">Author </span><span class="author vcard"><a class="url fn n" href="https://blog.cerbero.io/author/cerbero/">Erik Pistelli</a></span></span><span class="posted-on"><span class="screen-reader-text">Posted on </span><a href="https://blog.cerbero.io/torrent-support/" rel="bookmark"><time class="entry-date published" datetime="2015-09-23T16:50:31+00:00">September 23, 2015</time><time class="updated" datetime="2021-04-01T16:32:00+00:00">April 1, 2021</time></a></span><span class="cat-links"><span class="screen-reader-text">Categories </span><a href="https://blog.cerbero.io/category/forensics/" rel="category tag">Forensics</a>, <a href="https://blog.cerbero.io/category/suite-standard/" rel="category tag">Suite Standard</a></span><span class="tags-links"><span class="screen-reader-text">Tags </span><a href="https://blog.cerbero.io/tag/p2p/" rel="tag">P2P</a>, <a href="https://blog.cerbero.io/tag/torrent/" rel="tag">Torrent</a>, <a href="https://blog.cerbero.io/tag/trackers/" rel="tag">Trackers</a></span><span class="comments-link"><a href="https://blog.cerbero.io/torrent-support/#respond">Leave a comment<span class="screen-reader-text"> on Torrent Support</span></a></span> </footer><article id="post-1551" class="post-1551 post type-post status-publish format-standard hentry category-suite-standard">
<header class="entry-header">
<h2 class="entry-title"><a href="https://blog.cerbero.io/scan-providers/" rel="bookmark">Scan Providers</a></h2> </header>
<div class="entry-content"> <p>Version 2.5.0 is close to being released and comes with the last type of extension exposed to Python: scan providers. Scan providers extensions are not only the most complex type of extensions, but also the most powerful ones as they allow to add support for new file formats entirely from Python! </p> <p>This feature required exposing a lot more of the SDK to Python and can’t be completely discussed in one post. This post is going to introduce the topic, while future posts will show real life examples.</p> <p>Let’s start from the list of Python scan providers under Extensions -> Scan providers:</p> <p><a href="/wp-content/uploads/2015/09/scanp/extlist.png"><img decoding="async" class="post-img" src="/wp-content/uploads/2015/09/scanp/extlist.png" alt="Scan provider extensions"></a></p> <p>This list is retrieved from the configuration file ‘scanp.cfg’. Here’s an example entry:</p> <pre lang="ini">[TEST]
label = Test scan provider
ext = test2,test3
group = db
file = Test.py
allocator = allocator</pre> <p>The name of the section has two purposes: it specifies the name of the format being supported (in this case ‘TEST’) and also the name of the extension, which automatically is associated to that format (in this case ‘.test’, case insensitive). The hard limit for format names is 9 characters for now, this may change in the future if more are needed. The <strong>label</strong> is the description. The <strong>ext</strong> parameter is optional and specifies additional extensions to be associated to the format. <strong>group</strong> specifies the type of file which is being supported; available groups are: img, video, audio, doc, font, exe, manexe, arch, db, sys, cert, script. <strong>file</strong> specifies the Python source file and <strong>allocator</strong> the function which returns a new instance of the scan provider class.</p> <p>Let’s start with the allocator:</p> <pre lang="python">def allocator():
return TestScanProvider()</pre> <p>It just returns a new instance of <strong>TestScanProvider</strong>, which is a class dervided from <strong>ScanProvider</strong>:</p> <pre lang="python">class TestScanProvider(ScanProvider):
def __init__(self):
super(TestScanProvider, self).__init__()
self.obj = None</pre> <p>Every scan provider has some mandatory methods it must override, let’s begin with the first ones:</p> <pre lang="python"> def _clear(self):
self.obj = None
def _getObject(self):
return self.obj
def _initObject(self):
self.obj = TestObject()
self.obj.Load(self.getStream())
return self.SCAN_RESULT_OK</pre> <p><strong>_clear</strong> gives a chance to free internal resources when they’re no longer used. In Python this is not usually important as member objects will automatically be freed when their reference count reaches zero.</p> <p><strong>_getObject</strong> must return the internal instance of the object being parsed. This must return an instance of a <strong>CFFObject</strong> derived class.</p> <p><strong>_initObject</strong> creates the object instance and loads the data stream into it. In the sample above we assume it being successful. Otherwise, we would have to return <strong>SCAN_RESULT_ERROR</strong>. This method is not called by the main thread, so that it doesn’t block the UI during long parse operations.</p> <p>Let’s take a look at the <strong>TestObject</strong> class:</p> <pre lang="python">class TestObject(CFFObject):
def __init__(self):
super(TestObject, self).__init__()
self.SetObjectFormatName("TEST")
self.SetDefaultEndianness(ENDIANNESS_LITTLE)</pre> <p>This is a minimalistic implementation of a <strong>CFFObject</strong> derived class. Usually it should contain at least an override of the <strong>CustomLoad</strong> method, which gives the opportunity to fail when the data stream is first loaded through the <strong>Load</strong> method. <strong>SetDefaultEndianness</strong> wouldn’t even be necessary, as every object defaults to little endian by default. <strong>SetObjectFormatName</strong>, on the other hand, is very important, as it sets the internal format name of the object.</p> <p>Let’s now take a look at how we scan a file:</p> <pre lang="python"> def _startScan(self):
return self.SCAN_RESULT_OK
def _threadScan(self):
e = ScanEntryData()
e.category = SEC_Warn
e.type = CT_NativeCode
self.addEntry(e)</pre> <p>The code above will issue a single warning concerning native code. When <strong>_startScan</strong> returns <strong>SCAN_RESULT_OK</strong>, <strong>_threadScan</strong> will be called from a thread other than the main UI one. The logic behind this is that <strong>_startScan</strong> is actually called from the main thread and if the scan of the file doesn’t require complex operations, like in the case above, then the method could return <strong>SCAN_RESULT_FINISHED</strong> and then <strong>_threadScan</strong> won’t be called at all. During a threaded scan, an abort by the user can be detected via the <strong>isAborted</strong> method.</p> <p>From the UI side point of view, when a scan entry is clicked in summary, the scan provider is supposed to return UI information. </p> <pre lang="python"> def _scanViewData(self, xml, dnode, sdata):
if sdata.type == CT_NativeCode:
sdata.setViews(SCANVIEW_TEXT)
sdata.data.setData("Hello, world!")
return True
return False</pre> <p>This will display a text field with a predefined content when the user clicks the scan entry in the summary. This is fairly easy, but what happens when we have several entries of the same type and need to differentiate between them? There’s where the <strong>data</strong> member of <strong>ScanEntryData</strong> plays a role, this is a string which will be included in the report xml and passed again back to <strong>_scanViewData</strong> as an xml node.</p> <p>For instance:</p> <pre lang="python">e.data = "<o>1234</o>"</pre> <p>Becomes this in the final XML report:</p> <pre lang="xml"><d>
<o>1234</o>
</d></pre> <p>The <strong>dnode</strong> argument of <strong>_scanViewData</strong> points to the ‘d’ node and its first child will be the ‘o’ node we passed. the <strong>xml</strong> argument represents an instance of the <strong>NTXml</strong> class, which can be used to retrieve the children of the <strong>dnode</strong>.</p> <p>But this is only half of the story: some of the scan entries may represent embedded files (category <strong>SEC_File</strong>), in which case the <strong>_scanViewData</strong> method must return the data representing the file.</p> <p>Apart from scan entries, we may also want the user to explore the format of the file. To do that we must return a tree representing the structure of our file:</p> <pre lang="python"> def _getFormat(self):
ft = FormatTree()
ft.enableIDs(True)
fi = ft.appendChild(None, 1)
ft.appendChild(fi, 2)
return ft</pre> <p>The <strong>enableIDs</strong> method must be called right after creating a new <strong>FormatTree</strong> class. The code above creates a format item with id 1 with a child item with id 2, which results in the following:</p> <p><a href="/wp-content/uploads/2015/09/scanp/format.png"><img decoding="async" class="post-img" src="/wp-content/uploads/2015/09/scanp/format.png" alt="Format tree"></a></p> <p>But of course, we haven’t specified neither labels nor different icons in the function above. This information is retrieved for each item when required through the following method:</p> <pre lang="python"> def _formatViewInfo(self, finfo):
if finfo.fid == 1:
finfo.text = "directory"
finfo.icon = PubIcon_Dir
return True
elif finfo.fid == 2:
finfo.text = "entry"
return True
return False</pre> <p>The various items are identified by their id, which was specified during the creation of the tree.</p> <p>The UI data for each item is retrieved through the <strong>_formatViewData</strong> method:</p> <pre lang="python"> def _formatViewData(self, sdata):
if sdata.fid == 1:
sdata.setViews(SCANVIEW_CUSTOM)
sdata.data.setData("<ui><hsplitter csizes="40-*"></hsplitter></ui></pre></div></article><table id="1">
</table><hex id="2">")
sdata.setCallback(cb, None)
return True
return False <p>This will display a custom view with a table and a hex view separated by a splitter:</p> <p><a href="/wp-content/uploads/2015/09/scanp/cview.png"><img decoding="async" class="post-img" src="/wp-content/uploads/2015/09/scanp/cview.png" alt="Custom view"></a></p> <p>Of course, also have specified the callback for our custom view:</p> <pre lang="python">def cb(cv, ud, code, view, data):
if code == pvnInit:
return 1
return 0</pre> <p>It is good to remember that format item IDs and IDs used in custom views are used to encode bookmark jumps. So if they change, saved bookmark jumps become invalid.</p> <p>And here again the whole code for a better overview:</p> <pre lang="python">from Pro.Core import *
from Pro.UI import pvnInit, PubIcon_Dir
class TestObject(CFFObject):
def __init__(self):
super(TestObject, self).__init__()
self.SetObjectFormatName("TEST")
self.SetDefaultEndianness(ENDIANNESS_LITTLE)
def cb(cv, ud, code, view, data):
if code == pvnInit:
return 1
return 0
class TestScanProvider(ScanProvider):
def __init__(self):
super(TestScanProvider, self).__init__()
self.obj = None
def _clear(self):
self.obj = None
def _getObject(self):
return self.obj
def _initObject(self):
self.obj = TestObject()
self.obj.Load(self.getStream())
return self.SCAN_RESULT_OK
def _startScan(self):
return self.SCAN_RESULT_OK
def _threadScan(self):
print("thread msg")
e = ScanEntryData()
e.category = SEC_Warn
e.type = CT_NativeCode
self.addEntry(e)
def _scanViewData(self, xml, dnode, sdata):
if sdata.type == CT_NativeCode:
sdata.setViews(SCANVIEW_TEXT)
sdata.data.setData("Hello, world!")
return True
return False
def _getFormat(self):
ft = FormatTree()
ft.enableIDs(True)
fi = ft.appendChild(None, 1)
ft.appendChild(fi, 2)
return ft
def _formatViewInfo(self, finfo):
if finfo.fid == 1:
finfo.text = "directory"
finfo.icon = PubIcon_Dir
return True
elif finfo.fid == 2:
finfo.text = "entry"
return True
return False
def _formatViewData(self, sdata):
if sdata.fid == 1:
sdata.setViews(SCANVIEW_CUSTOM)
sdata.data.setData("<ui><hsplitter csizes="40-*"></hsplitter></ui></pre></hex><table id="1"></table><hex id="2">")
sdata.setCallback(cb, None)
return True
return False
def allocator():
return TestScanProvider() <p>If you have noticed from the screen-shot above, the analysed file is called ‘a.t’ and as such doesn’t automatically associate to our ‘test’ format. So how does it associate anyway?</p> <p>Clearly Profiler doesn’t rely on extensions alone to identify the format of a file. For external scan providers a signature mechanism based on YARA has been introduced. In the <strong>config</strong> directory of the user, you can create a file named ‘yara.plain’ and insert your identification rules in it, e.g.:</p> <pre lang="text">rule test
{
strings:
$sig = "test"
condition:
$sig at 0
}</pre> <p>This rule will identify the format as ‘test’ if the first 4 bytes of the file match the string ‘test’: the name of the rule identifies the format.</p> <p>The file ‘yara.plain’ will be compiled to the binary ‘yara.rules’ file at the first run. In order to refresh ‘yara.rules’, you must delete it.</p> <p>One important thing to remember is that a rule isn’t matched against an entire file, but only against the first 512 bytes.</p> <p>Of course, our provider behaves 100% like all other providers and can be used to load embedded files:</p> <p><a href="/wp-content/uploads/2015/09/scanp/embfiles.png"><img decoding="async" class="post-img" src="/wp-content/uploads/2015/09/scanp/embfiles.png" alt="Embedded files"></a></p> <p>Our new provider is used automatically when an embedded file is identified as matching our format.</p><footer class="entry-footer">
<span class="byline"><img alt="" src="https://secure.gravatar.com/avatar/7a86aa69922858b8d41989621fc1ea364aae1e027546f88a54d94ab1ec2187fc?s=49&d=mm&r=g" srcset="https://secure.gravatar.com/avatar/7a86aa69922858b8d41989621fc1ea364aae1e027546f88a54d94ab1ec2187fc?s=98&d=mm&r=g 2x" class="avatar avatar-49 photo" height="49" width="49" loading="lazy" decoding="async"><span class="screen-reader-text">Author </span><span class="author vcard"><a class="url fn n" href="https://blog.cerbero.io/author/cerbero/">Erik Pistelli</a></span></span><span class="posted-on"><span class="screen-reader-text">Posted on </span><a href="https://blog.cerbero.io/scan-providers/" rel="bookmark"><time class="entry-date published" datetime="2015-09-21T22:13:50+00:00">September 21, 2015</time><time class="updated" datetime="2021-04-01T16:32:53+00:00">April 1, 2021</time></a></span><span class="cat-links"><span class="screen-reader-text">Categories </span><a href="https://blog.cerbero.io/category/suite-standard/" rel="category tag">Suite Standard</a></span><span class="comments-link"><a href="https://blog.cerbero.io/scan-providers/#respond">Leave a comment<span class="screen-reader-text"> on Scan Providers</span></a></span> </footer>
<article id="post-1539" class="post-1539 post type-post status-publish format-standard hentry category-suite-standard tag-command-line tag-news">
<header class="entry-header">
<h2 class="entry-title"><a href="https://blog.cerbero.io/profiler-2-4/" rel="bookmark">Profiler 2.4</a></h2> </header>
<div class="entry-content"> <p>Profiler 2.4 is out with the following news:</p> <p>– <a href="/?p=1530">added initial support for PDB files (including export of types)</a><br> – <a href="#wscript">added support for Windows Encoded Scripts (VBE, JSE)</a><br> – introduced fixed xml structures<br> – <a href="#sdec">added automatic string decoding in struct tables</a><br> – <a href="#pyline">added Python string command line execution</a><br> – remember the last selected logic group<br> – fixed missing support for wchar_t in C types<br> – updated Qt to 5.4.1<br> – various bug fixes</p> <p>While the most important newly introduced feature is the support for PDB files, here are some interesting new features:</p> <p><a name="wscript"></a></p> <h2>Support for Windows Encoded Scripts (VBE, JSE)</h2> <p>Windows encoded scripts like VBE and JSE files (the encoded variants of VBS and JS script files) are now supported and automatically decoded.</p> <p><a href="/wp-content/uploads/2015/06/24/wscript.png"><img decoding="async" class="post-img" src="/wp-content/uploads/2015/06/24/wscript.png"></a></p> <p>In the screen-shot you can see the decoded output of an encoded file (showed at the bottom).</p> <p><a name="sdec"></a></p> <h2>Automatic string decoding in struct tables</h2> <p>A very basic feature: byte-arrays in structures are automatically checked for strings and in case decoded.</p> <p><a href="/wp-content/uploads/2015/06/24/sdec.png"><img decoding="async" class="post-img" src="/wp-content/uploads/2015/06/24/sdec.png"></a></p> <p>(notice the section name automatically displayed as ascii string)</p> <p><a name="pyline"></a></p> <h2>Python string command line execution</h2> <p>Apart from <a href="/?p=1464">executing script files passed as command line arguments</a>, now it is also possible to execute Python statements directly passed as argument. </p> <p>For instance:</p> <pre lang="text">cerpro -c -e "from Pro.Core import *;proCoreContext().msgBox(0, \"Hello world!\")"</pre> <p>The optional argument ‘-c’ specifies to not display the UI.</p> <p>Enjoy!</p></div><footer class="entry-footer">
<span class="byline"><img alt="" src="https://secure.gravatar.com/avatar/7a86aa69922858b8d41989621fc1ea364aae1e027546f88a54d94ab1ec2187fc?s=49&d=mm&r=g" srcset="https://secure.gravatar.com/avatar/7a86aa69922858b8d41989621fc1ea364aae1e027546f88a54d94ab1ec2187fc?s=98&d=mm&r=g 2x" class="avatar avatar-49 photo" height="49" width="49" loading="lazy" decoding="async"><span class="screen-reader-text">Author </span><span class="author vcard"><a class="url fn n" href="https://blog.cerbero.io/author/cerbero/">Erik Pistelli</a></span></span><span class="posted-on"><span class="screen-reader-text">Posted on </span><a href="https://blog.cerbero.io/profiler-2-4/" rel="bookmark"><time class="entry-date published" datetime="2015-06-06T16:43:45+00:00">June 6, 2015</time><time class="updated" datetime="2021-04-01T16:33:35+00:00">April 1, 2021</time></a></span><span class="cat-links"><span class="screen-reader-text">Categories </span><a href="https://blog.cerbero.io/category/suite-standard/" rel="category tag">Suite Standard</a></span><span class="tags-links"><span class="screen-reader-text">Tags </span><a href="https://blog.cerbero.io/tag/command-line/" rel="tag">Command-line</a>, <a href="https://blog.cerbero.io/tag/news/" rel="tag">News</a></span><span class="comments-link"><a href="https://blog.cerbero.io/profiler-2-4/#respond">Leave a comment<span class="screen-reader-text"> on Profiler 2.4</span></a></span> </footer>
</article>
<article id="post-1530" class="post-1530 post type-post status-publish format-standard hentry category-suite-standard tag-headers tag-pdb tag-python tag-sdk">
<header class="entry-header">
<h2 class="entry-title"><a href="https://blog.cerbero.io/pdb-support-including-export-of-types/" rel="bookmark">PDB support (including export of types)</a></h2> </header>
<div class="entry-content"> <p>The main feature of the upcoming 2.4 version of Profiler is the initial support for the PDB format. Our code doesn’t rely on the Microsoft DIA SDK and thus works also on OS X and Linux.</p> <p>Since the PDB format is undocumented, this task would’ve been extremely difficult without the <a href="http://undocumented.rawol.com/">fantastic work on PDBs</a> of the never too much revered Sven B. Schreiber.</p> <p>Let’s open a PDB file.</p> <p><a href="/wp-content/uploads/2015/06/pdb/streams.png"><img decoding="async" class="post-img" src="/wp-content/uploads/2015/06/pdb/streams.png"></a></p> <p>As you can see the streams in the PDB can be explored. The TPI stream (the one describing types) offers further inspection.</p> <p><a href="/wp-content/uploads/2015/06/pdb/tpi.png"><img decoding="async" class="post-img" src="/wp-content/uploads/2015/06/pdb/tpi.png"></a></p> <p>All the types contained in the PDB can be exported to a Profiler header by pressing Ctrl+R and executing the ‘Dump types to header’ action.</p> <p><img decoding="async" class="post-img" src="/wp-content/uploads/2015/06/pdb/action.png"></p> <p>Now the types can be used from both the hex editor and the Python SDK. </p> <p><a href="/wp-content/uploads/2015/06/pdb/hexed.png"><img decoding="async" class="post-img" src="/wp-content/uploads/2015/06/pdb/hexed.png"></a></p> <p>We can explore the dumped header by using, as usual, the Header Manager tool.</p> <p><a href="/wp-content/uploads/2015/06/pdb/hdrmgr.png"><img decoding="async" class="post-img" src="/wp-content/uploads/2015/06/pdb/hdrmgr.png"></a></p> <p>The type showed above in the hex editor is simple. So let’s look what a more complex PDB type may look like.</p> <pre lang="xml"><r id="CWnd" type="class" size="84">
<b>
<b type="CCmdTarget" offset="0" access="public">
</b>
<m id="_GetBaseClass" type="CRuntimeClass * ()">
<s id="classCWnd" type="CRuntimeClass const">
<m id="GetThisClass" type="CRuntimeClass * ()">
<m id="GetRuntimeClass" type="CRuntimeClass * ()">
<m id="CreateObject" type="CObject * ()">
<m id="GetCurrentMessage" type="tagMSG const * ()">
<f id="m_hWnd" type="HWND__ *" offset="32">
<m id="operator struct HWND__ *" type="HWND__ * ()">
<m id="operator==" type="int32 (CWnd const *)">
<m id="operator!=" type="int32 (CWnd const *)">
<m id="GetSafeHwnd" type="HWND__ * ()">
<m id="GetStyle" type="unsigned int ()">
<m id="GetExStyle" type="unsigned int ()">
<m id="ModifyStyle" type="int32 (HWND__ *, unsigned int, unsigned int, uint32)">
<m id="ModifyStyle" type="int32 (unsigned int, unsigned int, uint32)">
<m id="ModifyStyleEx" type="int32 (HWND__ *, unsigned int, unsigned int, uint32)">
<m id="ModifyStyleEx" type="int32 (unsigned int, unsigned int, uint32)">
<m id="GetOwner" type="CWnd * ()">
<m id="SetOwner" type="void (CWnd *)">
<m id="GetWindowInfo" type="int32 (tagWINDOWINFO *)">
<m id="GetTitleBarInfo" type="int32 (tagTITLEBARINFO *)">
<m id="CWnd" type="void (CWnd const *)">
<m id="CWnd" type="void (HWND__ *)">
<m id="CWnd" type="void ()">
<m id="FromHandle" type="CWnd * (HWND__ *)">
<m id="FromHandlePermanent" type="CWnd * (HWND__ *)">
<m id="DeleteTempMap" type="void ()">
<!-- etc. -->
</m></m></m></m></m></m></m></m></m></m></m></m></m></m></m></m></m></m></m></m></f></m></m></m></m></s></m></b></r></pre><b><s id="classCWnd" type="CRuntimeClass const"> <p>The PDB code is also exposed to the SDK. This is a small snippet of code, which dumps all the types to a text buffer and then displays them in a text view.</p> <pre lang="python">from Pro.Core import *
from Pro.UI import *
from Pro.PDB import *
def showPDBTypes():
ctx = proContext()
out = proTextStream()
out.setIndentSize(4)
obj = ctx.currentScanProvider().getObject()
tpi = obj.GetStreamObject(PDB_STREAM_ID_TPI)
tpihdr = obj.TPIHeader(tpi)
tiMin = tpihdr.Num("tiMin")
tiMax = tpihdr.Num("tiMax")
tctx = obj.CreateTypeContext(tpi)
for ti in range(tiMin, tiMax):
tctx.DumpType(out, ti)
view = ctx.createView(ProView.Type_Text, "PDB Test")
view.setLanguage("XML")
view.setText(out.buffer)
ctx.addView(view)
showPDBTypes()</pre> <p><a href="/wp-content/uploads/2015/06/pdb/pyresult.png"><img decoding="async" class="post-img" src="/wp-content/uploads/2015/06/pdb/pyresult.png"></a></p> <p>In order to dump all types to a single header, you can use the <strong>DumpAllToHeader</strong> method.</p></s></b></div><footer class="entry-footer"><b><s id="classCWnd" type="CRuntimeClass const">
<span class="byline"><img alt="" src="https://secure.gravatar.com/avatar/7a86aa69922858b8d41989621fc1ea364aae1e027546f88a54d94ab1ec2187fc?s=49&d=mm&r=g" srcset="https://secure.gravatar.com/avatar/7a86aa69922858b8d41989621fc1ea364aae1e027546f88a54d94ab1ec2187fc?s=98&d=mm&r=g 2x" class="avatar avatar-49 photo" height="49" width="49" loading="lazy" decoding="async"><span class="screen-reader-text">Author </span><span class="author vcard"><a class="url fn n" href="https://blog.cerbero.io/author/cerbero/">Erik Pistelli</a></span></span><span class="posted-on"><span class="screen-reader-text">Posted on </span><a href="https://blog.cerbero.io/pdb-support-including-export-of-types/" rel="bookmark"><time class="entry-date published" datetime="2015-06-01T08:09:04+00:00">June 1, 2015</time><time class="updated" datetime="2021-04-01T16:34:10+00:00">April 1, 2021</time></a></span><span class="cat-links"><span class="screen-reader-text">Categories </span><a href="https://blog.cerbero.io/category/suite-standard/" rel="category tag">Suite Standard</a></span><span class="tags-links"><span class="screen-reader-text">Tags </span><a href="https://blog.cerbero.io/tag/headers/" rel="tag">Headers</a>, <a href="https://blog.cerbero.io/tag/pdb/" rel="tag">PDB</a>, <a href="https://blog.cerbero.io/tag/python/" rel="tag">Python</a>, <a href="https://blog.cerbero.io/tag/sdk/" rel="tag">SDK</a></span><span class="comments-link"><a href="https://blog.cerbero.io/pdb-support-including-export-of-types/#respond">Leave a comment<span class="screen-reader-text"> on PDB support (including export of types)</span></a></span> </s></b></footer><b><s id="classCWnd" type="CRuntimeClass const">
</s></b></article><b><s id="classCWnd" type="CRuntimeClass const">
<article id="post-1519" class="post-1519 post type-post status-publish format-standard hentry category-suite-standard tag-news">
<header class="entry-header">
<h2 class="entry-title"><a href="https://blog.cerbero.io/profiler-2-3/" rel="bookmark">Profiler 2.3</a></h2> </header>
<div class="entry-content"> <p>Profiler 2.3 is out with the following news:</p> <p>– <a href="/?p=1506">introduced YARA 3.2 support</a><br> – <a href="#lgroups">added groups for logic providers</a><br> – <a href="#enctxt">added Python action to encode/decode text</a><br> – <a href="#x2t">added Python action to strip XML down to text</a><br> – <a href="#fixfont">added the possibility to choose the fixed font</a><br> – <a href="#colrand">added color randomization for structs and intervals</a><br> – <a href="#repapis">added close report and quit APIs</a><br> – <a href="#repapis">exposed more methods of the Report class (including save)</a><br> – improved indentation handling in the script editor<br> – <a href="#outsync">synchronized main and workspace output views</a><br> – improved output view<br> – updated libmagic to 5.21<br> – updated Capstone to 3.0<br> – many small improvements<br> – fixed libmagic on Linux<br> – removed the tray icon<br> – minor bug fixes</p> <p><a name="lgroups"></a></p> <h2>Logic provider groups</h2> <p>Logic providers can now be grouped in order to avoid clutter in the main window. Adding the following line to an existing logic provider will result in a new group being created:</p> <pre lang="python">group = Extra</pre> <p><a href="/wp-content/uploads/2014/12/23/lgroups.png"><img decoding="async" class="post-img" src="/wp-content/uploads/2014/12/23/lgroups.png"></a></p> <p><a name="enctxt"></a></p> <h2>Encode/decode text action</h2> <p>A handy Python action to convert from hex to text and vice-versa using all of Python’s supported encodings. Place yourself in a hex or text view and run the encoding/decoding action ‘Bytes to text’ or ‘Text to bytes’.</p> <p><a href="/wp-content/uploads/2014/12/23/enctext.png"><img decoding="async" class="post-img" src="/wp-content/uploads/2014/12/23/enctext.png"></a></p> <p>The operation will open a new text or hex view depending if it was an encoding or a decoding. </p> <p><a name="x2t"></a></p> <h2>XML to text action</h2> <p>Strips tags from an XML and displays only the text. The action can be performed both on a hex and text view. </p> <p><a href="/wp-content/uploads/2014/12/23/x2t.png"><img decoding="async" class="post-img" src="/wp-content/uploads/2014/12/23/x2t.png"></a></p> <p>And it will open a new text view. This is useful to view the text of a DOCX or ODT document. In the future the preview for these documents will be made available automatically, but in the meantime this action is helpful.</p> <p><a href="/wp-content/uploads/2014/12/23/docxprev.png"><img decoding="async" class="post-img" src="/wp-content/uploads/2014/12/23/docxprev.png"></a></p> <p><a name="fixfont"></a></p> <h2>Fixed font preferences</h2> <p>The fixed font used in most views can now be chosen from the ‘General’ settings.</p> <p><a name="colrand"></a></p> <h2>Struct/intervals color randomization</h2> <p>When adding a structure or interval to the hex view the chosen color is now being randomized every time the dialog shows up. This behaviour can be disabled from the dialog itself and it’s also possible to randomize again the color by clicking on the specific refresh button.</p> <p><a href="/wp-content/uploads/2014/12/23/colrand.png"><img decoding="async" class="post-img" src="/wp-content/uploads/2014/12/23/colrand.png"></a></p> <p>Manually picking a different color for every interval is time consuming and so this feature should speed up raw data analysis.</p> <p><a name="repapis"></a></p> <h2>Report APIs</h2> <p>Most of the report APIs have been exposed (check out the SDK documentation). This combined with the newly introduced ‘quit’ SDK method can be used to perform custom scans programmatically and save the resulting report.</p> <p>Here’s a small example which can be launched from the command line:</p> <pre lang="python">from Pro.Core import *
import sys
ctx = proCoreContext()
def init():
ctx.getSystem().addFile(sys.argv[1])
return True
def rload():
ctx.unregisterLogicProvider("test_logic")
ctx.getReport().saveAs("auto.cpro")
ctx.quit()
ctx.registerLogicProvider("test_logic", init, None, None, None, rload)
ctx.startScan("test_logic")</pre> <p>The command line syntax to run this script would be:</p> <pre lang="text">cerpro -r scan.py [file to scan]</pre> <p>The UI will show up and close automatically once the ‘quit’ method is called. Running this script in console mode using the ‘-c’ parameter is not yet possible, because of the differences in message handling on different platforms, but it will be in the future.</p> <p><a name="outsync"></a></p> <h2>Synchronized output views</h2> <p>The output view of the main window and of the workspace are now synchronized, thus avoiding missing important log messages being printed in one or the other context.</p> <p>Enjoy!</p></div><footer class="entry-footer">
<span class="byline"><img alt="" src="https://secure.gravatar.com/avatar/7a86aa69922858b8d41989621fc1ea364aae1e027546f88a54d94ab1ec2187fc?s=49&d=mm&r=g" srcset="https://secure.gravatar.com/avatar/7a86aa69922858b8d41989621fc1ea364aae1e027546f88a54d94ab1ec2187fc?s=98&d=mm&r=g 2x" class="avatar avatar-49 photo" height="49" width="49" loading="lazy" decoding="async"><span class="screen-reader-text">Author </span><span class="author vcard"><a class="url fn n" href="https://blog.cerbero.io/author/cerbero/">Erik Pistelli</a></span></span><span class="posted-on"><span class="screen-reader-text">Posted on </span><a href="https://blog.cerbero.io/profiler-2-3/" rel="bookmark"><time class="entry-date published" datetime="2014-12-27T02:16:53+00:00">December 27, 2014</time><time class="updated" datetime="2021-04-01T16:34:49+00:00">April 1, 2021</time></a></span><span class="cat-links"><span class="screen-reader-text">Categories </span><a href="https://blog.cerbero.io/category/suite-standard/" rel="category tag">Suite Standard</a></span><span class="tags-links"><span class="screen-reader-text">Tags </span><a href="https://blog.cerbero.io/tag/news/" rel="tag">News</a></span><span class="comments-link"><a href="https://blog.cerbero.io/profiler-2-3/#respond">Leave a comment<span class="screen-reader-text"> on Profiler 2.3</span></a></span> </footer>
</article>
<article id="post-1506" class="post-1506 post type-post status-publish format-standard hentry category-action category-suite-standard tag-signatures tag-yara">
<header class="entry-header">
<h2 class="entry-title"><a href="https://blog.cerbero.io/yara-3-2-0-support/" rel="bookmark">YARA 3.2.0 support</a></h2> </header>
<div class="entry-content"> <p>The upcoming 2.3 version of Profiler includes support for the latest YARA engine. This new release is scheduled for the first week of January and it will include YARA on all supported platforms.</p> <p>One inherent technical advantage of having YARA support in Profiler is that it will be possible to scan for YARA rules inside embedded files/objects, like files in a Zip archive, in a CHM file, in an OLEStream, streams in a PDF, etc.</p> <p>The YARA engine itself has been compiled with all standard modules (except for cuckoo). Even the <strong>magic</strong> module is available, since libmagic is also supported by Profiler.</p> <p>The initial YARA integration comes as a hook extension, an action and Python SDK support. The YARA Python support is the official one and differs from it only in the import statement. You can run existing YARA Python code without modification by using the following import syntax:</p> <pre lang="python">import Pro.yara as yara</pre> <p>So let’s start a YARA scan. To do that, we need to enable the YARA hook extension. On Windows remember to configure Python in case you haven’t yet, since all extensions have been written in it.</p> <p><a href="/wp-content/uploads/2014/12/yara/1.png"><img decoding="async" class="post-img" src="/wp-content/uploads/2014/12/yara/1.png"></a></p> <p>When a scan is started, a YARA settings dialog will show up. </p> <p><a href="/wp-content/uploads/2014/12/yara/2.png"><img decoding="async" class="post-img" src="/wp-content/uploads/2014/12/yara/2.png"></a></p> <p>This dialog lets us choose various settings including the type of rules to load.</p> <p><a href="/wp-content/uploads/2014/12/yara/3.png"><img decoding="async" class="post-img" src="/wp-content/uploads/2014/12/yara/3.png"></a></p> <p>There are four possibilities. A simple text field containing YARA rules, a plain text rules file, a compiled rules file or a custom expression which must <strong>eval</strong> to a valid <strong>Rules</strong> object.</p> <p>The report settings specify how we will be alerted of matches. The ‘only matches’ option makes sure that only files (or their sub-files) with a match will be included in the final report. The ‘add to meta-data” option causes the matches to be visible as meta-data strings of a file. The ‘as threats’ option reports every match as a 100% risk threat. The ‘print to output’ option prints the matches to the output view.</p> <p>Since we had the ‘only matches’ option enabled, we will find only matching files in our final report.</p> <p><a href="/wp-content/uploads/2014/12/yara/4.png"><img decoding="async" class="post-img" src="/wp-content/uploads/2014/12/yara/4.png"></a></p> <p>And since we had also the ‘to meta-data’ option enabled, we will see the matches when opening a file in the workspace.</p> <p><a href="/wp-content/uploads/2014/12/yara/5.png"><img decoding="async" class="post-img" src="/wp-content/uploads/2014/12/yara/5.png"></a></p> <p>The YARA scan functionality comes also as an action when we find ourselves in a hex view. You can either scan the whole hex data or select a range. Then press Ctrl+R to run an action and select ‘YARA scan’.</p> <p><a href="/wp-content/uploads/2014/12/yara/6.png"><img decoding="async" class="post-img" src="/wp-content/uploads/2014/12/yara/6.png"></a></p> <p>In this case we won’t be given report options, since the only thing which can be performed is to print out matches in the output view.</p> <p><a href="/wp-content/uploads/2014/12/yara/7.png"><img decoding="async" class="post-img" src="/wp-content/uploads/2014/12/yara/7.png"></a></p> <p>Like this:</p> <p><a href="/wp-content/uploads/2014/12/yara/8.png"><img decoding="async" class="post-img" src="/wp-content/uploads/2014/12/yara/8.png"></a></p> <p>Of course, all supported platforms come also with the official YARA command line utility.</p> <p><a href="/wp-content/uploads/2014/12/yara/9.png"><img decoding="async" class="post-img" src="/wp-content/uploads/2014/12/yara/9.png"></a></p> <p>Since this has been a customer request for quite some time, I think it will be appreciated by some of our users.</p></div><footer class="entry-footer">
<span class="byline"><img alt="" src="https://secure.gravatar.com/avatar/7a86aa69922858b8d41989621fc1ea364aae1e027546f88a54d94ab1ec2187fc?s=49&d=mm&r=g" srcset="https://secure.gravatar.com/avatar/7a86aa69922858b8d41989621fc1ea364aae1e027546f88a54d94ab1ec2187fc?s=98&d=mm&r=g 2x" class="avatar avatar-49 photo" height="49" width="49" loading="lazy" decoding="async"><span class="screen-reader-text">Author </span><span class="author vcard"><a class="url fn n" href="https://blog.cerbero.io/author/cerbero/">Erik Pistelli</a></span></span><span class="posted-on"><span class="screen-reader-text">Posted on </span><a href="https://blog.cerbero.io/yara-3-2-0-support/" rel="bookmark"><time class="entry-date published" datetime="2014-12-26T02:19:13+00:00">December 26, 2014</time><time class="updated" datetime="2021-04-01T16:35:26+00:00">April 1, 2021</time></a></span><span class="cat-links"><span class="screen-reader-text">Categories </span><a href="https://blog.cerbero.io/category/action/" rel="category tag">Action</a>, <a href="https://blog.cerbero.io/category/suite-standard/" rel="category tag">Suite Standard</a></span><span class="tags-links"><span class="screen-reader-text">Tags </span><a href="https://blog.cerbero.io/tag/signatures/" rel="tag">signatures</a>, <a href="https://blog.cerbero.io/tag/yara/" rel="tag">YARA</a></span><span class="comments-link"><a href="https://blog.cerbero.io/yara-3-2-0-support/#respond">Leave a comment<span class="screen-reader-text"> on YARA 3.2.0 support</span></a></span> </footer>
</article>
<nav class="navigation pagination" aria-label="Posts pagination">
<h2 class="screen-reader-text">Posts pagination</h2>
<div class="nav-links"><a class="prev page-numbers" href="https://blog.cerbero.io/page/16/">Previous page</a> <a class="page-numbers" href="https://blog.cerbero.io/"><span class="meta-nav screen-reader-text">Page </span>1</a> <span class="page-numbers dots">…</span> <a class="page-numbers" href="https://blog.cerbero.io/page/16/"><span class="meta-nav screen-reader-text">Page </span>16</a> <span aria-current="page" class="page-numbers current"><span class="meta-nav screen-reader-text">Page </span>17</span> <a class="page-numbers" href="https://blog.cerbero.io/page/18/"><span class="meta-nav screen-reader-text">Page </span>18</a> <span class="page-numbers dots">…</span> <a class="page-numbers" href="https://blog.cerbero.io/page/27/"><span class="meta-nav screen-reader-text">Page </span>27</a> <a class="next page-numbers" href="https://blog.cerbero.io/page/18/">Next page</a></div></nav>
<aside id="secondary" class="sidebar widget-area">
<section id="search-2" class="widget widget_search">
<form role="search" method="get" class="search-form" action="https://blog.cerbero.io/"></form>
<label>
<span class="screen-reader-text">
Search for: </span>
<input type="search" class="search-field" placeholder="Search …" value="" name="s">
</label>
<button type="submit" class="search-submit"><span class="screen-reader-text">
Search </span></button>
</section>
<section id="recent-posts-2" class="widget widget_recent_entries">
<h2 class="widget-title">Recent Posts</h2><nav aria-label="Recent Posts">
<ul>
<li> <a href="https://blog.cerbero.io/wim-format-package/" aria-current="page">WIM Format Package</a> </li>
<li> <a href="https://blog.cerbero.io/hfs-file-system/">HFS+ File System</a> </li>
<li> <a href="https://blog.cerbero.io/ext-file-systems/">EXT File Systems</a> </li>
<li> <a href="https://blog.cerbero.io/ntfs-file-system/">NTFS File System</a> </li>
<li> <a href="https://blog.cerbero.io/exfat-file-system/">ExFAT File System</a> </li>
<li> <a href="https://blog.cerbero.io/disk-format-package/">Disk Format Package</a> </li>
<li> <a href="https://blog.cerbero.io/fat-file-system/">FAT File System</a> </li>
<li> <a href="https://blog.cerbero.io/prototype-memory-services/">Prototype Memory & Services</a> </li>
<li> <a href="https://blog.cerbero.io/iso-format-2-0-package/">ISO Format 2.0 Package</a> </li>
<li> <a href="https://blog.cerbero.io/memory-decompression-pagefiles/">Memory Decompression & Pagefiles</a> </li>
</ul>
</nav></section><section id="archives-4" class="widget widget_archive"><h2 class="widget-title">Archives</h2> <label class="screen-reader-text" for="archives-dropdown-4">Archives</label>
<select id="archives-dropdown-4" name="archive-dropdown">
<option value="">Select Month</option>
<option value="https://blog.cerbero.io/2025/06/"> June 2025 (1)</option>
<option value="https://blog.cerbero.io/2025/05/"> May 2025 (7)</option>
<option value="https://blog.cerbero.io/2025/04/"> April 2025 (4)</option>
<option value="https://blog.cerbero.io/2025/03/"> March 2025 (2)</option>
<option value="https://blog.cerbero.io/2024/10/"> October 2024 (3)</option>
<option value="https://blog.cerbero.io/2024/09/"> September 2024 (1)</option>
<option value="https://blog.cerbero.io/2024/08/"> August 2024 (3)</option>
<option value="https://blog.cerbero.io/2024/07/"> July 2024 (5)</option>
<option value="https://blog.cerbero.io/2024/06/"> June 2024 (2)</option>
<option value="https://blog.cerbero.io/2024/04/"> April 2024 (4)</option>
<option value="https://blog.cerbero.io/2024/03/"> March 2024 (1)</option>
<option value="https://blog.cerbero.io/2024/02/"> February 2024 (1)</option>
<option value="https://blog.cerbero.io/2024/01/"> January 2024 (4)</option>
<option value="https://blog.cerbero.io/2023/12/"> December 2023 (3)</option>
<option value="https://blog.cerbero.io/2023/11/"> November 2023 (7)</option>
<option value="https://blog.cerbero.io/2023/10/"> October 2023 (3)</option>
<option value="https://blog.cerbero.io/2023/09/"> September 2023 (1)</option>
<option value="https://blog.cerbero.io/2023/07/"> July 2023 (1)</option>
<option value="https://blog.cerbero.io/2023/05/"> May 2023 (11)</option>
<option value="https://blog.cerbero.io/2023/03/"> March 2023 (9)</option>
<option value="https://blog.cerbero.io/2023/02/"> February 2023 (3)</option>
<option value="https://blog.cerbero.io/2023/01/"> January 2023 (1)</option>
<option value="https://blog.cerbero.io/2022/11/"> November 2022 (1)</option>
<option value="https://blog.cerbero.io/2022/09/"> September 2022 (2)</option>
<option value="https://blog.cerbero.io/2022/08/"> August 2022 (2)</option>
<option value="https://blog.cerbero.io/2022/07/"> July 2022 (3)</option>
<option value="https://blog.cerbero.io/2022/06/"> June 2022 (2)</option>
<option value="https://blog.cerbero.io/2022/05/"> May 2022 (5)</option>
<option value="https://blog.cerbero.io/2022/04/"> April 2022 (3)</option>
<option value="https://blog.cerbero.io/2022/03/"> March 2022 (4)</option>
<option value="https://blog.cerbero.io/2022/02/"> February 2022 (6)</option>
<option value="https://blog.cerbero.io/2022/01/"> January 2022 (1)</option>
<option value="https://blog.cerbero.io/2021/11/"> November 2021 (4)</option>
<option value="https://blog.cerbero.io/2021/10/"> October 2021 (5)</option>
<option value="https://blog.cerbero.io/2021/09/"> September 2021 (7)</option>
<option value="https://blog.cerbero.io/2021/06/"> June 2021 (1)</option>
<option value="https://blog.cerbero.io/2021/04/"> April 2021 (1)</option>
<option value="https://blog.cerbero.io/2021/03/"> March 2021 (4)</option>
<option value="https://blog.cerbero.io/2021/02/"> February 2021 (1)</option>
<option value="https://blog.cerbero.io/2020/12/"> December 2020 (1)</option>
<option value="https://blog.cerbero.io/2020/11/"> November 2020 (1)</option>
<option value="https://blog.cerbero.io/2020/10/"> October 2020 (1)</option>
<option value="https://blog.cerbero.io/2020/09/"> September 2020 (2)</option>
<option value="https://blog.cerbero.io/2020/07/"> July 2020 (2)</option>
<option value="https://blog.cerbero.io/2020/01/"> January 2020 (1)</option>
<option value="https://blog.cerbero.io/2019/09/"> September 2019 (1)</option>
<option value="https://blog.cerbero.io/2019/08/"> August 2019 (2)</option>
<option value="https://blog.cerbero.io/2019/07/"> July 2019 (1)</option>
<option value="https://blog.cerbero.io/2019/06/"> June 2019 (1)</option>
<option value="https://blog.cerbero.io/2019/05/"> May 2019 (3)</option>
<option value="https://blog.cerbero.io/2019/04/"> April 2019 (2)</option>
<option value="https://blog.cerbero.io/2018/06/"> June 2018 (1)</option>
<option value="https://blog.cerbero.io/2018/04/"> April 2018 (1)</option>
<option value="https://blog.cerbero.io/2018/03/"> March 2018 (1)</option>
<option value="https://blog.cerbero.io/2018/01/"> January 2018 (1)</option>
<option value="https://blog.cerbero.io/2017/11/"> November 2017 (2)</option>
<option value="https://blog.cerbero.io/2017/03/"> March 2017 (5)</option>
<option value="https://blog.cerbero.io/2016/07/"> July 2016 (2)</option>
<option value="https://blog.cerbero.io/2016/05/"> May 2016 (2)</option>
<option value="https://blog.cerbero.io/2016/04/"> April 2016 (1)</option>
<option value="https://blog.cerbero.io/2015/10/"> October 2015 (2)</option>
<option value="https://blog.cerbero.io/2015/09/"> September 2015 (2)</option>
<option value="https://blog.cerbero.io/2015/06/"> June 2015 (2)</option>
<option value="https://blog.cerbero.io/2014/12/"> December 2014 (2)</option>
<option value="https://blog.cerbero.io/2014/10/"> October 2014 (1)</option>
<option value="https://blog.cerbero.io/2014/09/"> September 2014 (3)</option>
<option value="https://blog.cerbero.io/2014/08/"> August 2014 (1)</option>
<option value="https://blog.cerbero.io/2014/07/"> July 2014 (1)</option>
<option value="https://blog.cerbero.io/2013/12/"> December 2013 (2)</option>
<option value="https://blog.cerbero.io/2013/11/"> November 2013 (5)</option>
<option value="https://blog.cerbero.io/2013/10/"> October 2013 (5)</option>
<option value="https://blog.cerbero.io/2013/09/"> September 2013 (6)</option>
<option value="https://blog.cerbero.io/2013/08/"> August 2013 (6)</option>
<option value="https://blog.cerbero.io/2013/07/"> July 2013 (1)</option>
<option value="https://blog.cerbero.io/2013/06/"> June 2013 (4)</option>
<option value="https://blog.cerbero.io/2013/05/"> May 2013 (7)</option>
<option value="https://blog.cerbero.io/2013/04/"> April 2013 (5)</option>
<option value="https://blog.cerbero.io/2013/03/"> March 2013 (3)</option>
<option value="https://blog.cerbero.io/2013/02/"> February 2013 (4)</option>
<option value="https://blog.cerbero.io/2013/01/"> January 2013 (3)</option>
<option value="https://blog.cerbero.io/2012/12/"> December 2012 (3)</option>
<option value="https://blog.cerbero.io/2012/11/"> November 2012 (5)</option>
<option value="https://blog.cerbero.io/2012/10/"> October 2012 (3)</option>
<option value="https://blog.cerbero.io/2012/09/"> September 2012 (1)</option>
<option value="https://blog.cerbero.io/2012/08/"> August 2012 (2)</option>
<option value="https://blog.cerbero.io/2012/07/"> July 2012 (2)</option>
<option value="https://blog.cerbero.io/2012/06/"> June 2012 (2)</option>
<option value="https://blog.cerbero.io/2012/05/"> May 2012 (2)</option>
<option value="https://blog.cerbero.io/2012/04/"> April 2012 (1)</option>
<option value="https://blog.cerbero.io/2012/03/"> March 2012 (6)</option>
<option value="https://blog.cerbero.io/2012/02/"> February 2012 (5)</option>
<option value="https://blog.cerbero.io/2012/01/"> January 2012 (8)</option>
<option value="https://blog.cerbero.io/2011/11/"> November 2011 (1)</option>
<option value="https://blog.cerbero.io/2011/08/"> August 2011 (1)</option>
</select>
<script>(function(){
var dropdown=document.getElementById("archives-dropdown-4");
function onSelectChange(){
if(dropdown.options[ dropdown.selectedIndex ].value!==''){
document.location.href=this.options[ this.selectedIndex ].value;
}}
dropdown.onchange=onSelectChange;
})();</script>
</section> </aside><footer id="colophon" class="site-footer">
<nav class="main-navigation" aria-label="Footer Primary Menu">
<div class="menu-main-container"><ul id="menu-main-1" class="primary-menu"><li class="menu-item menu-item-type-custom menu-item-object-custom menu-item-1923"><a href="https://cerbero.io">Home</a></li> <li class="menu-item menu-item-type-custom menu-item-object-custom menu-item-has-children menu-item-2790"><a href="#">Products</a> <ul class="sub-menu"> <li class="menu-item menu-item-type-custom menu-item-object-custom menu-item-2181"><a href="https://cerbero.io/suite/">Cerbero Suite</a></li> <li class="menu-item menu-item-type-custom menu-item-object-custom menu-item-2183"><a href="https://cerbero.io/engine/">Cerbero Engine</a></li> </ul> </li> <li class="menu-item menu-item-type-custom menu-item-object-custom menu-item-2567"><a href="https://cerbero.io/packages/">Packages</a></li> <li class="menu-item menu-item-type-custom menu-item-object-custom menu-item-2430"><a href="https://cerbero.io/e-zine/">E-Zine</a></li> <li class="menu-item menu-item-type-custom menu-item-object-custom menu-item-1926"><a href="/">Blog</a></li> <li class="menu-item menu-item-type-custom menu-item-object-custom menu-item-has-children menu-item-2791"><a href="#">Support</a> <ul class="sub-menu"> <li class="menu-item menu-item-type-custom menu-item-object-custom menu-item-3000"><a href="https://cerbero.io/manual/">User Manual</a></li> <li class="menu-item menu-item-type-custom menu-item-object-custom menu-item-2165"><a href="https://sdk.cerbero.io/">SDK Documentation</a></li> <li class="menu-item menu-item-type-custom menu-item-object-custom menu-item-2514"><a href="https://cerbero.io/faq/">FAQ</a></li> <li class="menu-item menu-item-type-custom menu-item-object-custom menu-item-1927"><a href="https://cerbero.io/resources/">Resources</a></li> <li class="menu-item menu-item-type-custom menu-item-object-custom menu-item-1930"><a href="https://cerbero.io/contact/">Contact</a></li> </ul> </li> <li class="menu-item menu-item-type-custom menu-item-object-custom menu-item-has-children menu-item-2792"><a href="https://cerbero.io/shop/">Shop</a> <ul class="sub-menu"> <li class="menu-item menu-item-type-custom menu-item-object-custom menu-item-1928"><a href="https://cerbero.io/my-account/">My account</a></li> <li class="menu-item menu-item-type-custom menu-item-object-custom menu-item-1929"><a href="https://cerbero.io/cart/">Cart</a></li> </ul> </li> </ul></div></nav>
<div class="site-info"> <span class="site-title"><a href="https://blog.cerbero.io/" rel="home">Cerbero Blog</a></span> <a href="https://wordpress.org/" class="imprint"> Proudly powered by WordPress </a></div></footer><script type="speculationrules">{"prefetch":[{"source":"document","where":{"and":[{"href_matches":"\/*"},{"not":{"href_matches":["\/wp-*.php","\/wp-admin\/*","\/wp-content\/uploads\/*","\/wp-content\/*","\/wp-content\/plugins\/*","\/wp-content\/themes\/twentysixteen-child\/*","\/wp-content\/themes\/twentysixteen\/*","\/*\\?(.+)"]}},{"not":{"selector_matches":"a[rel~=\"nofollow\"]"}},{"not":{"selector_matches":".no-prefetch, .no-prefetch a"}}]},"eagerness":"conservative"}]}</script>
<script src="//blog.cerbero.io/wp-content/cache/wpfc-minified/e5g10mzi/a6zsp.js"></script>
<script id="enlighterjs-js-after">!function(e,n){if("undefined"!=typeof EnlighterJS){var o={"selectors":{"block":"pre","inline":"code"},"options":{"indent":4,"ampersandCleanup":true,"linehover":false,"rawcodeDbclick":false,"textOverflow":"scroll","linenumbers":false,"theme":"enlighter","language":"generic","retainCssClasses":false,"collapse":false,"toolbarOuter":"","toolbarTop":"{BTN_RAW}{BTN_COPY}{BTN_WINDOW}{BTN_WEBSITE}","toolbarBottom":""}};(e.EnlighterJSINIT=function(){EnlighterJS.init(o.selectors.block,o.selectors.inline,o.options)})()}else{(n&&(n.error||n.log)||function(){})("Error: EnlighterJS resources not loaded yet!")}}(window,console);</script></s></b></hex><table id="1"></table></hl></ui>