├── .gitignore ├── LICENSE.md ├── README.md ├── screenshots ├── angr_decode_option.png ├── decode_all_ioctls_fail.PNG ├── decode_ioctl_capcom_decoded.PNG ├── decode_ioctl_display_all_defines.PNG ├── decode_ioctl_mark_invalid_only_delete_define.PNG ├── decode_ioctl_mark_ioctl_invalid.png ├── decode_ioctl_summary_table.PNG ├── define_tab.PNG ├── define_tab_right_click.PNG ├── dump_pool_tags.PNG ├── find_device_name_capcom.PNG ├── find_device_random_avg_driver.PNG ├── find_dispatch_different_avg_driver_fail.PNG ├── find_dispatch_random_avg_driver.PNG └── view_all.png ├── win_driver_plugin.py └── win_driver_plugin ├── __init__.py ├── angr_analysis.py ├── create_tab_table.py ├── device_finder.py ├── device_type.py ├── driverlib.py ├── dump_pool_tags.py └── ioctl_decoder.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | 27 | # PyInstaller 28 | # Usually these files are written by a python script from a template 29 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 30 | *.manifest 31 | *.spec 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .coverage.* 42 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | *,cover 46 | .hypothesis/ 47 | 48 | # Translations 49 | *.mo 50 | *.pot 51 | 52 | # Django stuff: 53 | *.log 54 | local_settings.py 55 | 56 | # Flask stuff: 57 | instance/ 58 | .webassets-cache 59 | 60 | # Scrapy stuff: 61 | .scrapy 62 | 63 | # Sphinx documentation 64 | docs/_build/ 65 | 66 | # PyBuilder 67 | target/ 68 | 69 | # IPython Notebook 70 | .ipynb_checkpoints 71 | 72 | # pyenv 73 | .python-version 74 | 75 | # celery beat schedule file 76 | celerybeat-schedule 77 | 78 | # dotenv 79 | .env 80 | 81 | # virtualenv 82 | venv/ 83 | ENV/ 84 | 85 | # Spyder project settings 86 | .spyderproject 87 | 88 | # Rope project settings 89 | .ropeproject 90 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016 MWR InfoSecurity 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | * Neither the name of MWR InfoSecurity nor the names of its contributors 13 | may be used to endorse or promote products derived from this software 14 | without specific prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL MWR INFOSECURITY BE LIABLE FOR ANY 20 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Windows Driver Plugin 2 | 3 | An IDA Pro plugin to help when working with IOCTL codes or reversing Windows drivers. 4 | 5 | ## Installation 6 | 7 | Just drop the 'win_driver_plugin.py' file and the 'win_driver_plugin' folder into IDA's plugin directory. 8 | If you want [FLOSS](https://github.com/fireeye/flare-floss) to be used when hunting for device names, you can install FLOSS with the following commands: 9 | ``` 10 | pip install https://github.com/williballenthin/vivisect/zipball/master 11 | pip install https://github.com/fireeye/flare-floss/zipball/master 12 | ``` 13 | If you want to use Angr to find IOCTL codes used in the dispatch function, the following links provide potential install instructions. 14 | [http://angr.horse](http://angr.horse) 15 | [https://github.com/andreafioraldi/angr-win64-wheels](https://github.com/andreafioraldi/angr-win64-wheels) 16 | 17 | ## Shortcuts 18 | 19 | *Ctrl+Alt+A* => Find potential device names 20 | *Ctrl+Alt+S* => Find the dispatch function 21 | *Ctrl+Alt+D* => Decode currently selected IOCTL code 22 | *Ctrl+Alt+Z* => Dump pooltags 23 | 24 | ## Usage 25 | 26 | ### Finding device names 27 | 28 | Using *Ctrl+Alt+A* it's possible to attempt to the find the drivers registered device paths, for example we get several potential paths when inspecting a random AVG driver: 29 | ![](/screenshots/find_device_random_avg_driver.PNG) 30 | If no paths can be found by looking at Unicode strings inside the binary then FLOSS will be used in an attempt to find obsfucated paths, for example inspecting the infamous [capcom driver](http://www.theregister.co.uk/2016/09/23/capcom_street_fighter_v/): 31 | ![](/screenshots/find_device_name_capcom.PNG) 32 | 33 | ### Finding dispatch functions 34 | 35 | Using *Ctrl+Alt+S* it's possible to attempt to find the currently inspected drivers dispatch function, this is quite hacky but seems to work most of the time - here's an example of this working on a random AVG driver: 36 | ![](/screenshots/find_dispatch_random_avg_driver.PNG) 37 | Trying this on a different AVG driver leads to it failing completely, in this case because the drivers IOCTL handler is basically a stub which sends some requests to a different function begore passing most to the actual IOCTL handler 38 | ![](/screenshots/find_dispatch_different_avg_driver_fail.PNG) 39 | 40 | ### Decoding IOCTL codes 41 | 42 | By right-clicking on a potential IOCTL code a context menu option can be used to decode the value, alternatively *Ctrl+Alt+D* can be used. 43 | ![](/screenshots/decode_ioctl_capcom_decoded.PNG) 44 | This will print a table with all decoded IOCTL codes each time a new one is decoded: 45 | ![](/screenshots/decode_ioctl_summary_table.PNG) 46 | By right-clicking on a decoded IOCTL code it's possible to mark it as invalid: 47 | ![](/screenshots/decode_ioctl_mark_ioctl_invalid.png) 48 | This will leave any non-IOCTL define based comment contents intact. 49 | ![](/screenshots/decode_ioctl_mark_invalid_only_delete_define.PNG) 50 | The right-click menu also included a display all defines option which display the CTL_CODE definitions for all IOCTL codes decoded in the current session: 51 | ![](/screenshots/decode_ioctl_display_all_defines.PNG) 52 | If you right click on the first instruction of the function you believe to be the IOCTL dispatcher a decode all options appears, this attempt to decode all IOCTL codes it can find in the function. This is super hacky but can speed things up most of the time. 53 | ![](/screenshots/decode_all_ioctls_fail.PNG) 54 | If you want to do this in a smarter way and can get [Angr](http://angr.horse) installed successfully, the 'Decode IOCTLs using Angr' option shown below will use symbolic execution to attempt to recover all IOCTL codes. This will deal with jump tables, optimizations etc whereas the dumb method is just looking for comparisons to constants. 55 | ![](/screenshots/angr_decode_option.png) 56 | 57 | ### Viewing IOCTL codes 58 | 59 | If you've decoder one or more IOCTLs a new option appears on the plugins right click context menu. 60 | 61 | ![](/screenshots/view_all.png) 62 | 63 | This will take you to a new tab which shows all the IOCTLs which have been found. 64 | 65 | ![](/screenshots/define_tab.PNG) 66 | 67 | Right clicking on any IOCTL opens up some more commands, such as copying them to the clipboard or attempting to load the driver and send them. 68 | 69 | ![](/screenshots/define_tab_right_click.PNG) 70 | 71 | ### Dumping pool tags 72 | 73 | Using *Ctrl+Alt+Z* it's possible to dump the pooltags in use by the binary in a format which works with pooltags.txt. This means the output can be copy pasted at the end of the file and then be picked up by windbg etc. 74 | ![](/screenshots/dump_pool_tags.PNG) 75 | 76 | ## Acknowledgements 77 | 78 | The IOCTL code parsing functions are mostly based off of Satoshi Tanda's https://github.com/tandasat/WinIoCtlDecoder/blob/master/plugins/WinIoCtlDecoder.py 79 | The original code for adding items to the right-click menu (and possibly some other random snippets) came from 'herrcore' https://gist.github.com/herrcore/b3143dde185cecda7c1dee7ffbce5d2c 80 | The logic for calling floss and the unicode string finding functions are taken from https://github.com/fireeye/flare-floss 81 | The driver type identification code logic is taken from NCC Group's DriverBuddy plugin https://github.com/nccgroup/DriverBuddy 82 | 83 | ## License 84 | 85 | This code is released under a 3-clause BSD License. See the LICENSE file for full details. -------------------------------------------------------------------------------- /screenshots/angr_decode_option.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FSecureLABS/win_driver_plugin/d4fd838ce40fc6af3c1802f644df3a4f83de4696/screenshots/angr_decode_option.png -------------------------------------------------------------------------------- /screenshots/decode_all_ioctls_fail.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FSecureLABS/win_driver_plugin/d4fd838ce40fc6af3c1802f644df3a4f83de4696/screenshots/decode_all_ioctls_fail.PNG -------------------------------------------------------------------------------- /screenshots/decode_ioctl_capcom_decoded.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FSecureLABS/win_driver_plugin/d4fd838ce40fc6af3c1802f644df3a4f83de4696/screenshots/decode_ioctl_capcom_decoded.PNG -------------------------------------------------------------------------------- /screenshots/decode_ioctl_display_all_defines.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FSecureLABS/win_driver_plugin/d4fd838ce40fc6af3c1802f644df3a4f83de4696/screenshots/decode_ioctl_display_all_defines.PNG -------------------------------------------------------------------------------- /screenshots/decode_ioctl_mark_invalid_only_delete_define.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FSecureLABS/win_driver_plugin/d4fd838ce40fc6af3c1802f644df3a4f83de4696/screenshots/decode_ioctl_mark_invalid_only_delete_define.PNG -------------------------------------------------------------------------------- /screenshots/decode_ioctl_mark_ioctl_invalid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FSecureLABS/win_driver_plugin/d4fd838ce40fc6af3c1802f644df3a4f83de4696/screenshots/decode_ioctl_mark_ioctl_invalid.png -------------------------------------------------------------------------------- /screenshots/decode_ioctl_summary_table.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FSecureLABS/win_driver_plugin/d4fd838ce40fc6af3c1802f644df3a4f83de4696/screenshots/decode_ioctl_summary_table.PNG -------------------------------------------------------------------------------- /screenshots/define_tab.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FSecureLABS/win_driver_plugin/d4fd838ce40fc6af3c1802f644df3a4f83de4696/screenshots/define_tab.PNG -------------------------------------------------------------------------------- /screenshots/define_tab_right_click.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FSecureLABS/win_driver_plugin/d4fd838ce40fc6af3c1802f644df3a4f83de4696/screenshots/define_tab_right_click.PNG -------------------------------------------------------------------------------- /screenshots/dump_pool_tags.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FSecureLABS/win_driver_plugin/d4fd838ce40fc6af3c1802f644df3a4f83de4696/screenshots/dump_pool_tags.PNG -------------------------------------------------------------------------------- /screenshots/find_device_name_capcom.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FSecureLABS/win_driver_plugin/d4fd838ce40fc6af3c1802f644df3a4f83de4696/screenshots/find_device_name_capcom.PNG -------------------------------------------------------------------------------- /screenshots/find_device_random_avg_driver.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FSecureLABS/win_driver_plugin/d4fd838ce40fc6af3c1802f644df3a4f83de4696/screenshots/find_device_random_avg_driver.PNG -------------------------------------------------------------------------------- /screenshots/find_dispatch_different_avg_driver_fail.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FSecureLABS/win_driver_plugin/d4fd838ce40fc6af3c1802f644df3a4f83de4696/screenshots/find_dispatch_different_avg_driver_fail.PNG -------------------------------------------------------------------------------- /screenshots/find_dispatch_random_avg_driver.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FSecureLABS/win_driver_plugin/d4fd838ce40fc6af3c1802f644df3a4f83de4696/screenshots/find_dispatch_random_avg_driver.PNG -------------------------------------------------------------------------------- /screenshots/view_all.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FSecureLABS/win_driver_plugin/d4fd838ce40fc6af3c1802f644df3a4f83de4696/screenshots/view_all.png -------------------------------------------------------------------------------- /win_driver_plugin.py: -------------------------------------------------------------------------------- 1 | """Decodes 32-Bit Windows Device I/O control codes. 2 | 3 | Author: 4 | Sam Brown 5 | 6 | Description: 7 | * Discover driver device names by search through present Unicode strings and if not found 8 | searches for stack based strings or obfuscated strings which could be the device name 9 | * Decodes Windows Device I/O control code into DeviceType, FunctionCode, 10 | AccessType, MethodType and a usable C define. 11 | * Attempts to locate the driver dispatch handler by doing some basic CFG analysis and checking 12 | offsets at which function pointers are loaded into memory. 13 | """ 14 | 15 | import idc 16 | import idaapi 17 | import idautils 18 | 19 | import win_driver_plugin.device_finder as device_finder 20 | import win_driver_plugin.ioctl_decoder as ioctl_decoder 21 | import win_driver_plugin.create_tab_table as create_tab_table 22 | import win_driver_plugin.device_type as device_type 23 | import win_driver_plugin.angr_analysis as angr_analysis 24 | import win_driver_plugin.dump_pool_tags as dump_pool_tags 25 | 26 | class UiAction(idaapi.action_handler_t): 27 | """Simple wrapper class for creating action handlers which add options to menu's and are triggered via hot keys""" 28 | 29 | def __init__(self, id, name, tooltip, menuPath, callback, shortcut): 30 | idaapi.action_handler_t.__init__(self) 31 | self.id = id 32 | self.name = name 33 | self.tooltip = tooltip 34 | self.menuPath = menuPath 35 | self.callback = callback 36 | self.shortcut = shortcut 37 | 38 | def registerAction(self): 39 | action_desc = idaapi.action_desc_t( 40 | self.id, 41 | self.name, 42 | self, 43 | self.shortcut, 44 | self.tooltip, 45 | 0 46 | ) 47 | if not idaapi.register_action(action_desc): 48 | return False 49 | if not idaapi.attach_action_to_menu(self.menuPath, self.id, 0): 50 | return False 51 | return True 52 | 53 | def unregisterAction(self): 54 | idaapi.detach_action_from_menu(self.menuPath, self.id) 55 | idaapi.unregister_action(self.id) 56 | 57 | def activate(self, ctx): 58 | self.callback() 59 | return 1 60 | 61 | def update(self, ctx): 62 | return idaapi.AST_ENABLE_ALWAYS 63 | 64 | 65 | def make_comment(pos, string): 66 | """ 67 | Creates a comment with contents `string` at address `pos`. 68 | If the address is already commented append the new comment to the existing comment 69 | """ 70 | 71 | current_comment = idc.Comment(pos) 72 | if not current_comment: 73 | idc.MakeComm(pos, string) 74 | elif string not in current_comment: 75 | idc.MakeComm(pos, current_comment + " " + string) 76 | 77 | 78 | def get_operand_value(addr): 79 | """Returns the value of the second operand to the instruction at `addr` masked to be a 32 bit value""" 80 | 81 | return idc.GetOperandValue(addr, 1) & 0xffffffff 82 | 83 | class IOCTLTracker: 84 | """A simple container to keep track of decoded IOCTL codes and codes marked as invalid""" 85 | 86 | def __init__(self): 87 | self.ioctl_locs = set() 88 | self.ioctls = [] 89 | 90 | def add_ioctl(self, addr, value): 91 | self.ioctl_locs.add(addr) 92 | self.ioctls.append((addr, value)) 93 | 94 | def remove_ioctl(self, addr, value): 95 | self.ioctl_locs.remove(addr) 96 | self.ioctls.remove((addr, value)) 97 | 98 | def print_table(self, ioctls): 99 | print "%-10s | %-10s | %-42s | %-10s | %-22s | %s" % ("Address", "IOCTL Code", "Device", "Function", "Method", "Access") 100 | for (addr, ioctl_code) in ioctls: 101 | function = ioctl_decoder.get_function(ioctl_code) 102 | device_name, device_code = ioctl_decoder.get_device(ioctl_code) 103 | method_name, method_code = ioctl_decoder.get_method(ioctl_code) 104 | access_name, access_code = ioctl_decoder.get_access(ioctl_code) 105 | all_vars = (addr, ioctl_code, device_name, device_code, function, method_name, method_code, access_name, access_code) 106 | print "0x%-8X | 0x%-8X | %-31s 0x%-8X | 0x%-8X | %-17s %-4d | %s (%d)" % all_vars 107 | 108 | 109 | def find_all_ioctls(): 110 | """ 111 | From the currently selected address attempts to traverse all blocks inside the current function to find all immediate values which 112 | are used for a comparison/sub immediately before a jz. Returns a list of address, second operand pairs. 113 | """ 114 | 115 | ioctls = [] 116 | # Find the currently selected function and get a list of all of it's basic blocks 117 | addr = idc.ScreenEA() 118 | f = idaapi.get_func(addr) 119 | fc = idaapi.FlowChart(f, flags=idaapi.FC_PREDS) 120 | for block in fc: 121 | # grab the last two instructions in the block 122 | last_inst = idc.PrevHead(block.endEA) 123 | penultimate_inst = idc.PrevHead(last_inst) 124 | # If the penultimate instruction is cmp or sub against an immediate value immediately preceding a 'jz' 125 | # then it's a decent guess that it's an IOCTL code (if this is a dispatch function) 126 | if idc.GetMnem(penultimate_inst) in ['cmp', 'sub'] and idc.GetOpType(penultimate_inst, 1) == 5: 127 | if idc.GetMnem(last_inst) == 'jz': 128 | value = get_operand_value(penultimate_inst) 129 | ioctls.append((penultimate_inst, value)) 130 | ioctl_tracker.add_ioctl(penultimate_inst, value) 131 | return ioctls 132 | 133 | def track_ioctls(ioctls): 134 | global ioctl_tracker 135 | for addr, ioctl_code in ioctls: 136 | ioctl_tracker.add_ioctl(addr, ioctl_code) 137 | define = ioctl_decoder.get_define(ioctl_code) 138 | make_comment(addr, define) 139 | ioctl_tracker.print_table(ioctls) 140 | 141 | def decode_all_ioctls(): 142 | """Attempts to locate all the IOCTLs in a function and decode them all""" 143 | 144 | global ioctl_tracker 145 | ioctls = find_all_ioctls() 146 | track_ioctls(ioctls) 147 | 148 | def decode_angr(): 149 | """Attempts to locate all the IOCTLs in a function and decode them all using symbolic execution""" 150 | 151 | path = idaapi.get_input_file_path() 152 | addr = idc.ScreenEA() 153 | ioctls = angr_analysis.angr_find_ioctls(path, addr) 154 | track_ioctls(ioctls) 155 | 156 | def get_position_and_translate(): 157 | """ 158 | Gets the current selected address and decodes the second parameter to the instruction if it exists/is an immediate 159 | then adds the C define for the code as a comment and prints a summary table of all decoded IOCTL codes. 160 | """ 161 | 162 | pos = idc.ScreenEA() 163 | if idc.GetOpType(pos, 1) != 5: # Check the second operand to the instruction is an immediate 164 | return 165 | 166 | value = get_operand_value(pos) 167 | ioctl_tracker.add_ioctl(pos, value) 168 | define = ioctl_decoder.get_define(value) 169 | make_comment(pos, define) 170 | # Print summary table each time a new IOCTL code is decoded 171 | ioctls = [] 172 | for inst in ioctl_tracker.ioctl_locs: 173 | value = get_operand_value(inst) 174 | ioctls.append((inst, value)) 175 | ioctl_tracker.print_table(ioctls) 176 | 177 | 178 | def find_dispatch_by_struct_index(): 179 | """Attempts to locate the dispatch function based off it being loaded in a structure 180 | at offset 70h, based off of https://github.com/kbandla/ImmunityDebugger/blob/master/1.73/Libs/driverlib.py """ 181 | 182 | out = set() 183 | for function_ea in idautils.Functions(): 184 | flags = idc.get_func_flags(function_ea) 185 | # skip library functions 186 | if flags & idc.FUNC_LIB: 187 | continue 188 | func = idaapi.get_func(function_ea) 189 | addr = func.startEA 190 | while addr < func.endEA: 191 | if idc.GetMnem(addr) == 'mov': 192 | if '+70h' in idc.GetOpnd(addr, 0) and idc.GetOpType(addr, 1) == 5: 193 | out.add(idc.GetOpnd(addr, 1)) 194 | addr = idc.NextHead(addr) 195 | return out 196 | 197 | 198 | def find_dispatch_by_cfg(): 199 | """ 200 | Finds the functions in the binary which are not directly called anywhere and counts how many other functions they call, 201 | returing all functions which call > 0 other functions but are not called themselves. As a dispatch function is not normally directly 202 | called but will normally many other functions this is a fairly good way to guess which function it is. 203 | """ 204 | 205 | out = [] 206 | called = set() 207 | caller = dict() 208 | # Loop through all the functions in the binary 209 | for function_ea in idautils.Functions(): 210 | flags = idc.get_func_flags(function_ea) 211 | # skip library functions 212 | if flags & idc.FUNC_LIB: 213 | continue 214 | f_name = idc.GetFunctionName(function_ea) 215 | # For each of the incoming references 216 | for ref_ea in idautils.CodeRefsTo(function_ea, 0): 217 | called.add(f_name) 218 | # Get the name of the referring function 219 | caller_name = idc.GetFunctionName(ref_ea) 220 | if caller_name not in caller.keys(): 221 | caller[caller_name] = 1 222 | else: 223 | caller[caller_name] += 1 224 | while True: 225 | if len(caller.keys()) == 0: 226 | break 227 | potential = max(caller, key=caller.get) 228 | if potential not in called: 229 | out.append(potential) 230 | del caller[potential] 231 | return out 232 | 233 | 234 | def find_dispatch_function(): 235 | """ 236 | Compares and processes results of `find_dispatch_by_struct_index` and `find_dispatch_by_cfg` 237 | to output potential dispatch function addresses 238 | """ 239 | 240 | index_funcs = find_dispatch_by_struct_index() 241 | cfg_funcs = find_dispatch_by_cfg() 242 | if len(index_funcs) == 0: 243 | cfg_finds_to_print = min(len(cfg_funcs),3) 244 | for i in range(cfg_finds_to_print): 245 | print "Based off of basic CFG analysis the potential dispatch functions are: " + cfg_funcs[i] 246 | elif len(index_funcs) == 1: 247 | func = index_funcs.pop() 248 | if func in cfg_funcs: 249 | print "The likely dispatch function is: " + func 250 | else: 251 | print "Based off of the offset it is loaded at a potential dispatch function is: " + func 252 | print "Based off of basic CFG analysis the likely dispatch function is: " + cfg_funcs[0] 253 | else: 254 | print "Potential dispatch functions: " 255 | for i in index_funcs: 256 | if i in cfg_funcs: 257 | print i 258 | 259 | def get_pool_tags(): 260 | """ Display a list of the pool tags in use by the current driver. 261 | """ 262 | 263 | pooltags = dump_pool_tags.get_all_pooltags() 264 | print("The following pooltags are in use by the binary: ") 265 | print(pooltags) 266 | 267 | class ActionHandler(idaapi.action_handler_t): 268 | """Basic wrapper class to avoid all action handlers needing to implement update identically""" 269 | 270 | def update(self, ctx): 271 | return idaapi.AST_ENABLE_ALWAYS 272 | 273 | 274 | class DecodeHandler(ActionHandler): 275 | """Wrapper for `get_position_and_translate` used for right-click context menu hook""" 276 | 277 | def activate(self, ctx): 278 | get_position_and_translate() 279 | 280 | 281 | class DecodeAllHandler(ActionHandler): 282 | """Wrapper for `decode_all_ioctls` used for right-click context menu hook""" 283 | 284 | def activate(self, ctx): 285 | decode_all_ioctls() 286 | 287 | class DecodeAngrHandler(ActionHandler): 288 | """Wrapper for `decode_angr` used for right-click context menu hook""" 289 | 290 | def activate(self, ctx): 291 | decode_angr() 292 | 293 | 294 | class ShowAllHandler(ActionHandler): 295 | """Used for Show All option in right-click context menu, creates a `DisplayIOCTLSForm` instance, remaining logic is contained within that class.""" 296 | 297 | def activate(self, ctx): 298 | create_tab_table.create_ioctl_tab(ioctl_tracker) 299 | 300 | 301 | class InvalidHandler(ActionHandler): 302 | """ 303 | Only available when right-clicking on an address marked as an IOCTL code location, removes it from the location list 304 | and deletes C define comment marking it (but leaves any other comment content at that location intact). 305 | """ 306 | 307 | def activate(self, ctx): 308 | pos = idc.ScreenEA() 309 | # Get current comment for this instruction and remove the C define from it, if present 310 | comment = idc.Comment(pos) 311 | code = get_operand_value(pos) 312 | define = ioctl_decoder.get_define(code) 313 | comment = comment.replace(define, "") 314 | idc.MakeComm(pos, comment) 315 | # Remove the ioctl from the valid list and add it to the invalid list to avoid 'find_all_ioctls' accidently re-indexing it. 316 | ioctl_tracker.remove_ioctl(pos, code) 317 | 318 | 319 | def register_dynamic_action(form, popup, description, handler): 320 | """Registers a new item in a popup which will trigger a function when selected""" 321 | 322 | # Note the 'None' as action name (1st parameter). 323 | # That's because the action will be deleted immediately 324 | # after the context menu is hidden anyway, so there's 325 | # really no need giving it a valid ID. 326 | action = idaapi.action_desc_t(None, description, handler) 327 | idaapi.attach_dynamic_action_to_popup(form, popup, action, 'Driver Plugin/') 328 | 329 | 330 | class WinDriverHooks(idaapi.UI_Hooks): 331 | """Installs hook function which is triggered when popup forms are created and adds extra menu options if it is the right-click disasm view menu""" 332 | 333 | def finish_populating_tform_popup(self, form, popup): 334 | tft = idaapi.get_tform_type(form) 335 | if tft != idaapi.BWN_DISASM: 336 | return 337 | 338 | pos = idc.ScreenEA() 339 | register_dynamic_action(form, popup, 'Decode All IOCTLs in Function', DecodeAllHandler()) 340 | register_dynamic_action(form, popup, 'Decode IOCTLs using Angr', DecodeAngrHandler()) 341 | # If the second argument to the current selected instruction is an immediately 342 | # then give the option to decode it. 343 | if idc.GetOpType(pos, 1) == 5: 344 | register_dynamic_action(form, popup, 'Decode IOCTL', DecodeHandler()) 345 | if pos in ioctl_tracker.ioctl_locs: 346 | register_dynamic_action(form, popup, 'Invalid IOCTL', InvalidHandler()) 347 | if len(ioctl_tracker.ioctl_locs) > 0: 348 | register_dynamic_action(form, popup, 'Show All IOCTLs', ShowAllHandler()) 349 | 350 | 351 | class WinDriverPlugin(idaapi.plugin_t): 352 | """Main plugin class, registers the various menu items, hot keys and menu hooks as well as initialising the plugin's global state.""" 353 | 354 | flags = idaapi.PLUGIN_UNL 355 | comment = "Decodes Windows Device I/O control codes into DeviceType, FunctionCode, AccessType and MethodType." 356 | help = '' 357 | wanted_name = 'Windows IOCTL code decoder' 358 | # No hot key for the plugin - individuals actions have their own 359 | wanted_hotkey = "" 360 | 361 | def init(self): 362 | if device_type.is_driver(): 363 | print("Driver type: {}".format(device_type.driver_type())) 364 | global ioctl_tracker 365 | ioctl_tracker = IOCTLTracker() 366 | global hooks 367 | hooks = WinDriverHooks() 368 | hooks.hook() 369 | device_name = UiAction( 370 | id="ioctl:find_device_name", 371 | name="Find Device Name", 372 | tooltip="Attempts to find the device name.", 373 | menuPath="Edit/IOCTL/", 374 | callback=device_finder.search, 375 | shortcut="Ctrl+Alt+A" 376 | ) 377 | device_name.registerAction() 378 | find_dispatch = UiAction( 379 | id="ioctl:find_dispatch", 380 | name="Find Dispatch", 381 | tooltip="Attempts to find the dispatch function.", 382 | menuPath="Edit/IOCTL/", 383 | callback=find_dispatch_function, 384 | shortcut="Ctrl+Alt+S" 385 | ) 386 | find_dispatch.registerAction() 387 | decode_ioctl = UiAction( 388 | id="ioctl:decode", 389 | name="Decode IOCTL", 390 | tooltip="Decodes the currently selected constant into its IOCTL details.", 391 | menuPath="", 392 | shortcut="Ctrl+Alt+D", 393 | callback=get_position_and_translate 394 | ) 395 | decode_ioctl.registerAction() 396 | pool_tags = UiAction( 397 | id="ioctl:pools_tags", 398 | name="Dump Pool Tags", 399 | tooltip="Attempts to find all pool tags used by the driver and display them a format which be included in pooltags.txt for debugging.", 400 | menuPath="Edit/IOCTL/", 401 | shortcut="Ctrl+Alt+Z", 402 | callback=get_pool_tags 403 | ) 404 | pool_tags.registerAction() 405 | return idaapi.PLUGIN_OK 406 | 407 | def run(self, _=0): 408 | pass 409 | 410 | def term(self): 411 | pass 412 | 413 | 414 | def PLUGIN_ENTRY(): 415 | return WinDriverPlugin() 416 | -------------------------------------------------------------------------------- /win_driver_plugin/__init__.py: -------------------------------------------------------------------------------- 1 | # Init -------------------------------------------------------------------------------- /win_driver_plugin/angr_analysis.py: -------------------------------------------------------------------------------- 1 | import idaapi 2 | 3 | def angr_find_ioctls(bin, dispatch_addr): 4 | """ Takes a path to a binary and the identified dispatch functions address, attempts to find all valid IOCTL codes 5 | """ 6 | 7 | try: 8 | import angr 9 | except ImportError: 10 | print "Please install angr to continue, see: https://github.com/andreafioraldi/angr-win64-wheels" 11 | return 12 | 13 | p = angr.Project(bin, auto_load_libs=False) 14 | print('loaded binary in angr') 15 | ioctls = find_ioctls(p, dispatch_addr) 16 | return ioctls 17 | 18 | def find_dispatch(p): 19 | """ Attempts to find the drivers dispatch function by analysing it's lifted code 20 | """ 21 | import pyvex 22 | 23 | cfg = p.analyses.CFGAccurate() 24 | 25 | all_vex = [p.factory.block(i.addr).vex for i in cfg.nodes()] 26 | dispatch_addr = None 27 | const_seen = False 28 | for vex in all_vex: 29 | for stmt in vex.statements: 30 | const = stmt.constants 31 | if len(const) > 0: 32 | if const[0].value == 0x70: 33 | const_seen = True 34 | if isinstance(stmt, pyvex.IRStmt.IMark): 35 | const_seen = False 36 | if isinstance(stmt, pyvex.IRStmt.Store) and const_seen: 37 | store_consts = stmt.constants 38 | if len(store_consts) > 0: 39 | dispatch_addr = store_consts[0].value 40 | break 41 | if not dispatch_addr: 42 | print "Could not find IOCTL dispatch function :(" 43 | else: 44 | print "Dispatch function found: " + hex(dispatch_addr) 45 | return dispatch_addr 46 | 47 | def find_ioctls(p, dispatch_addr): 48 | """ Returns a list of potential IOCTL codes by symbolically executing starting at the provided function address 49 | """ 50 | 51 | import pyvex 52 | import simuvex 53 | import claripy 54 | s = p.factory.blank_state(addr=dispatch_addr) 55 | pg = p.factory.path_group(s) 56 | 57 | generic_reg_vals = set() 58 | val_addr = {} 59 | steps = 0 60 | while len(pg.active) > 0 and steps < 25: 61 | for i in pg.active: 62 | if not idaapi.isLoaded(i.addr): 63 | print('Non mapped value for addr: {}'.format(hex(i.addr))) 64 | continue 65 | print('step: {}, addr: {}'.format(steps, hex(i.addr))) 66 | for reg in i.state.arch.default_symbolic_registers: 67 | try: 68 | val = i.state.se.eval(getattr(i.state.regs, reg)) 69 | #Always use first occurrence 70 | generic_reg_vals.add(val) 71 | if val not in val_addr: 72 | val_addr[val] = i.addr 73 | except simuvex.SimUnsatError: 74 | print("failed to get {}".format(reg)) 75 | except claripy.errors.ClaripyZeroDivisionError: 76 | print("failed to get {}".format(reg)) 77 | pg.step() 78 | steps += 1 79 | device_codes = {} 80 | 81 | generic_reg_vals = filter(lambda x: 0xfff0 > ((x >> 16) & 0xffff) > 0x10, generic_reg_vals) 82 | for i in generic_reg_vals: 83 | try: 84 | device_codes[((i >> 16) & 0xffff)] += 1 85 | except KeyError: 86 | device_codes[((i >> 16) & 0xffff)] = 1 87 | 88 | if len(device_codes.keys()) == 0: 89 | return [] 90 | print('potential device codes: {}'.format(device_codes)) 91 | likely_device_code = max(device_codes, key=device_codes.get) 92 | print "Likely device code: 0x%X" % (likely_device_code,) 93 | 94 | out = [] 95 | for i in generic_reg_vals: 96 | addr = val_addr[i] 97 | if (i >> 16) & 0xffff == likely_device_code: 98 | out.append((addr, i)) 99 | return out -------------------------------------------------------------------------------- /win_driver_plugin/create_tab_table.py: -------------------------------------------------------------------------------- 1 | import idc 2 | import idaapi 3 | from idaapi import Choose2 4 | import driverlib 5 | import ctypes 6 | import ioctl_decoder as ioctl_decoder 7 | 8 | # yoinked from https://stackoverflow.com/a/25678113 9 | OpenClipboard = ctypes.windll.user32.OpenClipboard 10 | EmptyClipboard = ctypes.windll.user32.EmptyClipboard 11 | GetClipboardData = ctypes.windll.user32.GetClipboardData 12 | SetClipboardData = ctypes.windll.user32.SetClipboardData 13 | CloseClipboard = ctypes.windll.user32.CloseClipboard 14 | CF_UNICODETEXT = 13 15 | 16 | GlobalAlloc = ctypes.windll.kernel32.GlobalAlloc 17 | GlobalLock = ctypes.windll.kernel32.GlobalLock 18 | GlobalUnlock = ctypes.windll.kernel32.GlobalUnlock 19 | GlobalSize = ctypes.windll.kernel32.GlobalSize 20 | GMEM_MOVEABLE = 0x0002 21 | GMEM_ZEROINIT = 0x0040 22 | 23 | unicode_type = type(u'') 24 | 25 | class stop_unload_handler_t(idaapi.action_handler_t): 26 | def __init__(self): 27 | idaapi.action_handler_t.__init__(self) 28 | 29 | def activate(self, ctx): 30 | if ctypes.windll.shell32.IsUserAnAdmin() == 0: 31 | print "Admin privileges required" 32 | return 33 | name = idc.GetInputFile().split('.')[0] 34 | driver = driverlib.Driver(idc.GetInputFilePath(),name) 35 | driver.stop() 36 | driver.unload() 37 | 38 | def update(self, ctx): 39 | return idaapi.AST_ENABLE_FOR_FORM if idaapi.is_chooser_tform(ctx.form_type) else idaapi.AST_DISABLE_FOR_FORM 40 | 41 | class start_load_handler_t(idaapi.action_handler_t): 42 | def __init__(self): 43 | idaapi.action_handler_t.__init__(self) 44 | 45 | def activate(self, ctx): 46 | if ctypes.windll.shell32.IsUserAnAdmin() == 0: 47 | print "Admin privileges required" 48 | return 49 | name = idc.GetInputFile().split('.')[0] 50 | driver = driverlib.Driver(idc.GetInputFilePath(),name) 51 | driver.load() 52 | driver.start() 53 | 54 | def update(self, ctx): 55 | return idaapi.AST_ENABLE_FOR_FORM if idaapi.is_chooser_tform(ctx.form_type) else idaapi.AST_DISABLE_FOR_FORM 56 | 57 | class send_ioctl_handler_t(idaapi.action_handler_t): 58 | def __init__(self, items): 59 | idaapi.action_handler_t.__init__(self) 60 | self.items = items 61 | 62 | def activate(self, ctx): 63 | ind = ctx.chooser_selection.at(0) 64 | ioctl = self.items[ind - 1] 65 | name = idc.GetInputFile().split('.')[0] 66 | driver = driverlib.Driver(idc.GetInputFilePath(),name) 67 | DisplayIOCTLSForm(ioctl, driver) 68 | 69 | def update(self, ctx): 70 | return idaapi.AST_ENABLE_FOR_FORM if idaapi.is_chooser_tform(ctx.form_type) else idaapi.AST_DISABLE_FOR_FORM 71 | 72 | class copy_defines_handler_t(idaapi.action_handler_t): 73 | def __init__(self, items): 74 | idaapi.action_handler_t.__init__(self) 75 | self.items = items 76 | 77 | def activate(self, ctx): 78 | defines = [] 79 | for item in self.items: 80 | defines.append(item[5]) 81 | print(defines) 82 | paste('\n'.join(defines)) 83 | 84 | def update(self, ctx): 85 | return idaapi.AST_ENABLE_FOR_FORM if idaapi.is_chooser_tform(ctx.form_type) else idaapi.AST_DISABLE_FOR_FORM 86 | 87 | class remove_ioctl(idaapi.action_handler_t): 88 | 89 | def __init__(self, items): 90 | idaapi.action_handler_t.__init__(self) 91 | self.items = items 92 | 93 | def activate(self, ctx): 94 | # get item and remove 95 | ind = ctx.chooser_selection.at(0) 96 | ioctl = self.items[ind - 1] 97 | pos = int(ioctl[0], 16) 98 | define = ioctl[5] 99 | global ioctl_tracker 100 | code = None 101 | for (addr, val) in ioctl_tracker.ioctls: 102 | if addr == pos: 103 | code = val 104 | break 105 | # Get current comment for this instruction and remove the C define from it, if present 106 | comment = idc.Comment(pos) 107 | comment = comment.replace(define, "") 108 | idc.MakeComm(pos, comment) 109 | # Remove the ioctl from the valid list and add it to the invalid list to avoid 'find_all_ioctls' accidentally re-indexing it. 110 | ioctl_tracker.remove_ioctl(pos, code) 111 | 112 | def update(self, ctx): 113 | return idaapi.AST_ENABLE_FOR_FORM if idaapi.is_chooser_tform(ctx.form_type) else idaapi.AST_DISABLE_FOR_FORM 114 | 115 | class MyChoose2(Choose2): 116 | 117 | def __init__(self, title, items, flags=0, width=None, height=None, embedded=False, modal=False): 118 | Choose2.__init__( 119 | self, 120 | title, 121 | [ ["Address", 5], ["Function", 5], ["Device", 15], ["Method", 15], ["Access", 30], ["C define", 100] ], 122 | flags = flags, 123 | width = width, 124 | height = height, 125 | embedded = embedded) 126 | self.n = 0 127 | self.items = items 128 | self.icon = 5 129 | self.selcount = 0 130 | self.modal = modal 131 | self.popup_names = ["Insert", "Delete", "Refresh"] 132 | 133 | def OnClose(self): 134 | pass 135 | 136 | def OnSelectLine(self, n): 137 | 138 | item = self.items[n] 139 | 140 | jump_ea = int(item[0], 16) 141 | # Only jump for valid addresses 142 | if idaapi.IDA_SDK_VERSION < 700: 143 | valid_addr = idc.isEnabled(jump_ea) 144 | else: 145 | valid_addr = idc.is_mapped(jump_ea) 146 | if valid_addr: 147 | idc.Jump(jump_ea) 148 | 149 | def OnGetLine(self, n): 150 | return self.items[n] 151 | 152 | def OnGetSize(self): 153 | n = len(self.items) 154 | return n 155 | 156 | def OnDeleteLine(self, n): 157 | global ioctl_tracker 158 | ioctl_tracker.remove_ioctl(int(self.items[n][0], 16)) 159 | del self.items[n] 160 | return n 161 | 162 | def OnRefresh(self, n): 163 | self.items = get_all_defines() 164 | return n 165 | 166 | def OnGetIcon(self, n): 167 | return -1 168 | 169 | def show(self): 170 | return self.Show(self.modal) >= 0 171 | 172 | def OnGetLineAttr(self, n): 173 | pass 174 | 175 | def get_operand_value(addr): 176 | """Returns the value of the second operand to the instruction at `addr` masked to be a 32 bit value""" 177 | 178 | return idc.GetOperandValue(addr, 1) & 0xffffffff 179 | 180 | 181 | def get_all_defines(): 182 | """Returns the C defines for all ICOTL codes which have been marked during the current session""" 183 | 184 | global ioctl_tracker 185 | defines = [] 186 | for (addr, value) in ioctl_tracker.ioctls: 187 | function = ioctl_decoder.get_function(value) 188 | device_name, device_code = ioctl_decoder.get_device(value) 189 | method_name, method_code = ioctl_decoder.get_method(value) 190 | access_name, access_code = ioctl_decoder.get_access(value) 191 | define = ioctl_decoder.get_define(value) 192 | defines.append(["0x%X" % (addr,), "0x%X" % (function,), "%s (0x%X)" % (device_name, device_code), "%s (0x%X)" % (method_name, method_code), "%s (0x%X)" % (access_name, access_code), define]) 193 | return defines 194 | 195 | def create_ioctl_tab(tracker, modal=False): 196 | global ioctl_tracker 197 | ioctl_tracker = tracker 198 | items = get_all_defines() 199 | idaapi.register_action( 200 | idaapi.action_desc_t( 201 | "choose2:remove_ioctl", 202 | "Invalid IOCTL", 203 | remove_ioctl(items) 204 | ) 205 | ) 206 | action = "send_ioctl" 207 | actname = "choose2:act%s" % action 208 | idaapi.register_action( 209 | idaapi.action_desc_t( 210 | actname, 211 | "Send IOCTL", 212 | send_ioctl_handler_t(items))) 213 | idaapi.register_action( 214 | idaapi.action_desc_t( 215 | "choose2:actcopy_defines", 216 | "Copy All Defines", 217 | copy_defines_handler_t(items))) 218 | 219 | idaapi.register_action( 220 | idaapi.action_desc_t( 221 | "choose2:actstop_unload", 222 | "Stop & Unload Driver", 223 | stop_unload_handler_t())) 224 | idaapi.register_action( 225 | idaapi.action_desc_t( 226 | "choose2:actstart_load", 227 | "Load & Start Driver", 228 | start_load_handler_t())) 229 | global c 230 | c = MyChoose2("IOCTL Code Viewer", items, modal=modal) 231 | c.show() 232 | form = idaapi.get_current_tform() 233 | idaapi.attach_action_to_popup(form, None, "choose2:act%s" % action) 234 | idaapi.attach_action_to_popup(form, None, "choose2:actcopy_defines") 235 | idaapi.attach_action_to_popup(form, None, "choose2:actstop_unload") 236 | idaapi.attach_action_to_popup(form, None, "choose2:actstart_load") 237 | idaapi.attach_action_to_popup(form, None, "choose2:remove_ioctl") 238 | 239 | def paste(s): 240 | if not isinstance(s, unicode_type): 241 | s = s.decode('mbcs') 242 | data = s.encode('utf-16le') 243 | OpenClipboard(None) 244 | EmptyClipboard() 245 | handle = GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT, len(data) + 2) 246 | pcontents = GlobalLock(handle) 247 | ctypes.memmove(pcontents, data, len(data)) 248 | GlobalUnlock(handle) 249 | SetClipboardData(CF_UNICODETEXT, handle) 250 | CloseClipboard() 251 | 252 | class DisplayIOCTLSForm(idaapi.Form): 253 | """Creates a pop up dialog with all indexed IOCTL code definitions inside of a multi line text box""" 254 | 255 | def __init__(self, ioctl, driver): 256 | idaapi.Form.__init__( 257 | self, 258 | """Send IOCTL 259 | {form_change} 260 | <#Input Buffer#~I~nput Buffer:{in_buf}> 261 | <#Input Buffer Size#~I~nput Buffer Size:{in_size}> 262 | <#Output Buffer#~O~utput Buffer:{out_buf}> 263 | <#Output Buffer Size#~O~utput Buffer Size:{out_size}> 264 | <#Send IOCTL#~S~end IOCTL:{sendIOCTL}> 265 | """, { 266 | "form_change": idaapi.Form.FormChangeCb(self.form_change), 267 | "in_buf": idaapi.Form.MultiLineTextControl(), 268 | "out_buf": idaapi.Form.MultiLineTextControl(), 269 | "in_size": idaapi.Form.NumericInput(), 270 | "out_size": idaapi.Form.NumericInput(), 271 | "sendIOCTL": idaapi.Form.ButtonInput(self.send_ioctl) 272 | } 273 | ) 274 | self.driver = driver 275 | global ioctl_tracker 276 | for inst in ioctl_tracker.ioctl_locs: 277 | value = get_operand_value(inst) 278 | function = ioctl_decoder.get_function(value) 279 | if function == int(ioctl[1],16): 280 | self.ioctl = value 281 | self.Compile() 282 | self.in_size.value = 0x20 283 | self.out_size.value = 0x20 284 | self.in_buf.value = "\\x41" * 0x20 285 | self.Execute() 286 | 287 | def form_change(self,fid): 288 | if fid == self.in_size.id: 289 | val = self.GetControlValue(self.in_size) 290 | self.in_size.value = val 291 | elif fid == self.out_size.id: 292 | val = self.GetControlValue(self.out_size) 293 | self.out_size.value = val 294 | elif fid == self.out_buf.id: 295 | val = self.GetControlValue(self.out_buf) 296 | self.out_buf.value = val.value 297 | elif fid == self.in_buf.id: 298 | val = self.GetControlValue(self.in_buf) 299 | self.in_buf.value = val.value 300 | elif fid == -1: 301 | pass 302 | elif fid == -2: 303 | self.Close(-1) 304 | elif fid == self.sendIOCTL.id: 305 | pass 306 | else: 307 | print fid 308 | 309 | def send_ioctl(self,fid): 310 | if not self.driver.handle: 311 | self.driver.open_device() 312 | in_buf = self.in_buf.value.decode('string_escape') 313 | in_size = self.in_size.value 314 | out_size = self.out_size.value 315 | out_buf = self.out_buf.value.decode('string_escape') 316 | self.driver.send_ioctl(self.ioctl, in_buf, in_size, out_buf, out_size) -------------------------------------------------------------------------------- /win_driver_plugin/device_finder.py: -------------------------------------------------------------------------------- 1 | """ Device name finding functions. Using a Unicode string search and searching for stack based and obfuscated strings. 2 | A bulk of this is taken from https://github.com/fireeye/flare-floss""" 3 | import mmap 4 | import re 5 | import collections 6 | import idc 7 | import logging 8 | 9 | ASCII_BYTE = " !\"#\$%&\'\(\)\*\+,-\./0123456789:;<=>\?@ABCDEFGHIJKLMNOPQRSTUVWXYZ\[\]\^_`abcdefghijklmnopqrstuvwxyz\{\|\}\\\~\t" 10 | UNICODE_RE_4 = re.compile(b"((?:[%s]\x00){%d,})" % (ASCII_BYTE, 4)) 11 | REPEATS = ["A", "\x00", "\xfe", "\xff"] 12 | SLICE_SIZE = 4096 13 | 14 | String = collections.namedtuple("String", ["s", "offset"]) 15 | 16 | 17 | def buf_filled_with(buf, character): 18 | """Returns true if the buffer is filled with the recurring character""" 19 | 20 | dupe_chunk = character * SLICE_SIZE 21 | for offset in xrange(0, len(buf), SLICE_SIZE): 22 | new_chunk = buf[offset: offset + SLICE_SIZE] 23 | if dupe_chunk[:len(new_chunk)] != new_chunk: 24 | return False 25 | return True 26 | 27 | 28 | def extract_unicode_strings(buf, n=4): 29 | """Extract naive UTF-16 strings from the given binary data.""" 30 | 31 | if not buf: 32 | return 33 | 34 | if (buf[0] in REPEATS) and buf_filled_with(buf, buf[0]): 35 | return 36 | 37 | if n == 4: 38 | r = UNICODE_RE_4 39 | else: 40 | reg = b"((?:[%s]\x00){%d,})" % (ASCII_BYTE, n) 41 | r = re.compile(reg) 42 | for match in r.finditer(buf): 43 | try: 44 | yield String(match.group().decode("utf-16"), match.start()) 45 | except UnicodeDecodeError: 46 | pass 47 | 48 | 49 | def get_unicode_device_names(): 50 | """Returns all Unicode strings within the binary currently being analysed in IDA which might be device names""" 51 | 52 | path = idc.GetInputFile() 53 | min_length = 4 54 | possible_names = set() 55 | with open(path, "rb") as f: 56 | b = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ) 57 | 58 | for s in extract_unicode_strings(b, n=min_length): 59 | s_str = str(s.s) 60 | if s_str.startswith('\\Device\\') or s_str.startswith('\\DosDevices\\'): 61 | possible_names.add(str(s.s)) 62 | return possible_names 63 | 64 | 65 | def find_unicode_device_name(): 66 | """Attempts to find and output potential device names - returning False if none are found so further analysis can be done""" 67 | 68 | possible_names = get_unicode_device_names() 69 | if len(possible_names) == 1 or len(possible_names) == 2: 70 | if '\\Device\\' in possible_names or '\\DosDevices\\' in possible_names: 71 | if len(possible_names) == 1: 72 | print "The Device prefix was found but no full device paths, the device name is likely obfuscated or created on the stack." 73 | return False 74 | elif '\\Device\\' in possible_names and '\\DosDevices\\' in possible_names: 75 | print "The Device prefixs were found but no full device paths, the device name is likely obfuscated or created on the stack." 76 | return False 77 | else: 78 | print "Potential device name: " 79 | for i in possible_names: 80 | if i != '\\Device\\' and i != '\\DosDevices\\': 81 | print i 82 | return True 83 | else: 84 | print "Potential device names: " 85 | for i in possible_names: 86 | print i 87 | return True 88 | elif len(possible_names) > 2: 89 | print "Possible devices names found:" 90 | for i in possible_names: 91 | print "\t" + i 92 | return True 93 | else: 94 | print "No potential device names found - it may be obfuscated or created on the stack in some way." 95 | return False 96 | 97 | 98 | def search(): 99 | """ 100 | Attempts to find potential device names in the currently opened binary, it starts by searching for Unicode device names, 101 | if this fails then it utilises FLOSS to search for stack based and obfuscated strings. 102 | """ 103 | 104 | if not find_unicode_device_name(): 105 | print "Unicode device name not found, attempting to find obfuscated and stack based strings." 106 | try: 107 | import floss 108 | import floss.identification_manager 109 | import floss.main 110 | import floss.stackstrings 111 | import viv_utils 112 | except ImportError: 113 | print "Please install FLOSS to continue, see: https://github.com/fireeye/flare-floss/" 114 | return 115 | logging.basicConfig() #To avoid logger handler not found errors, from https://github.com/fireeye/flare-floss/blob/66f67a49a38ae028a5e86f1de743c384d5271901/scripts/idaplugin.py#L154 116 | logging.getLogger('vtrace.platforms.win32').setLevel(logging.ERROR) 117 | sample_file_path = idc.GetInputFile() 118 | 119 | try: 120 | vw = viv_utils.getWorkspace(sample_file_path, should_save=False) 121 | except Exception, e: 122 | print("Vivisect failed to load the input file: {0}".format(e.message)) 123 | return 124 | 125 | functions = set(vw.getFunctions()) 126 | plugins = floss.main.get_all_plugins() 127 | device_names = set() 128 | 129 | stack_strings = floss.stackstrings.extract_stackstrings(vw, functions, 4, no_filter=True) 130 | for i in stack_strings: 131 | device_names.add(i) 132 | dec_func_candidates = floss.identification_manager.identify_decoding_functions(vw, plugins, functions) 133 | decoded_strings = floss.main.decode_strings(vw, dec_func_candidates, 4, no_filter=True) 134 | if len(decoded_strings) > 0: 135 | for i in decoded_strings: 136 | device_names.add(str(i.s)) 137 | print "Potential device names from obfuscated or stack strings:" 138 | for i in device_names: 139 | print i 140 | else: 141 | print "No obfuscated or stack strings found :(" 142 | -------------------------------------------------------------------------------- /win_driver_plugin/device_type.py: -------------------------------------------------------------------------------- 1 | import idautils 2 | import idaapi 3 | #Originally written by/modified from NCC Group's DriverBuddy https://github.com/nccgroup/DriverBuddy/tree/master/DriverBuddy 4 | 5 | names = set() 6 | 7 | def cb(ea, name, ord): 8 | names.add(name) 9 | return True 10 | 11 | #NDIS, Legacy printer?, Win USB, User Mode? 12 | def driver_type(): 13 | 14 | implist = idaapi.get_import_module_qty() 15 | 16 | for i in range(0, implist): 17 | name = idaapi.get_import_module_name(i) 18 | idaapi.enum_import_names(i, cb) 19 | for name in names: 20 | if name == "FltRegisterFilter": 21 | return "Mini-Filter" 22 | elif name == "WdfVersionBind": 23 | return "WDF" 24 | elif name == "StreamClassRegisterMinidriver": 25 | return "Stream Minidriver" 26 | elif name == "KsCreateFilterFactory": 27 | return "AVStream" 28 | elif name == "PcRegisterSubdevice": 29 | return "PortCls" 30 | return "WDM" 31 | 32 | def is_driver(): 33 | exports = set(x[3] for x in idautils.Entries()) 34 | return 'DriverEntry' in exports -------------------------------------------------------------------------------- /win_driver_plugin/driverlib.py: -------------------------------------------------------------------------------- 1 | import ctypes 2 | import ctypes.wintypes as wintypes 3 | 4 | LPCSTR = LPCTSTR = ctypes.c_char_p 5 | LPDWORD = ctypes.POINTER(wintypes.DWORD) 6 | LPOVERLAPPED = wintypes.LPVOID 7 | LPSECURITY_ATTRIBUTES = wintypes.LPVOID 8 | 9 | GENERIC_READ = 0x80000000 10 | GENERIC_WRITE = 0x40000000 11 | GENERIC_EXECUTE = 0x20000000 12 | GENERIC_ALL = 0x10000000 13 | 14 | CREATE_NEW = 1 15 | CREATE_ALWAYS = 2 16 | OPEN_EXISTING = 3 17 | OPEN_ALWAYS = 4 18 | TRUNCATE_EXISTING = 5 19 | 20 | FILE_ATTRIBUTE_NORMAL = 0x00000080 21 | 22 | INVALID_HANDLE_VALUE = -1 23 | 24 | NULL = 0 25 | FALSE = wintypes.BOOL(0) 26 | TRUE = wintypes.BOOL(1) 27 | 28 | #Service Manager Access - see https://msdn.microsoft.com/en-us/library/windows/desktop/ms685981(v=vs.85).aspx 29 | SC_MANAGER_ALL_ACCESS = 0xF003F 30 | SC_MANAGER_CREATE_SERVICE = 0x0002 31 | SC_MANAGER_CONNECT = 0x0001 32 | SC_MANAGER_ENUMERATE_SERVICE = 0x0004 33 | SC_MANAGER_LOCK = 0x0008 34 | SC_MANAGER_MODIFY_BOOT_CONFIG = 0x0020 35 | SC_MANAGER_QUERY_LOCK_STATUS = 0x0010 36 | 37 | #Service access constants - see https://msdn.microsoft.com/en-gb/library/windows/desktop/ms685981(v=vs.85).aspx 38 | 39 | SERVICE_ALL_ACCESS = 0xF01FF 40 | SERVICE_CHANGE_CONFIG = 0x0002 41 | SERVICE_ENUMERATE_DEPENDENTS = 0x0008 42 | SERVICE_INTERROGATE = 0x0080 43 | SERVICE_PAUSE_CONTINUE = 0x0040 44 | SERVICE_QUERY_CONFIG = 0x0001 45 | SERVICE_QUERY_STATUS = 0x0004 46 | SERVICE_START = 0x0010 47 | SERVICE_STOP = 0x0020 48 | SERVICE_USER_DEFINED_CONTROL = 0x0100 49 | 50 | #service control constants - see https://msdn.microsoft.com/en-gb/library/windows/desktop/ms682108(v=vs.85).aspx 51 | SERVICE_CONTROL_CONTINUE = 0x00000003 52 | SERVICE_CONTROL_INTERROGATE = 0x00000004 53 | SERVICE_CONTROL_NETBINDADD = 0x00000007 54 | SERVICE_CONTROL_NETBINDDISABLE = 0x0000000A 55 | SERVICE_CONTROL_NETBINDENABLE = 0x00000009 56 | SERVICE_CONTROL_NETBINDREMOVE = 0x00000008 57 | SERVICE_CONTROL_PARAMCHANGE = 0x00000006 58 | SERVICE_CONTROL_PAUSE = 0x00000002 59 | SERVICE_CONTROL_STOP = 0x00000001 60 | 61 | #service type constants - see https://msdn.microsoft.com/en-gb/library/windows/desktop/ms682450(v=vs.85).aspx 62 | SERVICE_ADAPTER = 0x00000004 63 | SERVICE_FILE_SYSTEM_DRIVER = 0x00000002 64 | SERVICE_KERNEL_DRIVER = 0x00000001 65 | SERVICE_RECOGNIZER_DRIVER = 0x00000008 66 | SERVICE_WIN32_OWN_PROCESS = 0x00000010 67 | SERVICE_WIN32_SHARE_PROCESS = 0x00000020 68 | 69 | #service start options constants - see https://msdn.microsoft.com/en-gb/library/windows/desktop/ms682450(v=vs.85).aspx 70 | SERVICE_AUTO_START = 0x00000002 71 | SERVICE_BOOT_START = 0x00000000 72 | SERVICE_DEMAND_START = 0x00000003 73 | SERVICE_DISABLED = 0x00000004 74 | SERVICE_SYSTEM_START = 0x00000001 75 | 76 | #service error control constants - see https://msdn.microsoft.com/en-gb/library/windows/desktop/ms682450(v=vs.85).aspx 77 | SERVICE_ERROR_CRITICAL = 0x00000003 78 | SERVICE_ERROR_IGNORE = 0x00000000 79 | SERVICE_ERROR_NORMAL = 0x00000001 80 | SERVICE_ERROR_SEVERE = 0x00000002 81 | 82 | #file constants - see https://msdn.microsoft.com/en-us/library/windows/desktop/aa363858(v=vs.85).aspx 83 | 84 | FILE_SHARE_DELETE = 0x00000004 85 | FILE_SHARE_READ = 0x00000001 86 | FILE_SHARE_WRITE = 0x00000002 87 | 88 | FILE_ATTRIBUTE_ARCHIVE = 0x20 89 | FILE_ATTRIBUTE_ENCRYPTED = 0x4000 90 | FILE_ATTRIBUTE_HIDDEN = 0x2 91 | FILE_ATTRIBUTE_NORMAL = 0x80 92 | FILE_ATTRIBUTE_OFFLINE = 0x1000 93 | FILE_ATTRIBUTE_READONLY = 0x1 94 | FILE_ATTRIBUTE_SYSTEM = 0x4 95 | FILE_ATTRIBUTE_TEMPORARY = 0x100 96 | 97 | FILE_FLAG_BACKUP_SEMANTICS = 0x02000000 98 | FILE_FLAG_DELETE_ON_CLOSE = 0x04000000 99 | FILE_FLAG_NO_BUFFERING = 0x20000000 100 | FILE_FLAG_OPEN_NO_RECALL = 0x00100000 101 | FILE_FLAG_OPEN_REPARSE_POINT = 0x00200000 102 | FILE_FLAG_OVERLAPPED = 0x40000000 103 | FILE_FLAG_POSIX_SEMANTICS = 0x0100000 104 | FILE_FLAG_RANDOM_ACCESS = 0x10000000 105 | FILE_FLAG_SESSION_AWARE = 0x00800000 106 | FILE_FLAG_SEQUENTIAL_SCAN = 0x08000000 107 | FILE_FLAG_WRITE_THROUGH = 0x80000000 108 | 109 | class Driver: 110 | 111 | def __init__(self, path, name): 112 | self.path = path 113 | self.name = name 114 | self.handle = None 115 | 116 | def open_device(self, access=GENERIC_READ | GENERIC_WRITE, mode=0, creation=OPEN_EXISTING, flags=FILE_ATTRIBUTE_NORMAL): 117 | """See: CreateFile function 118 | http://msdn.microsoft.com/en-us/library/windows/desktop/aa363858(v=vs.85).aspx 119 | """ 120 | CreateFile_Fn = ctypes.windll.kernel32.CreateFileA 121 | CreateFile_Fn.argtypes = [ 122 | wintypes.LPCSTR, # _In_ LPCTSTR lpFileName 123 | wintypes.DWORD, # _In_ DWORD dwDesiredAccess 124 | wintypes.DWORD, # _In_ DWORD dwShareMode 125 | LPSECURITY_ATTRIBUTES, # _In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes 126 | wintypes.DWORD, # _In_ DWORD dwCreationDisposition 127 | wintypes.DWORD, # _In_ DWORD dwFlagsAndAttributes 128 | wintypes.HANDLE] # _In_opt_ HANDLE hTemplateFile 129 | CreateFile_Fn.restype = wintypes.HANDLE 130 | 131 | 132 | self.handle = wintypes.HANDLE(CreateFile_Fn('\\\\.\\' + self.name, 133 | access, 134 | mode, 135 | NULL, 136 | creation, 137 | flags, 138 | NULL)) 139 | 140 | def send_ioctl(self, ioctl, inbuf, inbufsiz, outbuf, outbufsiz): 141 | """See: DeviceIoControl function 142 | http://msdn.microsoft.com/en-us/library/aa363216(v=vs.85).aspx 143 | """ 144 | DeviceIoControl_Fn = ctypes.windll.kernel32.DeviceIoControl 145 | DeviceIoControl_Fn.argtypes = [ 146 | wintypes.HANDLE, # _In_ HANDLE hDevice 147 | wintypes.DWORD, # _In_ DWORD dwIoControlCode 148 | wintypes.LPVOID, # _In_opt_ LPVOID lpInBuffer 149 | wintypes.DWORD, # _In_ DWORD nInBufferSize 150 | wintypes.LPVOID, # _Out_opt_ LPVOID lpOutBuffer 151 | wintypes.DWORD, # _In_ DWORD nOutBufferSize 152 | LPDWORD, # _Out_opt_ LPDWORD lpBytesReturned 153 | LPOVERLAPPED] # _Inout_opt_ LPOVERLAPPED lpOverlapped 154 | DeviceIoControl_Fn.restype = wintypes.BOOL 155 | # allocate a DWORD, and take its reference 156 | dwBytesReturned = wintypes.DWORD(0) 157 | lpBytesReturned = ctypes.byref(dwBytesReturned) 158 | status = DeviceIoControl_Fn(self.handle, 159 | ioctl, 160 | inbuf, 161 | inbufsiz, 162 | outbuf, 163 | outbufsiz, 164 | lpBytesReturned, 165 | None) 166 | 167 | return status, dwBytesReturned 168 | 169 | def load(self, machine_name=None, database_name=None, sc_manager_desired_access=SC_MANAGER_ALL_ACCESS, 170 | service_desired_access=SERVICE_ALL_ACCESS, service_type=SERVICE_KERNEL_DRIVER, start_type=SERVICE_DEMAND_START, 171 | error_control=SERVICE_ERROR_NORMAL, load_order_group=None, tag_id=None, dependencies=None, service_start_name=None, password=None): 172 | sc_manager_handle = open_sc_manager(machine_name, database_name, sc_manager_desired_access) 173 | if sc_manager_handle == NULL: 174 | return False 175 | service_handle = create_service( 176 | sc_manager_handle, 177 | self.name, 178 | self.name, 179 | service_desired_access, 180 | service_type, 181 | start_type, 182 | error_control, 183 | self.path, 184 | load_order_group, 185 | tag_id, 186 | dependencies, 187 | service_start_name, 188 | password 189 | ) 190 | if service_handle == NULL: 191 | return False 192 | close_service_handle(service_handle) 193 | return True 194 | 195 | def unload(self, machine_name=None, database_name=None, sc_manager_desired_access=SC_MANAGER_ALL_ACCESS, service_access=SERVICE_ALL_ACCESS): 196 | service_manager_handle = open_sc_manager(machine_name, database_name, sc_manager_desired_access) 197 | service_handle = open_service(service_manager_handle, self.name, service_access) 198 | if not service_handle: 199 | return False 200 | ret = delete_service(service_handle) 201 | close_service_handle(service_handle) 202 | return ret 203 | 204 | def stop(self, machine_name=None, database_name=None, sc_manager_desired_access=SC_MANAGER_ALL_ACCESS, service_access=SERVICE_ALL_ACCESS ): 205 | service_manager_handle = open_sc_manager(machine_name, database_name, sc_manager_desired_access) 206 | service_handle = open_service(service_manager_handle, self.name, service_access) 207 | if not service_handle: 208 | return False 209 | service_status = SERVICE_STATUS() 210 | ret = control_service(service_handle, SERVICE_CONTROL_STOP, ctypes.byref(service_status)) 211 | close_service_handle(service_handle) 212 | return ret 213 | 214 | def start(self, machine_name=None, database_name=None, sc_manager_desired_access=SC_MANAGER_ALL_ACCESS, service_access=SERVICE_ALL_ACCESS): 215 | service_manager_handle = open_sc_manager(machine_name, database_name, sc_manager_desired_access) 216 | service_handle = open_service(service_manager_handle, self.name, service_access) 217 | if not service_handle: 218 | return False 219 | ret = start_service(service_handle,wintypes.DWORD(0),None) 220 | close_service_handle(service_handle) 221 | return ret 222 | 223 | 224 | class SERVICE_STATUS(ctypes.Structure): 225 | _fields_ = ( 226 | ('dwServiceType',wintypes.DWORD), 227 | ('dwCurrentState',wintypes.DWORD), 228 | ('dwControlsAccepted',wintypes.DWORD), 229 | ('dwWin32ExitCode',wintypes.DWORD), 230 | ('dwServiceSpecificExitCode',wintypes.DWORD), 231 | ('dwCheckPoint',wintypes.DWORD), 232 | ('dwWaitHint',wintypes.DWORD), 233 | ) 234 | ################################ 235 | # Lower level support functions 236 | ################################ 237 | 238 | def create_service(service_manager_handle, service_name, display_name, desired_access, service_type, start_type, 239 | error_control, binary_path, load_order_group, tag_id, dependencies, service_start_name, password): 240 | """See: CreateService function 241 | https://msdn.microsoft.com/en-gb/library/windows/desktop/ms682450(v=vs.85).aspx 242 | """ 243 | 244 | CreateService_Fn = ctypes.windll.Advapi32.CreateServiceA #SC_HANDLE WINAPI CreateService( 245 | CreateService_Fn.argtypes = [ # 246 | wintypes.SC_HANDLE, # _In_ SC_HANDLE hSCManager, 247 | LPCTSTR, # _In_ LPCTSTR lpServiceName, 248 | LPCTSTR, # _In_opt_ LPCTSTR lpDisplayName, 249 | wintypes.DWORD, # _In_ DWORD dwDesiredAccess, 250 | wintypes.DWORD, # _In_ DWORD dwServiceType, 251 | wintypes.DWORD, # _In_ DWORD dwStartType, 252 | wintypes.DWORD, # _In_ DWORD dwErrorControl, 253 | LPCTSTR, # _In_opt_ LPCTSTR lpBinaryPathName, 254 | LPCTSTR, # _In_opt_ LPCTSTR lpLoadOrderGroup, 255 | LPDWORD, # _Out_opt_ LPDWORD lpdwTagId, 256 | LPCTSTR, # _In_opt_ LPCTSTR lpDependencies, 257 | LPCTSTR, # _In_opt_ LPCTSTR lpServiceStartName, 258 | LPCTSTR # _In_opt_ LPCTSTR lpPassword 259 | ] 260 | CreateService_Fn.restype = wintypes.SC_HANDLE 261 | handle = CreateService_Fn( 262 | service_manager_handle, 263 | service_name, 264 | display_name, 265 | desired_access, 266 | service_type, 267 | start_type, 268 | error_control, 269 | binary_path, 270 | load_order_group, 271 | tag_id, 272 | dependencies, 273 | service_start_name, 274 | password 275 | ) 276 | return handle 277 | 278 | def open_service(service_manager_handle, service_name, desired_access): 279 | """ See: OpenService function 280 | https://msdn.microsoft.com/en-us/library/windows/desktop/ms684330(v=vs.85).aspx 281 | """ 282 | OpenService_Fn = ctypes.windll.Advapi32.OpenServiceA #SC_HANDLE WINAPI OpenService( 283 | OpenService_Fn.argtypes = [ # 284 | wintypes.HANDLE, # _In_ SC_HANDLE hSCManager, 285 | LPCTSTR, # _In_ LPCTSTR lpServiceName, 286 | wintypes.DWORD # _In_ DWORD dwDesiredAccess 287 | ] 288 | OpenService_Fn.restype = wintypes.SC_HANDLE 289 | handle = OpenService_Fn( 290 | service_manager_handle, 291 | service_name, 292 | desired_access 293 | ) 294 | return handle 295 | 296 | def control_service(service_handle, control, service_status): 297 | """See: ControlService function 298 | https://msdn.microsoft.com/en-us/library/windows/desktop/ms682108(v=vs.85).aspx 299 | """ 300 | ControlService_Fn = ctypes.windll.Advapi32.ControlService #BOOL WINAPI ControlService( 301 | ControlService_Fn.argtypes = [ # 302 | wintypes.SC_HANDLE, # _In_ SC_HANDLE hService, 303 | wintypes.DWORD, # _In_ DWORD dwControl, 304 | wintypes.LPCVOID # _Out_ LPSERVICE_STATUS lpServiceStatus 305 | ] 306 | ControlService_Fn.restype = wintypes.BOOL 307 | bool = ControlService_Fn( 308 | service_handle, 309 | control, 310 | service_status 311 | ) 312 | return bool 313 | 314 | def close_service_handle(service_handle): 315 | """See: CloseServiceHandle function 316 | https://msdn.microsoft.com/en-us/library/windows/desktop/ms682028(v=vs.85).aspx 317 | """ 318 | CloseServiceHandle_Fn = ctypes.windll.Advapi32.CloseServiceHandle #BOOL WINAPI CloseServiceHandle( 319 | CloseServiceHandle_Fn.argtypes = [ 320 | wintypes.SC_HANDLE # _In_ SC_HANDLE hSCObject 321 | ] 322 | CloseServiceHandle_Fn.restype = wintypes.BOOL 323 | bool = CloseServiceHandle_Fn( 324 | service_handle 325 | ) 326 | return bool 327 | 328 | def delete_service(service_handle): 329 | """See: DeleteService function 330 | https://msdn.microsoft.com/en-us/library/windows/desktop/ms682562(v=vs.85).aspx 331 | """ 332 | DeleteService_Fn = ctypes.windll.Advapi32.DeleteService #BOOL WINAPI DeleteService( 333 | DeleteService_Fn.argtypes = [ # 334 | wintypes.SC_HANDLE # _In_ SC_HANDLE hService 335 | ] 336 | DeleteService_Fn.restype = wintypes.BOOL 337 | bool = DeleteService_Fn( 338 | service_handle 339 | ) 340 | return bool 341 | 342 | def open_sc_manager(machine_name, database_name, desired_access): 343 | """See: OpenSCManager function 344 | https://msdn.microsoft.com/en-us/library/windows/desktop/ms684323(v=vs.85).aspx 345 | """ 346 | OpenSCManager_Fn = ctypes.windll.Advapi32.OpenSCManagerA #SC_HANDLE WINAPI OpenSCManager( 347 | OpenSCManager_Fn.argtypes = [ # 348 | LPCTSTR, # _In_opt_ LPCTSTR lpMachineName, 349 | LPCTSTR, # _In_opt_ LPCTSTR lpDatabaseName, 350 | wintypes.DWORD # _In_ DWORD dwDesiredAccess 351 | ] 352 | OpenSCManager_Fn.restype = wintypes.SC_HANDLE 353 | handle = OpenSCManager_Fn( 354 | machine_name, 355 | database_name, 356 | desired_access 357 | ) 358 | return handle 359 | 360 | def start_service(service_handle, service_arg_count, service_arg_vectors): 361 | """See: StartService function 362 | https://msdn.microsoft.com/en-us/library/windows/desktop/ms686321(v=vs.85).aspx 363 | """ 364 | 365 | StartService_Fn = ctypes.windll.Advapi32.StartServiceA #BOOL WINAPI StartService( 366 | StartService_Fn.argtypes = [ # 367 | wintypes.SC_HANDLE, # _In_ SC_HANDLE hService, 368 | wintypes.DWORD, # _In_ DWORD dwNumServiceArgs, 369 | LPCTSTR # _In_opt_ LPCTSTR *lpServiceArgVectors 370 | ] 371 | StartService_Fn.restype = wintypes.BOOL 372 | bool = StartService_Fn( 373 | service_handle, 374 | service_arg_count, 375 | service_arg_vectors 376 | ) 377 | return bool 378 | 379 | def remove_driver(SchSCManager, driver_name): 380 | 381 | schService = open_service(SchSCManager, driver_name, SERVICE_ALL_ACCESS ) 382 | 383 | if schService is None: 384 | return False 385 | 386 | ret = delete_service(schService) 387 | close_service_handle(schService) 388 | 389 | return ret -------------------------------------------------------------------------------- /win_driver_plugin/dump_pool_tags.py: -------------------------------------------------------------------------------- 1 | import idc 2 | import idaapi 3 | import idautils 4 | 5 | def find_pool_tags(): 6 | """ Dirty hack around IDA's type information, find references to tag using functions then the comment marking the tag 7 | then add the function caller/tag to output dictionary. 8 | """ 9 | 10 | funcs = [ 11 | 'ExAllocatePoolWithTag', 12 | 'ExFreePoolWithTag', 13 | 'ExAllocatePoolWithTagPriority' 14 | ] 15 | 16 | tags = {} 17 | 18 | def imp_cb(ea, name, ord): 19 | if name in funcs: 20 | for xref in idautils.XrefsTo(ea): 21 | call_addr = xref.frm 22 | caller_name = idc.GetFunctionName(call_addr) 23 | prev = idc.PrevHead(call_addr) 24 | for _ in range(10): 25 | if idc.Comment(prev) == 'Tag' and idc.GetOpType(prev, 1) == 5: 26 | tag_raw = idc.GetOperandValue(prev, 1) 27 | tag = '' 28 | for i in range(3, -1, -1): 29 | tag += chr((tag_raw >> 8 * i) & 0xFF) 30 | if tag in tags.keys(): 31 | tags[tag].add(caller_name) 32 | else: 33 | tags[tag] = set([caller_name]) 34 | break 35 | prev = idc.PrevHead(prev) 36 | return True 37 | 38 | nimps = idaapi.get_import_module_qty() 39 | 40 | for i in xrange(0, nimps): 41 | name = idaapi.get_import_module_name(i) 42 | if not name: 43 | continue 44 | 45 | idaapi.enum_import_names(i, imp_cb) 46 | return tags 47 | 48 | def get_all_pooltags(): 49 | """ Returns a string with a 'pooltags.txt' formatted string of 'pool tag' - 'driver' - 'functions which use it'. 50 | """ 51 | 52 | tags = find_pool_tags() 53 | out = '' 54 | file_name = idaapi.get_root_filename() 55 | for tag in tags.keys(): 56 | desc = 'Called by: ' 57 | desc += ', '.join(tags[tag]) 58 | out += '{} - {} - {}\n'.format(tag, file_name, desc) 59 | return out 60 | -------------------------------------------------------------------------------- /win_driver_plugin/ioctl_decoder.py: -------------------------------------------------------------------------------- 1 | """ 2 | Provides functions for decoding 32 bit IOCTL codes into the constants used for them and C defines which use the CTL_CODE macro 3 | A bulk of the code here is taken from Satoshi Tanda's https://github.com/tandasat/WinIoCtlDecoder/blob/master/plugins/WinIoCtlDecoder.py 4 | """ 5 | import idc 6 | 7 | 8 | def get_device(ioctl_code): 9 | """Returns the correct device name for a 32 bit IOCTL code""" 10 | 11 | device_name_unknown = '' 12 | device_names = [ 13 | device_name_unknown, # 0x00000000 14 | 'FILE_DEVICE_BEEP', # 0x00000001 15 | 'FILE_DEVICE_CD_ROM', # 0x00000002 16 | 'FILE_DEVICE_CD_ROM_FILE_SYSTEM', # 0x00000003 17 | 'FILE_DEVICE_CONTROLLER', # 0x00000004 18 | 'FILE_DEVICE_DATALINK', # 0x00000005 19 | 'FILE_DEVICE_DFS', # 0x00000006 20 | 'FILE_DEVICE_DISK', # 0x00000007 21 | 'FILE_DEVICE_DISK_FILE_SYSTEM', # 0x00000008 22 | 'FILE_DEVICE_FILE_SYSTEM', # 0x00000009 23 | 'FILE_DEVICE_INPORT_PORT', # 0x0000000a 24 | 'FILE_DEVICE_KEYBOARD', # 0x0000000b 25 | 'FILE_DEVICE_MAILSLOT', # 0x0000000c 26 | 'FILE_DEVICE_MIDI_IN', # 0x0000000d 27 | 'FILE_DEVICE_MIDI_OUT', # 0x0000000e 28 | 'FILE_DEVICE_MOUSE', # 0x0000000f 29 | 'FILE_DEVICE_MULTI_UNC_PROVIDER', # 0x00000010 30 | 'FILE_DEVICE_NAMED_PIPE', # 0x00000011 31 | 'FILE_DEVICE_NETWORK', # 0x00000012 32 | 'FILE_DEVICE_NETWORK_BROWSER', # 0x00000013 33 | 'FILE_DEVICE_NETWORK_FILE_SYSTEM', # 0x00000014 34 | 'FILE_DEVICE_NULL', # 0x00000015 35 | 'FILE_DEVICE_PARALLEL_PORT', # 0x00000016 36 | 'FILE_DEVICE_PHYSICAL_NETCARD', # 0x00000017 37 | 'FILE_DEVICE_PRINTER', # 0x00000018 38 | 'FILE_DEVICE_SCANNER', # 0x00000019 39 | 'FILE_DEVICE_SERIAL_MOUSE_PORT', # 0x0000001a 40 | 'FILE_DEVICE_SERIAL_PORT', # 0x0000001b 41 | 'FILE_DEVICE_SCREEN', # 0x0000001c 42 | 'FILE_DEVICE_SOUND', # 0x0000001d 43 | 'FILE_DEVICE_STREAMS', # 0x0000001e 44 | 'FILE_DEVICE_TAPE', # 0x0000001f 45 | 'FILE_DEVICE_TAPE_FILE_SYSTEM', # 0x00000020 46 | 'FILE_DEVICE_TRANSPORT', # 0x00000021 47 | 'FILE_DEVICE_UNKNOWN', # 0x00000022 48 | 'FILE_DEVICE_VIDEO', # 0x00000023 49 | 'FILE_DEVICE_VIRTUAL_DISK', # 0x00000024 50 | 'FILE_DEVICE_WAVE_IN', # 0x00000025 51 | 'FILE_DEVICE_WAVE_OUT', # 0x00000026 52 | 'FILE_DEVICE_8042_PORT', # 0x00000027 53 | 'FILE_DEVICE_NETWORK_REDIRECTOR', # 0x00000028 54 | 'FILE_DEVICE_BATTERY', # 0x00000029 55 | 'FILE_DEVICE_BUS_EXTENDER', # 0x0000002a 56 | 'FILE_DEVICE_MODEM', # 0x0000002b 57 | 'FILE_DEVICE_VDM', # 0x0000002c 58 | 'FILE_DEVICE_MASS_STORAGE', # 0x0000002d 59 | 'FILE_DEVICE_SMB', # 0x0000002e 60 | 'FILE_DEVICE_KS', # 0x0000002f 61 | 'FILE_DEVICE_CHANGER', # 0x00000030 62 | 'FILE_DEVICE_SMARTCARD', # 0x00000031 63 | 'FILE_DEVICE_ACPI', # 0x00000032 64 | 'FILE_DEVICE_DVD', # 0x00000033 65 | 'FILE_DEVICE_FULLSCREEN_VIDEO', # 0x00000034 66 | 'FILE_DEVICE_DFS_FILE_SYSTEM', # 0x00000035 67 | 'FILE_DEVICE_DFS_VOLUME', # 0x00000036 68 | 'FILE_DEVICE_SERENUM', # 0x00000037 69 | 'FILE_DEVICE_TERMSRV', # 0x00000038 70 | 'FILE_DEVICE_KSEC', # 0x00000039 71 | 'FILE_DEVICE_FIPS', # 0x0000003A 72 | 'FILE_DEVICE_INFINIBAND', # 0x0000003B 73 | device_name_unknown, # 0x0000003C 74 | device_name_unknown, # 0x0000003D 75 | 'FILE_DEVICE_VMBUS', # 0x0000003E 76 | 'FILE_DEVICE_CRYPT_PROVIDER', # 0x0000003F 77 | 'FILE_DEVICE_WPD', # 0x00000040 78 | 'FILE_DEVICE_BLUETOOTH', # 0x00000041 79 | 'FILE_DEVICE_MT_COMPOSITE', # 0x00000042 80 | 'FILE_DEVICE_MT_TRANSPORT', # 0x00000043 81 | 'FILE_DEVICE_BIOMETRIC', # 0x00000044 82 | 'FILE_DEVICE_PMI', # 0x00000045 83 | ] 84 | device_names2 = [ 85 | {'name': 'MOUNTMGRCONTROLTYPE', 'code': 0x0000006d}, 86 | ] 87 | 88 | device = (ioctl_code >> 16) & 0xffff 89 | if device >= len(device_names): 90 | device_name = device_name_unknown 91 | for dev in device_names2: 92 | if device == dev['code']: 93 | device_name = dev['name'] 94 | break 95 | else: 96 | device_name = device_names[device] 97 | return device_name, device 98 | 99 | 100 | def get_method(ioctl_code): 101 | """Returns the correct method type name for a 32 bit IOCTL code""" 102 | 103 | method_names = [ 104 | 'METHOD_BUFFERED', 105 | 'METHOD_IN_DIRECT', 106 | 'METHOD_OUT_DIRECT', 107 | 'METHOD_NEITHER', 108 | ] 109 | method = ioctl_code & 3 110 | return method_names[method], method 111 | 112 | 113 | def get_access(ioctl_code): 114 | """Returns the correct access type name for a 32 bit IOCTL code""" 115 | 116 | access_names = [ 117 | 'FILE_ANY_ACCESS', 118 | 'FILE_READ_ACCESS', 119 | 'FILE_WRITE_ACCESS', 120 | 'FILE_READ_ACCESS | FILE_WRITE_ACCESS', 121 | ] 122 | access = (ioctl_code >> 14) & 3 123 | return access_names[access], access 124 | 125 | 126 | def get_function(ioctl_code): 127 | """Calculates the function code from a 32 bit IOCTL code""" 128 | 129 | return (ioctl_code >> 2) & 0xfff 130 | 131 | 132 | def get_define(ioctl_code): 133 | """Decodes an ioctl code and returns a C define for it using the CTL_CODE macro""" 134 | 135 | function = get_function(ioctl_code) 136 | device_name, device_code = get_device(ioctl_code) 137 | method_name, method_code = get_method(ioctl_code) 138 | access_name, access_code = get_access(ioctl_code) 139 | 140 | name = "%s_0x%08X" % (idc.GetInputFile().split('.')[0], ioctl_code) 141 | return "#define %s CTL_CODE(0x%X, 0x%X, %s, %s)" % (name, device_code, function, method_name, access_name) 142 | --------------------------------------------------------------------------------