1#!/usr/local/bin/python3.8
2
3#    This file is part of python-registry.
4#
5#   Copyright 2011 Will Ballenthin <william.ballenthin@mandiant.com>
6#                    while at Mandiant <http://www.mandiant.com>
7#
8#   Licensed under the Apache License, Version 2.0 (the "License");
9#   you may not use this file except in compliance with the License.
10#   You may obtain a copy of the License at
11#
12#       http://www.apache.org/licenses/LICENSE-2.0
13#
14#   Unless required by applicable law or agreed to in writing, software
15#   distributed under the License is distributed on an "AS IS" BASIS,
16#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17#   See the License for the specific language governing permissions and
18#   limitations under the License.
19from __future__ import print_function
20from __future__ import unicode_literals
21
22import sys
23import os
24import wx
25from Registry import Registry
26
27ID_FILE_OPEN = wx.NewId()
28ID_FILE_SESSION_SAVE = wx.NewId()
29ID_FILE_SESSION_OPEN = wx.NewId()
30ID_TAB_CLOSE = wx.NewId()
31ID_FILE_EXIT = wx.NewId()
32ID_HELP_ABOUT = wx.NewId()
33
34
35def nop(*args, **kwargs):
36    pass
37
38
39def basename(path):
40    if "/" in path:
41        path = path.split("/")[-1]
42    if "\\" in path:
43        path = path.split("\\")[-1]
44    return path
45
46
47def _expand_into(dest, src):
48    vbox = wx.BoxSizer(wx.VERTICAL)
49    vbox.Add(src, 1, wx.EXPAND | wx.ALL)
50    dest.SetSizer(vbox)
51
52
53def _format_hex(data):
54    """
55    see http://code.activestate.com/recipes/142812/
56    """
57    byte_format = {}
58    for c in xrange(256):
59        if c > 126:
60            byte_format[c] = '.'
61        elif len(repr(chr(c))) == 3 and chr(c):
62            byte_format[c] = chr(c)
63        else:
64            byte_format[c] = '.'
65
66    def format_bytes(s):
67        return "".join([byte_format[ord(c)] for c in s])
68
69    def dump(src, length=16):
70        N = 0
71        result = ''
72        while src:
73            s, src = src[:length], src[length:]
74            hexa = ' '.join(["%02X" % ord(x) for x in s])
75            s = format_bytes(s)
76            result += "%04X   %-*s   %s\n" % (N, length * 3, hexa, s)
77            N += length
78        return result
79    return dump(data)
80
81
82class DataPanel(wx.Panel):
83    """
84    Displays the contents of a Registry value.
85    Shows a text string where appropriate, or a hex dump.
86    """
87    def __init__(self, *args, **kwargs):
88        super(DataPanel, self).__init__(*args, **kwargs)
89        self._sizer = wx.BoxSizer(wx.VERTICAL)
90        self.SetSizer(self._sizer)
91
92    def display_value(self, value):
93        self._sizer.Clear()
94        data_type = value.value_type()
95
96        if data_type == Registry.RegSZ or \
97                data_type == Registry.RegExpandSZ or \
98                data_type == Registry.RegDWord or \
99                data_type == Registry.RegQWord:
100            view = wx.TextCtrl(self, style=wx.TE_MULTILINE)
101            view.SetValue(unicode(value.value()))
102
103        elif data_type == Registry.RegMultiSZ:
104            view = wx.ListCtrl(self, style=wx.LC_LIST)
105            for string in value.value():
106                view.InsertStringItem(view.GetItemCount(), string)
107
108        elif data_type == Registry.RegBin or \
109                data_type == Registry.RegNone:
110            view = wx.TextCtrl(self, style=wx.TE_MULTILINE)
111            font = wx.Font(8, wx.SWISS, wx.NORMAL, wx.NORMAL, False, u'Courier')
112            view.SetFont(font)
113            view.SetValue(_format_hex(value.value()))
114
115        else:
116            view = wx.TextCtrl(self, style=wx.TE_MULTILINE)
117            view.SetValue(unicode(value.value()))
118
119        self._sizer.Add(view, 1, wx.EXPAND)
120        self._sizer.Layout()
121
122    def clear_value(self):
123        self._sizer.Clear()
124        self._sizer.Add(wx.Panel(self, -1), 1, wx.EXPAND)
125        self._sizer.Layout()
126
127
128class ValuesListCtrl(wx.ListCtrl):
129    """
130    Shows a list of values associated with a Registry key.
131    """
132    def __init__(self, *args, **kwargs):
133        super(ValuesListCtrl, self).__init__(*args, **kwargs)
134        self.InsertColumn(0, "Value name")
135        self.InsertColumn(1, "Value type")
136        self.SetColumnWidth(1, 100)
137        self.SetColumnWidth(0, 300)
138        self.values = {}
139
140    def clear_values(self):
141        self.DeleteAllItems()
142        self.values = {}
143
144    def add_value(self, value):
145        n = self.GetItemCount()
146        self.InsertStringItem(n, value.name())
147        self.SetStringItem(n, 1, value.value_type_str())
148        self.values[value.name()] = value
149
150    def get_value(self, valuename):
151        return self.values[valuename]
152
153
154class RegistryTreeCtrl(wx.TreeCtrl):
155    """
156    Treeview control that displays the Registry key structure.
157    """
158    def __init__(self, *args, **kwargs):
159        super(RegistryTreeCtrl, self).__init__(*args, **kwargs)
160        self.Bind(wx.EVT_TREE_ITEM_EXPANDING, self.OnExpandKey)
161
162    def add_registry(self, registry):
163        """
164        Add the registry to the control as the (a?) root element.
165        """
166        root_key = registry.root()
167        root_item = self.AddRoot(root_key.name())
168        self.SetPyData(root_item, {"key": root_key,
169                                   "has_expanded": False})
170
171        if len(root_key.subkeys()) > 0:
172            self.SetItemHasChildren(root_item)
173
174    def delete_registry(self):
175        """
176        Removes all elements from the control.
177        """
178        self.DeleteAllItems()
179
180    def select_path(self, path):
181        """
182        Take a Registry key path separated by back slashes and select
183        that key. The path should not contain the root key name.
184        If the key is not found, the most specific ancestor key is selected.
185        """
186        parts = path.split("\\")
187        node = self.GetRootItem()
188
189        for part in parts:
190            self._extend(node)
191            (node, cookie) = self.GetFirstChild(node)
192
193            cont = True
194            while node and cont:
195                key = self.GetPyData(node)["key"]
196                if key.name() == part:
197                    self.SelectItem(node)
198                    cont = False
199                else:
200                    node = self.GetNextSibling(node)
201
202    def _extend(self, item):
203        """
204        Lazily parse and add children items to the tree.
205        """
206        if self.GetPyData(item)["has_expanded"]:
207            return
208
209        key = self.GetPyData(item)["key"]
210
211        for subkey in key.subkeys():
212            subkey_item = self.AppendItem(item, subkey.name())
213            self.SetPyData(subkey_item, {"key": subkey,
214                                         "has_expanded": False})
215
216            if len(subkey.subkeys()) > 0:
217                self.SetItemHasChildren(subkey_item)
218
219        self.GetPyData(item)["has_expanded"] = True
220
221    def OnExpandKey(self, event):
222        item = event.GetItem()
223        if not item.IsOk():
224            item = self.GetSelection()
225
226        if not self.GetPyData(item)["has_expanded"]:
227            self._extend(item)
228
229
230class RegistryFileView(wx.Panel):
231    """
232    A three-paned display of the RegistryTreeCtrl, ValueListCtrl, and DataPanel.
233    """
234    def __init__(self, parent, registry, filename):
235        super(RegistryFileView, self).__init__(parent, -1, size=(800, 600))
236        self._filename = filename
237
238        vsplitter = wx.SplitterWindow(self, -1)
239        panel_left = wx.Panel(vsplitter, -1)
240        self._tree = RegistryTreeCtrl(panel_left, -1)
241        _expand_into(panel_left, self._tree)
242
243        hsplitter = wx.SplitterWindow(vsplitter, -1)
244        panel_top = wx.Panel(hsplitter, -1)
245        panel_bottom = wx.Panel(hsplitter, -1)
246
247        self._value_list_view = ValuesListCtrl(panel_top, -1, style=wx.LC_REPORT)
248        self._data_view = DataPanel(panel_bottom, -1)
249
250        _expand_into(panel_top,    self._value_list_view)
251        _expand_into(panel_bottom, self._data_view)
252
253        hsplitter.SplitHorizontally(panel_top, panel_bottom)
254        vsplitter.SplitVertically(panel_left, hsplitter)
255
256        # give enough space in the data display for the hex output
257        vsplitter.SetSashPosition(325, True)
258        _expand_into(self, vsplitter)
259        self.Centre()
260
261        self._value_list_view.Bind(wx.EVT_LIST_ITEM_SELECTED, self.OnValueSelected)
262        self._tree.Bind(wx.EVT_TREE_SEL_CHANGED, self.OnKeySelected)
263
264        self._tree.add_registry(registry)
265
266    def OnKeySelected(self, event):
267        item = event.GetItem()
268        if not item.IsOk():
269            item = self._tree.GetSelection()
270
271        key = self._tree.GetPyData(item)["key"]
272
273        parent = self.GetParent()
274        while parent:
275            try:
276                parent.SetStatusText(key.path())
277            except AttributeError:
278                pass
279            parent = parent.GetParent()
280
281        self._data_view.clear_value()
282        self._value_list_view.clear_values()
283        for value in key.values():
284            self._value_list_view.add_value(value)
285
286    def OnValueSelected(self, event):
287        item = event.GetItem()
288
289        value = self._value_list_view.get_value(item.GetText())
290        self._data_view.display_value(value)
291
292    def filename(self):
293        """
294        Return the filename of the current Registry file as a string.
295        """
296        return self._filename
297
298    def selected_path(self):
299        """
300        Return the Registry key path of the currently selected item.
301        """
302        item = self._tree.GetSelection()
303        if item:
304            return self._tree.GetPyData(item)["key"].path()
305        return False
306
307    def select_path(self, path):
308        """
309        Select a Registry key path specified as a string in the relevant panes.
310        """
311        self._tree.select_path(path)
312
313
314class RegistryFileViewer(wx.Frame):
315    """
316    The main RegView GUI application.
317    """
318    def __init__(self, parent, files):
319        super(RegistryFileViewer, self).__init__(parent, -1, "Registry File Viewer", size=(800, 600))
320        self.CreateStatusBar()
321
322        menu_bar = wx.MenuBar()
323        file_menu = wx.Menu()
324        _open = file_menu.Append(ID_FILE_OPEN, '&Open File')
325        self.Bind(wx.EVT_MENU, self.menu_file_open, _open)
326        file_menu.AppendSeparator()
327        _session_save = file_menu.Append(ID_FILE_SESSION_SAVE, '&Save Session')
328        self.Bind(wx.EVT_MENU, self.menu_file_session_save, _session_save)
329        _session_open = file_menu.Append(ID_FILE_SESSION_OPEN, '&Open Session')
330        self.Bind(wx.EVT_MENU, self.menu_file_session_open, _session_open)
331        file_menu.AppendSeparator()
332        _exit = file_menu.Append(ID_FILE_EXIT, 'E&xit Program')
333        self.Bind(wx.EVT_MENU, self.menu_file_exit, _exit)
334        menu_bar.Append(file_menu, "&File")
335
336        tab_menu = wx.Menu()
337        _close = tab_menu.Append(ID_TAB_CLOSE, '&Close')
338        self.Bind(wx.EVT_MENU, self.menu_tab_close, _close)
339        menu_bar.Append(tab_menu, "&Tab")
340
341        help_menu = wx.Menu()
342        _about = help_menu.Append(ID_HELP_ABOUT, '&About')
343        self.Bind(wx.EVT_MENU, self.menu_help_about, _about)
344        menu_bar.Append(help_menu, "&Help")
345        self.SetMenuBar(menu_bar)
346
347        p = wx.Panel(self)
348        self._nb = wx.Notebook(p)
349
350        for filename in files:
351            self._open_registry_file(filename)
352
353        sizer = wx.BoxSizer(wx.VERTICAL)
354        sizer.Add(self._nb, 1, wx.EXPAND)
355        p.SetSizer(sizer)
356        self.Layout()
357
358    def _open_registry_file(self, filename):
359        """
360        Open a Registry file by filename into a new tab and return the window.
361        """
362        with open(filename, "rb") as f:
363            registry = Registry.Registry(f)
364            view = RegistryFileView(self._nb, registry=registry, filename=filename)
365            self._nb.AddPage(view, basename(filename))
366            return view
367        # TODO handle error
368
369    def menu_file_open(self, evt):
370        dialog = wx.FileDialog(None, "Choose Registry File", "", "", "*", wx.OPEN)
371        if dialog.ShowModal() != wx.ID_OK:
372            return
373        filename = os.path.join(dialog.GetDirectory(), dialog.GetFilename())
374        self._open_registry_file(filename)
375
376    def menu_file_exit(self, evt):
377        sys.exit(0)
378
379    def menu_file_session_open(self, evt):
380        self._nb.DeleteAllPages()
381
382        dialog = wx.FileDialog(None, "Open Session File", "", "", "*", wx.OPEN)
383        if dialog.ShowModal() != wx.ID_OK:
384            return
385        filename = os.path.join(dialog.GetDirectory(), dialog.GetFilename())
386        with open(filename, "rb") as f:
387            t = f.read()
388
389            lines = t.split("\n")
390
391            if len(lines) % 2 != 1:  # there is a trailing newline
392                self.SetStatusText("Malformed session file!")
393                return
394
395            while len(lines) > 1:
396                filename = lines.pop(0)
397                path = lines.pop(0)
398
399                view = self._open_registry_file(filename)
400                view.select_path(path.partition("\\")[2])
401
402            self.SetStatusText("Opened session")
403
404    def menu_file_session_save(self, evt):
405        dialog = wx.FileDialog(None, "Save Session File", "", "", "*", wx.SAVE)
406        if dialog.ShowModal() != wx.ID_OK:
407            return
408        filename = os.path.join(dialog.GetDirectory(), dialog.GetFilename())
409        with open(filename, "wb") as f:
410            for i in range(0, self._nb.GetPageCount()):
411                page = self._nb.GetPage(i)
412                f.write(page.filename() + "\n")
413
414                path = page.selected_path()
415                if path:
416                    f.write(path)
417                f.write("\n")
418            self.SetStatusText("Saved session")
419        # TODO handle error
420
421    def menu_tab_close(self, evt):
422        self._nb.RemovePage(self._nb.GetSelection())
423
424    def menu_help_about(self, evt):
425        wx.MessageBox("regview.py, a part of `python-registry`\n\nhttp://www.williballenthin.com/registry/", "info")
426
427
428if __name__ == '__main__':
429    app = wx.App(False)
430
431    filenames = []
432    filenames = sys.argv[1:]
433
434    frame = RegistryFileViewer(None, filenames)
435    frame.Show()
436    app.MainLoop()
437