├── .gitignore ├── LICENSE ├── README.md ├── images ├── alt_comment.png ├── alt_comment_opcodes.png ├── alt_instr.png ├── alt_window.png ├── altinstructions.png ├── altreplacements.png ├── prompt.png ├── prompt_rdtscp.png ├── rdtsc.png └── rdtscp.png ├── linux_alternatives.py └── linux_alternatives_lib ├── __init__ ├── lib.py └── utils.py /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2021, Open Source Security, Inc. 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | About 2 | ===== 3 | 4 | This is an IDA Pro (Interactive Disassembler) plugin allowing to automatically analyze and annotate Linux kernel alternatives (content of `.altinstructions` and `.altinstr_replacement` sections). 5 | 6 | Requirements 7 | ============ 8 | 9 | This is an IDAPython-based plugin supporting IDA Pro 7.x with Python 3. 10 | 11 | Currently only `x86/x86_64` architecture is supported. 12 | 13 | Installation 14 | ============ 15 | 16 | ## System-wide installation: 17 | 18 | Copy `linux_alternatives.py` file and `linux_alternatives_lib` directory into your `IDADIR/plugins` directory: 19 | 20 | | OS | Typical global plugins directory path | 21 | | ------- | ------------------------------------------- | 22 | | Windows | `%ProgramFiles%\IDA Pro 7.x\plugins` | 23 | | macOS | `/Applications/IDA Pro 7.x/idabin/plugins` | 24 | | Linux | `/opt/idapro-7.x/plugins` | 25 | 26 | Where `x` should be the actual version number installed. 27 | 28 | ## User installation: 29 | 30 | Copy `linux_alternatives.py` file and `linux_alternatives_lib` directory into your local user IDA plugins directory: 31 | 32 | | OS | Typical user plugins directory path | 33 | | ----------- | ------------------------------------ | 34 | | Windows | `%AppData%\Hex-Rays\IDA Pro\plugins` | 35 | | Linux/macOS | `~/.idapro/plugins` | 36 | 37 | Usage 38 | ===== 39 | 40 | To use the plugin click `Linux Alternatives` entry from the `Edit / Plugins` menu bar. Alternatively, invoke the plugin with a shortcut `Alt + F9`. 41 | 42 | The plugin also registers three additional options (available from `Edit / Linux Alternatives` menu bar): 43 | 44 | * `Import cpufeatures.h file` - This option opens up a file chooser allowing to specify a `cpufeatures.h` file corresponding to the kernel being analyzed. 45 | 46 | * `Remove alternative comments` - This option closes the `Alternatives` window and removes all annotations from the database. **Note**: This option appears only after the annotations are applied. 47 | 48 | * `Patch selected alternatives` - This option allows to specify a comma-separated list of CPU feature flags and patch into binary corresponding alternatives. **Note: after providing the list of feature flags, the corresponding alternatives are automatically patched in. No need to re-run the plugin.** 49 | 50 | What does it do? 51 | ================ 52 | 53 | The plugin performs the following steps upon invocation: 54 | 55 | ### 1. **Obtain the memory layout of `struct alt_instr`:** 56 | * If DWARF-based definition of the structure is available, it is used directly. 57 | * Otherwise, the plugin heuristically determines: 58 | - type (and size) of the first two structure members (address or relative offset of instruction and replacement). 59 | - size of the structure 60 | - offset of the length field members

![struct alt_instr](images/alt_instr.png) 61 | 62 | ### 2. **Obtain available CPUFEATURE and X86_BUGS flag names** 63 | * Analyze string references in: `x86_cap_flags` and `x86_bug_flags` array symbols. 64 | * If `cpufeatures.h` file has been loaded, the plugin parses it and uses CPUFEATURE and X86_BUGS flags from it. 65 | 66 | ### 3. **Analyze and annotate content of `.altinstructions` and `.altinstr_replacement` sections** 67 | 68 | `.altinstructions` | `.altinstr_replacement` 69 | :----------------------------------------------:|:----------------------------------------------------: 70 | ![.altinstructions](images/altinstructions.png) | ![.altinstr_replacement](images/altreplacements.png) 71 | 72 | ### 4. **Apply alternatives comments in the disassembly for all alternative entries found** 73 | 74 | without opcodes | with opcodes 75 | :---------------------------------------------:|:-------------------------------------------------------------------: 76 | ![alternative comment](images/alt_comment.png) | ![alternative comment with opcodes](images/alt_comment_opcodes.png) 77 | 78 | 79 | ### 5. **Open a new window with a tabular listing of the alternatives** 80 | * columns are sortable and addresses clickable

![alternatives window](images/alt_window.png) 81 | 82 | Patching alternatives 83 | ===================== 84 | 85 | Main purpose of this feature is to simulate presence of specified CPU feature flags and update binary with their corresponding alternatives for static analysis purposes. This feature might be helpful for inspecting alternative entries for correctness and security, without the need to run the Linux kernel binary. 86 | 87 | Upon clicking the `Patch selected alternatives` option in `Edit / Linux Alternatives` menu bar, the following prompt is displayed:

![patching prompt](images/prompt.png)

88 | User can specify comma-separated list of feature flags either by their name (case insensitive) or by their integer value as calculated in typical `cpufeatures.h` file:

