├── .gitignore ├── LICENSE ├── README.md ├── __init__.py ├── analyze.py ├── constants.py ├── demo.gif ├── ioctl.py ├── plugin.json └── util.py /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | *.pyc 3 | __pycache__/ 4 | .vscode/ 5 | generate_plugininfo.py 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018 shareef12. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Windows Driver Analyzer (v1.0.0) 2 | 3 | Author: **shareef12** 4 | _Find IRP dispatch routines and valid IOCTLs in a Windows kernel driver_ 5 | 6 | 7 | ## Description: 8 | 9 | This plugin will try to find and label IRP dispatch routines initialized in the 10 | DriverEntry routine. Additionally, this plugin will attempt to identify valid 11 | IOCTL control codes that the driver supports. Handler code for detected IOCTLs 12 | will be labeled, and CTL\_CODE macros will be generated. 13 | 14 | ![Demo Video](./demo.gif) 15 | 16 | 17 | ## Installation Instructions 18 | 19 | This plugin requires angr to be installed and available on sys.path. 20 | 21 | ### Windows 22 | 23 | The Windows distribution of Binary Ninja bundles a copy of python2.7. In order 24 | to install angr, you must install python2.7 on your system or in a virtualenv 25 | and make it available on sys.path. Note that attempting to install angr in the 26 | bundled python error will likely run into errors. On Windows systems, this 27 | plugin will automatically add "C:\Python27\Lib\site-packages" to sys.path. 28 | 29 | 1. Install Python2.7 to "C:\Python27". 30 | 31 | 1. From an Administrator command prompt, install angr. 32 | 33 | ``` 34 | C:\Python27\python.exe -m pip install angr 35 | ``` 36 | 37 | 1. Copy this plugin to the Binary Ninja plugins folder at 38 | "%appdata%\Binary Ninja\plugins". 39 | 40 | ### Linux 41 | 42 | 1. Install angr. 43 | 44 | ``` 45 | pip install angr 46 | ``` 47 | 48 | 1. Copy this plugin to the Binary Ninja plugins folder at 49 | "~/.binaryninja/plugins/". 50 | 51 | 52 | ## Minimum Version 53 | 54 | This plugin requires the following minimum version of Binary Ninja: 55 | 56 | * 1689 57 | 58 | 59 | ## Required Dependencies 60 | 61 | The following dependencies are required for this plugin: 62 | 63 | * pip - angr 64 | 65 | 66 | ## License 67 | 68 | This plugin is released under a MIT license. 69 | 70 | 71 | ## Metadata Version 72 | 73 | 2 74 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Windows Driver Analyzer Binary Ninja Plugin 4 | 5 | This plugin can be used within the Binary Ninja GUI or standalone with the 6 | commercial or enterprise version to detect IRP dispatch routines and valid 7 | IOCTL codes in Windows kernel drivers. 8 | 9 | TODO: 10 | - Function detection for ThreadCreateNotify, ProcessCreateNotify, Workitems, IoCsqInitialize, etc. 11 | - Recursively follow calls from DriverEntry for code that initializes the DriverObject when 12 | finding dispatch routines. We currently only search DriverEntry. 13 | - Set function signatures for known functions 14 | """ 15 | 16 | from __future__ import print_function 17 | import argparse 18 | import os 19 | import sys 20 | 21 | from binaryninja import BinaryViewType, BackgroundTaskThread, PluginCommand 22 | 23 | if sys.platform == "win32": 24 | sys.path.append("C:\\Python27\\Lib\\site-packages") 25 | 26 | import analyze 27 | 28 | class LabelDriverDispatchRoutinesTask(BackgroundTaskThread): 29 | def __init__(self, bv): 30 | BackgroundTaskThread.__init__(self, "Labeling Driver Dispatch Routines", can_cancel=True) 31 | self.bv = bv 32 | 33 | def run(self): 34 | self.bv.begin_undo_actions() 35 | a = analyze.Analysis(self.bv) 36 | a.label_driver_dispatch_routines() 37 | self.bv.commit_undo_actions() 38 | self.bv.update_analysis() 39 | 40 | 41 | class LabelCallbackRoutinesTask(BackgroundTaskThread): 42 | def __init__(self, bv): 43 | BackgroundTaskThread.__init__(self, "Labeling Callback Routines", can_cancel=True) 44 | self.bv = bv 45 | 46 | def run(self): 47 | self.bv.begin_undo_actions() 48 | a = analyze.Analysis(self.bv) 49 | a.label_callback_routines() 50 | self.bv.commit_undo_actions() 51 | self.bv.update_analysis() 52 | 53 | 54 | class FindIoctlsTask(BackgroundTaskThread): 55 | def __init__(self, bv, function=None): 56 | BackgroundTaskThread.__init__(self, "Finding IOCTLs", can_cancel=True) 57 | self.bv = bv 58 | if function: 59 | self.function = function.start 60 | else: 61 | self.function = None 62 | 63 | def run(self): 64 | self.bv.begin_undo_actions() 65 | a = analyze.Analysis(self.bv) 66 | a.find_ioctls(self.function) 67 | self.bv.commit_undo_actions() 68 | self.bv.update_analysis() 69 | 70 | 71 | def label_driver_dispatch_routines(bv): 72 | t = LabelDriverDispatchRoutinesTask(bv) 73 | t.start() 74 | 75 | 76 | def label_callback_routines(bv): 77 | t = LabelCallbackRoutinesTask(bv) 78 | t.start() 79 | 80 | 81 | def find_ioctls(bv, function=None): 82 | t = FindIoctlsTask(bv, function) 83 | t.start() 84 | 85 | 86 | def cmdline_main(): 87 | parser = argparse.ArgumentParser(description="Auto-detect IRP Dispatch routines and IOCTLs.", 88 | formatter_class=argparse.ArgumentDefaultsHelpFormatter) 89 | parser.add_argument("-i", "--ioctls", action="store_true", default=False, 90 | help="Detect supported IOCTL control codes.") 91 | parser.add_argument("driver", help="Windows driver to analyze.") 92 | 93 | args = parser.parse_args() 94 | 95 | if not os.path.isfile(args.driver): 96 | print("[-] '{:s}' is not a file".format(args.driver), file=sys.stderr) 97 | return 1 98 | 99 | # TODO: This line always returns None 100 | bv = BinaryViewType["PE"].open(args.driver) 101 | if not bv: 102 | print("[-] Error loading file: {:s}".format(args.driver), file=sys.stderr) 103 | return 1 104 | 105 | analysis = analyze.Analysis(bv) 106 | analysis.label_driver_dispatch_routines() 107 | if args.ioctls: 108 | analysis.find_ioctls() 109 | 110 | 111 | if __name__ == "__main__": 112 | cmdline_main() 113 | else: 114 | PluginCommand.register( 115 | "Label Driver Dispatch Routines", "Label driver dispatch routines for IRPs and other callbacks", 116 | action=label_driver_dispatch_routines) 117 | #PluginCommand.register( 118 | # "Label Callback Routines", "Label callback routines used in common kernel APIs", 119 | # action=label_callback_routines) 120 | PluginCommand.register( 121 | "Find IOCTLs [global]", "Find supported IOCTLs and generate CTL_CODE macros", 122 | action=find_ioctls) 123 | PluginCommand.register_for_function( 124 | "Find IOCTLs [current function]", "Find supported IOCTLs and generate CTL_CODE macros", 125 | action=find_ioctls) 126 | -------------------------------------------------------------------------------- /analyze.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | 3 | import collections 4 | 5 | from binaryninja import LowLevelILOperation 6 | 7 | import constants 8 | import ioctl 9 | import time 10 | import util 11 | 12 | 13 | class IrpDispatchRoutine(object): 14 | """Helper class to name dispatch routines semi-intelligently. 15 | 16 | If the dispatch routine handles: 17 | More than half the IRPs : "DispatchDefault" 18 | One IRP : "Dispatch" 19 | Multiple IRPs : "Dispatch__..." 20 | """ 21 | def __init__(self, irps): 22 | self.irps = irps 23 | 24 | @property 25 | def name(self): 26 | if len(self.irps) > constants.IRP_MJ_MAXIMUM_FUNCTION / 2: 27 | return "DispatchDefault" 28 | irp_names = [constants.IRP_MJ_NAMES[i] for i in self.irps] 29 | if len(irp_names) == 1: 30 | return "Dispatch{:s}".format(irp_names[0]) 31 | return "Dispatch_{:s}".format("_".join(irp_names)) 32 | 33 | @property 34 | def comment(self): 35 | irp_names = ["IRP_MJ_" + constants.IRP_MJ_NAMES[i] for i in self.irps] 36 | return "Dispatch routine for:\n * " + "\n * ".join(irp_names) 37 | 38 | 39 | class FastIoDispatchRoutine(object): 40 | """Helper class to name dispatch routines semi-intelligently. 41 | 42 | If the dispatch routine handles: 43 | More than half the callbacks : "FastIoDefault" 44 | One callback : "FastIo" 45 | Multiple callbacks : "FastIo__..." 46 | """ 47 | def __init__(self, routines): 48 | self.routines = routines 49 | 50 | @property 51 | def name(self): 52 | if len(self.routines) > len(constants.FAST_IO_NAMES) / 2: 53 | return "FastIoDefault" 54 | routine_names = [constants.FAST_IO_NAMES[i] for i in self.routines] 55 | if len(routine_names) == 1: 56 | return "FastIo{:s}".format(routine_names[0]) 57 | return "FastIo_{:s}".format("_".join(routine_names)) 58 | 59 | @property 60 | def comment(self): 61 | routine_names = ["FastIo" + constants.FAST_IO_NAMES[i] for i in self.routines] 62 | return "Dispatch routine for:\n * " + "\n * ".join(routine_names) 63 | 64 | 65 | class DispatchTable(object): 66 | """Helper class to maintain the state of a dispatch table. 67 | 68 | Maintain two structures: 69 | 1. Map of routine addresses to a list of callbacks they support (1 to many) 70 | 2. List of routine addresses indexed by the callback they support (1 to 1) 71 | 72 | :member routine_type: The class type of the routine (IrpDispatchRoutine or 73 | FastIoDispatchRoutine) 74 | :member routines: A dictionary mapping a single routine address to a list 75 | of codes it implements. 76 | :member table: A table mapping each code to a routine address. 77 | """ 78 | def __init__(self, routine_type, size): 79 | self.routine_type = routine_type 80 | self.routines = collections.defaultdict(list) 81 | self.table = [None] * size 82 | 83 | def add(self, address, routine): 84 | self.routines[address].append(routine) 85 | self.table[routine] = address 86 | 87 | def label_all(self, bv): 88 | for address, routines in self.routines.items(): 89 | routine = self.routine_type(routines) 90 | print(" {:s}: 0x{:x}".format(routine.name, address)) 91 | util.create_named_function(bv, address, routine.name, routine.comment) 92 | 93 | 94 | class Analysis: 95 | """Top-level class used to cache analysis results. 96 | 97 | The IOCTL finding analysis requires the results of other analysis runs. 98 | Caching the results of previous analysis runs will allow us to get fatster 99 | results to the user. 100 | """ 101 | 102 | def __init__(self, bv): 103 | self.bv = bv 104 | self._offsets = constants.Offsets(bv.arch.address_size) 105 | 106 | self.driver_entry = None 107 | self.driver_unload = None 108 | self.driver_start_io = None 109 | self.major_function_table = None 110 | self.fast_io_dispatch_table = None 111 | 112 | def _get_driver_entry(self): 113 | """Get the DriverEntry function. 114 | 115 | The first basic block at _start is provided by the compiler, and has two 116 | function calls. The second is always DriverEntry. Sometimes, tail-call 117 | optimization is applied and _start and DriverEntry are folded into a 118 | single function. 119 | """ 120 | _start = self.bv.entry_function 121 | 122 | # Tail-call optimized - binja didn't detect the optimization. _start 123 | # and DriverEntry will appear to be a single function with multiple 124 | # basic blocks. Common on x86. 125 | if len(_start.basic_blocks) > 1: 126 | return _start 127 | 128 | # Tail-call optimized - binja detected the optimization. _start will 129 | # appear as a single basic block with a tailcall to DriverEntry. Common 130 | # on x86. 131 | block = _start.low_level_il.basic_blocks[0] 132 | if block[-1].operation == LowLevelILOperation.LLIL_TAILCALL: 133 | call_target = block[-1].dest.value.value 134 | return self.bv.get_function_at(call_target) 135 | 136 | # Non-optimized. The second call in the basic block should be to 137 | # DriverEntry. Common on x86_64. 138 | calls = [inst for inst in block if inst.operation == LowLevelILOperation.LLIL_CALL] 139 | assert (len(calls) == 2) 140 | 141 | call_target = calls[1].dest.value.value 142 | return self.bv.get_function_at(call_target) 143 | 144 | 145 | def _get_irp_major_function_table(self, drvobj_stores): 146 | """Find IRP MajorFunction dispatch routines. 147 | 148 | :param drvobj_stores: A dict of stores that occurred at offsets from 149 | the DriverObject. Maps offsets to the contents of the store. 150 | :return: A DispatchTable object representing the MajorFunction 151 | dispatch table in the DriverObject. 152 | """ 153 | # IRP dispatch routine addresses should never by dynamically computed. 154 | # The src for the store instruction should always be a constant. 155 | constant_src_stores = {k: v.value.value for k, v in drvobj_stores.items() if v.value.is_constant} 156 | 157 | # Search all stores for ones that occur to the MajorFunction table. 158 | major_function_table = DispatchTable(IrpDispatchRoutine, constants.IRP_MJ_MAXIMUM_FUNCTION) 159 | for offset, address in constant_src_stores.items(): 160 | if self._offsets.DRVOBJ_MAJOR_FUNCTION_OFFSET <= offset <= self._offsets.DRVOBJ_LAST_MAJOR_FUNCTION_OFFSET: 161 | mj_function = (offset - self._offsets.DRVOBJ_MAJOR_FUNCTION_OFFSET) / self.bv.arch.address_size 162 | major_function_table.add(address, mj_function) 163 | 164 | return major_function_table 165 | 166 | def _get_fast_io_dispatch_table(self, drvobj_stores): 167 | """Find FastIo dispatch routines. 168 | 169 | :param drvobj_stores: A dict of stores that occurred at offsets from 170 | the DriverObject. Maps offsets to the contents of the store. 171 | :return: A DispatchTable object representing the FastIoDispatch 172 | table in the DriverObject. 173 | """ 174 | fast_io_dispatch_table = DispatchTable(FastIoDispatchRoutine, len(constants.FAST_IO_NAMES)) 175 | 176 | # Unlike the IRP MajorFunction table, the FastIoDispatch table is a pointer. 177 | # Dereference this pointer before finding constant source stores. 178 | fastio_dispatch = drvobj_stores.get(self._offsets.DRVOBJ_FAST_IO_DISPATCH_OFFSET) 179 | if fastio_dispatch is None: 180 | return fast_io_dispatch_table 181 | 182 | # Get stores setting up the FastIoDispatch table. These routine 183 | # addresses should never by dynamically computed. The src for the 184 | # store instruction should always be a constant. 185 | fastio_dispatch_stores = util.get_stores_by_offset(self.driver_entry, fastio_dispatch.src) 186 | constant_src_stores = {k: v.value.value for k, v in fastio_dispatch_stores.items() if v.value.is_constant} 187 | 188 | for offset, address in constant_src_stores.iteritems(): 189 | if self._offsets.FAST_IO_DISPATCH_START <= offset <= self._offsets.FAST_IO_DISPATCH_END: 190 | routine = (offset - self._offsets.FAST_IO_DISPATCH_START) / self.bv.arch.address_size 191 | fast_io_dispatch_table.add(address, routine) 192 | 193 | return fast_io_dispatch_table 194 | 195 | def _analyze_driver(self): 196 | """Analyze the DriverEntry to find dispatch routines. 197 | 198 | This function will attempt to identify all DriverObject dispatch 199 | routines, including DriverEntry, DriverUnload, DriverStartIo, 200 | the IRP MajorFunction table, and the FastIoDispatch table. 201 | """ 202 | self.driver_entry = self._get_driver_entry() 203 | if len(self.driver_entry.parameter_vars) == 0: 204 | print("[-] Bad DriverEntry (0x{:x}): detected 0 parameters", self.driver_entry.start) 205 | return 206 | 207 | # Get all stores that occurred where the destination was an offset from the DriverObject 208 | driver_object = self.driver_entry.parameter_vars[0] 209 | drvobj_stores = util.get_stores_by_offset(self.driver_entry, driver_object) 210 | 211 | # DriverUnload and DriverStartIo address should never by dynamically computed. 212 | # The src for the store instruction should always be a constant. 213 | constant_src_stores = {k: v.value.value for k, v in drvobj_stores.items() if v.value.is_constant} 214 | self.driver_start_io = constant_src_stores.get(self._offsets.DRVOBJ_START_IO_OFFSET) 215 | self.driver_unload = constant_src_stores.get(self._offsets.DRVOBJ_DRIVER_UNLOAD_OFFSET) 216 | 217 | # Find routines for the driver's MajorFunction and FastIoDispatch tables 218 | if not self.major_function_table: 219 | self.major_function_table = self._get_irp_major_function_table(drvobj_stores) 220 | if not self.fast_io_dispatch_table: 221 | self.fast_io_dispatch_table = self._get_fast_io_dispatch_table(drvobj_stores) 222 | 223 | def label_driver_dispatch_routines(self): 224 | print("[*] Labeling DriverObject callback routines") 225 | if not self.driver_entry: 226 | self._analyze_driver() 227 | 228 | # Label dispatch routines in the DriverObject 229 | util.create_named_function(self.bv, self.driver_entry.start, "DriverEntry") 230 | if self.driver_unload: 231 | print(" DriverUnload: 0x{:x}".format(self.driver_unload)) 232 | util.create_named_function(self.bv, self.driver_unload, "DriverUnload") 233 | if self.driver_start_io: 234 | print(" DriverStartIo: 0x{:x}".format(self.driver_start_io)) 235 | util.create_named_function(self.bv, self.driver_start_io, "DriverStartIo") 236 | 237 | # Label IRP MajorFunciton and FastIoDispatch routines 238 | print("[+] Detected {:d} IRP Dispatch Routines".format(len(self.major_function_table.routines))) 239 | self.major_function_table.label_all(self.bv) 240 | 241 | print("[+] Detected {:d} FastIoDispatch Routines".format(len(self.fast_io_dispatch_table.routines))) 242 | self.fast_io_dispatch_table.label_all(self.bv) 243 | 244 | def label_callback_routines(self): 245 | raise NotImplementedError() 246 | 247 | def find_ioctls(self, dispatch_device_control=None): 248 | """Find supported IOCTLs and print corresponding CTL_CODE macros. 249 | 250 | For each IOCTL, add a comment at the start of the handling code denoting 251 | which IOCTL is being handled. Print the CTL_CODE macro to the log console. 252 | 253 | :param dispatch_device_control: Address of the IRP_MJ_DEVICE_CONTROL 254 | handler function. This parameter is optional, as we can find this 255 | function automatically in most cases. 256 | """ 257 | print("[*] Finding IOCTLs") 258 | if not dispatch_device_control: 259 | if not self.major_function_table: 260 | self._analyze_driver() 261 | 262 | dispatch_device_control = self.major_function_table.table[constants.IRP_MJ_DEVICE_CONTROL] 263 | if not dispatch_device_control: 264 | print("[-] Could not find IRP_MJ_DEVICE_CONTROL dispatch routine") 265 | return 266 | 267 | print("[*] Running symbolic analysis") 268 | start = time.time() 269 | ioctls = ioctl.find_ioctls(self.bv.file.filename, dispatch_device_control, self.bv.arch.address_size) 270 | stop = time.time() 271 | print("[+] Done. Found {:d} IOCTLs in {:f} seconds.".format(len(ioctls), stop - start)) 272 | 273 | for code in sorted(ioctls): 274 | print(ioctl.get_macro(code)) 275 | for address in ioctls[code]: 276 | funcs = self.bv.get_functions_containing(address) 277 | assert len(funcs) == 1 278 | funcs[0].set_comment_at(address, "Handler for IOCTL_{:x}".format(code)) 279 | -------------------------------------------------------------------------------- /constants.py: -------------------------------------------------------------------------------- 1 | class Offsets(object): 2 | def __init__(self, address_size): 3 | if address_size == 4: 4 | self.FAST_IO_DISPATCH_START = 0x4 5 | self.DRVOBJ_FAST_IO_DISPATCH_OFFSET = 0x28 6 | self.DRVOBJ_START_IO_OFFSET = 0x30 7 | self.DRVOBJ_DRIVER_UNLOAD_OFFSET = 0x34 8 | self.DRVOBJ_MAJOR_FUNCTION_OFFSET = 0x38 9 | 10 | elif address_size == 8: 11 | self.FAST_IO_DISPATCH_START = 0x8 12 | self.DRVOBJ_FAST_IO_DISPATCH_OFFSET = 0x50 13 | self.DRVOBJ_START_IO_OFFSET = 0x60 14 | self.DRVOBJ_DRIVER_UNLOAD_OFFSET = 0x68 15 | self.DRVOBJ_MAJOR_FUNCTION_OFFSET = 0x70 16 | 17 | self.FAST_IO_DISPATCH_END = \ 18 | self.FAST_IO_DISPATCH_START + address_size * len(FAST_IO_NAMES) 19 | self.DRVOBJ_LAST_MAJOR_FUNCTION_OFFSET = \ 20 | self.DRVOBJ_MAJOR_FUNCTION_OFFSET + address_size * IRP_MJ_MAXIMUM_FUNCTION 21 | 22 | 23 | IRP_MJ_CREATE = 0x00 24 | IRP_MJ_CREATE_NAMED_PIPE = 0x01 25 | IRP_MJ_CLOSE = 0x02 26 | IRP_MJ_READ = 0x03 27 | IRP_MJ_WRITE = 0x04 28 | IRP_MJ_QUERY_INFORMATION = 0x05 29 | IRP_MJ_SET_INFORMATION = 0x06 30 | IRP_MJ_QUERY_EA = 0x07 31 | IRP_MJ_SET_EA = 0x08 32 | IRP_MJ_FLUSH_BUFFERS = 0x09 33 | IRP_MJ_QUERY_VOLUME_INFORMATION = 0x0a 34 | IRP_MJ_SET_VOLUME_INFORMATION = 0x0b 35 | IRP_MJ_DIRECTORY_CONTROL = 0x0c 36 | IRP_MJ_FILE_SYSTEM_CONTROL = 0x0d 37 | IRP_MJ_DEVICE_CONTROL = 0x0e 38 | IRP_MJ_INTERNAL_DEVICE_CONTROL = 0x0f 39 | IRP_MJ_SHUTDOWN = 0x10 40 | IRP_MJ_LOCK_CONTROL = 0x11 41 | IRP_MJ_CLEANUP = 0x12 42 | IRP_MJ_CREATE_MAILSLOT = 0x13 43 | IRP_MJ_QUERY_SECURITY = 0x14 44 | IRP_MJ_SET_SECURITY = 0x15 45 | IRP_MJ_POWER = 0x16 46 | IRP_MJ_SYSTEM_CONTROL = 0x17 47 | IRP_MJ_DEVICE_CHANGE = 0x18 48 | IRP_MJ_QUERY_QUOTA = 0x19 49 | IRP_MJ_SET_QUOTA = 0x1a 50 | IRP_MJ_PNP = 0x1b 51 | IRP_MJ_PNP_POWER = IRP_MJ_PNP 52 | IRP_MJ_MAXIMUM_FUNCTION = 0x1b 53 | 54 | IRP_MJ_NAMES = { 55 | IRP_MJ_CREATE: "Create", 56 | IRP_MJ_CREATE_NAMED_PIPE: "CreateNamedPipe", 57 | IRP_MJ_CLOSE: "Close", 58 | IRP_MJ_READ: "Read", 59 | IRP_MJ_WRITE: "Write", 60 | IRP_MJ_QUERY_INFORMATION: "QueryInformation", 61 | IRP_MJ_SET_INFORMATION: "SetInformation", 62 | IRP_MJ_QUERY_EA: "QueryEa", 63 | IRP_MJ_SET_EA: "SetEa", 64 | IRP_MJ_FLUSH_BUFFERS: "FlushBuffers", 65 | IRP_MJ_QUERY_VOLUME_INFORMATION: "QueryVolumeInformation", 66 | IRP_MJ_SET_VOLUME_INFORMATION: "SetVolumeInformation", 67 | IRP_MJ_DIRECTORY_CONTROL: "DirectoryControl", 68 | IRP_MJ_FILE_SYSTEM_CONTROL: "FileSystemControl", 69 | IRP_MJ_DEVICE_CONTROL: "DeviceControl", 70 | IRP_MJ_INTERNAL_DEVICE_CONTROL: "InternalDeviceControl", 71 | IRP_MJ_SHUTDOWN: "Shutdown", 72 | IRP_MJ_LOCK_CONTROL: "LockControl", 73 | IRP_MJ_CLEANUP: "Cleanup", 74 | IRP_MJ_CREATE_MAILSLOT: "CreateMailslot", 75 | IRP_MJ_QUERY_SECURITY: "QuerySecurity", 76 | IRP_MJ_SET_SECURITY: "SetSecurity", 77 | IRP_MJ_POWER: "Power", 78 | IRP_MJ_SYSTEM_CONTROL: "SystemControl", 79 | IRP_MJ_DEVICE_CHANGE: "DeviceChange", 80 | IRP_MJ_QUERY_QUOTA: "QueryQuota", 81 | IRP_MJ_SET_QUOTA: "SetQuota", 82 | IRP_MJ_PNP: "Pnp", 83 | } 84 | 85 | FAST_IO_NAMES = [ 86 | "CheckIfPossible", 87 | "Read", 88 | "Write", 89 | "QueryBasicInfo", 90 | "QueryStandardInfo", 91 | "Lock", 92 | "UnlockSingle", 93 | "UnlockAll", 94 | "UnlockAllByKey", 95 | "DeviceControl", 96 | "AcquireFileForNtCreateSection", 97 | "ReleaseFileForNtCreateSection", 98 | "DetachDevice", 99 | "QueryNetworkOpenInfo", 100 | "AcquireForModWrite", 101 | "MdlRead", 102 | "MdlReadComplete", 103 | "PrepareMdlWrite", 104 | "MdlWriteComplete", 105 | "ReadCompressed", 106 | "WriteCompressed", 107 | "MdlReadCompleteCompressed", 108 | "MdlWriteCompleteCompressed", 109 | "QueryOpen", 110 | "ReleaseForModWrite", 111 | "AcquireForCcFlush", 112 | "ReleaseForCcFlush", 113 | ] 114 | 115 | DEVICE_TYPES = { 116 | 0x01: "FILE_DEVICE_BEEP", 117 | 0x02: "FILE_DEVICE_CD_ROM", 118 | 0x03: "FILE_DEVICE_CD_ROM_FILE_SYSTEM", 119 | 0x04: "FILE_DEVICE_CONTROLLER", 120 | 0x05: "FILE_DEVICE_DATALINK", 121 | 0x06: "FILE_DEVICE_DFS", 122 | 0x07: "FILE_DEVICE_DISK", 123 | 0x08: "FILE_DEVICE_DISK_FILE_SYSTEM", 124 | 0x09: "FILE_DEVICE_FILE_SYSTEM", 125 | 0x0a: "FILE_DEVICE_INPORT_PORT", 126 | 0x0b: "FILE_DEVICE_KEYBOARD", 127 | 0x0c: "FILE_DEVICE_MAILSLOT", 128 | 0x0d: "FILE_DEVICE_MIDI_IN", 129 | 0x0e: "FILE_DEVICE_MIDI_OUT", 130 | 0x0f: "FILE_DEVICE_MOUSE", 131 | 0x10: "FILE_DEVICE_MULTI_UNC_PROVIDER", 132 | 0x11: "FILE_DEVICE_NAMED_PIPE", 133 | 0x12: "FILE_DEVICE_NETWORK", 134 | 0x13: "FILE_DEVICE_NETWORK_BROWSER", 135 | 0x14: "FILE_DEVICE_NETWORK_FILE_SYSTEM", 136 | 0x15: "FILE_DEVICE_NULL", 137 | 0x16: "FILE_DEVICE_PARALLEL_PORT", 138 | 0x17: "FILE_DEVICE_PHYSICAL_NETCARD", 139 | 0x18: "FILE_DEVICE_PRINTER", 140 | 0x19: "FILE_DEVICE_SCANNER", 141 | 0x1a: "FILE_DEVICE_SERIAL_MOUSE_PORT", 142 | 0x1b: "FILE_DEVICE_SERIAL_PORT", 143 | 0x1c: "FILE_DEVICE_SCREEN", 144 | 0x1d: "FILE_DEVICE_SOUND", 145 | 0x1e: "FILE_DEVICE_STREAMS", 146 | 0x1f: "FILE_DEVICE_TAPE", 147 | 0x20: "FILE_DEVICE_TAPE_FILE_SYSTEM", 148 | 0x21: "FILE_DEVICE_TRANSPORT", 149 | 0x22: "FILE_DEVICE_UNKNOWN", 150 | 0x23: "FILE_DEVICE_VIDEO", 151 | 0x24: "FILE_DEVICE_VIRTUAL_DISK", 152 | 0x25: "FILE_DEVICE_WAVE_IN", 153 | 0x26: "FILE_DEVICE_WAVE_OUT", 154 | 0x27: "FILE_DEVICE_8042_PORT", 155 | 0x28: "FILE_DEVICE_NETWORK_REDIRECTOR", 156 | 0x29: "FILE_DEVICE_BATTERY", 157 | 0x2a: "FILE_DEVICE_BUS_EXTENDER", 158 | 0x2b: "FILE_DEVICE_MODEM", 159 | 0x2c: "FILE_DEVICE_VDM", 160 | 0x2d: "FILE_DEVICE_MASS_STORAGE", 161 | 0x2e: "FILE_DEVICE_SMB", 162 | 0x2f: "FILE_DEVICE_KS", 163 | 0x30: "FILE_DEVICE_CHANGER", 164 | 0x31: "FILE_DEVICE_SMARTCARD", 165 | 0x32: "FILE_DEVICE_ACPI", 166 | 0x33: "FILE_DEVICE_DVD", 167 | 0x34: "FILE_DEVICE_FULLSCREEN_VIDEO", 168 | 0x35: "FILE_DEVICE_DFS_FILE_SYSTEM", 169 | 0x36: "FILE_DEVICE_DFS_VOLUME", 170 | 0x37: "FILE_DEVICE_SERENUM", 171 | 0x38: "FILE_DEVICE_TERMSRV", 172 | 0x39: "FILE_DEVICE_KSEC", 173 | 0x3A: "FILE_DEVICE_FIPS", 174 | 0x3B: "FILE_DEVICE_INFINIBAND", 175 | 0x3E: "FILE_DEVICE_VMBUS", 176 | 0x3F: "FILE_DEVICE_CRYPT_PROVIDER", 177 | 0x40: "FILE_DEVICE_WPD", 178 | 0x41: "FILE_DEVICE_BLUETOOTH", 179 | 0x42: "FILE_DEVICE_MT_COMPOSITE", 180 | 0x43: "FILE_DEVICE_MT_TRANSPORT", 181 | 0x44: "FILE_DEVICE_BIOMETRIC", 182 | 0x45: "FILE_DEVICE_PMI", 183 | 0x46: "FILE_DEVICE_EHSTOR", 184 | 0x47: "FILE_DEVICE_DEVAPI", 185 | 0x48: "FILE_DEVICE_GPIO", 186 | 0x49: "FILE_DEVICE_USBEX", 187 | 0x50: "FILE_DEVICE_CONSOLE", 188 | 0x51: "FILE_DEVICE_NFP", 189 | 0x52: "FILE_DEVICE_SYSENV", 190 | 0x53: "FILE_DEVICE_VIRTUAL_BLOCK", 191 | 0x54: "FILE_DEVICE_POINT_OF_SERVICE", 192 | 0x55: "FILE_DEVICE_STORAGE_REPLICATION", 193 | 0x56: "FILE_DEVICE_TRUST_ENV", 194 | 0x57: "FILE_DEVICE_UCM", 195 | 0x58: "FILE_DEVICE_UCMTCPCI", 196 | } 197 | 198 | METHODS = { 199 | 0x00: "METHOD_BUFFERED", 200 | 0x01: "METHOD_IN_DIRECT", 201 | 0x02: "METHOD_OUT_DIRECT", 202 | 0x03: "METHOD_NEITHER", 203 | } 204 | 205 | ACCESS = { 206 | 0x00: "FILE_ANY_ACCESS", 207 | 0x01: "FILE_READ_ACCESS", 208 | 0x02: "FILE_WRITE_ACCESS", 209 | } 210 | -------------------------------------------------------------------------------- /demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shareef12/driveranalyzer/36dcd34e0fc7f634171050a4806f055bbe1cc47d/demo.gif -------------------------------------------------------------------------------- /ioctl.py: -------------------------------------------------------------------------------- 1 | """Use symbolic execution to determine supported IOCTLs. 2 | 3 | This function will attempt to symbolically execute the driver's IOCTL 4 | dispatch routine with a symbolid IRP. Once the IRP's io_control_code 5 | field is constrained to a single value, terminate the symbolic execution 6 | for that path and report the code as supported. 7 | 8 | TODO: 9 | - memory_endness isn't set correctly for x64 calling convention. Have to `swap32` the 10 | solved ioctl. Figure out how to fix this and remove the `swap32` hack. 11 | - State explosion can occur on a memcpy with a symbolic state. This can occur if 12 | an IOCTL handling routine reads the len parameter from the symbolic IRP SystemBuffer. 13 | - NT_ASSERT compiles to int 0x2c, which pyvex doesn't lift 14 | - cr3 reading/writing isn't modeled well by pyvex 15 | """ 16 | 17 | from __future__ import print_function 18 | from builtins import int # python2/3 compatibility for isinstance() checks 19 | import collections 20 | import struct 21 | 22 | import angr 23 | from angr.calling_conventions import SimCCStdcall, SimCCSystemVAMD64, PointerWrapper 24 | import claripy 25 | 26 | import constants 27 | 28 | 29 | class SimCCMicrosoftAMD64(SimCCSystemVAMD64): 30 | """Angr doesn't define the Microsoft x64 calling convention.""" 31 | ARG_REGS = ['rcx', 'rdx', 'r8', 'r9'] 32 | FP_ARG_REGS = ['xmm0', 'xmm1', 'xmm2', 'xmm3'] 33 | STACKARG_SP_BUFF = 32 # Shadow space 34 | 35 | 36 | class SymbolicStructure(object): 37 | """Base class for symbolic structures. 38 | 39 | Child classes should define the _fields member as a list of tuples, where 40 | each tuple is a (name, size) pair. If size is an int, the field will be 41 | converted to a symbolic variable. Otherwise, it will remain as defined in 42 | the child class. 43 | """ 44 | 45 | def __init__(self): 46 | self._fields = getattr(self, "_fields", []) 47 | for name, value in self._fields: 48 | if isinstance(value, int): 49 | setattr(self, name, claripy.BVS(name, value)) 50 | else: 51 | setattr(self, name, value) 52 | 53 | def pack(self): 54 | """Return a representation that can be used by angr as a function argument.""" 55 | return tuple(getattr(self, n) for n, _ in self._fields) 56 | 57 | 58 | class SymbolicIrp(SymbolicStructure): 59 | def __init__(self, address_bits): 60 | if address_bits == 32: 61 | self._fields = [ 62 | ("type", 16), 63 | ("size", 16), 64 | ("mdl_address", address_bits), 65 | ("flags", 32)] 66 | else: 67 | self._fields = [ 68 | ("type", 16), 69 | ("size", 16), 70 | ("allocation_processor_number", 16), 71 | ("reserved", 16), 72 | ("mdl_address", address_bits), 73 | ("flags", 32), 74 | ("pad1", claripy.BVV(0, 32))] # Concretize padding 75 | 76 | self._fields += [ 77 | ("associated_irp", address_bits), 78 | ("thread_list_entry", address_bits * 2), 79 | ("io_status_status", address_bits), 80 | ("io_status_information", address_bits), 81 | 82 | ("requestor_mode", 8), 83 | ("pending_returned", 8), 84 | ("stack_count", 8), 85 | ("current_location", 8), 86 | ("cancel", 8), 87 | ("cancel_irql", 8), 88 | ("apc_environment", 8), 89 | ("allocation_flags", 8), 90 | 91 | # User fields 92 | ("user_iosb", address_bits), 93 | ("user_event", address_bits), 94 | ("overlay", address_bits * 2), 95 | ("cancel_routine", address_bits), 96 | ("user_buffer", address_bits), 97 | 98 | # Kernel fields. Members of Tail.Overlay. 99 | ("driver_context", address_bits * 4), 100 | ("thread", address_bits), 101 | ("auxiliary_buffer", address_bits), 102 | ("list_entry", address_bits * 2), 103 | ("current_stack_location", address_bits), 104 | ("original_file_object", address_bits)] 105 | if address_bits == 64: 106 | self._fields += [("irp_extension", address_bits)] 107 | 108 | super(SymbolicIrp, self).__init__() 109 | 110 | 111 | class SymbolicDeviceIoControlIoStackLocation(SymbolicStructure): 112 | def __init__(self, address_bits): 113 | self._fields = [ 114 | ("major_function", claripy.BVV(constants.IRP_MJ_DEVICE_CONTROL, 8)), 115 | ("minor_function", 8), 116 | ("flags", 8), 117 | ("control", 8)] 118 | 119 | if address_bits == 32: 120 | self._fields += [ 121 | ("output_buffer_length", 32), 122 | ("input_buffer_length", 32), 123 | ("io_control_code", 32), 124 | ("type3_input_buffer", address_bits)] 125 | else: 126 | self._fields += [ 127 | ("pad1", claripy.BVV(0, 32)), # concretize padding 128 | ("output_buffer_length", 32), 129 | ("pad2", claripy.BVV(0, 32)), 130 | ("input_buffer_length", 32), 131 | ("pad3", claripy.BVV(0, 32)), 132 | ("io_control_code", 32), 133 | ("pad4", claripy.BVV(0, 32)), 134 | ("type3_input_buffer", address_bits)] 135 | 136 | self._fields += [ 137 | ("device_object", address_bits), 138 | ("file_object", address_bits), 139 | ("completion_routine", address_bits), 140 | ("context", address_bits)] 141 | 142 | super(SymbolicDeviceIoControlIoStackLocation, self).__init__() 143 | 144 | 145 | def swap32(n): 146 | return struct.unpack("I", n))[0] 147 | 148 | 149 | def get_macro(code): 150 | """Convert a IOCTL code into a CTL_CODE macro string.""" 151 | type = (code >> 16) & 0xffff 152 | access = (code >> 14) & 0x03 153 | function = (code >> 2) & 0x0fff 154 | method = code & 0x03 155 | 156 | try: 157 | stype = constants.DEVICE_TYPES[type] 158 | except KeyError: 159 | stype = "FILE_DEVICE_CUSTOM_{:x}".format(type) 160 | saccess = constants.ACCESS[access] 161 | smethod = constants.METHODS[method] 162 | return "#define IOCTL_{:x} CTL_CODE({:s}, {:d}, {:s}, {:s})".format( 163 | code, stype, function, smethod, saccess) 164 | 165 | 166 | def find_ioctls(filename, dispatch_device_control, address_size=8): 167 | """Symbolically explore the dispatch function to find supported IOCTLs. 168 | 169 | We want to symbolically explore the function until we enter a state 170 | where the IOCTL is constrained to a single value. Return a map of IOCTL 171 | codes to a list of addresses where the handling code starts. 172 | """ 173 | proj = angr.Project(filename, auto_load_libs=False) 174 | 175 | # Create a call state with a symbolic IRP 176 | sirp = SymbolicIrp(address_size * 8) 177 | siosl = SymbolicDeviceIoControlIoStackLocation(address_size * 8) 178 | sirp.current_stack_location = PointerWrapper(siosl.pack()) 179 | irp = sirp.pack() 180 | 181 | if address_size == 4: 182 | cc = SimCCStdcall(proj.arch) 183 | else: 184 | cc = SimCCMicrosoftAMD64(proj.arch) 185 | state = proj.factory.call_state(dispatch_device_control, 186 | claripy.BVS("DeviceObject", 64), irp, 187 | cc=cc, ret_addr=0xdeadbeef) 188 | 189 | def ioctl_constrained(st): 190 | """Return true if the IOCTL code is constrained to a single value.""" 191 | try: 192 | st.solver.eval_one(siosl.io_control_code) 193 | return True 194 | except angr.SimValueError: 195 | return False 196 | 197 | # Run until all states finish 198 | simgr = proj.factory.simgr(state) 199 | while len(simgr.active) > 0: 200 | simgr.explore(find=ioctl_constrained, avoid=0xdeadbeef) 201 | #print(simgr) 202 | 203 | # Return a map of IOCTL codes to a list of handler addresses 204 | ioctls = collections.defaultdict(list) 205 | for s in simgr.found: 206 | # For some reason, the memory_endness for x64 symbolic variables isn't getting 207 | # set correctly. Account for little-endian manually. 208 | if address_size == 4: 209 | code = s.solver.eval_one(siosl.io_control_code) 210 | start = s.solver.eval_one(s.regs.eip) 211 | else: 212 | code = swap32(s.solver.eval_one(siosl.io_control_code)) 213 | start = s.solver.eval_one(s.regs.rip) 214 | ioctls[code].append(start) 215 | 216 | return ioctls 217 | -------------------------------------------------------------------------------- /plugin.json: -------------------------------------------------------------------------------- 1 | { 2 | "pluginmetadataversion": 2, 3 | "name": "Windows Driver Analyzer", 4 | "type": ["helper"], 5 | "api": ["python2", "python3"], 6 | "description": "Find IRP dispatch routines and valid IOCTLs in a Windows kernel driver", 7 | "longdescription": "This plugin will try to find and label IRP dispatch routines initialized in the\nDriverEntry routine. Additionally, this plugin will attempt to identify valid\nIOCTL control codes that the driver supports. Handler code for detected IOCTLs\nwill be labeled, and CTL\\_CODE macros will be generated.", 8 | "license": { 9 | "name": "MIT", 10 | "text": "Copyright (c) 2019 shareef12.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE." 11 | }, 12 | "platforms": ["Windows", "Linux"], 13 | "installinstructions": { 14 | "Windows": "The Windows distribution of Binary Ninja bundles a copy of python2.7. In order\nto install angr, you must install python2.7 on your system or in a virtualenv\nand make it available on sys.path. Note that attempting to install angr in the\nbundled python error will likely run into errors. On Windows systems, this\nplugin will automatically add \"C:\\Python27\\Lib\\site-packages\" to sys.path.\n\n1. Install Python2.7 to \"C:\\Python27\".\n\n1. From an Administrator command prompt, install angr.\n\n ```\n C:\\Python27\\python.exe -m pip install angr\n ```\n\n1. Copy this plugin to the Binary Ninja plugins folder at\n \"%appdata%\\Binary Ninja\\plugins\".", 15 | "Linux": "1. Install angr.\n\n ```\n pip install angr\n ```\n\n1. Copy this plugin to the Binary Ninja plugins folder at\n \"~/.binaryninja/plugins/\"." 16 | }, 17 | "dependencies": { 18 | "pip": ["angr"] 19 | }, 20 | "version": "1.0.0", 21 | "author": "shareef12", 22 | "minimumbinaryninjaversion": 1689 23 | } 24 | -------------------------------------------------------------------------------- /util.py: -------------------------------------------------------------------------------- 1 | import itertools 2 | 3 | from binaryninja import MediumLevelILOperation 4 | 5 | 6 | def create_named_function(bv, address, name, comment=None): 7 | """Create and name a function. Add an optional comment.""" 8 | bv.add_function(address) 9 | func = bv.get_function_at(address) 10 | func.name = name 11 | if comment is not None: 12 | func.set_comment_at(address, comment) 13 | 14 | 15 | def get_store_offset(inst): 16 | """Get the store offset for an MLIL store instuction.""" 17 | assert inst.operation == MediumLevelILOperation.MLIL_STORE 18 | 19 | offset = 0 20 | inst = inst.dest 21 | while inst.operation != MediumLevelILOperation.MLIL_VAR: 22 | if inst.operation == MediumLevelILOperation.MLIL_ADD: 23 | assert inst.right.value.is_constant 24 | offset += inst.right.value.value 25 | inst = inst.left 26 | else: 27 | raise RuntimeError(inst) 28 | return offset 29 | 30 | 31 | def get_stores_by_offset(function, variable): 32 | """Get a list of all stores that occurred at an offset from the given 33 | variable. 34 | 35 | Before finding stores, this function will try to identify potential aliases 36 | by performing a breadth-first search of all variable defs and uses. Note 37 | that this is a flow-insensitive analysis, and assumes only a single store 38 | per offset. 39 | 40 | :param function: binaryninja.function.Function representing the function 41 | to analyze. 42 | :param variable: binaryninja.function.Variable representing the base 43 | address for store operations to find. 44 | :return: Dictionary of stores, mapping offsets to the value stored at that 45 | offset. {int: MediumLevelILInstruction, int: MediumLevelILInstruction}. 46 | """ 47 | stores = {} 48 | aliases = set([variable]) 49 | mlil = function.medium_level_il 50 | 51 | # Follow the use-def chain to build an initial list of aliases for this 52 | # variable. If the definition was an assignment from another variable, record 53 | # the source variable as an alias and add it to the set of variables to search. 54 | defs = mlil.get_var_definitions(variable) 55 | while len(defs) > 0: 56 | inst = mlil[defs.pop(0)] 57 | if (inst.operation == MediumLevelILOperation.MLIL_SET_VAR and 58 | inst.src.operation == MediumLevelILOperation.MLIL_VAR): 59 | src = inst.src.src 60 | aliases.add(src) 61 | defs += mlil.get_var_definitions(src) 62 | 63 | # Now follow the def-use chain for the variable and all known aliases in 64 | # order to find additional aliases. This check should always find a 65 | # superset of the currently known aliases, since the def-use chain will 66 | # include any of the SET_VAR instructions we used to construct the initial 67 | # alias set. 68 | uses = [mlil.get_var_uses(var) for var in aliases] 69 | uses = list(itertools.chain.from_iterable(uses)) 70 | while len(uses) > 0: 71 | inst = mlil[uses.pop(0)] 72 | if (inst.operation == MediumLevelILOperation.MLIL_SET_VAR and 73 | inst.src.operation == MediumLevelILOperation.MLIL_VAR): 74 | dest = inst.dest 75 | aliases.add(dest) 76 | uses += mlil.get_var_uses(dest) 77 | 78 | # We now have most (hopefully all) aliases for the given variable. Record 79 | # all stores that occurred using one of the aliases as the base address. 80 | for var in aliases: 81 | for use in mlil.get_var_uses(var): 82 | inst = mlil[use] 83 | if (inst.operation == MediumLevelILOperation.MLIL_STORE and 84 | inst.src != var): 85 | offset = get_store_offset(inst) 86 | stores[offset] = inst.src 87 | 88 | return stores 89 | --------------------------------------------------------------------------------