├── .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

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 |  | 
71 |
72 | ### 4. **Apply alternatives comments in the disassembly for all alternative entries found**
73 |
74 | without opcodes | with opcodes
75 | :---------------------------------------------:|:-------------------------------------------------------------------:
76 |  | 
77 |
78 |
79 | ### 5. **Open a new window with a tabular listing of the alternatives**
80 | * columns are sortable and addresses clickable

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:

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:

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 |  | 
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 |
--------------------------------------------------------------------------------