![patching prompt](images/prompt_rdtscp.png)
89 | 90 | Clicking `OK` will automatically patch and re-analyze the entire database with alternatives selected with the feature flags: 91 | 92 | Before | After 93 | :--------------------------:|:---------------------------: 94 | ![before](images/rdtsc.png) | ![after](images/rdtscp.png) 95 | -------------------------------------------------------------------------------- /images/alt_comment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensrcsec/ida-linux-alternatives/7a5aaea3cc0526d2e244dbb0860e82b117aa4a2e/images/alt_comment.png -------------------------------------------------------------------------------- /images/alt_comment_opcodes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensrcsec/ida-linux-alternatives/7a5aaea3cc0526d2e244dbb0860e82b117aa4a2e/images/alt_comment_opcodes.png -------------------------------------------------------------------------------- /images/alt_instr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensrcsec/ida-linux-alternatives/7a5aaea3cc0526d2e244dbb0860e82b117aa4a2e/images/alt_instr.png -------------------------------------------------------------------------------- /images/alt_window.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensrcsec/ida-linux-alternatives/7a5aaea3cc0526d2e244dbb0860e82b117aa4a2e/images/alt_window.png -------------------------------------------------------------------------------- /images/altinstructions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensrcsec/ida-linux-alternatives/7a5aaea3cc0526d2e244dbb0860e82b117aa4a2e/images/altinstructions.png -------------------------------------------------------------------------------- /images/altreplacements.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensrcsec/ida-linux-alternatives/7a5aaea3cc0526d2e244dbb0860e82b117aa4a2e/images/altreplacements.png -------------------------------------------------------------------------------- /images/prompt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensrcsec/ida-linux-alternatives/7a5aaea3cc0526d2e244dbb0860e82b117aa4a2e/images/prompt.png -------------------------------------------------------------------------------- /images/prompt_rdtscp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensrcsec/ida-linux-alternatives/7a5aaea3cc0526d2e244dbb0860e82b117aa4a2e/images/prompt_rdtscp.png -------------------------------------------------------------------------------- /images/rdtsc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensrcsec/ida-linux-alternatives/7a5aaea3cc0526d2e244dbb0860e82b117aa4a2e/images/rdtsc.png -------------------------------------------------------------------------------- /images/rdtscp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensrcsec/ida-linux-alternatives/7a5aaea3cc0526d2e244dbb0860e82b117aa4a2e/images/rdtscp.png -------------------------------------------------------------------------------- /linux_alternatives.py: -------------------------------------------------------------------------------- 1 | # BSD 3-Clause License 2 | # 3 | # Copyright (c) 2021, Open Source Security, Inc. 4 | # All rights reserved. 5 | # 6 | # Redistribution and use in source and binary forms, with or without 7 | # modification, are permitted provided that the following conditions are met: 8 | # 9 | # 1. Redistributions of source code must retain the above copyright notice, this 10 | # list of conditions and the following disclaimer. 11 | # 12 | # 2. Redistributions in binary form must reproduce the above copyright notice, 13 | # this list of conditions and the following disclaimer in the documentation 14 | # and/or other materials provided with the distribution. 15 | # 16 | # 3. Neither the name of the copyright holder nor the names of its 17 | # contributors may be used to endorse or promote products derived from 18 | # this software without specific prior written permission. 19 | # 20 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | # 31 | # Author: Pawel Wieczorkiewicz 32 | # 33 | from ida_lines import delete_extra_cmts, E_PREV 34 | from PyQt5 import QtWidgets 35 | import ida_kernwin 36 | import ida_netnode 37 | 38 | from linux_alternatives_lib.lib import * 39 | 40 | PLUGIN_NAME = "Linux Alternatives" 41 | WINDOW_NAME = "Alternatives" 42 | 43 | 44 | class Alternatives_viewer_t(PluginForm): 45 | def __init__(self, alternatives, column_width=150): 46 | super(Alternatives_viewer_t, self).__init__() 47 | self.alternatives = alternatives 48 | self.column_width = column_width 49 | 50 | def OnCreate(self, form): 51 | self.parent = self.FormToPyQtWidget(form) 52 | self.PopulateForm() 53 | 54 | def PopulateForm(self): 55 | # Create layout 56 | layout = QtWidgets.QVBoxLayout() 57 | 58 | # Table View 59 | self.table = QtWidgets.QTableWidget() 60 | self.table.setSortingEnabled(True) 61 | self.table.setRowCount(len(self.alternatives["rows"])) 62 | self.table.setColumnCount(len(self.alternatives["header"])) 63 | for i in range(len(self.alternatives["header"])): 64 | self.table.setColumnWidth(i, self.column_width) 65 | 66 | self.table.setHorizontalHeaderLabels(self.alternatives["header"]) 67 | for i, row in enumerate(self.alternatives["rows"]): 68 | for j, elem in enumerate(row): 69 | fmt = "%s" if isinstance(elem, str) else "0x%04x" 70 | self.table.setItem(i, j, QtWidgets.QTableWidgetItem(fmt % elem)) 71 | 72 | self.table.cellDoubleClicked.connect(self.jumpto) 73 | 74 | layout.addWidget(self.table) 75 | self.parent.setLayout(layout) 76 | 77 | def jumpto(self, row, column): 78 | try: 79 | jumpto(int(self.table.item(row, max(column, 1)).text(), 16)) 80 | except: 81 | pass 82 | 83 | def OnClose(self, form): 84 | self.alternatives = None 85 | 86 | 87 | class File_loader_t(ida_kernwin.action_handler_t): 88 | def __init__(self, action_name, node): 89 | ida_kernwin.action_handler_t.__init__(self) 90 | self.action_name = action_name 91 | self.node = node 92 | 93 | def activate(self, ctx): 94 | filepath = ida_kernwin.ask_file(False, None, self.action_name) 95 | self.node[0].supset(self.node[1], filepath) 96 | print("Loaded %s file" % filepath) 97 | 98 | def update(self, ctx): 99 | return ida_kernwin.AST_ENABLE_ALWAYS 100 | 101 | 102 | class Remover_t(ida_kernwin.action_handler_t): 103 | def __init__(self, reset, action_name): 104 | ida_kernwin.action_handler_t.__init__(self) 105 | self.reset = reset 106 | self.action_name = action_name 107 | 108 | def activate(self, ctx): 109 | self.reset() 110 | 111 | def update(self, ctx): 112 | return ida_kernwin.AST_ENABLE_ALWAYS 113 | 114 | 115 | class Patch_input_t(ida_kernwin.action_handler_t): 116 | def __init__(self, action_name, cpufeat_node): 117 | ida_kernwin.action_handler_t.__init__(self) 118 | self.action_name = action_name 119 | self.cpufeat_node = cpufeat_node 120 | self.current_flags = None 121 | 122 | def activate(self, ctx): 123 | prompt = "Enter comma-separated list of CPU features" 124 | defval = self.current_flags if self.current_flags else "" 125 | 126 | patch_features_str = ida_kernwin.ask_str(defval, 0, prompt) 127 | # Ignore when nothing has been specified 128 | if not patch_features_str: 129 | return 130 | 131 | self.current_flags = patch_features_str.upper() 132 | 133 | patcher = Alternative_patcher_t(self.cpufeat_node) 134 | patcher.patch(self.current_flags) 135 | 136 | def update(self, ctx): 137 | return ida_kernwin.AST_ENABLE_ALWAYS 138 | 139 | 140 | class Linux_alternatives_t(plugin_t): 141 | flags = 0 142 | comment = "Analyze and annotate linux kernel alternatives" 143 | help = "" 144 | wanted_name = PLUGIN_NAME 145 | wanted_hotkey = "Alt-F9" 146 | 147 | TOPMENU = "Edit" 148 | 149 | cpufeat_name = "cpufeatures.h" 150 | cpufeat_node = [None, 0] 151 | 152 | cpu_flags = None 153 | alt_instr_struct = None 154 | 155 | cpufeatures_action_name = None 156 | 157 | remove_action_name = "Remove alternative comments" 158 | 159 | patch_input_name = "patch_input" 160 | patch_input_action_name = "Patch selected alternatives" 161 | patch_input_node = [None, 0] 162 | 163 | def __init__(self): 164 | self.view = None 165 | self.alternatives = {} 166 | 167 | self.cpufeatures_action_name = "Import %s file" % self.cpufeat_name 168 | 169 | def init(self): 170 | ida_kernwin.create_menu(PLUGIN_NAME, PLUGIN_NAME, "%s/" % self.TOPMENU) 171 | 172 | self.cpufeat_node[0] = ida_netnode.netnode("$ %s" % self.cpufeat_name, self.cpufeat_node[1], True) 173 | self._register_cpufeatures_action() 174 | 175 | self.patch_input_node[0] = ida_netnode.netnode("$ %s" % self.patch_input_name, self.patch_input_node[1], True) 176 | self._register_patch_input_action() 177 | 178 | return PLUGIN_KEEP 179 | 180 | def run(self, arg): 181 | self.reset() 182 | print("Running %s plugin..." % PLUGIN_NAME) 183 | 184 | alt_gen = Alternative_generator_t(self.cpufeat_node) 185 | struct_metadata = alt_gen.alt_instr_struct.get_struct_metadata() 186 | 187 | alternatives = alt_gen.gen_alternatives(alt_gen.add_alternatives_cmts) 188 | self._register_remove_action() 189 | 190 | self.alternatives["header"] = ["index"] + [name for name, _, _ in struct_metadata] 191 | for _, rows in alternatives.items(): 192 | for row in rows: 193 | self.alternatives["rows"].append(row) 194 | self.display_alternatives() 195 | 196 | def term(self): 197 | self._reset_alternatives() 198 | 199 | if self.view: 200 | self.view.Close(ida_kernwin.WCLS_DONT_SAVE_SIZE) 201 | 202 | ida_kernwin.unregister_action(self.remove_action_name) 203 | ida_kernwin.unregister_action(self.cpufeatures_action_name) 204 | 205 | def reset(self): 206 | self.term() 207 | self.init() 208 | 209 | def _reset_alternatives(self): 210 | # Remove applied alternatives comments 211 | for row in self.alternatives.get("rows", []): 212 | ea, size = row[1], row[4] 213 | 214 | delete_extra_cmts(ea, E_PREV) 215 | delete_extra_cmts(ea + size, E_PREV) 216 | 217 | self.alternatives["header"] = [] 218 | self.alternatives["rows"] = [] 219 | 220 | def _register_cpufeatures_action(self): 221 | action_name = self.cpufeatures_action_name 222 | desc = ida_kernwin.action_desc_t(action_name, action_name, File_loader_t(action_name, self.cpufeat_node)) 223 | ida_kernwin.register_action(desc) 224 | ida_kernwin.attach_action_to_menu("%s/%s/" % (self.TOPMENU, PLUGIN_NAME), action_name, ida_kernwin.SETMENU_INS) 225 | 226 | def _register_patch_input_action(self): 227 | action_name = self.patch_input_action_name 228 | desc = ida_kernwin.action_desc_t(action_name, action_name, Patch_input_t(action_name, self.cpufeat_node)) 229 | ida_kernwin.register_action(desc) 230 | ida_kernwin.attach_action_to_menu("%s/%s/" % (self.TOPMENU, PLUGIN_NAME), action_name, ida_kernwin.SETMENU_INS) 231 | 232 | def _register_remove_action(self): 233 | action_name = self.remove_action_name 234 | desc = ida_kernwin.action_desc_t(action_name, action_name, Remover_t(self.reset, action_name)) 235 | ida_kernwin.register_action(desc) 236 | ida_kernwin.attach_action_to_menu("%s/%s/" % (self.TOPMENU, PLUGIN_NAME), action_name, ida_kernwin.SETMENU_INS) 237 | 238 | def display_alternatives(self): 239 | self.view = Alternatives_viewer_t(self.alternatives) 240 | self.view.Show("Alternatives") 241 | 242 | 243 | def PLUGIN_ENTRY(): 244 | return Linux_alternatives_t() 245 | -------------------------------------------------------------------------------- /linux_alternatives_lib/__init__: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensrcsec/ida-linux-alternatives/7a5aaea3cc0526d2e244dbb0860e82b117aa4a2e/linux_alternatives_lib/__init__ -------------------------------------------------------------------------------- /linux_alternatives_lib/lib.py: -------------------------------------------------------------------------------- 1 | # BSD 3-Clause License 2 | # 3 | # Copyright (c) 2021, Open Source Security, Inc. 4 | # All rights reserved. 5 | # 6 | # Redistribution and use in source and binary forms, with or without 7 | # modification, are permitted provided that the following conditions are met: 8 | # 9 | # 1. Redistributions of source code must retain the above copyright notice, this 10 | # list of conditions and the following disclaimer. 11 | # 12 | # 2. Redistributions in binary form must reproduce the above copyright notice, 13 | # this list of conditions and the following disclaimer in the documentation 14 | # and/or other materials provided with the distribution. 15 | # 16 | # 3. Neither the name of the copyright holder nor the names of its 17 | # contributors may be used to endorse or promote products derived from 18 | # this software without specific prior written permission. 19 | # 20 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | # 31 | # Author: Pawel Wieczorkiewicz 32 | # 33 | from ida_name import get_name_ea, get_name 34 | from ida_nalt import STRTYPE_C 35 | from ida_ua import create_insn 36 | from ida_lines import generate_disasm_line, del_extra_cmt, add_extra_cmt 37 | from idautils import Segments 38 | from ida_ida import inf_get_bin_prefix_size 39 | from ida_kernwin import warning 40 | from ida_segment import * 41 | from ida_struct import * 42 | from ida_auto import * 43 | from idaapi import * 44 | 45 | import ctypes 46 | import re 47 | import os 48 | 49 | from linux_alternatives_lib.utils import * 50 | 51 | ALT_INSTR_SECTION = ".altinstructions" 52 | ALT_REPL_SECTION = ".altinstr_replacement" 53 | 54 | 55 | class Alt_instr_struct_t(object): 56 | name = 'alt_instr' 57 | 58 | sid = None 59 | size = None 60 | struct = None 61 | 62 | alt_instr_seg = None 63 | alt_repl_seg = None 64 | text_segs = [] 65 | 66 | DEFAULT_STRUCT_SIZE = 12 67 | 68 | DEFAULT_INSTR_REPL_SIZE = 4 69 | LEN_MEMBER_SIZE = 1 70 | DEFAULT_INSTR_LEN_OFFSET = 10 71 | DEFAULT_REPL_LEN_OFFSET = 11 72 | 73 | FIELD_INSTR_NAME = 'instr_offset' 74 | FIELD_REPL_NAME = 'repl_offset' 75 | FIELD_CPUID_NAME = 'cpuid' 76 | FIELD_INSTR_LEN_NAME = 'instrlen' 77 | FIELD_REPL_LEN_NAME = 'replacementlen' 78 | 79 | def __init__(self): 80 | self.alt_instr_seg = get_segm_by_name(ALT_INSTR_SECTION) 81 | self.alt_repl_seg = get_segm_by_name(ALT_REPL_SECTION) 82 | 83 | # Collect CODE segments (except from ALT_REPL_SECTION) 84 | for segm_ea in Segments(): 85 | segm = getseg(segm_ea) 86 | segm_class = get_segm_class(segm) 87 | segm_name = get_segm_name(segm) 88 | if segm_class == "CODE" and segm_name not in [get_segm_name(self.alt_repl_seg)]: 89 | self.text_segs.append(segm) 90 | 91 | # Try to find structure created from DWARF info 92 | sid = get_struc_id(self.name) 93 | if sid == BADADDR: 94 | sid = self._create_alt_instr_struct() 95 | 96 | self.sid = sid 97 | self.size = get_struc_size(sid) 98 | self.struct = get_struc(sid) 99 | 100 | if self.size == 0 or (self.alt_instr_seg.size() % self.size) != 0: 101 | warning("Incorrect layout of struct alt_instr detected!") 102 | return 103 | 104 | create_data(self.alt_instr_seg.start_ea, FF_STRUCT, self.alt_instr_seg.size(), sid) 105 | 106 | def _get_instr_repl_size(self, ea, segments): 107 | # Does 8-byte value (direct EA) belong to requested segments 108 | if any([get_sign_value_by_size(ea, 8) in range(segm.start_ea, segm.end_ea) for segm in segments]): 109 | return 8 110 | 111 | # Does 4-byte value (relative offset) belong to requested segments 112 | data = uint64(ea + get_sign_value_by_size(ea, 4)) 113 | if any([data in range(segm.start_ea, segm.end_ea) for segm in segments]): 114 | return 4 115 | 116 | return self.DEFAULT_INSTR_REPL_SIZE 117 | 118 | def _get_alt_instr_struct_size(self, instr_size, repl_size): 119 | # Start the search after instruction and replacement fields 120 | for instr_ea in range(self.alt_instr_seg.start_ea + instr_size + repl_size, self.alt_instr_seg.end_ea): 121 | repl_ea = instr_ea + instr_size 122 | if repl_size == 4: 123 | data_instr = uint64(instr_ea + get_sign_value_by_size(instr_ea, instr_size)) 124 | data_repl = uint64(repl_ea + get_sign_value_by_size(repl_ea, repl_size)) 125 | elif repl_size == 8: 126 | data_instr = get_sign_value_by_size(instr_ea, instr_size) 127 | data_repl = get_sign_value_by_size(repl_ea, repl_size) 128 | else: 129 | warning("Unsupported instruction/replacement offset/address size: %u" % repl_size) 130 | return None 131 | 132 | # Replacement field value must point into .altinstr_replacement section 133 | if data_repl not in range(self.alt_repl_seg.start_ea, self.alt_repl_seg.end_ea): 134 | continue 135 | 136 | # Instruction field value must point into a text section, but not .altinstr_replacement 137 | if all([data_instr not in range(seg.start_ea, seg.end_ea) for seg in self.text_segs]): 138 | continue 139 | 140 | # Multiplied detected structure size must cover .altinstructions sections exactly 141 | size = instr_ea - self.alt_instr_seg.start_ea 142 | if (self.alt_instr_seg.size() % size) != 0: 143 | continue 144 | return size 145 | 146 | return self.DEFAULT_STRUCT_SIZE 147 | 148 | def _get_byte_value_sum(self, segm, offset): 149 | return sum([get_byte(ea + offset) for ea in range(segm.start_ea, segm.end_ea, self.size)]) 150 | 151 | def _get_byte_value_set(self, segm, offset): 152 | return set([get_byte(ea + offset) for ea in range(segm.start_ea, segm.end_ea, self.size)]) 153 | 154 | def _find_len_fields_offsets(self, cpuid_off): 155 | # Start the search after first byte of cpuid field 156 | for instrlen_offset in range(cpuid_off + 1, self.size): 157 | instrlen_set = self._get_byte_value_set(self.alt_instr_seg, instrlen_offset) 158 | # Instruction len field cannot have 0 value 159 | if 0 in instrlen_set: 160 | continue 161 | 162 | repllen_offset = instrlen_offset + self.LEN_MEMBER_SIZE 163 | 164 | sum = self._get_byte_value_sum(self.alt_instr_seg, repllen_offset) 165 | # Match if sum of all Replacement len fields is equal to 166 | # the size of entire .altinstr_replacement section 167 | if sum == self.alt_repl_seg.size(): 168 | return instrlen_offset, repllen_offset 169 | # If the sum is smaller than the section size it cannot be 170 | # the Replacement len field 171 | elif sum < self.alt_repl_seg.size(): 172 | continue 173 | 174 | repllen_set = self._get_byte_value_set(self.alt_instr_seg, repllen_offset) 175 | # Not a match if exists Replacement len value bigger 176 | # than any of the Instruction len value 177 | if max(repllen_set) > max(instrlen_set): 178 | continue 179 | 180 | return instrlen_offset, repllen_offset 181 | 182 | return self.DEFAULT_INSTR_LEN_OFFSET, self.DEFAULT_REPL_LEN_OFFSET 183 | 184 | def _create_alt_instr_struct(self): 185 | sid = add_struc(BADADDR, self.name) 186 | struct = get_struc(sid) 187 | 188 | instr_size = self._get_instr_repl_size(self.alt_instr_seg.start_ea, self.text_segs) 189 | repl_size = self._get_instr_repl_size(self.alt_instr_seg.start_ea + instr_size, [self.alt_repl_seg]) 190 | 191 | self.size = self._get_alt_instr_struct_size(instr_size, repl_size) 192 | 193 | cpuid_off = instr_size + repl_size 194 | 195 | instr_len_off, repl_len_off = self._find_len_fields_offsets(cpuid_off) 196 | instr_len_size = repl_len_size = repl_len_off - instr_len_off 197 | 198 | cpuid_size = instr_len_off - cpuid_off 199 | 200 | add_struc_member(struct, self.FIELD_INSTR_NAME, BADADDR, SIZE_TO_FLAG[instr_size], None, instr_size) 201 | add_struc_member(struct, self.FIELD_REPL_NAME, BADADDR, SIZE_TO_FLAG[repl_size], None, repl_size) 202 | add_struc_member(struct, self.FIELD_CPUID_NAME, BADADDR, SIZE_TO_FLAG[cpuid_size], None, cpuid_size) 203 | add_struc_member(struct, self.FIELD_INSTR_LEN_NAME, BADADDR, SIZE_TO_FLAG[instr_len_size], None, instr_len_size) 204 | add_struc_member(struct, self.FIELD_REPL_LEN_NAME, BADADDR, SIZE_TO_FLAG[repl_len_size], None, repl_len_size) 205 | 206 | for i in range(self.size - get_struc_size(struct)): 207 | add_struc_member(struct, "padlen%u" % i, BADADDR, SIZE_TO_FLAG[1], None, 1) 208 | 209 | return sid 210 | 211 | def get_struct_metadata(self): 212 | return [(get_member_name(member.id), get_member_size(member), member.get_soff()) for member in self.struct.members] 213 | 214 | 215 | class CPU_flags_t(object): 216 | CPU_FLAG_SYMBOLS = { 217 | 'caps': { 'x86_cap_flags': None }, 218 | 'bugs': { 'x86_bug_flags': None }, 219 | } 220 | 221 | NCAPINTS = 20 222 | ncapints = None 223 | 224 | NBUGINTS = 1 225 | nbugints = None 226 | 227 | cpufeatures = None 228 | 229 | ALT_INSTR_FLAG_INV = 1 << 15 230 | INV_PREFIX = "! " 231 | 232 | def __init__(self, cpufeat_node): 233 | for _, symbols in self.CPU_FLAG_SYMBOLS.items(): 234 | for symbol, _ in symbols.items(): 235 | symbols[symbol] = get_name_ea(BADADDR, symbol) 236 | 237 | # Try to find feature flag names in binary 238 | self.ncapints = self._analyze_flag_names('caps') // (PTR_SIZE * 32) 239 | if not self.ncapints: 240 | self.ncapints = self.NCAPINTS 241 | self.nbugints = self._analyze_flag_names('bugs') // (PTR_SIZE * 32) 242 | if not self.nbugints: 243 | self.nbugints = self.NBUGINTS 244 | 245 | # Parse cpufeatures.h file if specified 246 | filepath = cpufeat_node[0].supval(cpufeat_node[1]) 247 | if filepath and os.path.exists(filepath): 248 | print("Parsing %s for flags" % filepath.decode("utf-8")) 249 | self.cpufeatures = self._parse_cpufeatures_file(filepath) 250 | 251 | def _analyze_flag_names(self, kind): 252 | def __analyze_flag_names(symbol, symbol_ea): 253 | if symbol_ea == BADADDR: 254 | return None 255 | 256 | print("Analyzing flags for symbol '%s' at %x..." % (symbol, symbol_ea)) 257 | 258 | for ea in range(symbol_ea, BADADDR, PTR_SIZE): 259 | # Stop processing when new symbol begins 260 | if ea > symbol_ea and len(get_name(ea)): 261 | return ea - symbol_ea # Array size 262 | 263 | (create_qword if PTR_SIZE == 8 else create_dword)(ea, PTR_SIZE, True) 264 | 265 | str_ea = get_qword(ea) 266 | del_items(str_ea) 267 | 268 | create_strlit(str_ea, 0, STRTYPE_C) 269 | add_data_xref(ea, str_ea) 270 | 271 | return sum([__analyze_flag_names(name, ea) for name, ea in self.CPU_FLAG_SYMBOLS[kind].items() if ea != BADADDR]) 272 | 273 | def _parse_cpufeatures_file(self, filepath): 274 | features = {} 275 | with open(filepath, "r") as f: 276 | for line in f.readlines(): 277 | line = line.lstrip() 278 | # Get NCAPINTS value 279 | match = re.search('^#define\s+NCAPINTS\s+([0-9]+)\s+', line) 280 | if match: 281 | self.ncapints = int(match.group(1)) 282 | continue 283 | 284 | # Get NBUGINTS value 285 | match = re.search('^#define\s+NBUGINTS\s+([0-9]+)\s+', line) 286 | if match: 287 | self.nbugints = int(match.group(1)) 288 | continue 289 | 290 | # Get X86_FEATURE_* value 291 | match = re.search('^#define\s+X86_FEATURE_([^\s]+)\s+\(([\s0-9]+?)\*32\+([\s0-9]+?)\)\s+', line) 292 | if match: 293 | feature, word, bit = match.group(1), int(match.group(2)), int(match.group(3)) 294 | features[feature] = (word, bit) 295 | continue 296 | 297 | # Get X86_BUG_* value 298 | match = re.search('^#define\s+X86_BUG_([^\s]+)\s+X86_BUG\(([\s0-9]+?)\)\s+', line) 299 | if match: 300 | feature, word, bit = match.group(1), self.ncapints, int(match.group(2)) 301 | features[feature] = (word, bit) 302 | 303 | return dict((v, k) for k, v in features.items()) 304 | 305 | def get_flag_name(self, cpuid, kind='caps'): 306 | def _get_feature_string(array_ea, _flag): 307 | ea = array_ea + _flag * PTR_SIZE 308 | return get_strlit_contents(get_ptr(ea), -1, STRTYPE_C) 309 | 310 | # Handle inverted alternatives 311 | if cpuid & self.ALT_INSTR_FLAG_INV: 312 | flag = cpuid & ~self.ALT_INSTR_FLAG_INV 313 | prefix = self.INV_PREFIX 314 | else: 315 | flag = cpuid 316 | prefix = "" 317 | 318 | word, bit = (flag >> 5, flag & 0b011111) 319 | 320 | # Try to take flag from the imported file 321 | if self.cpufeatures: 322 | flag_name = self.cpufeatures.get((word, bit), None) 323 | if flag_name: 324 | return prefix, flag_name 325 | 326 | # Try to take flag from symbols in binary 327 | for _, array_ea in self.CPU_FLAG_SYMBOLS[kind].items(): 328 | if array_ea == BADADDR: 329 | continue 330 | 331 | flag_name = _get_feature_string(array_ea, flag) 332 | if flag_name: 333 | return prefix, flag_name.decode("utf-8").upper() 334 | 335 | # Default to (word, bit) 336 | return prefix, "%u, %u" % (word, bit) 337 | 338 | 339 | class Alternative_generator_t(object): 340 | def __init__(self, cpufeat_node): 341 | self.view = None 342 | 343 | self.alt_instr_struct = Alt_instr_struct_t() 344 | self.cpu_flags = CPU_flags_t(cpufeat_node) 345 | 346 | @staticmethod 347 | def create_replacement(repl_ea, repl_len): 348 | def create_instruction(ea): 349 | size = create_insn(ea) 350 | if size == 0: 351 | return get_item_size(ea) 352 | return size 353 | 354 | _len = 0 355 | while _len < repl_len: 356 | _len += create_instruction(repl_ea + _len) 357 | 358 | @staticmethod 359 | def get_replacement_lines(ea, repl_len, instr_len, indent): 360 | lines = [] 361 | 362 | num_opcodes = inf_get_bin_prefix_size() 363 | max_opcodes = len(indent) 364 | 365 | _len = 0 366 | while _len < repl_len: 367 | line, size = generate_disasm_line(ea + _len), get_item_size(ea + _len) 368 | opcodes = " ".join(["%02x" % get_byte(ea + _len + i) for i in range(min(size, num_opcodes))]) 369 | if num_opcodes > 0: 370 | if size > num_opcodes: 371 | opcodes += "+" 372 | if len(opcodes) >= max_opcodes: 373 | opcodes += " " * 3 374 | lines.append((opcodes, line)) 375 | max_opcodes = max(max_opcodes, len(opcodes)) 376 | _len += size 377 | 378 | if repl_len == 0: 379 | while _len < instr_len: 380 | line, size = "nop", 1 381 | opcodes = " ".join(["90" for i in range(min(size, num_opcodes))]) 382 | lines.append((opcodes, line)) 383 | max_opcodes = max(max_opcodes, len(opcodes)) 384 | _len += size 385 | 386 | return ["%s%s%s" % (opcodes, " " * (max_opcodes - len(opcodes)), line) for opcodes, line in lines] 387 | 388 | def _gen_replacement_line(self, row, processed_alternatives, indent=1): 389 | index, instr_ea, repl_ea, flag_str, instr_len, repl_len = row[:6] 390 | line = [] 391 | 392 | index_str = "[0x%04x]" % index 393 | indent_str = get_indent(4 + len(index_str)) * indent 394 | line.append("%s%sif feature: %s" % (index_str, indent_str, flag_str)) 395 | 396 | # Not just NOPs 397 | if repl_len != 0: 398 | # Generate nested alternatives 399 | repl_rows = processed_alternatives.get(repl_ea, []) 400 | if len(repl_rows) > 0: 401 | for repl_row in repl_rows: 402 | line += self._gen_replacement_line(repl_row, indent + 1) 403 | 404 | line.append("%s" % ("\n".join(self.get_replacement_lines(repl_ea, repl_len, instr_len, get_indent() * (indent + 1))))) 405 | line.append("%sendif" % (get_indent(4) * (indent + 1))) 406 | 407 | line.append("%s" % ("\n".join(self.get_replacement_lines(repl_ea, repl_len, instr_len, get_indent() * indent)))) 408 | line.append("%selse" % (get_indent(4) * indent)) 409 | 410 | return line 411 | 412 | def add_alternatives_cmts(self, row, processed_alternatives): 413 | ea, size = row[1], row[4] 414 | line = [] 415 | 416 | if ea not in processed_alternatives: 417 | line.append("Alternatives:") 418 | 419 | line += self._gen_replacement_line(row, processed_alternatives) 420 | add_extra_cmt(ea, True, "\n".join(line)) 421 | 422 | if ea not in processed_alternatives: 423 | add_extra_cmt(ea + size, True, '%sendif' % get_indent(4)) 424 | 425 | def _get_alternative_row(self, ea, metadata): 426 | vinstr_ea = vrepl_ea = cpuid = instr_len = repl_len = 0 427 | padlens = [] 428 | 429 | for name, size, offset in metadata: 430 | if name.startswith("instr") and not name.endswith("len"): 431 | instr_off = get_sign_value_by_size(ea + offset, size) 432 | vinstr_ea = uint64(instr_off + ea + offset) if size == 4 else instr_off 433 | elif name.startswith("repl") and not name.endswith('len'): 434 | repl_off = get_sign_value_by_size(ea + offset, size) 435 | vrepl_ea = uint64(repl_off + ea + offset) if size == 4 else repl_off 436 | elif name.startswith("cpuid"): 437 | cpuid = get_unsign_value_by_size(ea + offset, size) 438 | elif name.startswith("instr") and name.endswith("len"): 439 | instr_len = get_sign_value_by_size(ea + offset, size) 440 | elif name.startswith("repl") and name.endswith("len"): 441 | repl_len = get_sign_value_by_size(ea + offset, size) 442 | else: 443 | padlens.append(get_sign_value_by_size(ea + offset, size)) 444 | 445 | return vinstr_ea, vrepl_ea, cpuid, instr_len, repl_len, padlens 446 | 447 | def gen_alternatives(self, cb=None, req_features=[]): 448 | alt_instr_seg = get_segm_by_name(ALT_INSTR_SECTION) 449 | 450 | metadata = self.alt_instr_struct.get_struct_metadata() 451 | 452 | index = 0 453 | processed_alternatives = {} 454 | for ea in range(alt_instr_seg.start_ea, alt_instr_seg.end_ea, self.alt_instr_struct.size): 455 | vinstr_ea, vrepl_ea, cpuid, instr_len, repl_len, padlens = self._get_alternative_row(ea, metadata) 456 | 457 | prefix, flag_name = self.cpu_flags.get_flag_name(cpuid) 458 | flag_str = "%s%s" % (prefix, flag_name) 459 | 460 | if len(req_features) > 0: 461 | if flag_str.startswith(self.cpu_flags.INV_PREFIX): 462 | if flag_str[len(self.cpu_flags.INV_PREFIX):] in req_features: 463 | continue 464 | elif flag_str not in req_features: 465 | continue 466 | 467 | self.create_replacement(vrepl_ea, repl_len) 468 | if repl_len > 0: 469 | add_data_xref(vinstr_ea, vrepl_ea) 470 | 471 | row = [index, vinstr_ea, vrepl_ea, flag_str, instr_len, repl_len] + padlens 472 | index += 1 473 | 474 | if cb: 475 | cb(row, processed_alternatives) 476 | 477 | if vinstr_ea not in processed_alternatives: 478 | processed_alternatives[vinstr_ea] = [] 479 | processed_alternatives[vinstr_ea].append(row) 480 | 481 | return processed_alternatives 482 | 483 | 484 | class Alternative_patcher_t(object): 485 | def __init__(self, cpufeat_node): 486 | self.alt_gen = Alternative_generator_t(cpufeat_node) 487 | 488 | def patch(self, features): 489 | features = features.upper() 490 | print("Patching alternatives for feature flags: %s" % features) 491 | 492 | self.alt_gen.gen_alternatives(self._patch_rows, self._process_features(features)) 493 | auto_wait() 494 | 495 | def unpatch(self): 496 | visit_patched_bytes(0, BADADDR, self._unpatch_byte) 497 | auto_wait() 498 | 499 | @staticmethod 500 | def _unpatch_byte(ea, fpos, org_val, patch_val): 501 | del_extra_cmt(ea, E_PREV) 502 | revert_byte(ea) 503 | auto_make_code(ea) 504 | restore_xrefs(ea) 505 | return 0 506 | 507 | def _patch_rows(self, row, processed_alternatives): 508 | instr_ea, repl_ea, flag_str, instr_len, repl_len = row[1:6] 509 | 510 | repl_bytes = get_bytes(repl_ea, repl_len) if repl_len > 0 else b'' 511 | repl_bytes = self._recompute_branches(repl_bytes, instr_ea, repl_ea, instr_len, repl_len) 512 | 513 | # Save original disassembly 514 | orig_insns = self.alt_gen.get_replacement_lines(instr_ea, instr_len, instr_len, get_indent(-4)) 515 | 516 | patch_bytes(instr_ea, repl_bytes) 517 | 518 | # Tell IDA to make code explicitely 519 | self.alt_gen.create_replacement(instr_ea, instr_len) 520 | restore_xrefs(instr_ea) 521 | 522 | cmt = ["%sAlternative %s applied" % (get_indent(), flag_str)] 523 | cmt.append("%sOriginal instructions:\n%s" % (get_indent(), "\n".join(orig_insns))) 524 | add_extra_cmt(instr_ea, True, "\n".join(cmt)) 525 | 526 | def _recompute_branches(self, opcodes, instr_ea, repl_ea, instr_len, repl_len): 527 | def is_rel_call(opcodes, size): 528 | return size == 5 and opcodes[0] == 0xe8 529 | 530 | def is_jmp(opcodes, size): 531 | return size in [5, 6] and opcodes[0] in [0xeb, 0xe9] # 6 covers for SLS barrier 532 | 533 | def add_nops(opcodes, instr_len, repl_len): 534 | return opcodes + b'\x90' * (instr_len - repl_len) 535 | 536 | def two_byte_jmp(displ): 537 | displ -= 2 538 | return add_nops(b'\xeb' + displ.to_bytes(1, 'little', signed=True), 5, 2) 539 | 540 | def five_byte_jmp(displ): 541 | displ -= 5 542 | return b'\xe9' + displ.to_bytes(4, 'little', signed=True) 543 | 544 | new_opcodes = opcodes 545 | 546 | if is_rel_call(opcodes, repl_len): 547 | o_disp = ctypes.c_int(int.from_bytes(opcodes[1:], "little")).value 548 | new_opcodes = b'\xe8' + ctypes.c_long(o_disp + (repl_ea - instr_ea)).value.to_bytes(4, 'little', signed=True) 549 | elif is_jmp(opcodes, repl_len): 550 | o_disp = ctypes.c_int(int.from_bytes(opcodes[1:], "little")).value 551 | next_rip = uint64(ctypes.c_long(repl_ea + 5).value) 552 | tgt_rip = uint64(ctypes.c_long(next_rip + o_disp).value) 553 | n_dspl = ctypes.c_int(tgt_rip - instr_ea).value 554 | 555 | if ctypes.c_long(tgt_rip - instr_ea).value >= 0: 556 | new_opcodes = two_byte_jmp(n_dspl) if n_dspl - 2 <= 127 else five_byte_jmp(n_dspl) 557 | else: 558 | new_opcodes = two_byte_jmp(n_dspl) if ((n_dspl - 2) & 0xff) == n_dspl - 2 else five_byte_jmp(n_dspl) 559 | elif instr_len > repl_len: 560 | new_opcodes = add_nops(opcodes, instr_len, repl_len) 561 | 562 | return new_opcodes 563 | 564 | def _process_features(self, input_features): 565 | features = [] 566 | 567 | for feature in input_features.split(','): 568 | feature = feature.strip() 569 | try: 570 | cpuid = int(feature, 0) 571 | _, name = self.cpu_flags.get_flag_name(cpuid) 572 | features.append(name) 573 | except: 574 | features.append(feature.upper()) 575 | 576 | return features 577 | -------------------------------------------------------------------------------- /linux_alternatives_lib/utils.py: -------------------------------------------------------------------------------- 1 | # BSD 3-Clause License 2 | # 3 | # Copyright (c) 2021, Open Source Security, Inc. 4 | # All rights reserved. 5 | # 6 | # Redistribution and use in source and binary forms, with or without 7 | # modification, are permitted provided that the following conditions are met: 8 | # 9 | # 1. Redistributions of source code must retain the above copyright notice, this 10 | # list of conditions and the following disclaimer. 11 | # 12 | # 2. Redistributions in binary form must reproduce the above copyright notice, 13 | # this list of conditions and the following disclaimer in the documentation 14 | # and/or other materials provided with the distribution. 15 | # 16 | # 3. Neither the name of the copyright holder nor the names of its 17 | # contributors may be used to endorse or promote products derived from 18 | # this software without specific prior written permission. 19 | # 20 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | # 31 | # Author: Pawel Wieczorkiewicz 32 | # 33 | from ida_bytes import * 34 | from ida_xref import * 35 | from ida_ida import inf_get_indent, inf_is_64bit 36 | from idautils import XrefsFrom 37 | from ida_funcs import get_func, reanalyze_function 38 | import ctypes 39 | 40 | SIZE_TO_FLAG = { 41 | 8: qword_flag(), 42 | 4: dword_flag(), 43 | 2: word_flag(), 44 | 1: byte_flag(), 45 | } 46 | 47 | 48 | def get_pointer_size(): 49 | return 8 if inf_is_64bit() else 4 50 | 51 | 52 | PTR_SIZE = get_pointer_size() 53 | 54 | 55 | def get_indent(off=0): 56 | return " " * (inf_get_indent() - 2 - off) 57 | 58 | 59 | def get_ptr(ea): 60 | return get_qword(ea) if PTR_SIZE == 8 else get_dword(ea) 61 | 62 | 63 | def is_code_ea(ea): 64 | return is_code(get_flags(ea)) 65 | 66 | 67 | def is_data_ea(ea): 68 | return is_data(get_flags(ea)) 69 | 70 | 71 | def is_defined_ea(ea): 72 | return is_code_ea(ea) or is_data_ea(ea) 73 | 74 | 75 | def add_data_xref(_from, _to): 76 | if is_defined_ea(_from) and is_defined_ea(_to): 77 | add_dref(_from, _to, XREF_DATA) 78 | 79 | def restore_xrefs(ea): 80 | # Remove stale code and data references 81 | for xref in XrefsFrom(ea): 82 | del_cref(xref.frm, xref.to, 0) 83 | del_dref(xref.frm, xref.to) 84 | 85 | # Reanalyze the function to get new references 86 | reanalyze_function(get_func(ea)) 87 | 88 | def uint64(value): 89 | return value % (1 << 64) 90 | 91 | 92 | def get_sign_value_by_size(ea, size): 93 | if size == 8: 94 | return ctypes.c_long(get_qword(ea)).value 95 | elif size == 4: 96 | return ctypes.c_int(get_dword(ea)).value 97 | elif size == 2: 98 | return ctypes.c_short(get_word(ea)).value 99 | elif size == 1: 100 | return ctypes.c_byte(get_byte(ea)).value 101 | return None 102 | 103 | 104 | def get_unsign_value_by_size(ea, size): 105 | if size == 8: 106 | return ctypes.c_ulong(get_qword(ea)).value 107 | elif size == 4: 108 | return ctypes.c_uint(get_dword(ea)).value 109 | elif size == 2: 110 | return ctypes.c_ushort(get_word(ea)).value 111 | elif size == 1: 112 | return ctypes.c_ubyte(get_byte(ea)).value 113 | return None 114 | --------------------------------------------------------------------------------