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