├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── idasec.py ├── idasec ├── __init__.py ├── analysis │ ├── __init__.py │ ├── callret_analysis.py │ ├── default_analysis.py │ ├── generic_analysis.py │ ├── opaque_analysis.py │ └── static_opaque_analysis.py ├── configuration_file.py ├── dba.py ├── dba_io.py ├── dba_printer.py ├── exception.py ├── formula.py ├── ida_utils.py ├── idasec_core.py ├── network │ ├── __init__.py │ ├── broker.py │ ├── commands.py │ └── message.py ├── proto │ ├── Makefile │ ├── __init__.py │ ├── analysis_config.proto │ ├── analysis_config_pb2.py │ ├── common.proto │ ├── common_pb2.py │ ├── config.proto │ ├── config_pb2.py │ ├── dba.proto │ ├── dba_pb2.py │ ├── instruction.proto │ ├── instruction_pb2.py │ ├── libcall.proto │ ├── libcall_pb2.py │ ├── message.proto │ ├── message_pb2.py │ ├── syscall.proto │ ├── syscall_pb2.py │ ├── trace.proto │ └── trace_pb2.py ├── protobuf_json.py ├── report_generator.py ├── trace.py ├── ui │ ├── Makefile │ ├── __init__.py │ ├── analysis.ui │ ├── custom_widgets.py │ ├── generic_analysis.ui │ ├── generic_analysis_result.ui │ ├── icons │ │ ├── idasec.png │ │ ├── idasec_small.png │ │ ├── open-iconic-master │ │ │ └── png │ │ │ │ └── 3x │ │ │ │ ├── magnifying-glass-3x.png │ │ │ │ ├── minus-3x.png │ │ │ │ ├── plus-3x.png │ │ │ │ ├── question-mark-3x.png │ │ │ │ └── target-3x.png │ │ └── oxygen │ │ │ └── 22x22 │ │ │ ├── ko.png │ │ │ └── ok.png │ ├── main.ui │ ├── master.ui │ ├── resources.qrc │ ├── standard_params.ui │ ├── standard_result.ui │ ├── static_iteration_config.ui │ └── trace.ui ├── utils.py └── widgets │ ├── AnalysisWidget.py │ ├── MainWidget.py │ ├── StandardParamWidget.py │ ├── StandardResultWidget.py │ ├── TraceWidget.py │ └── __init__.py └── screenshot ├── idasec1.png ├── idasec2.png └── idasec3.png /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | 27 | # PyInstaller 28 | # Usually these files are written by a python script from a template 29 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 30 | *.manifest 31 | *.spec 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .coverage.* 42 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | *,cover 46 | .hypothesis/ 47 | 48 | # Translations 49 | *.mo 50 | *.pot 51 | 52 | # Django stuff: 53 | *.log 54 | local_settings.py 55 | 56 | # Flask stuff: 57 | instance/ 58 | .webassets-cache 59 | 60 | # Scrapy stuff: 61 | .scrapy 62 | 63 | # Sphinx documentation 64 | docs/_build/ 65 | 66 | # PyBuilder 67 | target/ 68 | 69 | # IPython Notebook 70 | .ipynb_checkpoints 71 | 72 | # pyenv 73 | .python-version 74 | 75 | # celery beat schedule file 76 | celerybeat-schedule 77 | 78 | # dotenv 79 | .env 80 | 81 | # virtualenv 82 | venv/ 83 | ENV/ 84 | 85 | # Spyder project settings 86 | .spyderproject 87 | 88 | # Rope project settings 89 | .ropeproject 90 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | cd idasec/proto && $(MAKE) 3 | cd ../.. 4 | cd idasec/ui && $(MAKE) 5 | 6 | 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # idasec 2 | 3 | IDA plugin for reverse-engineering and dynamic interactions with the Binsec platform 4 | 5 | ## Disclaimer 6 | 7 | *IDAsec is prototype tool under development, some features are likely buggy and should be used with care.* 8 | 9 | ## Features 10 | 11 | * Decoding an instruction (in DBA IR) 12 | * Loading execution traces generated by **Pinsec** 13 | * Triggering analyzes on Binsec and retrieving results 14 | 15 | ## Dependencies 16 | 17 | * protobuf 18 | * ZMQ 19 | * capstone *(for trace disassembly)* 20 | * graphviz *(to draw dependency within a formula)* 21 | * pyparsing 22 | * enum 23 | * path.py 24 | * plotly *(optional)* 25 | 26 | ## Running Idasec 27 | 28 | 1. In IDA: Copy the `idasec` folder in the python directory of IDA and then load `idasec.py` with Ctrl+F7 29 | 2. As a standalone app, just run `./idasec.py` (no yet ready) 30 | 31 | ## Documentation 32 | 33 | Yet to come.. 34 | 35 | ## Screenshots 36 | 37 | ![idasec 1](https://raw.github.com/RobinDavid/idasec/master/screenshot/idasec1.png) 38 | 39 | ![idasec 2](https://raw.github.com/RobinDavid/idasec/master/screenshot/idasec2.png) 40 | 41 | ![idasec 3](https://raw.github.com/RobinDavid/idasec/master/screenshot/idasec3.png) 42 | 43 | ## TODO 44 | 45 | Too much to be listed.. 46 | -------------------------------------------------------------------------------- /idasec.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding: utf-8 3 | ######################################################################## 4 | # Copyright (c) 2016 5 | # Author: Robin David ceacom> 6 | # CEA (Commissariat à l'énergie atomique et aux énergies alternatives) 7 | # All rights reserved. 8 | ######################################################################## 9 | # 10 | # This file is part of IDASec 11 | # 12 | # IDAsec is free software: you can redistribute it and/or modify it 13 | # under the terms of the GNU General Public License as published by 14 | # the Free Software Foundation, version 2.1 15 | # 16 | # This program is distributed in the hope that it will be useful, but 17 | # WITHOUT ANY WARRANTY; without even the implied warranty of 18 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 19 | # General Public License for more details. 20 | # 21 | # You should have received a copy of the GNU General Public License 22 | # along with this program. If not, see 23 | # . 24 | # 25 | ######################################################################## 26 | import sys 27 | import datetime 28 | import re 29 | import sip 30 | 31 | from PyQt5 import QtCore, QtWidgets 32 | from path import Path 33 | 34 | from idasec.widgets.MainWidget import MainWidget 35 | from idasec.widgets.TraceWidget import TraceWidget 36 | from idasec.idasec_core import IDASecCore 37 | from idasec.widgets.AnalysisWidget import AnalysisWidget 38 | from idasec.analysis.default_analysis import DefaultAnalysis 39 | from idasec.analysis.generic_analysis import GenericAnalysis 40 | from idasec.analysis.callret_analysis import CallRetAnalysis 41 | from idasec.analysis.opaque_analysis import OpaqueAnalysis 42 | from idasec.analysis.static_opaque_analysis import StaticOpaqueAnalysis 43 | 44 | IDA_ENABLED = False 45 | try: 46 | import idaapi 47 | from idaapi import PluginForm, plugin_t 48 | IDA_ENABLED = True 49 | except ImportError: 50 | class PluginForm: 51 | def __init__(self): 52 | pass 53 | 54 | class plugin_t: 55 | def __init__(self): 56 | pass 57 | 58 | class idaapi: 59 | PLUGIN_UNL = None 60 | PLUGIN_OK = None 61 | 62 | def __init__(self): 63 | pass 64 | IDA_ENABLED = False 65 | 66 | 67 | IDASEC = None 68 | NAME = "IDASec" 69 | 70 | 71 | class IDASecForm(PluginForm): 72 | def __init__(self): 73 | super(IDASecForm, self).__init__() 74 | global HOTKEYS 75 | HOTKEYS = [] 76 | 77 | def OnCreate(self, form): 78 | # Internal data structures 79 | self.running_analyses = {} 80 | self.core = IDASecCore() 81 | self.parent = self.FormToPyQtWidget(form) 82 | self.setupUi(self.parent) 83 | self.main_widget = MainWidget(self) 84 | self.trace_widget = TraceWidget(self) 85 | self.analysis_widget = AnalysisWidget(self) 86 | # --------------------------------- 87 | 88 | # -- ui stuff 89 | self.tab_widget.setTabsClosable(True) 90 | self.tab_widget.tabCloseRequested.connect(self.close_tab_action) 91 | self.tab_widget.addTab(self.main_widget, "Main") 92 | self.tab_widget.addTab(self.trace_widget, "Trace") 93 | self.tab_widget.addTab(self.analysis_widget, "Analysis") 94 | self.tab_widget.tabBar().tabButton(0, self.tab_widget.tabBar().RightSide).hide() 95 | self.tab_widget.tabBar().tabButton(1, self.tab_widget.tabBar().RightSide).hide() 96 | self.tab_widget.tabBar().tabButton(2, self.tab_widget.tabBar().RightSide).hide() 97 | 98 | def OnClose(self, form): 99 | global IDASEC 100 | try: 101 | del IDASEC 102 | except NameError: 103 | print "IDASec apparently already deleted !" 104 | 105 | def setTabFocus(self, name): 106 | widget = {"Main": self.main_widget, "Trace": self.trace_widget, "Analysis": self.analysis_widget}[name] 107 | index = self.tab_widget.indexOf(widget) 108 | self.tab_widget.setCurrentIndex(index) 109 | 110 | def close_tab_action(self, i): 111 | idx = id(self.tab_widget.widget(i)) 112 | analyse = self.running_analyses[idx] 113 | analyse.stop() 114 | self.tab_widget.removeTab(i) 115 | self.running_analyses.pop(idx) 116 | 117 | def start_analysis(self, name, conf, is_stream=False, trace=None): 118 | binsec_ip, binsec_port = self.main_widget.binsec_ip_field.text(), self.main_widget.binsec_port_field.text() 119 | pinsec_ip, pinsec_port = self.main_widget.pinsec_ip_field.text(), self.main_widget.pinsec_port_field.text() 120 | 121 | if binsec_ip == "" or binsec_port == "": 122 | print "No IP or port specified for Binsec" 123 | elif is_stream and (pinsec_ip == "" or pinsec_port == ""): 124 | print "No IP or port specified for Pinsec" 125 | else: 126 | analysis = self.analysis_from_name(name)(self, conf, is_stream, trace) 127 | index = self.tab_widget.addTab(analysis.result_widget, name.capitalize()) 128 | self.tab_widget.setCurrentIndex(index) 129 | self.running_analyses[id(analysis.result_widget)] = analysis 130 | 131 | analysis.broker.connect_binsec(binsec_ip, binsec_port) 132 | if is_stream: 133 | analysis.broker.connect_pinsec(pinsec_ip, pinsec_port) 134 | analysis.run() 135 | 136 | def get_current_analysis(self): 137 | widget = self.tab_widget.widget(self.tab_widget.currentIndex()) 138 | return self.running_analyses[id(widget)] 139 | 140 | @staticmethod 141 | def analysis_from_name(name): 142 | name = name.upper() 143 | if name == "GENERIC": 144 | return GenericAnalysis 145 | elif name == "CALLRET": 146 | return CallRetAnalysis 147 | elif name == "OPAQUE": 148 | return OpaqueAnalysis 149 | elif name == "STATIC OPAQUE": 150 | return StaticOpaqueAnalysis 151 | else: 152 | return DefaultAnalysis 153 | 154 | ''' 155 | def Show(self): 156 | return PluginForm.Show(self, NAME, options=(PluginForm.FORM_CLOSE_LATER | PluginForm.FORM_RESTORE | PluginForm.FORM_SAVE)) 157 | ''' 158 | 159 | def add_trace(self, t): 160 | index = self.core.add_trace(t) 161 | self.analysis_widget.trace_selector.addItem("#%d %s" % (index, Path(t.filename).name)) 162 | return index 163 | 164 | def add_solvers(self, solvers): 165 | for s in solvers: 166 | self.analysis_widget.solver_selector.addItem(s) 167 | self.core.solvers = solvers 168 | 169 | def add_analyses(self, analyses): 170 | self.analysis_widget.analysis_name_selector.clear() 171 | for a in analyses: 172 | self.analysis_widget.analysis_name_selector.addItem(a) 173 | self.core.analyses = analyses 174 | self.add_internal_analyses(analyses) 175 | 176 | def add_internal_analyses(self, dyn_analyses): 177 | if "OPAQUE" in dyn_analyses: 178 | self.analysis_widget.analysis_name_selector.addItem("STATIC OPAQUE") 179 | self.core.analyses.append("STATIC OPAQUE") 180 | 181 | def remove_trace(self, tr_id): 182 | tr = self.core.traces[tr_id] 183 | index = self.analysis_widget.trace_selector.findText("#%d %s" % (tr_id, Path(tr.filename).name)) 184 | self.analysis_widget.trace_selector.removeItem(index) 185 | self.core.remove_trace(tr_id) 186 | 187 | def setupUi(self, Master): 188 | Master.setObjectName("Master") 189 | Master.resize(718, 477) 190 | self.verticalLayout = QtWidgets.QVBoxLayout(Master) 191 | self.verticalLayout.setObjectName("verticalLayout") 192 | self.splitter = QtWidgets.QSplitter(Master) 193 | self.splitter.setOrientation(QtCore.Qt.Vertical) 194 | self.splitter.setObjectName("splitter") 195 | self.tab_widget = QtWidgets.QTabWidget(self.splitter) 196 | self.tab_widget.setObjectName("tab_widget") 197 | 198 | self.docker = QtWidgets.QDockWidget(self.splitter) 199 | self.docker.setObjectName("docker") 200 | self.docker.setAllowedAreas(QtCore.Qt.BottomDockWidgetArea) 201 | 202 | self.log_widget = QtWidgets.QTreeWidget(self.docker) 203 | self.log_widget.setHeaderItem(QtWidgets.QTreeWidgetItem(["date", "origin", "type", "message"])) 204 | self.docker.setWidget(self.log_widget) 205 | 206 | self.verticalLayout.addWidget(self.splitter) 207 | self.tab_widget.setCurrentIndex(-1) 208 | QtCore.QMetaObject.connectSlotsByName(Master) 209 | Master.setWindowTitle("IDASec") 210 | 211 | def log(self, type, message, origin="IDASec"): 212 | date = datetime.datetime.now().strftime("%H:%M:%S") 213 | res = re.match("^(\[[A-Za-z]*\])",message) 214 | if res: 215 | type = res.groups()[0] 216 | message = message[len(type):].lstrip() 217 | message = message.rstrip() 218 | self.log_widget.addTopLevelItem(QtWidgets.QTreeWidgetItem([date, origin, type, message])) 219 | self.log_widget.scrollToBottom() 220 | 221 | 222 | ################################################################################ 223 | # Usage as plugin 224 | ################################################################################ 225 | 226 | def PLUGIN_ENTRY(): 227 | return IDASecPlugin() 228 | 229 | 230 | class IDASecPlugin(plugin_t): 231 | 232 | flags = idaapi.PLUGIN_UNL 233 | comment = NAME 234 | help = "IDASec - IDA Interface for the Binsec platform" 235 | wanted_name = "IDASec" 236 | wanted_hotkey = "Ctrl-F1" 237 | 238 | def init(self): 239 | self.icon_id = 0 240 | return idaapi.PLUGIN_OK 241 | 242 | def run(self, arg=0): 243 | print "Run IDASec" 244 | f = IDASecForm() 245 | f.Show() 246 | return 247 | 248 | def term(self): 249 | pass 250 | 251 | 252 | class IDASecStandalone: 253 | def __init__(self): 254 | self.core = IDASecCore() 255 | self.traces = [] 256 | 257 | def remove_trace(self,tr_id): 258 | print "remove trace %d" % tr_id 259 | 260 | def add_trace(self, tr): 261 | print "Add trace" 262 | 263 | ################################################################################ 264 | # Usage as script 265 | ################################################################################ 266 | def main(): 267 | idaapi.msg("Loading IDASEC\n") 268 | global IDASEC 269 | try: 270 | IDASEC 271 | IDASEC.OnClose(IDASEC) 272 | idaapi.msg("reloading IDASec\n") 273 | IDASEC = IDASecForm() 274 | return 275 | except Exception: 276 | IDASEC = IDASecForm() 277 | IDASEC.Show("Idasec") 278 | 279 | 280 | def main_standalone(): 281 | app = QtWidgets.QApplication(sys.argv) 282 | ida_app = IDASecStandalone() 283 | form = AnalysisWidget(ida_app) 284 | form.show() 285 | app.exec_() 286 | 287 | if __name__ == "__main__": 288 | if IDA_ENABLED: 289 | main() 290 | else: 291 | main_standalone() 292 | -------------------------------------------------------------------------------- /idasec/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RobinDavid/idasec/e7d9328f9291dcadf34540a31608995b3697d047/idasec/__init__.py -------------------------------------------------------------------------------- /idasec/analysis/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RobinDavid/idasec/e7d9328f9291dcadf34540a31608995b3697d047/idasec/analysis/__init__.py -------------------------------------------------------------------------------- /idasec/analysis/callret_analysis.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import idc 4 | 5 | from idasec.analysis.default_analysis import DefaultAnalysis 6 | from idasec.network.commands import * 7 | from idasec.proto.analysis_config_pb2 import callret_analysis_results 8 | from idasec.report_generator import make_cell, RED, GREEN, HTMLReport 9 | from idasec.widgets.StandardParamWidget import StandardParamConfigWidget 10 | from idasec.widgets.StandardResultWidget import StandardResultWidget 11 | 12 | 13 | # ============================ RESULT CLASS (pb dependant) ============================= 14 | def to_status_name(x): 15 | return {callret_analysis_results.OK: "OK", callret_analysis_results.VIOL:"VIOLATION"}[x] 16 | 17 | 18 | def to_label_name(x): 19 | return callret_analysis_results.callret_labels.DESCRIPTOR.values_by_number[x].name 20 | 21 | 22 | class RetInfo: 23 | def __init__(self, addr, status, labels, returnsites, count): 24 | self.addr = addr 25 | self.status = status 26 | self.labels = labels 27 | self.returnsites = returnsites 28 | self.solve_count = count 29 | self.calls = [] 30 | 31 | def get_status(self): 32 | return to_status_name(self.status) 33 | 34 | def is_tampering(self): 35 | return self.status == callret_analysis_results.VIOL 36 | 37 | def is_aligned(self): 38 | return len([x == callret_analysis_results.ALIGNED for x in self.labels]) != 0 39 | 40 | def is_disaligned(self): 41 | return len([x == callret_analysis_results.DISALIGNED for x in self.labels]) != 0 42 | 43 | def is_single(self): 44 | return len([x == callret_analysis_results.SINGLE for x in self.labels]) != 0 45 | 46 | def get_labels(self): 47 | return [to_label_name(x) for x in self.labels] 48 | 49 | def add_call(self, addr, st): 50 | self.calls.append((addr, st)) 51 | 52 | def to_string(self): 53 | st = to_status_name(self.status) 54 | str_label = [to_label_name(x) for x in self.labels] 55 | calls_s = str([("%x" % x[0], to_status_name(x[1])) for x in self.calls]) 56 | return "%x(%d): %s labels:%s returnsites:%s calls:%s" % \ 57 | (self.addr, self.solve_count, st, str(str_label), str(["%x" % x for x in self.returnsites]), calls_s) 58 | 59 | 60 | class CallRetResults: 61 | def __init__(self): 62 | self.rets = [] 63 | 64 | def parse(self, data): 65 | pb = callret_analysis_results() 66 | pb.ParseFromString(data) 67 | 68 | for data in pb.values: 69 | ret = RetInfo(data.ret_addr, data.status, data.labels, data.returnsites, data.solve_count) 70 | for call in data.calls: 71 | ret.add_call(call.addr, call.status) 72 | self.rets.append(ret) 73 | 74 | def get_ok_viol(self): 75 | ok = 0 76 | viol = 0 77 | for ret in self.rets: 78 | if ret.is_tampering(): 79 | viol += 1 80 | else: 81 | ok += 1 82 | return ok, viol 83 | 84 | def __iter__(self): 85 | return iter(self.rets) 86 | 87 | 88 | # ===================================== ANALYSIS ======================================= 89 | # ====================================================================================== 90 | 91 | class CallRetAnalysis(DefaultAnalysis): 92 | 93 | config_widget = StandardParamConfigWidget() 94 | name = "callret" 95 | 96 | ANNOT_CODE = "Annotate code" 97 | GENERATE_PLOT = "Generate plot chart" 98 | 99 | def __init__(self, parent, config, is_stream=False, trace=None): 100 | DefaultAnalysis.__init__(self, parent, config, is_stream, trace) 101 | self.actions = {self.ANNOT_CODE: (self.annotate_code, False), 102 | self.GENERATE_PLOT: (self.generate_chart, False)} 103 | self.results = CallRetResults() 104 | self.result_widget = StandardResultWidget(self) 105 | 106 | def binsec_message_received(self, cmd, data): 107 | if cmd == ANALYSIS_RESULTS: 108 | print "Analysis results received !" 109 | self.results.parse(data) 110 | else: 111 | self.log(cmd, data, origin="BINSEC") 112 | 113 | def analysis_terminated(self): 114 | self.result_widget.action_selector.setEnabled(True) 115 | self.result_widget.action_button.setEnabled(True) 116 | report = HTMLReport() 117 | report.add_title("Call stack results", size=2) 118 | report.add_table_header(['address', "status", "hit count", "labels", "return addresses", "calls"]) 119 | for ret in self.results: 120 | addr = make_cell("%x" % ret.addr) 121 | status = make_cell(ret.get_status(), bold=ret.is_tampering(), color=RED if ret.is_tampering() else GREEN) 122 | labels_s = make_cell(''.join(["[%s]" % x for x in ret.get_labels()])) 123 | return_s = make_cell(''.join(["%x," % x for x in ret.returnsites])[:-1]) 124 | call_s = make_cell(''.join(["%x:%s
" % (x[0], to_status_name(x[1])) for x in ret.calls])[:-5]) 125 | report.add_table_line([addr, status, make_cell(str(ret.solve_count)), labels_s, return_s, call_s]) 126 | report.end_table() 127 | data = report.generate() 128 | self.result_widget.webview.setHtml(data) 129 | 130 | def annotate_code(self, enabled): 131 | for ret_data in self.results: 132 | addr = ret_data.addr 133 | if not enabled: # Set the comment 134 | status_s = ret_data.get_status() 135 | labels_s = ''.join(["[%s]" % x for x in ret_data.get_labels()]) 136 | comment = "Status:%s %s" % (status_s, labels_s) 137 | if ret_data.is_tampering(): 138 | comment += ' Ret:%s' % str(["%x" % x for x in ret_data.returnsites]) 139 | idc.MakeRptCmt(addr, comment) 140 | else: # Remove the comment 141 | idc.MakeRptCmt(addr, "") 142 | 143 | self.actions[self.ANNOT_CODE] = (self.annotate_code, not enabled) 144 | self.result_widget.action_selector_changed(self.ANNOT_CODE) 145 | 146 | def generate_chart(self, _): 147 | try: 148 | import plotly 149 | import plotly.graph_objs as go 150 | data = [[0, 0, 0], [0, 0, 0]] 151 | ok, viol = self.results.get_ok_viol() 152 | x = ["OK (%d)" % ok, "Tampering (%d)" % viol] 153 | for ret in self.results: 154 | i = 1 if ret.is_tampering() else 0 155 | data[i][0] += ret.is_aligned() 156 | data[i][1] += ret.is_disaligned() 157 | data[i][2] += ret.is_single() 158 | final_data = [go.Bar(x=x, y=[x[0] for x in data], name="Aligned"), go.Bar(x=x, y=[x[1] for x in data], name="Disaligned"), go.Bar(x=x, y=[x[2] for x in data], name="Single")] 159 | fig = go.Figure(data=final_data, layout=go.Layout(barmode='group', title='Call stack tampering labels')) 160 | plotly.offline.plot(fig, output_type='file', include_plotlyjs=True, auto_open=True) 161 | except ImportError: 162 | self.log("ERROR", "Plotly module not available") 163 | -------------------------------------------------------------------------------- /idasec/analysis/default_analysis.py: -------------------------------------------------------------------------------- 1 | from PyQt5 import QtWidgets 2 | 3 | from idasec.network.broker import Broker 4 | from idasec.network.commands import * 5 | from idasec.trace import raw_parse_trace 6 | 7 | STATIC = 0 8 | DYNAMIC = 1 9 | STATIC_AND_DYNAMIC = 2 10 | 11 | 12 | class DefaultAnalysis: 13 | config_widget = None 14 | kind = DYNAMIC 15 | name = "Default" 16 | 17 | @staticmethod 18 | def on_analysis_selected(widget): 19 | pass 20 | 21 | def __init__(self, parent, config, is_stream=False, trace=None): 22 | self.parent = parent 23 | self.configuration = config 24 | self.broker = Broker() 25 | self.result_widget = QtWidgets.QTextEdit() 26 | self.is_stream = is_stream 27 | self.trace_input_generator = None 28 | self.analyse_finished = False 29 | self.trace_finished = False 30 | if not is_stream and trace is not None: 31 | self.trace_input_generator = raw_parse_trace(trace.filename) 32 | 33 | def run(self): 34 | self.log("LOG", "Start analysis:"+self.name) 35 | raw_conf = self.configuration.SerializeToString() 36 | self.broker.send_binsec_message(START_ANALYSIS, raw_conf) 37 | 38 | for origin, cmd, data in self.broker.run_broker_loop_generator(): 39 | QtWidgets.QApplication.processEvents() 40 | 41 | if not self.is_stream: 42 | self.send_trace_chunk_if_any() 43 | 44 | if origin == BINSEC: 45 | if cmd == "END": 46 | self.analyse_finished = True 47 | self.analysis_terminated() 48 | break 49 | else: 50 | self.binsec_message_received(cmd, data) 51 | elif origin == PINSEC: 52 | self.pinsec_message_received(cmd, data) 53 | 54 | def send_trace_chunk_if_any(self): 55 | if not self.trace_finished: 56 | try: 57 | trace_cmd, trace_data = self.trace_input_generator.next() 58 | self.broker.send_binsec_message(trace_cmd, trace_data) 59 | except StopIteration: 60 | self.trace_finished = True 61 | self.log("LOG", "All the trace sent") 62 | self.broker.send_binsec_message(END, EMPTY) 63 | 64 | def pinsec_message_received(self, cmd, data): 65 | self.log("LOG", "Message received:%s" % cmd, origin="PINSEC") 66 | self.broker.send_binsec_message(cmd, data) 67 | 68 | def binsec_message_received(self, cmd, data): 69 | self.result_widget.append("Binsec message received:%s -> %s" % (cmd, data)) 70 | if cmd in ["PATCH_ZF", "RESUME"]: 71 | self.broker.send_pinsec_message(cmd, data) 72 | 73 | def analysis_terminated(self): 74 | self.log("LOG", "Analysis %s terminated" % self.name) 75 | self.broker.terminate() 76 | 77 | def stop(self): 78 | self.log("LOG", "Close analysis %s" % self.name) 79 | if not self.analyse_finished: 80 | self.broker.send_binsec_message(EXIT, EMPTY) 81 | self.broker.terminate() 82 | 83 | def log(self, typ, message, origin="IDASec"): 84 | self.parent.log(typ, message, origin=origin) 85 | -------------------------------------------------------------------------------- /idasec/analysis/generic_analysis.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import cgi 4 | import subprocess 5 | from PyQt5 import QtWidgets 6 | 7 | import idasec.utils as utils 8 | import idc 9 | from idasec.analysis.default_analysis import DefaultAnalysis 10 | from idasec.formula import SMTFormula 11 | from idasec.network.commands import * 12 | from idasec.proto.analysis_config_pb2 import generic_analysis, generic_analysis_results, specific_parameters_t 13 | from idasec.proto.common_pb2 import * 14 | from idasec.report_generator import * 15 | from idasec.ui.generic_analysis_result_ui import Ui_generic_analysis_result 16 | from idasec.ui.generic_analysis_ui import Ui_generic_analysis_widget 17 | 18 | 19 | # ======================== RESULT CLASS (pb dependant) ======================== 20 | def smtpb_to_result(val): 21 | return {SAT: ("SAT", GREEN), UNKNOWN: ("UNKNOWN", PURPLE), UNSAT: ("UNSAT", RED), TIMEOUT: ("TIMEOUT", BLUE)}[val] 22 | 23 | 24 | class GenericResults: 25 | def __init__(self, params): 26 | # -- params 27 | self.query = params.dba 28 | self.from_addr, self.to_addr = params.from_addr, params.to_addr 29 | self.get_formula = params.get_formula 30 | self.target = params.target_addr 31 | 32 | # -- results 33 | self.values = [] 34 | self.status = None 35 | self.color = None 36 | self.formula = '"' 37 | 38 | def parse(self, data): 39 | res = generic_analysis_results() 40 | res.ParseFromString(data) 41 | for v in res.values: 42 | self.values.append(v) 43 | self.formula = res.smt_formula 44 | self.status, self.color = smtpb_to_result(res.result) 45 | 46 | def has_formula(self): 47 | return self.get_formula and self.formula != "" 48 | 49 | def has_values(self): 50 | return self.values != [] 51 | 52 | def get_status(self): 53 | return self.status 54 | 55 | 56 | # ================================ CONFIG CLASS ===================================== 57 | # ==================================================================================== 58 | class GenericAnalysisConfigWidget(QtWidgets.QWidget, Ui_generic_analysis_widget): 59 | 60 | def __init__(self): 61 | super(GenericAnalysisConfigWidget, self).__init__() 62 | self.conf = generic_analysis() 63 | self.setupUi(self) 64 | self.set_visbility_stuff(False) 65 | self.satisfiability_radiobutton.setChecked(True) 66 | self.from_button.clicked.connect(self.from_button_clicked) 67 | self.to_button.clicked.connect(self.to_button_clicked) 68 | self.restrict_from_button.clicked.connect(self.restrict_from_button_clicked) 69 | self.restrict_to_button.clicked.connect(self.restrict_to_button_clicked) 70 | self.target_addr_button.clicked.connect(self.target_addr_button_clicked) 71 | self.dba_help_button.clicked.connect(self.dba_help_button_clicked) 72 | self.values_radiobutton.toggled.connect(self.values_radiobutton_toggled) 73 | 74 | def set_fields(self, json_fields): 75 | gen = json_fields["generic_params"] 76 | if "target_addr" in gen: 77 | self.target_addr_field.setText(hex(gen["target_addr"])) 78 | if "dba" in gen: 79 | self.dba_expr_field.setText(gen["dba"]) 80 | if "limit_values" in gen: 81 | self.values_limit_spinbox.setValue(gen['limit_values']) 82 | if "get_formula" in gen: 83 | self.get_formula_checkbox.setChecked(gen["get_formula"]) 84 | if "from_addr" in gen: 85 | self.from_field.setText(hex(gen["from_addr"])) 86 | if "to_addr" in gen: 87 | self.to_field.setText(hex(gen["to_addr"])) 88 | if "restrict_values_from" in gen: 89 | self.restrict_from_field.setText(hex(gen["restrict_values_from"])) 90 | if "restrict_values_to" in gen: 91 | self.restrict_to_field.setText(hex(gen['restrict_values_to'])) 92 | if "kind" in gen: 93 | if gen["kind"] == "VALUES": 94 | self.values_radiobutton.setChecked(True) 95 | else: 96 | self.satisfiability_radiobutton.setChecked(True) 97 | 98 | def serialize(self): 99 | from_field, to_field = self.from_field.text(), self.to_field.text() 100 | target_addr = self.target_addr_field.text() 101 | restrict_from, restrict_to = self.restrict_from_field.text(), self.restrict_to_field.text() 102 | try: 103 | if from_field != "": 104 | self.conf.from_addr = utils.to_addr(from_field) 105 | if to_field != "": 106 | self.conf.to_addr = utils.to_addr(to_field) 107 | if target_addr != "": 108 | self.conf.target_addr = utils.to_addr(target_addr) 109 | else: 110 | print "Target address is mandatory for generic analysis" 111 | return None 112 | if restrict_from != "": 113 | self.conf.restrict_values_from = utils.to_addr(restrict_from) 114 | if restrict_to != "": 115 | self.conf.restrict_values_to = utils.to_addr(restrict_to) 116 | except ValueError: 117 | print "Invalid values for either from/to or target address" 118 | 119 | dba_expr = self.dba_expr_field.text() 120 | if dba_expr == "": 121 | print "DBA Expr field must be filled !" 122 | return None 123 | else: 124 | self.conf.dba = dba_expr 125 | 126 | if self.satisfiability_radiobutton.isChecked(): 127 | self.conf.kind = self.conf.SATISFIABILITY 128 | 129 | if self.values_radiobutton.isChecked(): 130 | self.conf.kind = self.conf.VALUES 131 | self.conf.limit_values = self.values_limit_spinbox.value() 132 | 133 | if self.get_formula_checkbox.isChecked(): 134 | self.conf.get_formula = True 135 | 136 | try: 137 | params = specific_parameters_t() 138 | params.typeid = params.GENERIC 139 | params.generic_params.CopyFrom(self.conf) 140 | return params 141 | except: 142 | print "Analysis specific arguments serialization failed" 143 | return None 144 | 145 | def from_button_clicked(self): 146 | self.from_field.setText(hex(idc.here())) 147 | 148 | def to_button_clicked(self): 149 | self.to_field.setText(hex(idc.here())) 150 | 151 | def restrict_from_button_clicked(self): 152 | self.restrict_from_field.setText(hex(idc.here())) 153 | 154 | def restrict_to_button_clicked(self): 155 | self.restrict_to_field.setText(hex(idc.here())) 156 | 157 | def target_addr_button_clicked(self): 158 | ea = idc.here() 159 | self.target_addr_field.setText(hex(ea)) 160 | cmt = idc.RptCmt(ea) 161 | if cmt is not None: 162 | if cmt.startswith("//@assert:"): 163 | expr = cmt.split(":")[1].lstrip() 164 | self.dba_expr_field.setText(expr) 165 | 166 | @staticmethod 167 | def dba_help_button_clicked(): 168 | s = ''' 169 | All the expression usable are: 170 | - cst: val, val, hexa 171 | - var: eax, al .. 172 | - load/store: @[addr], @[addr,size] 173 | - unary: !e, -e 174 | - binary: e1 bop e2 175 | - restrict: {e, low, high} 176 | - ite: if c e1 else e2 177 | 178 | With: 179 | - uop: [-, !(not)] 180 | - bop: [+, -, *u, *s, /, /s, modu, mods, or, and, xor, >>(concat), lshift, rshiftu, 181 | rshifts, lrotate, rrotate, =, <>, <=u, =u, >u, <=s, =s, >s, extu, exts] 182 | ''' 183 | QtWidgets.QMessageBox.about(None, u"DBA langage help", unicode(s)) 184 | 185 | def values_radiobutton_toggled(self, toggled): 186 | if toggled: 187 | self.set_visbility_stuff(True) 188 | else: 189 | self.set_visbility_stuff(False) 190 | 191 | def set_visbility_stuff(self, value): 192 | self.values_limit_spinbox.setVisible(value) 193 | self.restrict_label.setVisible(value) 194 | self.restrict_from_label.setVisible(value) 195 | self.restrict_from_field.setVisible(value) 196 | self.restrict_from_button.setVisible(value) 197 | self.restrict_to_label.setVisible(value) 198 | self.restrict_to_field.setVisible(value) 199 | self.restrict_to_button.setVisible(value) 200 | 201 | 202 | # ================================= GENERIC ANALYSIS ================================= 203 | # ==================================================================================== 204 | 205 | class GenericAnalysis(DefaultAnalysis): 206 | 207 | config_widget = GenericAnalysisConfigWidget() 208 | name = "Generic" 209 | 210 | ANNOT_CODE = "Annotate code" 211 | HIGHLIGHT_CODE = "Highlight dependencies" 212 | GRAPH_DEPENDENCY = "Generate dependency graph" 213 | DISASS_UNKNOWN_TARGET = "Disassemble unknown targets" 214 | 215 | def __init__(self, parent, config, is_stream=False, trace=None): 216 | DefaultAnalysis.__init__(self, parent, config, is_stream, trace) 217 | self.results = GenericResults(config.additional_parameters.generic_params) 218 | self.result_widget = GenericAnalysisResultWidget(self) 219 | self.actions = {self.ANNOT_CODE: (self.annotate_code, False), 220 | self.HIGHLIGHT_CODE: (self.highlight_dependency, False), 221 | self.GRAPH_DEPENDENCY: (self.graph_dependency, False), 222 | self.DISASS_UNKNOWN_TARGET: (self.disassemble_new_targets, False)} 223 | self.addresses_lighted = set() 224 | self.backup_comment = {} 225 | self.formula = SMTFormula() 226 | 227 | def binsec_message_received(self, cmd, data): 228 | if cmd == ANALYSIS_RESULTS: 229 | print "Analysis results received !" 230 | self.results.parse(data) 231 | else: 232 | self.log(cmd, data, origin="BINSEC") 233 | 234 | def analysis_terminated(self): 235 | self.result_widget.post_analysis_stuff(self.results) 236 | if self.results.has_formula(): 237 | self.formula.parse(self.results.formula) 238 | 239 | def annotate_code(self, enabled): 240 | if not enabled: # Annotate 241 | s = ":["+self.results.get_status()+"]" 242 | if self.results.has_values(): 243 | s += " vals:["+''.join(["%x," % x for x in self.results.values])[:-1] + "]" 244 | cmt = idc.RptCmt(self.results.target) 245 | if cmt != "": 246 | self.backup_comment[self.results.target] = cmt 247 | if cmt.startswith("//@assert"): 248 | s = cmt + s 249 | else: 250 | s = cmt + "\n" + self.results.query + s 251 | else: 252 | s = self.results.query + s 253 | self.backup_comment[self.results.target] = "" 254 | idc.MakeRptCmt(self.results.target, s.encode("utf-8", "ignore")) 255 | else: 256 | for addr, cmt in self.backup_comment.items(): 257 | idc.MakeRptCmt(addr, cmt) 258 | self.backup_comment.clear() 259 | self.actions[self.ANNOT_CODE] = (self.annotate_code, not enabled) 260 | self.result_widget.action_selector_changed(self.ANNOT_CODE) 261 | 262 | def highlight_dependency(self, enabled): 263 | if self.results.has_formula(): 264 | color = 0xffffff if enabled else 0x98FF98 265 | for addr in self.formula.get_addresses(): 266 | idc.SetColor(addr, idc.CIC_ITEM, color) 267 | else: 268 | print "woot ?" 269 | self.actions[self.HIGHLIGHT_CODE] = (self.highlight_dependency, not enabled) 270 | self.result_widget.action_selector_changed(self.HIGHLIGHT_CODE) 271 | 272 | def graph_dependency(self, _): 273 | output = "/tmp/slice_rendered" 274 | self.formula.slice(output) 275 | res = subprocess.call(["dot", "-Tpdf", output, "-o", output+".pdf"]) 276 | if res != 0: 277 | print "Something went wrong with dot" 278 | subprocess.Popen(["xdg-open", output+".pdf"]) 279 | 280 | def disassemble_new_targets(self, _): 281 | for value in self.results.values: 282 | flag = idc.GetFlags(value) 283 | if not idc.isCode(flag) and idc.isUnknown(flag): 284 | res = idc.MakeCode(value) 285 | if res == 0: 286 | print "Try disassemble at:"+hex(value)+" KO" 287 | # TODO: Rollback ? 288 | else: 289 | print "Try disassemble at:"+hex(value)+" Success !" 290 | 291 | 292 | # ============================= RESULT WIDGET =============================== 293 | # =========================================================================== 294 | class GenericAnalysisResultWidget(QtWidgets.QWidget, Ui_generic_analysis_result): 295 | def __init__(self, parent): 296 | QtWidgets.QWidget.__init__(self) 297 | self.setupUi(self) 298 | self.parent = parent 299 | # self.result_area.setEnabled(False) 300 | if self.parent.results.get_formula: 301 | self.formula_label.setVisible(True) 302 | self.formula_area.setEnabled(True) 303 | else: 304 | self.formula_label.setVisible(False) 305 | self.formula_area.setVisible(False) 306 | self.action_selector.setEnabled(False) 307 | self.action_button.setEnabled(False) 308 | self.action_selector.addItem(self.parent.ANNOT_CODE) 309 | self.action_button.clicked.connect(self.action_clicked) 310 | self.action_selector.currentIndexChanged[str].connect(self.action_selector_changed) 311 | 312 | def action_selector_changed(self, s): 313 | _, enabled = self.parent.actions[s] 314 | if enabled: 315 | self.action_button.setText("Undo !") 316 | else: 317 | self.action_button.setText("Do !") 318 | 319 | def action_clicked(self): 320 | s = self.action_selector.currentText() 321 | fun, enabled = self.parent.actions[s] 322 | fun(enabled) 323 | 324 | def post_analysis_stuff(self, results): 325 | if results.has_formula(): 326 | self.action_selector.addItem(self.parent.HIGHLIGHT_CODE) 327 | self.action_selector.addItem(self.parent.GRAPH_DEPENDENCY) 328 | self.formula_area.setText(self.parent.results.formula) 329 | if results.has_values(): 330 | self.action_selector.addItem(self.parent.DISASS_UNKNOWN_TARGET) 331 | self.action_selector.setEnabled(True) 332 | self.action_button.setEnabled(True) 333 | 334 | report = HTMLReport() 335 | report.add_title("Results", size=3) 336 | report.add_table_header(["address", "assertion", "status", "values"]) 337 | addr = make_cell("%x" % results.target) 338 | status = make_cell(results.get_status(), color=results.color, bold=True) 339 | vals = "" 340 | for value in results.values: 341 | flag = idc.GetFlags(value) 342 | typ = self.type_to_string(flag) 343 | vals += "%x type:%s seg:%s fun:%s
" % (value, typ, idc.SegName(value), idc.GetFunctionName(value)) 344 | report.add_table_line([addr, make_cell(cgi.escape(results.query)), status, make_cell(vals)]) 345 | report.end_table() 346 | data = report.generate() 347 | self.result_area.setHtml(data) 348 | 349 | @staticmethod 350 | def type_to_string(t): 351 | if idc.isCode(t): 352 | return "C" 353 | elif idc.isData(t): 354 | return "D" 355 | elif idc.isTail(t): 356 | return "T" 357 | elif idc.isUnknown(t): 358 | return "Ukn" 359 | else: 360 | return "Err" 361 | -------------------------------------------------------------------------------- /idasec/analysis/opaque_analysis.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from collections import namedtuple 4 | 5 | import idautils 6 | 7 | import idc 8 | from idasec.analysis.default_analysis import DefaultAnalysis 9 | from idasec.network.commands import * 10 | from idasec.proto.analysis_config_pb2 import po_analysis_results 11 | from idasec.report_generator import make_cell, RED, GREEN, PURPLE, HTMLReport 12 | from idasec.widgets.StandardParamWidget import StandardParamConfigWidget 13 | from idasec.widgets.StandardResultWidget import StandardResultWidget 14 | 15 | 16 | # ============================= RESULT CLASS (pb dependant) ========================== 17 | def to_status_name(x): 18 | return {po_analysis_results.UNKNOWN: "Unknown", 19 | po_analysis_results.NOT_OPAQUE: "Covered", 20 | po_analysis_results.OPAQUE: "Opaque", 21 | po_analysis_results.LIKELY: "Unknown"}[x] 22 | 23 | 24 | def to_alive_branch(status, branch): 25 | return {po_analysis_results.UNKNOWN: "/", 26 | po_analysis_results.NOT_OPAQUE: "*", 27 | po_analysis_results.OPAQUE: "%x" % branch, 28 | po_analysis_results.LIKELY: "/"}[status] 29 | 30 | 31 | def status_to_color(x): 32 | return {po_analysis_results.UNKNOWN: PURPLE, 33 | po_analysis_results.LIKELY: PURPLE, 34 | po_analysis_results.NOT_OPAQUE: GREEN, 35 | po_analysis_results.OPAQUE: RED}[x] 36 | 37 | AddrRet = namedtuple("AddrRet", "status nb_paths alive_branch") 38 | 39 | 40 | class POResults(dict): 41 | def __init__(self): 42 | super(POResults, self).__init__() 43 | self.k = 0 44 | 45 | def parse(self, data): 46 | pb = po_analysis_results() 47 | pb.ParseFromString(data) 48 | 49 | for data in pb.values: 50 | addr = data.jmp_addr 51 | self.k = data.ksteps 52 | self.__setitem__(addr, AddrRet(data.status, data.nb_paths, data.alive_branch)) 53 | 54 | # ===================================== ANALYSIS ======================================= 55 | # ====================================================================================== 56 | 57 | 58 | class OpaqueAnalysis(DefaultAnalysis): 59 | 60 | config_widget = StandardParamConfigWidget() 61 | name = "opaque" 62 | 63 | ANNOT_CODE = "Annotate code" 64 | HIGHLIGHT_DEAD_BRANCHES = "Highlight dead branches" 65 | 66 | @staticmethod 67 | def on_analysis_selected(widget): 68 | widget.direction_selector_changed("Backward") 69 | 70 | def __init__(self, parent, config, is_stream=False, trace=None): 71 | DefaultAnalysis.__init__(self, parent, config, is_stream, trace) 72 | self.actions = {self.ANNOT_CODE: (self.annotate_code, False), 73 | self.HIGHLIGHT_DEAD_BRANCHES: (self.highlight_dead, False)} 74 | self.results = POResults() 75 | self.marked_addresses = {} 76 | self.result_widget = StandardResultWidget(self) 77 | 78 | def binsec_message_received(self, cmd, data): 79 | if cmd == ANALYSIS_RESULTS: 80 | print "Analysis results received !" 81 | self.results.parse(data) 82 | else: 83 | self.log(cmd, data, origin="BINSEC") 84 | 85 | def analysis_terminated(self): 86 | self.result_widget.action_selector.setEnabled(True) 87 | self.result_widget.action_button.setEnabled(True) 88 | report = HTMLReport() 89 | report.add_title("Opaque predicates results k="+(str(self.results.k)), size=3) 90 | report.add_table_header(['address', "status", "nb path(tested)", "alive branch"]) 91 | for addr in sorted(self.results.keys()): 92 | infos = self.results[addr] 93 | addr = make_cell("%x" % addr) 94 | status, color = to_status_name(infos.status), status_to_color(infos.status) 95 | status = make_cell(status, bold=True, color=color) 96 | alive_br = to_alive_branch(infos.status, infos.alive_branch) 97 | report.add_table_line([addr, status, make_cell(str(infos.nb_paths)), make_cell(alive_br)]) 98 | # TODO: Compute the number of possible paths for each predicate 99 | report.end_table() 100 | data = report.generate() 101 | self.result_widget.webview.setHtml(data) 102 | 103 | def annotate_code(self, enabled): 104 | for addr, infos in self.results.items(): 105 | if not enabled: 106 | status = to_status_name(infos.status) 107 | idc.MakeRptCmt(addr, status) 108 | else: 109 | idc.MakeRptCmt(addr, "") 110 | self.actions[self.ANNOT_CODE] = (self.annotate_code, not enabled) 111 | self.result_widget.action_selector_changed(self.ANNOT_CODE) 112 | 113 | @staticmethod 114 | def make_po_pair(ea, alive): 115 | dead = [x for x in idautils.CodeRefsFrom(ea, True) if x != alive] 116 | return alive, dead[0] 117 | 118 | def highlight_dead(self, enabled): 119 | opaque_map = {k: self.make_po_pair(k, v.alive_branch) for k, v in self.results.items() 120 | if v.status == po_analysis_results.OPAQUE} 121 | for addr, (good, dead) in opaque_map.items(): 122 | if not enabled: # Mark instructions 123 | print "propagate dead branch:%x" % addr 124 | self.propagate_dead_code(dead, opaque_map) 125 | else: 126 | for addr2 in self.marked_addresses.keys(): 127 | idc.SetColor(addr2, idc.CIC_ITEM, 0xffffff) 128 | self.marked_addresses.clear() 129 | self.actions[self.HIGHLIGHT_DEAD_BRANCHES] = (self.highlight_dead, not enabled) 130 | self.result_widget.action_selector_changed(self.HIGHLIGHT_DEAD_BRANCHES) 131 | 132 | @staticmethod 133 | def dead_br_of_op(ea, pred, op_map): 134 | if pred in op_map: 135 | good, bad = op_map[pred] 136 | return ea == bad 137 | else: 138 | return False 139 | 140 | def propagate_dead_code(self, ea, op_map): 141 | prevs = [x for x in idautils.CodeRefsTo(ea, True) if x not in self.marked_addresses and 142 | not self.dead_br_of_op(ea, x, op_map)] 143 | if prevs: # IF there is no legit predecessors 144 | idc.SetColor(ea, idc.CIC_ITEM, 0x0000ff) 145 | self.marked_addresses[ea] = None 146 | succs = [x for x in idautils.CodeRefsFrom(ea, True)] 147 | for succ in succs: 148 | self.propagate_dead_code(succ, op_map) 149 | else: 150 | return 151 | -------------------------------------------------------------------------------- /idasec/configuration_file.py: -------------------------------------------------------------------------------- 1 | """ 2 | Author: Robin David (robin.david@cea.fr) 3 | 4 | Binsec is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | any later version http://www.gnu.org/licenses/. 8 | """ 9 | 10 | import json 11 | 12 | from idasec.exception import assert_ida_available 13 | from idasec.proto.config_pb2 import configuration 14 | from idasec.protobuf_json import json2pb, pb2json 15 | 16 | 17 | PE = "\x4d\x5a" 18 | ELF = "\x7fE" 19 | 20 | 21 | class Configuration: 22 | def __init__(self): 23 | self.config = configuration() 24 | 25 | def set_start_stop(self, ftype): 26 | assert_ida_available() 27 | import idc 28 | import idaapi 29 | import idautils 30 | fun_mapping = {idc.GetFunctionName(x): (idaapi.get_func(x).startEA, idaapi.get_func(x).endEA-1) 31 | for x in idautils.Functions()} 32 | start = idc.BeginEA() 33 | stop = 0 34 | if ftype == PE: 35 | start, stop = fun_mapping["start"] 36 | else: 37 | if not idc.isCode(idc.GetFlags(start)): 38 | if idc.MakeCode(start) == 0: 39 | print "Fail to decode instr !" 40 | idaapi.autoWait() 41 | if idc.GetFunctionName(start) == "": 42 | if idc.MakeFunction(start) == 0: 43 | print "Fail to create function !" 44 | idaapi.autoWait() 45 | fun_mapping = {idc.GetFunctionName(x): (idaapi.get_func(x).startEA, idaapi.get_func(x).endEA-1) 46 | for x in idautils.Functions()} 47 | 48 | if "main" in fun_mapping: 49 | start, stop = fun_mapping["main"] 50 | elif "start" in fun_mapping: 51 | if "__libc_start_main" in fun_mapping: 52 | instrs = list(idautils.FuncItems(fun_mapping["start"][0])) 53 | instrs.reverse() 54 | for inst in instrs: 55 | arg1 = idc.GetOperandValue(inst, 0) 56 | if idc.GetMnem(inst) == "push": 57 | start, stop = arg1, fun_mapping["start"][1] 58 | break 59 | else: 60 | start, stop = fun_mapping["start"] 61 | self.config.start, self.config.stop = start, stop 62 | 63 | def create_call_map(self, ftype): 64 | assert_ida_available() 65 | import idc 66 | import idautils 67 | seg_mapping = {idc.SegName(x): (idc.SegStart(x), idc.SegEnd(x)) for x in idautils.Segments()} 68 | imports = seg_mapping[".idata"] if ftype == PE else seg_mapping['.plt'] 69 | start, stop = seg_mapping[".text"] 70 | current = start 71 | while current <= stop: 72 | inst = current 73 | if idc.GetMnem(inst) in ["call", "jmp"]: 74 | value = idc.GetOperandValue(inst, 0) 75 | name = idc.GetOpnd(inst, 0) 76 | if imports[0] <= value <= imports[1]: 77 | entry = self.config.call_map.add() 78 | entry.address = inst 79 | entry.name = name 80 | current = idc.NextHead(current, stop) 81 | 82 | def from_file(self, filename): 83 | data = open(filename, "r").read() 84 | return self.from_string(data) 85 | 86 | def from_string(self, s): 87 | return json2pb(self.config, json.loads(s)) 88 | 89 | def to_json(self): 90 | return pb2json(self.config) 91 | 92 | def to_str(self): 93 | return json.dumps(self.to_json(), indent=2) 94 | 95 | def to_file(self, filename): 96 | f = open(filename, "w") 97 | f.write(self.to_str()) 98 | f.close() 99 | 100 | def __str__(self): 101 | return self.to_str() 102 | 103 | 104 | if __name__ == "__main__": 105 | import idaapi 106 | import idc 107 | idaapi.autoWait() 108 | FT = open(idaapi.get_input_file_path()).read(2) 109 | import os 110 | print os.getcwd() 111 | conf = Configuration() 112 | conf.set_start_stop(FT) 113 | conf.create_call_map(FT) 114 | if len(idc.ARGV) < 2: 115 | print conf.to_str() 116 | else: 117 | conf.to_file(idc.ARGV[1]) 118 | idc.Exit(0) 119 | -------------------------------------------------------------------------------- /idasec/dba.py: -------------------------------------------------------------------------------- 1 | from collections import namedtuple 2 | 3 | # Unary ops 4 | UMinus = 100 5 | Not = 101 6 | 7 | Little = 200 8 | Big = 201 9 | 10 | Near = 300 11 | Far = 301 12 | 13 | Call = 400 14 | Return = 401 15 | 16 | # Binary ops 17 | Plus = 500 18 | Minus = 501 19 | MulU = 502 20 | MulS = 503 21 | DivU = 504 22 | DivS = 505 23 | ModU = 506 24 | ModS = 507 25 | Or = 508 26 | And = 509 27 | Xor = 510 28 | Concat = 511 29 | Lshift = 512 30 | RshiftU = 513 31 | RshiftS = 514 32 | Lrotate = 515 33 | Rrotate = 516 34 | Equal = 517 35 | Diff = 518 36 | LeqU = 519 37 | LtU = 520 38 | GeqU = 521 39 | GtU = 522 40 | LeqS = 523 41 | LtS = 524 42 | GeqS = 525 43 | GtS = 526 44 | ExtU = 527 45 | ExtS = 528 46 | 47 | # bitvector are simply tuple 48 | Bv = namedtuple("Bitvector", "value size") 49 | Addr = namedtuple("DbaAddr", "addr offset") 50 | JmpAddr = namedtuple("DbaJmpAddr", "loc addr") # addr either offset or Addr 51 | 52 | # Expression 53 | Var = namedtuple("DbaExprVar", "name size") 54 | Load = namedtuple("DbaExprLoad", "expr size endian") 55 | UnOp = namedtuple("DbaExprUnary", "uop expr") 56 | BinOp = namedtuple("DbaExprBinary", "left bop right") 57 | Restrict = namedtuple("DbaExprRestrict", "expr low high") 58 | Ite = namedtuple("DbaExprIte", "cond expr1 expr2") 59 | Undef = namedtuple("DbaExprUndef", "") 60 | 61 | # Cond 62 | UnCond = namedtuple("DbaUnaryCond", "uop cond") 63 | BinCond = namedtuple("DbaBinaryCond", "cond1 bop cond2") 64 | 65 | # Lhs 66 | Store = namedtuple("DbaStore", "expr size endian") 67 | 68 | # Instr 69 | Assign = namedtuple("DbaIkAssign", "lhs expr") 70 | Jump = namedtuple("DbaJump", "target") 71 | If = namedtuple("DbaIf", "cond target1 target2") 72 | 73 | # Unusually used 74 | Stop = namedtuple("DbaStop", "") 75 | Assert = namedtuple("DbaAssert", "cond") 76 | Assume = namedtuple("DbaAssume", "cond") 77 | NonDet = namedtuple("DbaNonDet", "lhs") 78 | # Undef = namedtuple("DbaUndef", "lhs") 79 | Malloc = namedtuple("DbaMalloc", "lhs expr") 80 | Free = namedtuple("DbaFree", "expr") 81 | 82 | Instr = namedtuple("DbaInstr", "addr instr offset") 83 | 84 | 85 | class DbaException(Exception): 86 | pass 87 | 88 | 89 | def is_expression(e): 90 | return type(e) in [Bv, Var, Load, UnOp, BinOp, Restrict, Ite] 91 | 92 | 93 | def is_strict_condition(c): 94 | return type(c) in [bool, UnCond, BinCond] 95 | 96 | 97 | def dbaexpr_size(e): 98 | if type(e) in [Bv, Var, Load]: 99 | return e.size 100 | elif isinstance(e, UnOp): 101 | return dbaexpr_size(e.expr) 102 | elif isinstance(e, BinOp): 103 | return dbaexpr_size(e.left) 104 | elif isinstance(e, Restrict): 105 | return e.high - e.low+1 106 | elif isinstance(e, Ite): 107 | return dbaexpr_size(e.expr1) 108 | else: 109 | raise DbaException("Unknown expression type:", type(e)) 110 | -------------------------------------------------------------------------------- /idasec/dba_printer.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from idasec.dba_io import * 5 | 6 | def to_indice_str(s): 7 | matrix = ["₀", "₁", "₂", "₃", "₄", "₅", "₆", "₇", "₈", "₉"] 8 | ret = "" 9 | for c in s: 10 | val = int(c) 11 | ret += matrix[val] if 0 <= val <= 9 else "X" 12 | return ret 13 | 14 | 15 | def bitvector_to_string(bv): 16 | val = to_indice_str(str(bv.size)) 17 | return hex(bv.value)+"₍"+val+"₎" 18 | 19 | 20 | def dbacodeaddress_to_string(addr): 21 | return "("+bitvector_to_string(addr.addr)+","+str(addr.offset)+")" 22 | 23 | 24 | def codeaddress_to_string(addr): 25 | if addr.loc == Near: 26 | return str(addr.addr) 27 | elif addr.loc == Far: 28 | return dbacodeaddress_to_string(addr.addr) 29 | 30 | 31 | def binaryop_to_string(bop): 32 | return {Plus: " + ", Minus: " - ", MulU: " *𝒖 ", MulS: " *𝒔 ", DivU: " / ", DivS: " /𝒔 ", ModU: " mod𝒖 ", 33 | ModS: " mod𝒔 ", Or: " || ", And: " && ", Xor: " ⨁ ", Concat: " :: ", Lshift: " ≪ ", RshiftU: " ≫𝒖 ", 34 | RshiftS: " ≫𝒔 ", Lrotate: " lrotate ", Rrotate: " rrotate ", Equal: " = ", Diff: " ≠ ", LeqU: " ≤𝒖 ", 35 | LtU: " <𝒖 ", GeqU: " ≥𝒖 ", GtU: " >𝒖 ", LeqS: " ≤𝒔 ", LtS: " <𝒔 ", GeqS: " ≥𝒔 ", GtS: " >𝒔 ", 36 | ExtU: " ext𝒖 ", ExtS: " ext𝒔 "}[bop] 37 | 38 | 39 | def unaryop_to_string(op): 40 | return {UMinus: "-", Not: "¬"}[op] 41 | 42 | 43 | def endianess_to_string(en): 44 | return {Little: "𝐿", Big: "𝐵"}[en] 45 | 46 | 47 | def dbaexpr_to_string(e, toplevel=True): 48 | op = "" if toplevel else "(" 49 | oc = "" if toplevel else ")" 50 | if isinstance(e, Bv): 51 | return bitvector_to_string(e) 52 | elif isinstance(e, Var): 53 | return e.name 54 | elif isinstance(e, Load): 55 | return "@["+dbaexpr_to_string(e.expr, True)+"]"+endianess_to_string(e.endian)+to_indice_str(str(e.size)) 56 | elif isinstance(e, UnOp): 57 | return op+unaryop_to_string(e.uop)+dbaexpr_to_string(e.expr, False)+oc 58 | elif isinstance(e, BinOp): 59 | se1 = dbaexpr_to_string(e.left, False) 60 | se2 = dbaexpr_to_string(e.right, False) 61 | return op+se1+binaryop_to_string(e.bop)+se2+oc 62 | elif isinstance(e, Restrict): 63 | return op+dbaexpr_to_string(e.expr, False)+"{"+("" if e.low == e.high else str(e.low)+",")+str(e.high)+"}"+oc 64 | elif isinstance(e, Ite): 65 | return '%sif %s %s else %s%s' % (op, dbacond_to_string(e.cond), dbaexpr_to_string(e.expr1, False), 66 | dbaexpr_to_string(e.expr2, False), oc) 67 | elif isinstance(e, int): 68 | return str(e) 69 | else: 70 | return "INVALID" 71 | 72 | 73 | def dbacond_to_string(c): 74 | if isinstance(c, bool): 75 | return str(c) 76 | elif is_expression(c): 77 | return dbaexpr_to_string(c, True) 78 | elif isinstance(c, UnCond): 79 | if c.uop == Not: 80 | return "¬"+dbacond_to_string(c.cond) 81 | else: 82 | return "INVALID" 83 | elif isinstance(c, BinCond): 84 | if c.bop in [Or, And]: 85 | return dbacond_to_string(c.cond1)+" "+binaryop_to_string(c.bop)+" "+dbacond_to_string(c.cond2) 86 | else: 87 | return "INVALID" 88 | else: 89 | return "INVALID" 90 | 91 | 92 | def lhs_to_string(lhs): 93 | if isinstance(lhs, Var): 94 | return lhs.name 95 | elif isinstance(lhs, Store): 96 | return "@["+dbaexpr_to_string(lhs.expr, True)+"]"+endianess_to_string(lhs.endian)+to_indice_str(str(lhs.size)) 97 | else: 98 | return "INVALID" 99 | 100 | 101 | def instr_to_string(inst): 102 | if isinstance(inst.instr, Assign): 103 | if isinstance(inst.instr.expr, Undef): 104 | return lhs_to_string(inst.instr.lhs) + " := \undef" 105 | else: 106 | return lhs_to_string(inst.instr.lhs) + " := "+dbaexpr_to_string(inst.instr.expr, True) 107 | elif isinstance(inst.instr, Jump): 108 | if isinstance(inst.instr.target, JmpAddr): 109 | return "goto "+codeaddress_to_string(inst.instr.target) 110 | elif is_expression(inst.instr.target): 111 | return "goto "+dbaexpr_to_string(inst.instr.target) 112 | else: 113 | return "INVALID" 114 | elif isinstance(inst.instr, If): 115 | c1 = dbacond_to_string(inst.instr.cond) 116 | t1 = codeaddress_to_string(inst.instr.target1) 117 | t2 = str(inst.instr.target2) 118 | return u"if (%s) goto %s else %s" % (c1, t1, t2) 119 | else: 120 | return u"INVALID" 121 | 122 | 123 | if __name__ == "__main__": 124 | data = open("out2.dba", "rb").read() 125 | from proto import dba_pb2 126 | mylist = dba_pb2.dba_list() 127 | mylist.ParseFromString(data) 128 | l = parse_dbalist(mylist) 129 | for inst in l: 130 | print instr_to_string(inst) 131 | -------------------------------------------------------------------------------- /idasec/exception.py: -------------------------------------------------------------------------------- 1 | class IDARequired(Exception): 2 | pass 3 | 4 | 5 | def assert_ida_available(): 6 | try: 7 | import idc 8 | except ImportError: 9 | raise IDARequired() -------------------------------------------------------------------------------- /idasec/ida_utils.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | from enum import Enum 4 | from heapq import heappop, heappush 5 | 6 | import idautils 7 | import idaapi 8 | import idc 9 | 10 | 11 | def get_succs(ea): 12 | return [x for x in idautils.CodeRefsFrom(ea, True)] 13 | 14 | 15 | class BasicBlockNotFound(Exception): 16 | pass 17 | 18 | class Status(Enum): 19 | DEAD = 1 20 | ALIVE = 2 21 | UNKNOWN = 3 22 | 23 | 24 | class MyBasicBlock: 25 | def __init__(self, basicb): 26 | self._bb = basicb 27 | self.instrs = [] 28 | self.instrs_status = {} 29 | self._preds = [] 30 | self._succs = [] 31 | self.startEA = self._bb.startEA 32 | self.endEA = self._bb.endEA 33 | self.id = self._bb.id 34 | self._fill_instrs() 35 | self.status = Status.UNKNOWN 36 | 37 | def concat(self, bb): 38 | print "concat %d :: %d" % (self.id, bb.id) 39 | print " before instr:%d::%d status:%d::%d" % \ 40 | (len(self.instrs), len(bb.instrs), len(self.instrs_status), len(bb.instrs_status)) 41 | self.instrs.extend(bb.instrs) 42 | self.instrs_status.update(bb.instrs_status) 43 | print " before instr:%d::%d status:%d::%d" % \ 44 | (len(self.instrs), len(bb.instrs), len(self.instrs_status), len(bb.instrs_status)) 45 | self._succs = list(bb.succs()) 46 | self.endEA = bb.endEA 47 | for succ in bb.succs(): # Relocate 48 | succ.remove_pred(bb) 49 | succ.add_pred(self) 50 | if self.status != bb.status: 51 | print "Concat basic blocs with two different states" 52 | 53 | def reset_links(self): 54 | self._preds = [] 55 | self._succs = [] 56 | self._succs = [] 57 | 58 | def add_pred(self, bb): 59 | self._preds.append(bb) 60 | 61 | def add_succ(self, bb): 62 | self._succs.append(bb) 63 | 64 | def remove_pred(self, bb): 65 | self._preds.remove(bb) 66 | 67 | def remove_succ(self, bb): 68 | self._succs.remove(bb) 69 | 70 | def old_succs(self): 71 | return self._bb.succs() 72 | 73 | def succs(self): 74 | for s in self._succs: 75 | yield s 76 | 77 | def nb_succs(self): 78 | return len(self._succs) 79 | 80 | def preds(self): 81 | for p in self._preds: 82 | yield p 83 | 84 | def nb_preds(self): 85 | return len(self._preds) 86 | 87 | def __iter__(self): 88 | return iter(self.instrs) 89 | 90 | def iter_alive(self): 91 | return iter([x for x in self.instrs if self.instrs_status[x] == Status.ALIVE]) 92 | 93 | def _fill_instrs(self): 94 | cur_addr = self.startEA 95 | while cur_addr != idc.BADADDR: 96 | self.instrs.append(cur_addr) 97 | cur_addr = idc.NextHead(cur_addr, self.endEA) 98 | 99 | def size(self): 100 | return len(self.instrs) 101 | 102 | def last(self): 103 | return self.instrs[-1] 104 | 105 | def is_alive(self): 106 | return self.status == Status.ALIVE 107 | 108 | def is_dead(self): 109 | return self.status == Status.DEAD 110 | 111 | def is_unknown(self): 112 | return self.status == Status.UNKNOWN 113 | 114 | def is_full_spurious(self): 115 | return sum([self.instrs_status[x] != Status.DEAD for x in self.instrs]) == 0 116 | 117 | def set_instr_status(self, i, status): 118 | self.instrs_status[i] = status 119 | 120 | def dump(self): 121 | return '\n'.join([idc.GetDisasm(x) for x in self.instrs]) 122 | 123 | def dump_alive(self): 124 | return '\n'.join([idc.GetDisasm(x) for x in self.instrs if self.instrs_status[x] == Status.ALIVE]) 125 | 126 | 127 | class MyFlowGraph(dict): 128 | def __init__(self, fun_addr): 129 | super(MyFlowGraph, self).__init__() 130 | self.fun = idaapi.get_func(fun_addr) 131 | self.startEA = self.fun.startEA 132 | self.endEA = self.fun.endEA 133 | for bb in idaapi.FlowChart(self.fun): 134 | self.__setitem__(bb.id, MyBasicBlock(bb)) 135 | self._compute_links() 136 | self.edge_map = self.make_graph() 137 | self.shortest_path_map = self.dijkstra(self.edge_map) 138 | self.size = sum([x.size() for x in self.values()]) 139 | self.viewer = MyFlowGraphViewer(self, "Extract(%s)" % idc.GetFunctionName(self.startEA)) 140 | 141 | def reset(self): 142 | self._compute_links() 143 | 144 | def _compute_links(self): 145 | for bb in self.values(): 146 | bb.reset_links() 147 | for bb in self.values(): 148 | succs = [x.id for x in bb.old_succs()] 149 | for idx in succs: 150 | bb.add_succ(self.__getitem__(idx)) 151 | succ_bb = self.__getitem__(idx) 152 | succ_bb.add_pred(bb) 153 | 154 | def make_graph(self): 155 | graph = {k: [] for k in self.keys()} 156 | for bb in self.values(): 157 | size = bb.size() 158 | for succ in bb.succs(): 159 | graph[bb.id].append((size, succ.id)) 160 | return graph 161 | 162 | @staticmethod 163 | def dijkstra(graph): 164 | seen = set() # élément traités 165 | d = {0: 0} # distance map 166 | p = {} # path map 167 | worklist = [(0, 0)] # worklist d'éléments à traiter (distance, id) 168 | 169 | while worklist: # tant qu'il reste des éléments dans la worklist à traiter 170 | 171 | dx, x_id = heappop(worklist) # distance, and id 172 | if x_id in seen: # si l'élément est déjà traité on continue 173 | continue 174 | 175 | seen.add(x_id) # l'ajoute aux éléments traités 176 | 177 | for w, y in graph[x_id]: # itère successeurs du noeud traité 178 | if y in seen: # si le succ à déjà été traité continue 179 | continue 180 | dy = dx + w # pondération du succ 181 | if y not in d or d[y] > dy: # si succ n'est pas encore referencé ou new distance < alors update 182 | d[y] = dy # met à jour la distance pour succ dans la distance map 183 | heappush(worklist, (dy, y)) # met le succ dans la worklist (avec sa pondération) 184 | p[y] = x_id # met à jour le prédecesseur le plus court pour succ 185 | # TODO: Do something with orphan BB 186 | return p 187 | 188 | def bb_id_path_to(self, x): 189 | path = [x] 190 | tmp = x 191 | if tmp not in self.shortest_path_map: # In case of orphan BB 192 | return path 193 | while tmp != 0: 194 | tmp = self.shortest_path_map[tmp] 195 | path.insert(0, tmp) 196 | return path 197 | 198 | def get_basic_block(self, addr): 199 | if self.endEA < addr < self.startEA: 200 | raise BasicBlockNotFound() 201 | bb_addrs = {v.startEA: k for k, v in self.items() if v.startEA < addr} 202 | b_id = bb_addrs[max(bb_addrs.keys())] 203 | if addr in self.__getitem__(b_id).instrs: 204 | return b_id 205 | else: 206 | for b_id, bb in self.items(): 207 | if addr in bb.instrs: 208 | return b_id 209 | raise BasicBlockNotFound() 210 | 211 | def bb_path_to(self, addr): 212 | bb_id = self.get_basic_block(addr) 213 | return [x for x in self.__getitem__(bb_id) if x <= addr] 214 | 215 | def full_path_to(self, addr): 216 | bb_id = self.get_basic_block(addr) 217 | path = [] 218 | bb_path = self.bb_id_path_to(bb_id) 219 | for bb_id in bb_path[:-1]: 220 | path += self.__getitem__(bb_id).instrs 221 | path += self.bb_path_to(addr) 222 | return path 223 | 224 | def safe_path_to(self, addr): 225 | path = self.full_path_to(addr) # Start from the full path 226 | i = -1 227 | for ea, k in zip(path, range(len(path))): # Compute i such that it is safe 228 | nb_preds = len([x for x in idautils.CodeRefsTo(ea, True)]) 229 | if nb_preds > 1: 230 | i = k 231 | elif idc.GetDisasm(ea).startswith("call"): 232 | i = k+1 233 | print i 234 | if i == -1: 235 | return path 236 | else: 237 | return path[i:] 238 | 239 | def remove_dead_bb(self): 240 | for idx, bb in self.items(): 241 | if bb.status == Status.DEAD: 242 | for pred in bb.preds(): 243 | pred.remove_succ(bb) 244 | for succ in bb.succs(): 245 | succ.remove_pred(bb) 246 | self.pop(idx) 247 | 248 | def OnRefresh(self): 249 | self.viewer.OnRefresh() 250 | 251 | def OnGetText(self, node_id): 252 | self.viewer.OnGetText(node_id) 253 | 254 | def Show(self): 255 | self.viewer.Show() 256 | 257 | def __str__(self): 258 | return "" % self.startEA 259 | 260 | 261 | class MyFlowGraphViewer(idaapi.GraphViewer): 262 | 263 | def __init__(self, flow_graph, title): 264 | idaapi.GraphViewer.__init__(self, title) 265 | self.flow_graph = flow_graph 266 | self.result = None 267 | self.names = {} 268 | 269 | def OnRefresh(self): 270 | print 'refresh' 271 | self.Clear() 272 | self.make_cfg() 273 | return True 274 | 275 | def make_cfg(self): 276 | addr_id = {} 277 | for idx, bb in self.flow_graph.items(): 278 | addr_id[idx] = self.AddNode(bb.dump_alive()) 279 | 280 | for idx, bb in self.flow_graph.items(): 281 | for succ in bb.succs(): 282 | try: 283 | self.AddEdge(addr_id[idx], addr_id[succ.id]) 284 | except KeyError as e: 285 | print "Edge %d->%d (%d) not found" % (idx, succ.id, e.args[0]) 286 | 287 | def OnGetText(self, node_id): 288 | b = self[node_id] 289 | return str(b).lower() 290 | 291 | def OnSelect(self, node_id): 292 | return True 293 | 294 | def OnClick(self, node_id): 295 | return True 296 | 297 | def OnCommand(self, cmd_id): 298 | if self.cmd_test == cmd_id: 299 | print 'TEST!' 300 | return 301 | print "command:", cmd_id 302 | 303 | def Show(self): 304 | if not idaapi.GraphViewer.Show(self): 305 | return False 306 | self.cmd_test = self.AddCommand("Test", "F2") 307 | if self.cmd_test == 0: 308 | print "Failed to add popup menu item!" 309 | return True 310 | -------------------------------------------------------------------------------- /idasec/idasec_core.py: -------------------------------------------------------------------------------- 1 | from idasec.network.broker import Broker 2 | from idasec.proto.config_pb2 import configuration 3 | 4 | import idc 5 | import idaapi 6 | import idautils 7 | 8 | PE = "\x4d\x5a" 9 | ELF = "\x7fE" 10 | 11 | 12 | class IDASecCore: 13 | def __init__(self): 14 | self.broker = Broker() 15 | self.trace_id = 0 16 | self.traces = {} 17 | self.configuration = configuration() 18 | self.solvers = [] 19 | self.analyses = [] 20 | self.nb_cpus = 1 21 | self.binsec_connected = False 22 | self.pinsec_connected = False 23 | self.seg_mapping = None 24 | self.fun_mapping = None 25 | self.update_mapping() 26 | self.nb_instr = self.compute_nb_instr() 27 | self.ftype = "ELF" if open(idaapi.get_input_file_path()).read(2) == ELF else "PE" 28 | self.imports = self.compute_imports() 29 | 30 | def update_mapping(self): 31 | pass 32 | self.fun_mapping = {idc.GetFunctionName(x): (idaapi.get_func(x).startEA, idaapi.get_func(x).endEA-1) for x in 33 | idautils.Functions()} 34 | self.seg_mapping = {idc.SegName(x): (idc.SegStart(x), idc.SegEnd(x)) for x in idautils.Segments()} 35 | 36 | def add_trace(self, t): 37 | self.traces[self.trace_id] = t 38 | self.trace_id += 1 39 | return self.trace_id - 1 40 | 41 | def remove_trace(self, tr_id): 42 | t = self.traces.pop(tr_id) 43 | del t 44 | 45 | def compute_nb_instr(self): 46 | return 0 # FIXME: by iterating all segments 47 | count = 0 48 | start, stop = self.seg_mapping[".text"] # TODO: Iterate all executable segs 49 | current = start 50 | while current <= stop: 51 | if idc.isCode(idc.GetFlags(current)): 52 | count += 1 53 | current = idc.NextHead(current, stop) 54 | return count 55 | 56 | @staticmethod 57 | def compute_imports(): 58 | imports = {} 59 | current = "" 60 | 61 | def callback(ea, name, ordinal): 62 | imports[current].append((ea, name, ordinal)) 63 | return True 64 | 65 | nimps = idaapi.get_import_module_qty() 66 | for i in xrange(0, nimps): 67 | current = idaapi.get_import_module_name(i) 68 | imports[current] = [] 69 | idaapi.enum_import_names(i, callback) 70 | return imports 71 | -------------------------------------------------------------------------------- /idasec/network/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RobinDavid/idasec/e7d9328f9291dcadf34540a31608995b3697d047/idasec/network/__init__.py -------------------------------------------------------------------------------- /idasec/network/broker.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import json 3 | import random 4 | import struct 5 | from threading import Thread 6 | 7 | import zmq 8 | 9 | from idasec.dba_printer import * 10 | from idasec.network.commands import * 11 | from idasec.network.message import * 12 | from idasec.proto import config_pb2 13 | from idasec.protobuf_json import json2pb 14 | 15 | 16 | class Broker: 17 | def __init__(self, binsec_recv_cb=None, pinsec_recv_cb=None): 18 | self.context = zmq.Context.instance() 19 | self.pinsec_socket = self.context.socket(zmq.DEALER) 20 | self.binsec_socket = self.context.socket(zmq.DEALER) 21 | self.binsec_callback = binsec_recv_cb if binsec_recv_cb is not None else self.dispatch_from_binsec 22 | self.pinsec_callback = pinsec_recv_cb if pinsec_recv_cb is not None else self.disconnect_pinsec 23 | self.binsec_socket.identity = str(random.randint(0, sys.maxint)) 24 | self.th = None 25 | self.stop = False 26 | self.binsec_addr = "" 27 | self.pinsec_addr = "" 28 | 29 | def connect_pinsec(self, ip, port): 30 | self.pinsec_addr = "tcp://"+ip+":"+str(port) 31 | self.pinsec_socket.connect(self.pinsec_addr) 32 | 33 | def connect_binsec(self, ip, port): 34 | self.binsec_addr = "tcp://"+ip+":"+str(port) 35 | self.binsec_socket.connect(self.binsec_addr) 36 | 37 | def disconnect_binsec(self): 38 | self.binsec_socket.disconnect(self.binsec_addr) 39 | 40 | def disconnect_pinsec(self): 41 | self.pinsec_socket.disconnect(self.pinsec_addr) 42 | 43 | def bind_pin(self, port): 44 | self.pinsec_socket.bind("tcp://*:"+port) 45 | 46 | def bind_binsec(self, port): 47 | self.binsec_socket.bind("tcp://*:"+port) 48 | 49 | def run_broker_loop_thread(self): 50 | self.th = Thread(target=self.run_broker_loop) 51 | self.th.start() 52 | 53 | def run_broker_loop(self): 54 | for origin, cmd, data in self.run_broker_loop_generator(): 55 | if origin == "PINSEC": 56 | self.pinsec_callback(cmd, data) 57 | elif origin == "BINSEC": 58 | self.binsec_callback(cmd, data) 59 | else: 60 | pass 61 | 62 | def run_broker_loop_generator(self): 63 | poll = zmq.Poller() 64 | poll.register(self.binsec_socket, zmq.POLLIN) 65 | poll.register(self.pinsec_socket, zmq.POLLIN) 66 | # print "start iterating" 67 | try: 68 | while True: 69 | sockets = dict(poll.poll(timeout=100)) 70 | if sockets == {}: 71 | yield None, None, None 72 | if self.stop: 73 | print "Thread loop stop" 74 | break 75 | 76 | if self.pinsec_socket in sockets: 77 | cmd, data = self.pinsec_socket.recv_multipart() 78 | yield PINSEC, cmd, data 79 | # self.pinsec_callback(cmd, data) 80 | 81 | if self.binsec_socket in sockets: 82 | cmd, data = self.binsec_socket.recv_multipart() 83 | yield BINSEC, cmd, data 84 | # self.binsec_callback(cmd, msg) 85 | 86 | except KeyboardInterrupt: 87 | self.binsec_socket.close() 88 | self.pinsec_socket.close() 89 | 90 | def dispatch_from_pin(self, cmd, data): 91 | self.binsec_socket.send_multipart([cmd, data]) 92 | 93 | def dispatch_from_binsec(self, cmd, data): 94 | self.pinsec_socket.send_multipart([cmd, data]) 95 | 96 | def send_binsec_message(self, cmd, data, blocking=True): 97 | self.send_message(self.binsec_socket, cmd, data, blocking=blocking) 98 | 99 | def send_pinsec_message(self, cmd, data, blocking=True): 100 | self.send_message(self.pinsec_socket, cmd, data, blocking=blocking) 101 | 102 | @staticmethod 103 | def send_message(socket, cmd, data, blocking=True): 104 | flags = 0 if blocking else zmq.DONTWAIT 105 | socket.send_multipart([cmd, data], flags=flags) 106 | 107 | def receive_binsec_message(self, blocking=True): 108 | return self.receive_message(self.binsec_socket, blocking=blocking) 109 | 110 | def receive_pinsec_message(self, blocking=True): 111 | return self.receive_message(self.pinsec_socket, blocking=blocking) 112 | 113 | @staticmethod 114 | def receive_message(socket, blocking=True): 115 | flags = 0 if blocking else zmq.NOBLOCK 116 | try: 117 | cmd, data = socket.recv_multipart(flags=flags) 118 | return cmd, data 119 | except zmq.Again: 120 | return None, None 121 | except zmq.ContextTerminated: 122 | print("Context terminated ..") 123 | return None, None 124 | except KeyboardInterrupt: 125 | return None, None 126 | 127 | def terminate(self): 128 | if self.th is not None: 129 | self.stop = True 130 | self.binsec_socket.close() 131 | self.pinsec_socket.close() 132 | 133 | 134 | def decode_instr(opcode): 135 | mess = MessageDecodeInstr(kind="hexa", instrs=opcode, base_addrs=0) 136 | raw = mess.serialize() 137 | broker = Broker() 138 | broker.connect_binsec("localhost", "5570") 139 | broker.send_binsec_message("DECODE_INSTR", raw) 140 | cmd, data = broker.receive_binsec_message() 141 | print "CMD:", cmd, "data:", len(data) 142 | reply = MessageDecodeInstrReply() 143 | reply.parse(data) 144 | for opc, dbainsts in reply.instrs: 145 | print(opc) 146 | for i in dbainsts: 147 | print instr_to_string(i) 148 | broker.terminate() 149 | 150 | 151 | def load_config(name): 152 | f = open(name, "r") 153 | js = json.loads(f.read()) 154 | f.close() 155 | return json2pb(config_pb2.configuration(), js) 156 | 157 | 158 | class AnalysisBroker(Broker): 159 | def __init__(self): 160 | Broker.__init__(self) 161 | 162 | def dispatch_from_binsec(self, cmd, data): 163 | if cmd in ["PATCH_ZF", "RESUME"]: 164 | self.send_pinsec_message(cmd, data) 165 | else: 166 | print cmd, data 167 | 168 | 169 | def launch_full_proxy_analysis(conf_name): 170 | conf = load_config(conf_name) 171 | broker = AnalysisBroker() 172 | broker.connect_binsec("127.0.0.1", '5570') 173 | data = conf.SerializeToString() 174 | broker.send_binsec_message("START_ANALYSIS", data) 175 | broker.connect_pinsec("192.168.56.101", "5555") 176 | broker.run_broker_loop() 177 | 178 | 179 | def launch_analysis(trace_name, conf_name): 180 | conf = load_config(conf_name) 181 | broker = Broker() 182 | broker.connect_binsec("127.0.0.1", '5570') 183 | data = conf.SerializeToString() 184 | f = open("config_serialized.pb", "w") 185 | f.write(data) 186 | f.close() 187 | broker.send_binsec_message("START_ANALYSIS", data) 188 | f = open(trace_name, "rb") 189 | read_finished = False 190 | first_chunk = True 191 | i = 1 192 | while 1: 193 | cmd, data = broker.receive_binsec_message(read_finished) 194 | if cmd is not None and data is not None: 195 | print i, ":", cmd, repr(data) 196 | i += 1 197 | if cmd == "END": 198 | break 199 | if not read_finished: 200 | data = f.read(4) 201 | if data == "": 202 | read_finished = True 203 | broker.send_binsec_message("END", "STUB") 204 | f.close() 205 | else: 206 | size, = struct.unpack("I", data) 207 | data = f.read(size) 208 | if first_chunk: 209 | broker.send_binsec_message("TRACE_HEADER", data) 210 | first_chunk = False 211 | else: 212 | broker.send_binsec_message("TRACE_CHUNK", data) 213 | broker.terminate() 214 | 215 | 216 | def main(): 217 | if len(sys.argv) <= 1: 218 | print("Usage: ./broker.py COMMAND args") 219 | exit(1) 220 | command = sys.argv[1] 221 | if command == "broker": 222 | broker = Broker() 223 | broker.bind_binsec("5555") 224 | broker.connect_pinsec(sys.argv[2], sys.argv[3]) 225 | broker.run_broker_loop() 226 | elif command == "DECODE_INSTR": 227 | decode_instr(sys.argv[2]) 228 | elif command == "START_ANALYSIS": 229 | launch_analysis(sys.argv[2], sys.argv[3]) 230 | elif command == "FULL_PROXY": 231 | launch_full_proxy_analysis(sys.argv[2]) 232 | else: 233 | pass 234 | 235 | 236 | if __name__ == "__main__": 237 | main() 238 | -------------------------------------------------------------------------------- /idasec/network/commands.py: -------------------------------------------------------------------------------- 1 | 2 | BINSEC = 0 3 | PINSEC = 1 4 | 5 | EMPTY = "EMPTY" 6 | END = "END" 7 | EXIT = "EXIT" 8 | START_ANALYSIS = "START_ANALYSIS" 9 | ANALYSIS_RESULTS = "ANALYSIS_RESULTS" 10 | DECODE_INSTR_REPLY = "DECODE_INSTR_REPLY" 11 | 12 | TRACE_HEADER = "TRACE_HEADER" 13 | TRACE_CHUNK = "TRACE_CHUNK" 14 | -------------------------------------------------------------------------------- /idasec/network/message.py: -------------------------------------------------------------------------------- 1 | from idasec.proto.message_pb2 import * 2 | from idasec.proto.common_pb2 import * 3 | from idasec.dba_io import parse_dbalist 4 | 5 | 6 | class AbstractMessage: 7 | def __init__(self): 8 | pass 9 | 10 | def parse(self, raw): 11 | pass 12 | 13 | def serialize(self): 14 | pass 15 | 16 | 17 | lookup_irkind = {"DBA": DBA, "BAP": BAP, "MIASM": MIASM} 18 | reverse_lookup_irkind = {DBA: "DBA", BAP: "BAP", MIASM: "MIASM"} 19 | 20 | 21 | class MessageDecodeInstr(AbstractMessage): 22 | 23 | lookup_kind = {"hexa": message_decode_instr.HEXA, "bin": message_decode_instr.BIN} 24 | reverse_lookup = {message_decode_instr.HEXA: "hexa", message_decode_instr.BIN: "bin"} 25 | 26 | def __init__(self, irkind="DBA", kind="bin", instrs=[], base_addrs=[]): 27 | AbstractMessage.__init__(self) 28 | self.message = message_decode_instr() 29 | self.kind = kind 30 | self.irkind = irkind 31 | self.instrs = instrs 32 | self.base_addrs = base_addrs 33 | 34 | def serialize(self): 35 | self.message.kind = self.lookup_kind[self.kind] 36 | self.message.irkind = lookup_irkind[self.irkind] 37 | if isinstance(self.instrs, str): 38 | elt = self.message.instrs.add() 39 | elt.instr = self.instrs 40 | elt.base_addr = self.base_addrs 41 | elif isinstance(self.instrs, list): 42 | for i in xrange(len(self.instrs)): 43 | print("Loop instrs"+str(i)) 44 | elt = self.message.instrs.add() 45 | elt.instr = self.instrs[i] 46 | if len(self.base_addrs) > i: 47 | elt.base_addr = self.base_addrs[i] 48 | else: 49 | elt.base_addr = 0 50 | return self.message.SerializeToString() 51 | 52 | def parse(self, raw): 53 | self.message.ParseFromString(raw) 54 | self.kind = self.reverse_lookup[self.message.kind] 55 | self.irkind = reverse_lookup_irkind[self.message.irkind] 56 | self.instrs = self.message.instrs 57 | self.base_addrs = self.message.base_addrs 58 | 59 | 60 | class MessageDecodeInstrReply(AbstractMessage): 61 | 62 | def __init__(self, kind="DBA", instrs=[]): 63 | self.message = message_decode_instr_reply() 64 | self.instrs = instrs 65 | self.irkind = kind 66 | 67 | def serialize(self): 68 | # TODO: Writing a real serialization module 69 | return self.message.SerializeToString() 70 | 71 | def parse(self, raw): 72 | if type(raw) == unicode: 73 | data = bytearray(raw, "utf-8") 74 | raw = bytes(data) 75 | self.message.ParseFromString(raw) 76 | for entry in self.message.instrs: 77 | opcode = entry.opcode 78 | self.irkind = reverse_lookup_irkind[entry.irkind] 79 | if self.irkind == "DBA": 80 | self.instrs.append((opcode, parse_dbalist(entry.dba_instrs))) 81 | else: 82 | print "IR kind not supported" 83 | # else: Take them in the other field 84 | 85 | 86 | class MessageInfos(AbstractMessage): 87 | def __init__(self): 88 | self.message = message_infos() 89 | 90 | def serialize(self): 91 | # TODO: Writing a real serialization module 92 | return self.message.SerializeToString() 93 | 94 | def parse(self, raw): 95 | self.message.ParseFromString(raw) 96 | 97 | def get_infos(self): 98 | return self.message.nb_workers, self.message.analyses, self.message.solvers 99 | -------------------------------------------------------------------------------- /idasec/proto/Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | protoc --proto_path=./ --python_out=./ common.proto dba.proto analysis_config.proto config.proto instruction.proto syscall.proto libcall.proto message.proto trace.proto 3 | -------------------------------------------------------------------------------- /idasec/proto/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RobinDavid/idasec/e7d9328f9291dcadf34540a31608995b3697d047/idasec/proto/__init__.py -------------------------------------------------------------------------------- /idasec/proto/analysis_config.proto: -------------------------------------------------------------------------------- 1 | // 2 | // This file is part of Binsec. 3 | // 4 | // Copyright (C) 2016-2017 5 | // CEA (Commissariat à l'énergie atomique et aux énergies 6 | // alternatives) 7 | // 8 | // you can redistribute it and/or modify it under the terms of the GNU 9 | // Lesser General Public License as published by the Free Software 10 | // Foundation, version 2.1. 11 | // 12 | // It is distributed in the hope that it will be useful, 13 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | // GNU Lesser General Public License for more details. 16 | // 17 | // See the GNU Lesser General Public License version 2.1 18 | // for more details (enclosed in the file licenses/LGPLv2.1). 19 | // 20 | // 21 | 22 | syntax = "proto2"; 23 | 24 | package analysis_config; 25 | 26 | import "common.proto"; 27 | 28 | message specific_parameters_t { 29 | enum analyse_type { 30 | NONE = 0; 31 | GENERIC = 1; 32 | STANDARD = 2; 33 | /* Put yours at 100 */ 34 | } 35 | required analyse_type typeid = 1; 36 | oneof analyse_param { 37 | generic_analysis generic_params = 2; 38 | standard_analysis standard_params = 3; 39 | /* Put yours at 100 */ 40 | } 41 | } 42 | 43 | /* ----------- Simple params ------------ */ 44 | message standard_analysis { 45 | optional uint64 target_addr = 1; 46 | optional bool uniq = 2; 47 | optional bool get_formula = 3; 48 | } 49 | /* ---------------------------------------- */ 50 | 51 | /* ------------ Generic Analysis ------------- */ 52 | message generic_analysis { 53 | enum query_type { 54 | SATISFIABILITY = 0; 55 | VALUES = 1; 56 | } 57 | 58 | required query_type kind = 1; 59 | required uint64 target_addr = 2; 60 | required string dba = 3; 61 | optional uint32 limit_values = 4; 62 | optional bool get_formula = 5; 63 | optional uint64 from_addr = 6; 64 | optional uint64 to_addr = 7; 65 | optional uint64 restrict_values_from = 8; 66 | optional uint64 restrict_values_to = 9; 67 | 68 | } 69 | 70 | message generic_analysis_results { 71 | required common.smt_result result = 1; 72 | repeated uint64 values = 2; 73 | optional string smt_formula = 3; 74 | } 75 | /* ---------------------------------------------- */ 76 | 77 | /* ------------- Call/Ret Analysis --------------- */ 78 | message callret_analysis_results { 79 | enum callret_labels { 80 | VIOLABLE = 1; 81 | ALIGNED = 2; 82 | DISALIGNED = 3; 83 | CAN_RETURN = 4; 84 | SINGLE = 5; 85 | MULTIPLE = 6; 86 | STRONG = 7; 87 | WEAK = 8; 88 | SOLVER_WRONG = 9; 89 | NO_CALL = 10; 90 | HAS_RETURNED = 11; 91 | } 92 | 93 | enum callret_status { 94 | OK = 1; 95 | VIOL = 2; 96 | } 97 | 98 | message call_data { 99 | required uint64 addr = 1; 100 | required callret_status status = 2; 101 | } 102 | 103 | message ret_data { 104 | required uint64 ret_addr = 1; 105 | required callret_status status = 2; 106 | repeated callret_labels labels = 3; 107 | repeated uint64 returnsites = 4; 108 | required uint32 solve_count = 5; 109 | repeated call_data calls = 6; 110 | } 111 | 112 | repeated ret_data values = 1; 113 | } 114 | /* ----------------------------------------------- */ 115 | 116 | /* --------- Opaque Predicates Analysis ---------- */ 117 | message po_analysis_results { 118 | enum po_status { 119 | UNKNOWN = 1; /* Partially covered formula SAT */ 120 | NOT_OPAQUE = 2; /* Not opaque because fully covered */ 121 | OPAQUE = 3; /* Formula UNSAT */ 122 | LIKELY = 4; /* Formula TO */ 123 | } 124 | 125 | message po_data { 126 | required uint64 jmp_addr = 1; 127 | required po_status status = 2; 128 | required uint32 ksteps = 3; 129 | required float computation_time = 4; 130 | optional uint32 nb_paths = 5; 131 | optional uint64 alive_branch = 6; 132 | optional string formula = 7; 133 | } 134 | 135 | repeated po_data values = 1; 136 | } 137 | /* ----------------------------------------------- */ -------------------------------------------------------------------------------- /idasec/proto/common.proto: -------------------------------------------------------------------------------- 1 | // 2 | // This file is part of Binsec. 3 | // 4 | // Copyright (C) 2016-2017 5 | // CEA (Commissariat à l'énergie atomique et aux énergies 6 | // alternatives) 7 | // 8 | // you can redistribute it and/or modify it under the terms of the GNU 9 | // Lesser General Public License as published by the Free Software 10 | // Foundation, version 2.1. 11 | // 12 | // It is distributed in the hope that it will be useful, 13 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | // GNU Lesser General Public License for more details. 16 | // 17 | // See the GNU Lesser General Public License version 2.1 18 | // for more details (enclosed in the file licenses/LGPLv2.1). 19 | // 20 | // 21 | 22 | syntax = "proto2"; 23 | 24 | package common; 25 | 26 | enum smt_result { 27 | UNKNOWN = 0; 28 | SAT = 1; 29 | UNSAT = 2; 30 | TIMEOUT = 3; 31 | } 32 | 33 | enum tracing_action { 34 | SKIP = 0; 35 | INTO = 1; 36 | } 37 | 38 | enum action { 39 | DEFAULT = 0; // 40 | PATCH = 1; // Pinsec:patch Binsec:/ 41 | CONC = 2; // Pinsec:get val Binsec:Concretize 42 | SYMB = 3; // Pinsec:get val Binsec:Symbolize 43 | LOGIC = 4; // Pinsec:get val Binsec:Apply logical computation if possible 44 | IGNORE = 5; 45 | } 46 | 47 | enum call_convention_t { 48 | UNKNOWN_CVT = 0; 49 | CDECL = 1; 50 | FASTCALL = 2; 51 | STDCALL = 3; 52 | THISCALL = 4; 53 | } 54 | 55 | enum ir_kind_t { 56 | DBA = 1; 57 | BAP = 2; 58 | MIASM = 3; 59 | /* ....... */ 60 | } 61 | 62 | enum solver_t { 63 | Z3 = 1; 64 | BOOLECTOR = 2; 65 | CVC4 = 3; 66 | YICES = 4; 67 | } 68 | 69 | enum analysis_direction_t { 70 | FORWARD = 1; 71 | BACKWARD = 2; 72 | } 73 | 74 | enum proto_size_t { 75 | INVALID_SIZE = 0; 76 | BIT8 = 1; 77 | BIT16 = 2; 78 | BIT32 = 3; 79 | BIT64 = 4; 80 | BIT80 = 5; 81 | BIT128 = 6; 82 | BIT256 = 7; 83 | }; 84 | 85 | message register_value_t { 86 | required proto_size_t typeid = 1; 87 | oneof value_cnt { 88 | uint32 value_8 = 2; 89 | uint32 value_16 = 3; 90 | uint32 value_32 = 4; 91 | uint64 value_64 = 5; 92 | bytes value_80 = 6; 93 | bytes value_128 = 7; 94 | bytes value_256 = 8; 95 | } 96 | } 97 | 98 | message register_t { 99 | required string name = 1; 100 | required register_value_t value = 2; 101 | } 102 | 103 | message memory_t { 104 | required uint64 addr = 1; 105 | required bytes value = 2; 106 | } 107 | 108 | message indirect_register_t { 109 | required string name = 1; 110 | required bytes value = 2; 111 | } 112 | 113 | 114 | 115 | message memory_pol { 116 | optional action addr = 1 [default = DEFAULT]; 117 | optional action value = 2 [default = DEFAULT]; 118 | } 119 | -------------------------------------------------------------------------------- /idasec/proto/config.proto: -------------------------------------------------------------------------------- 1 | // 2 | // This file is part of Binsec. 3 | // 4 | // Copyright (C) 2016-2017 5 | // CEA (Commissariat à l'énergie atomique et aux énergies 6 | // alternatives) 7 | // 8 | // you can redistribute it and/or modify it under the terms of the GNU 9 | // Lesser General Public License as published by the Free Software 10 | // Foundation, version 2.1. 11 | // 12 | // It is distributed in the hope that it will be useful, 13 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | // GNU Lesser General Public License for more details. 16 | // 17 | // See the GNU Lesser General Public License version 2.1 18 | // for more details (enclosed in the file licenses/LGPLv2.1). 19 | // 20 | // 21 | 22 | syntax = "proto2"; 23 | 24 | package configuration; 25 | 26 | import "common.proto"; 27 | import "libcall.proto"; 28 | import "syscall.proto"; 29 | import "instruction.proto"; 30 | import "analysis_config.proto"; 31 | 32 | message call_name_t { 33 | required uint64 address = 1; 34 | required string name = 2; 35 | } 36 | 37 | message configuration { 38 | optional uint64 start = 1; 39 | optional uint64 stop = 2; 40 | repeated uint64 call_skips = 3; /* equivalent to old skip-full */ 41 | repeated uint64 fun_skips = 4; /* equivalent to skip-auto */ 42 | repeated libcall_types.libcall_pol libcalls = 5; 43 | repeated syscall_types.syscall_pol syscalls = 6; 44 | repeated instruction_pol.instr_pol instrs = 7; /* Used by Binsec only */ 45 | repeated string policy = 8; /* Used by Binsec only */ 46 | repeated input_t inputs = 9; /* use both to patch or gather additional concrete infos */ 47 | repeated call_name_t call_map = 10; 48 | repeated uint64 breakpoints = 11; 49 | repeated common.memory_t initial_state= 12; 50 | 51 | /* Analysis config */ 52 | optional common.analysis_direction_t direction = 13 [default = FORWARD]; 53 | optional uint32 ksteps = 14 [default = 0]; 54 | optional string analysis_name = 15 [default = ""]; 55 | optional common.solver_t solver = 16 [default = Z3]; 56 | optional bool incremental = 17 [default = false]; 57 | optional uint32 timeout = 18 [default = 0]; 58 | optional bool optim_cstprop = 19 [default = false]; 59 | optional bool optim_rebase = 20 [default = false]; 60 | optional bool optim_row = 21 [default = false]; 61 | optional bool optim_rowplus = 26 [default = false]; 62 | optional bool optim_eqprop = 27 [default = false]; 63 | optional common.call_convention_t callcvt = 22 [default = CDECL]; 64 | optional common.action default_action = 23 [default = SYMB]; 65 | optional uint32 verbosity = 24 [default = 0]; 66 | optional analysis_config.specific_parameters_t additional_parameters = 25; /* Analysis specific parameters */ 67 | } 68 | 69 | /* Maybe not useful to such complex description of a rule 70 | message policy_rule { 71 | 72 | enum policy_rule_kind { 73 | RULE = 0; 74 | DEFAULT = 1; 75 | } 76 | required policy_rule_kind typeid = 0; 77 | required string action = 1; 78 | optional string pred_loc = 2; 79 | optional string pred_inst = 3; 80 | optional string pred_exp = 4; 81 | optional string pred_sigma = 5; 82 | } 83 | */ 84 | 85 | message input_t { 86 | enum input_kind { 87 | REG = 0; 88 | MEM = 1; 89 | INDIRECT = 2; 90 | } 91 | enum when_t { 92 | BEFORE = 0; 93 | AFTER = 1; 94 | } 95 | required input_kind typeid = 1; 96 | required uint64 address = 2; 97 | required when_t when = 3; 98 | required common.action action = 4; //Pin DEFAULT,SYMB=/, CONC=retrieve valu, PATH=patch value 99 | // iteration for patching point 100 | optional uint32 iteration = 100 [default = 0]; 101 | oneof input_cnt { 102 | common.register_t reg= 7; /* for reg */ 103 | common.memory_t mem = 8; /* for mem */ 104 | common.indirect_register_t indirect = 9; /* for indirect */ 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /idasec/proto/dba.proto: -------------------------------------------------------------------------------- 1 | // 2 | // This file is part of Binsec. 3 | // 4 | // Copyright (C) 2016-2017 5 | // CEA (Commissariat à l'énergie atomique et aux énergies 6 | // alternatives) 7 | // 8 | // you can redistribute it and/or modify it under the terms of the GNU 9 | // Lesser General Public License as published by the Free Software 10 | // Foundation, version 2.1. 11 | // 12 | // It is distributed in the hope that it will be useful, 13 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | // GNU Lesser General Public License for more details. 16 | // 17 | // See the GNU Lesser General Public License version 2.1 18 | // for more details (enclosed in the file licenses/LGPLv2.1). 19 | // 20 | // 21 | 22 | /* 23 | This protobuf, describe the DBA language. This is the protobuf representation of dba.ml 24 | */ 25 | 26 | syntax = "proto2"; 27 | 28 | package dba; 29 | 30 | message bitvector { 31 | required uint64 bv = 1; 32 | required uint32 size = 2; 33 | } 34 | 35 | message dbacodeaddress { 36 | required bitvector bitvector = 1; 37 | required uint32 dbaoffset = 2; 38 | } 39 | 40 | message codeaddress { 41 | enum typeid_codeaddress { 42 | Local = 0; 43 | NonLocal = 1; 44 | } 45 | 46 | required typeid_codeaddress typeid = 1; 47 | optional uint32 offset = 2; 48 | optional dbacodeaddress address = 3; 49 | } 50 | 51 | message dbatag { 52 | enum typeid_dbatag { 53 | DbaCall = 1; 54 | DbaReturn = 2; 55 | } 56 | required typeid_dbatag typeid = 1; 57 | optional dbacodeaddress address = 2; 58 | } 59 | 60 | enum endianness { 61 | Little = 1; 62 | Big = 2; 63 | } 64 | 65 | message dbastopstate { 66 | enum typeid_dbastate { 67 | Ok = 1; 68 | Ko = 2; 69 | Undefined = 3; 70 | Unsupported = 4; 71 | } 72 | 73 | required typeid_dbastate typeid = 1; 74 | optional string infos = 2; 75 | } 76 | 77 | message dbaexpr { 78 | enum typeid_dbaexpr { 79 | DbaExprVar = 1; 80 | DbaLoad = 2; 81 | DbaExprCst = 3; 82 | DbaExprUnary = 4; 83 | DbaExprBinary = 5; 84 | DbaExprRestrict = 6; 85 | DbaExprExtU = 7; 86 | DbaExprExtS = 8; 87 | DbaExprIte = 9; 88 | DbaExprAlternative = 10; 89 | } 90 | 91 | enum dbaunary { 92 | DbaUnaryMinus = 1; 93 | DbaUnaryNot = 2; 94 | } 95 | 96 | enum dbabinary { 97 | DbaPlus = 1; 98 | DbaMinus = 2; 99 | DbaMultU = 3; 100 | DbaMultS = 4; 101 | DbaDivU = 6; 102 | DbaDivS = 7; 103 | DbaModU = 8; 104 | DbaModS = 9; 105 | DbaOr = 10; 106 | DbaAnd = 11; 107 | DbaXor = 12; 108 | DbaConcat = 13; 109 | DbaLShiftU = 14; 110 | DbaRShiftU = 15; 111 | DbaRShiftS = 16; 112 | DbaLeftRotate = 17; 113 | DbaRightRotate = 18; 114 | DbaEq = 19; 115 | DbaDiff = 20; 116 | DbaLeqU = 21; 117 | DbaLtU = 22; 118 | DbaGeqU = 23; 119 | DbaGtU = 24; 120 | DbaLeqS = 25; 121 | DbaLtS = 26; 122 | DbaGeqS = 27; 123 | DbaGtS = 28; 124 | } 125 | 126 | required typeid_dbaexpr typeid = 1; 127 | optional string name = 2; 128 | optional uint32 size = 3; 129 | optional endianness endian = 4; 130 | optional bitvector bitvector = 5; 131 | optional dbaexpr expr1 = 6; 132 | optional dbaexpr expr2 = 7; 133 | optional dbaunary unaryop = 9; 134 | optional dbabinary binaryop = 10; 135 | optional uint32 low = 11; 136 | optional uint32 high = 12; 137 | optional dbacond cond = 13; 138 | } 139 | 140 | 141 | message dbacond { 142 | enum typeid_dbacond { 143 | DbaCondReif = 1; 144 | DbaCondNot = 2; 145 | DbaCondAnd = 3; 146 | DbaCondOr = 4; 147 | DbaTrue = 5; 148 | DbaFalse = 6; 149 | } 150 | 151 | required typeid_dbacond typeid = 1; 152 | optional dbaexpr expr = 2; 153 | optional dbacond cond1 = 3; 154 | optional dbacond cond2 = 4; 155 | } 156 | 157 | message dbaLhs { 158 | enum typeid_dbalhs { 159 | DbaLhsVar = 1; 160 | DbaLhsVarRestrict = 2; 161 | DbaStore = 3; 162 | } 163 | 164 | required typeid_dbalhs typeid = 1; 165 | optional string name = 2; 166 | optional uint32 size = 3; 167 | optional uint32 low = 4; 168 | optional uint32 high = 5; 169 | optional endianness endian = 6; 170 | optional dbaexpr expr = 7; 171 | } 172 | 173 | message dbainstr { 174 | enum typeid_instrkind { 175 | DbaIkAssign = 1; 176 | DbaIkSJump = 2; 177 | DbaIkDJump = 3; 178 | DbaIkIf = 4; 179 | DbaIkStop = 5; 180 | DbaIkAssert = 6; 181 | DbaIkAssume = 7; 182 | DbaIkNondetAssume = 8; 183 | DbaIkNondet = 9; 184 | DbaIkUndef = 10; 185 | DbaIkMalloc = 11; 186 | DbaIkFree = 12; 187 | DbaIkPrint = 13; 188 | } 189 | 190 | required typeid_instrkind typeid = 1; 191 | required dbacodeaddress location = 2; 192 | optional dbaLhs lhs = 3; 193 | optional dbaexpr expr = 4; 194 | optional uint32 offset = 5; 195 | optional codeaddress address = 6; 196 | optional dbatag tags = 7; 197 | optional dbacond cond = 8; 198 | optional dbastopstate stopinfos = 9; 199 | repeated dbaexpr exprs = 10; 200 | } 201 | 202 | message dba_list { 203 | repeated dbainstr instrs = 1; 204 | } 205 | 206 | -------------------------------------------------------------------------------- /idasec/proto/instruction.proto: -------------------------------------------------------------------------------- 1 | // 2 | // This file is part of Binsec. 3 | // 4 | // Copyright (C) 2016-2017 5 | // CEA (Commissariat à l'énergie atomique et aux énergies 6 | // alternatives) 7 | // 8 | // you can redistribute it and/or modify it under the terms of the GNU 9 | // Lesser General Public License as published by the Free Software 10 | // Foundation, version 2.1. 11 | // 12 | // It is distributed in the hope that it will be useful, 13 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | // GNU Lesser General Public License for more details. 16 | // 17 | // See the GNU Lesser General Public License version 2.1 18 | // for more details (enclosed in the file licenses/LGPLv2.1). 19 | // 20 | // 21 | 22 | syntax = "proto2"; 23 | 24 | package instruction_pol; 25 | 26 | import "common.proto"; 27 | 28 | 29 | enum instr_ident { 30 | INVALID_INST = 0; 31 | CPUID = 1; 32 | /* .... */ 33 | } 34 | 35 | 36 | message instr_pol { 37 | required instr_ident ident = 1; 38 | required string opcode = 2; 39 | 40 | oneof instr_cnt { 41 | cpuid_pol cpuid = 3; 42 | /* ..... */ 43 | } 44 | } 45 | 46 | 47 | message cpuid_pol { 48 | optional common.action eax = 1 [default = DEFAULT]; 49 | optional common.action ebx = 2 [default = DEFAULT]; 50 | optional common.action ecx = 3 [default = DEFAULT]; 51 | optional common.action edx = 4 [default = DEFAULT]; 52 | } 53 | -------------------------------------------------------------------------------- /idasec/proto/instruction_pb2.py: -------------------------------------------------------------------------------- 1 | # Generated by the protocol buffer compiler. DO NOT EDIT! 2 | # source: instruction.proto 3 | 4 | import sys 5 | _b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1')) 6 | from google.protobuf.internal import enum_type_wrapper 7 | from google.protobuf import descriptor as _descriptor 8 | from google.protobuf import message as _message 9 | from google.protobuf import reflection as _reflection 10 | from google.protobuf import symbol_database as _symbol_database 11 | from google.protobuf import descriptor_pb2 12 | # @@protoc_insertion_point(imports) 13 | 14 | _sym_db = _symbol_database.Default() 15 | 16 | 17 | import common_pb2 as common__pb2 18 | 19 | 20 | DESCRIPTOR = _descriptor.FileDescriptor( 21 | name='instruction.proto', 22 | package='instruction_pol', 23 | syntax='proto2', 24 | serialized_pb=_b('\n\x11instruction.proto\x12\x0finstruction_pol\x1a\x0c\x63ommon.proto\"\x82\x01\n\tinstr_pol\x12+\n\x05ident\x18\x01 \x02(\x0e\x32\x1c.instruction_pol.instr_ident\x12\x0e\n\x06opcode\x18\x02 \x02(\t\x12+\n\x05\x63puid\x18\x03 \x01(\x0b\x32\x1a.instruction_pol.cpuid_polH\x00\x42\x0b\n\tinstr_cnt\"\xa3\x01\n\tcpuid_pol\x12$\n\x03\x65\x61x\x18\x01 \x01(\x0e\x32\x0e.common.action:\x07\x44\x45\x46\x41ULT\x12$\n\x03\x65\x62x\x18\x02 \x01(\x0e\x32\x0e.common.action:\x07\x44\x45\x46\x41ULT\x12$\n\x03\x65\x63x\x18\x03 \x01(\x0e\x32\x0e.common.action:\x07\x44\x45\x46\x41ULT\x12$\n\x03\x65\x64x\x18\x04 \x01(\x0e\x32\x0e.common.action:\x07\x44\x45\x46\x41ULT**\n\x0binstr_ident\x12\x10\n\x0cINVALID_INST\x10\x00\x12\t\n\x05\x43PUID\x10\x01') 25 | , 26 | dependencies=[common__pb2.DESCRIPTOR,]) 27 | _sym_db.RegisterFileDescriptor(DESCRIPTOR) 28 | 29 | _INSTR_IDENT = _descriptor.EnumDescriptor( 30 | name='instr_ident', 31 | full_name='instruction_pol.instr_ident', 32 | filename=None, 33 | file=DESCRIPTOR, 34 | values=[ 35 | _descriptor.EnumValueDescriptor( 36 | name='INVALID_INST', index=0, number=0, 37 | options=None, 38 | type=None), 39 | _descriptor.EnumValueDescriptor( 40 | name='CPUID', index=1, number=1, 41 | options=None, 42 | type=None), 43 | ], 44 | containing_type=None, 45 | options=None, 46 | serialized_start=351, 47 | serialized_end=393, 48 | ) 49 | _sym_db.RegisterEnumDescriptor(_INSTR_IDENT) 50 | 51 | instr_ident = enum_type_wrapper.EnumTypeWrapper(_INSTR_IDENT) 52 | INVALID_INST = 0 53 | CPUID = 1 54 | 55 | 56 | 57 | _INSTR_POL = _descriptor.Descriptor( 58 | name='instr_pol', 59 | full_name='instruction_pol.instr_pol', 60 | filename=None, 61 | file=DESCRIPTOR, 62 | containing_type=None, 63 | fields=[ 64 | _descriptor.FieldDescriptor( 65 | name='ident', full_name='instruction_pol.instr_pol.ident', index=0, 66 | number=1, type=14, cpp_type=8, label=2, 67 | has_default_value=False, default_value=0, 68 | message_type=None, enum_type=None, containing_type=None, 69 | is_extension=False, extension_scope=None, 70 | options=None), 71 | _descriptor.FieldDescriptor( 72 | name='opcode', full_name='instruction_pol.instr_pol.opcode', index=1, 73 | number=2, type=9, cpp_type=9, label=2, 74 | has_default_value=False, default_value=_b("").decode('utf-8'), 75 | message_type=None, enum_type=None, containing_type=None, 76 | is_extension=False, extension_scope=None, 77 | options=None), 78 | _descriptor.FieldDescriptor( 79 | name='cpuid', full_name='instruction_pol.instr_pol.cpuid', index=2, 80 | number=3, type=11, cpp_type=10, label=1, 81 | has_default_value=False, default_value=None, 82 | message_type=None, enum_type=None, containing_type=None, 83 | is_extension=False, extension_scope=None, 84 | options=None), 85 | ], 86 | extensions=[ 87 | ], 88 | nested_types=[], 89 | enum_types=[ 90 | ], 91 | options=None, 92 | is_extendable=False, 93 | syntax='proto2', 94 | extension_ranges=[], 95 | oneofs=[ 96 | _descriptor.OneofDescriptor( 97 | name='instr_cnt', full_name='instruction_pol.instr_pol.instr_cnt', 98 | index=0, containing_type=None, fields=[]), 99 | ], 100 | serialized_start=53, 101 | serialized_end=183, 102 | ) 103 | 104 | 105 | _CPUID_POL = _descriptor.Descriptor( 106 | name='cpuid_pol', 107 | full_name='instruction_pol.cpuid_pol', 108 | filename=None, 109 | file=DESCRIPTOR, 110 | containing_type=None, 111 | fields=[ 112 | _descriptor.FieldDescriptor( 113 | name='eax', full_name='instruction_pol.cpuid_pol.eax', index=0, 114 | number=1, type=14, cpp_type=8, label=1, 115 | has_default_value=True, default_value=0, 116 | message_type=None, enum_type=None, containing_type=None, 117 | is_extension=False, extension_scope=None, 118 | options=None), 119 | _descriptor.FieldDescriptor( 120 | name='ebx', full_name='instruction_pol.cpuid_pol.ebx', index=1, 121 | number=2, type=14, cpp_type=8, label=1, 122 | has_default_value=True, default_value=0, 123 | message_type=None, enum_type=None, containing_type=None, 124 | is_extension=False, extension_scope=None, 125 | options=None), 126 | _descriptor.FieldDescriptor( 127 | name='ecx', full_name='instruction_pol.cpuid_pol.ecx', index=2, 128 | number=3, type=14, cpp_type=8, label=1, 129 | has_default_value=True, default_value=0, 130 | message_type=None, enum_type=None, containing_type=None, 131 | is_extension=False, extension_scope=None, 132 | options=None), 133 | _descriptor.FieldDescriptor( 134 | name='edx', full_name='instruction_pol.cpuid_pol.edx', index=3, 135 | number=4, type=14, cpp_type=8, label=1, 136 | has_default_value=True, default_value=0, 137 | message_type=None, enum_type=None, containing_type=None, 138 | is_extension=False, extension_scope=None, 139 | options=None), 140 | ], 141 | extensions=[ 142 | ], 143 | nested_types=[], 144 | enum_types=[ 145 | ], 146 | options=None, 147 | is_extendable=False, 148 | syntax='proto2', 149 | extension_ranges=[], 150 | oneofs=[ 151 | ], 152 | serialized_start=186, 153 | serialized_end=349, 154 | ) 155 | 156 | _INSTR_POL.fields_by_name['ident'].enum_type = _INSTR_IDENT 157 | _INSTR_POL.fields_by_name['cpuid'].message_type = _CPUID_POL 158 | _INSTR_POL.oneofs_by_name['instr_cnt'].fields.append( 159 | _INSTR_POL.fields_by_name['cpuid']) 160 | _INSTR_POL.fields_by_name['cpuid'].containing_oneof = _INSTR_POL.oneofs_by_name['instr_cnt'] 161 | _CPUID_POL.fields_by_name['eax'].enum_type = common__pb2._ACTION 162 | _CPUID_POL.fields_by_name['ebx'].enum_type = common__pb2._ACTION 163 | _CPUID_POL.fields_by_name['ecx'].enum_type = common__pb2._ACTION 164 | _CPUID_POL.fields_by_name['edx'].enum_type = common__pb2._ACTION 165 | DESCRIPTOR.message_types_by_name['instr_pol'] = _INSTR_POL 166 | DESCRIPTOR.message_types_by_name['cpuid_pol'] = _CPUID_POL 167 | DESCRIPTOR.enum_types_by_name['instr_ident'] = _INSTR_IDENT 168 | 169 | instr_pol = _reflection.GeneratedProtocolMessageType('instr_pol', (_message.Message,), dict( 170 | DESCRIPTOR = _INSTR_POL, 171 | __module__ = 'instruction_pb2' 172 | # @@protoc_insertion_point(class_scope:instruction_pol.instr_pol) 173 | )) 174 | _sym_db.RegisterMessage(instr_pol) 175 | 176 | cpuid_pol = _reflection.GeneratedProtocolMessageType('cpuid_pol', (_message.Message,), dict( 177 | DESCRIPTOR = _CPUID_POL, 178 | __module__ = 'instruction_pb2' 179 | # @@protoc_insertion_point(class_scope:instruction_pol.cpuid_pol) 180 | )) 181 | _sym_db.RegisterMessage(cpuid_pol) 182 | 183 | 184 | # @@protoc_insertion_point(module_scope) 185 | -------------------------------------------------------------------------------- /idasec/proto/message.proto: -------------------------------------------------------------------------------- 1 | // 2 | // This file is part of Binsec. 3 | // 4 | // Copyright (C) 2016-2017 5 | // CEA (Commissariat à l'énergie atomique et aux énergies 6 | // alternatives) 7 | // 8 | // you can redistribute it and/or modify it under the terms of the GNU 9 | // Lesser General Public License as published by the Free Software 10 | // Foundation, version 2.1. 11 | // 12 | // It is distributed in the hope that it will be useful, 13 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | // GNU Lesser General Public License for more details. 16 | // 17 | // See the GNU Lesser General Public License version 2.1 18 | // for more details (enclosed in the file licenses/LGPLv2.1). 19 | // 20 | // 21 | 22 | syntax = "proto2"; 23 | 24 | import "common.proto"; 25 | import "dba.proto"; 26 | import "config.proto"; 27 | 28 | /* --------------- Broker <-> Pin ---------------- */ 29 | 30 | 31 | message message_start_exec { 32 | required uint64 start = 1; 33 | required uint64 stop = 2; 34 | optional uint32 size_chunks = 3 [default = 1000]; 35 | repeated uint64 breakpoints = 4; 36 | repeated configuration.input_t inputs = 5; 37 | repeated uint64 skips = 6; 38 | } 39 | 40 | /* message_trace_header see trace.proto */ 41 | 42 | /* message_trace_chunk see trace.proto */ 43 | 44 | message message_bp_reached { 45 | required uint64 addr = 1; 46 | } 47 | 48 | /* message_exec_step */ 49 | 50 | message message_patch { 51 | repeated configuration.input_t patch = 1; 52 | } 53 | 54 | /* message_exec_resume */ 55 | /* message_exec_end */ 56 | /* message exec_terminate */ 57 | 58 | 59 | /* ---------------- Broker <-> Frontend -------------- */ 60 | 61 | /* message_get_infos */ 62 | 63 | message message_infos { 64 | required uint32 nb_workers = 1; 65 | repeated string analyses = 2; 66 | repeated string solvers = 3; 67 | } 68 | 69 | message message_decode_instr { 70 | enum bin_kind { 71 | HEXA = 1; 72 | BIN = 2; 73 | } 74 | 75 | message instr_entry { 76 | required string instr = 1; 77 | optional uint64 base_addr = 2 [default = 0]; 78 | } 79 | 80 | repeated instr_entry instrs = 1; 81 | optional bin_kind kind = 2 [default = BIN]; 82 | optional common.ir_kind_t irkind = 3 [default = DBA]; 83 | } 84 | 85 | message message_decode_instr_reply { 86 | message instr_entry { 87 | required string opcode = 1; 88 | required common.ir_kind_t irkind = 2; 89 | oneof instrs_cnt { 90 | dba.dba_list dba_instrs = 3; 91 | } 92 | } 93 | 94 | repeated instr_entry instrs = 1; 95 | } 96 | 97 | /* message decode_trace see trace.proto */ 98 | /* message decode_trace_reply see trace.proto */ 99 | 100 | message message_start_symb_exec { 101 | enum trace_kind { 102 | FILE = 1; 103 | STREAM = 2; 104 | } 105 | required trace_kind trace_type = 1; 106 | required string name = 2; 107 | optional common.solver_t solver = 3 [default = Z3]; 108 | optional configuration.configuration config = 4; 109 | optional common.ir_kind_t irkind = 5 [default = DBA]; 110 | optional bool formula_optim_cstfold = 6; 111 | optional bool formula_optim_rebase = 7; 112 | optional bool formula_optim_row = 8; 113 | required uint32 verbose = 9; 114 | optional uint64 addr_predicate = 10; 115 | optional string predicate = 11; 116 | optional string trace_filename = 12; 117 | 118 | } 119 | 120 | 121 | message message_output { 122 | enum output_kind { 123 | SUCCESS = 1; 124 | RESULT = 2; 125 | DEBUG = 3; 126 | WARNING = 4; 127 | ERROR = 5; 128 | FAILURE = 6; 129 | } 130 | 131 | required output_kind type = 1; 132 | required string message = 2; 133 | optional uint32 ram_total = 3; 134 | optional uint32 ram_free = 4; 135 | optional uint32 ram_available = 5; 136 | } 137 | -------------------------------------------------------------------------------- /idasec/proto/syscall.proto: -------------------------------------------------------------------------------- 1 | // 2 | // This file is part of Binsec. 3 | // 4 | // Copyright (C) 2016-2017 5 | // CEA (Commissariat à l'énergie atomique et aux énergies 6 | // alternatives) 7 | // 8 | // you can redistribute it and/or modify it under the terms of the GNU 9 | // Lesser General Public License as published by the Free Software 10 | // Foundation, version 2.1. 11 | // 12 | // It is distributed in the hope that it will be useful, 13 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | // GNU Lesser General Public License for more details. 16 | // 17 | // See the GNU Lesser General Public License version 2.1 18 | // for more details (enclosed in the file licenses/LGPLv2.1). 19 | // 20 | // 21 | 22 | syntax = "proto2"; 23 | 24 | package syscall_types; 25 | 26 | import "common.proto"; 27 | 28 | 29 | enum syscall_ident { 30 | NR_READ = 0; 31 | NR_WRITE = 1; 32 | NR_OPEN = 2; 33 | GENERIC = 3; 34 | /* .... */ 35 | } 36 | 37 | /* =============== Main types ============== */ 38 | message syscall_pol { 39 | required uint32 id = 1; 40 | required string name = 2; 41 | required syscall_ident ident = 3; 42 | required common.tracing_action action = 4; 43 | repeated uint64 restrict_addresses = 5; 44 | 45 | oneof syscall_pol_cnt { 46 | nr_read_pol nr_read = 6; 47 | /* ..... */ 48 | } 49 | } 50 | 51 | message syscall_t { 52 | required uint32 id = 1; 53 | required syscall_ident ident = 2; 54 | 55 | oneof syscall_t_cnt { 56 | nr_open_t open_syscall = 3; 57 | nr_read_t read_syscall = 4; 58 | nr_write_t write_sycall = 5; 59 | sys_generic_t generic_sycall = 6; 60 | } 61 | } 62 | /* ============================================ */ 63 | 64 | 65 | /* ================ Policies ================ */ 66 | message nr_read_pol { 67 | required string name = 1; /* TODO: complete */ 68 | } 69 | /* ========================================== */ 70 | 71 | /* ========== Syscall concrete infos ========= */ 72 | message nr_open_t { /* TODO: Redefine */ 73 | required string file_name = 1; 74 | required sint32 flags = 2; 75 | required uint32 mode = 3; 76 | required int32 file_descriptor = 4; 77 | } 78 | 79 | message nr_read_t { 80 | required uint32 file_descriptor = 1; 81 | required uint64 buffer_address = 2; 82 | required uint32 count = 3; 83 | required bytes buffer_data = 4; 84 | required uint32 count_effective = 5; 85 | } 86 | 87 | message nr_write_t { 88 | required uint32 file_descriptor = 1; 89 | required uint64 buffer_address = 2; 90 | required uint32 count = 3; 91 | required bytes buffer_data = 4; 92 | required uint32 count_effective = 5; 93 | } 94 | 95 | message sys_generic_t { 96 | required string name = 1; 97 | required uint32 address = 2; 98 | } 99 | /* ============================================= */ 100 | -------------------------------------------------------------------------------- /idasec/proto/trace.proto: -------------------------------------------------------------------------------- 1 | // 2 | // This file is part of Binsec. 3 | // 4 | // Copyright (C) 2016-2017 5 | // CEA (Commissariat à l'énergie atomique et aux énergies 6 | // alternatives) 7 | // 8 | // you can redistribute it and/or modify it under the terms of the GNU 9 | // Lesser General Public License as published by the Free Software 10 | // Foundation, version 2.1. 11 | // 12 | // It is distributed in the hope that it will be useful, 13 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | // GNU Lesser General Public License for more details. 16 | // 17 | // See the GNU Lesser General Public License version 2.1 18 | // for more details (enclosed in the file licenses/LGPLv2.1). 19 | // 20 | // 21 | 22 | /* 23 | * A trace consists of header information and a list of instruction information. 24 | * Different instructions have different information, so the main trick is to use 25 | * variant type to represent instructions. 26 | * 27 | * Since protobuf does not support variant type, the main trick is to use a message 28 | * have multiple optional sub-messages, and an id to identify which is the real 29 | * sub-message of the message. 30 | */ 31 | 32 | syntax = "proto2"; 33 | 34 | import "dba.proto"; 35 | import "common.proto"; 36 | 37 | package trace_format; 38 | 39 | import "syscall.proto"; 40 | import "libcall.proto"; 41 | 42 | /* =========== Header ========= */ 43 | message header_t { 44 | enum architecture_t { 45 | X86 = 0; 46 | X86_64 = 1; 47 | } 48 | 49 | required architecture_t architecture = 1; 50 | required common.proto_size_t address_size = 2; 51 | optional common.ir_kind_t kind = 3 [default = DBA]; 52 | } 53 | 54 | 55 | message metadata_t { 56 | enum typeid_metadata_t { 57 | INVALID_METADATA = 0; 58 | EXCEPTION_TYPE = 1; 59 | MODULE_TYPE = 2; 60 | WAVE_TYPE = 3; 61 | } 62 | 63 | message exception_t { 64 | required uint32 type_exception = 1; 65 | required uint64 handler = 2; 66 | } 67 | 68 | required typeid_metadata_t typeid = 1; 69 | oneof metadata_content { 70 | string module_metadata = 2; 71 | exception_t exception_metadata = 3; 72 | uint32 wave_metadata = 4; 73 | } 74 | } 75 | /* ================================= */ 76 | 77 | 78 | /* ======== Concrete infos ======== */ 79 | message ins_con_info_t { 80 | enum typeid_con_info_t { 81 | INVALID = 0; 82 | REGREAD = 1; 83 | REGWRITE = 2; 84 | MEMLOAD = 3; 85 | MEMSTORE = 4; 86 | CALL = 5; 87 | SYSCALL = 6; 88 | NOT_RETRIEVED = 7; 89 | NEXT_ADDRESS = 8; 90 | COMMENT = 9; 91 | WAVE = 10; 92 | } 93 | 94 | required typeid_con_info_t typeid = 1; 95 | oneof conc_info_content { 96 | common.register_t read_register = 2; 97 | common.register_t write_register = 3; 98 | common.memory_t load_memory = 4; 99 | common.memory_t store_memory = 5; 100 | libcall_types.libcall_t call = 6; 101 | syscall_types.syscall_t system_call = 8; 102 | uint64 next_address = 9; 103 | string reserved_comment = 10; 104 | uint32 wave = 11; 105 | } 106 | } 107 | /* ================================= */ 108 | 109 | /* ============ Main containers ========== */ 110 | message instruction_t { 111 | required uint32 thread_id = 1; 112 | required uint64 address = 2; 113 | required bytes opcode = 3; 114 | repeated ins_con_info_t concrete_infos = 4; 115 | optional dba.dba_list dba_instrs = 5; 116 | } 117 | 118 | message body_t { 119 | enum typeid_body_t { 120 | METADATA = 0; 121 | INSTRUCTION = 1; 122 | } 123 | 124 | required typeid_body_t typeid = 1; 125 | oneof body_content { 126 | metadata_t metadata = 2; 127 | instruction_t instruction = 3; 128 | } 129 | } 130 | 131 | message chunk_t { 132 | repeated body_t body = 1; 133 | } 134 | /* ======================================= */ 135 | 136 | 137 | message trace_t {//Not used anymore 138 | required header_t header = 1; 139 | repeated body_t body = 2; 140 | } 141 | -------------------------------------------------------------------------------- /idasec/protobuf_json.py: -------------------------------------------------------------------------------- 1 | # JSON serialization support for Google's protobuf Messages 2 | # Copyright (c) 2009, Paul Dovbush 3 | # All rights reserved. 4 | # http://code.google.com/p/protobuf-json/ 5 | # 6 | # Redistribution and use in source and binary forms, with or without 7 | # modification, are permitted provided that the following conditions are 8 | # met: 9 | # 10 | # * Redistributions of source code must retain the above copyright 11 | # notice, this list of conditions and the following disclaimer. 12 | # * Redistributions in binary form must reproduce the above 13 | # copyright notice, this list of conditions and the following disclaimer 14 | # in the documentation and/or other materials provided with the 15 | # distribution. 16 | # * Neither the name of 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 21 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 | # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24 | # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25 | # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 26 | # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 | # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 | # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 | # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | 32 | """ 33 | Provide serialization and de-serialization of Google's protobuf Messages into/from JSON format. 34 | """ 35 | 36 | # groups are deprecated and not supported; 37 | # Note that preservation of unknown fields is currently not available for Python (c) google docs 38 | # extensions is not supported from 0.0.5 (due to gpb2.3 changes) 39 | 40 | # Modified by Robin David - robin.david@cea.fr 41 | 42 | 43 | from google.protobuf.descriptor import FieldDescriptor as FD 44 | import base64 45 | 46 | __version__ = '0.0.5' 47 | __author__ = 'Paul Dovbush ' 48 | 49 | 50 | class ParseError(Exception): 51 | pass 52 | 53 | 54 | def json2pb(pb, js): 55 | """ convert JSON string to google.protobuf.descriptor instance 56 | :param pb: protobuf class to fill 57 | :param js: json input data 58 | """ 59 | for field in pb.DESCRIPTOR.fields: 60 | if field.name not in js: 61 | continue 62 | if field.type == FD.TYPE_MESSAGE: 63 | pass 64 | elif field.type in _js2ftype: 65 | ftype = _js2ftype[field.type] 66 | else: 67 | raise ParseError( 68 | "Field %s.%s of type '%d' is not supported" % (pb.__class__.__name__, field.name, field.type,)) 69 | value = js[field.name] 70 | if field.label == FD.LABEL_REPEATED: 71 | pb_value = getattr(pb, field.name, None) 72 | for v in value: 73 | if field.type == FD.TYPE_MESSAGE: 74 | json2pb(pb_value.add(), v) 75 | else: 76 | pb_value.append(ftype(v)) 77 | else: 78 | if field.type == FD.TYPE_MESSAGE: 79 | json2pb(getattr(pb, field.name, None), value) 80 | elif field.type == FD.TYPE_ENUM: 81 | real_value = field.enum_type.values_by_name[value].number 82 | setattr(pb, field.name, real_value) 83 | else: 84 | setattr(pb, field.name, ftype(value)) 85 | return pb 86 | 87 | 88 | def pb2json(pb): 89 | """ convert google.protobuf.descriptor instance to JSON string 90 | :param pb: protobuf class to be converted in json 91 | """ 92 | js = {} 93 | # fields = pb.DESCRIPTOR.fields #all fields 94 | fields = pb.ListFields() # only filled (including extensions) 95 | for field, value in fields: 96 | if field.type == FD.TYPE_MESSAGE: 97 | ftype = pb2json 98 | elif field.type == FD.TYPE_ENUM: 99 | ftype = str 100 | value = field.enum_type.values_by_number[value].name 101 | elif field.type in _ftype2js: 102 | ftype = _ftype2js[field.type] 103 | else: 104 | raise ParseError( 105 | "Field %s.%s of type '%d' is not supported" % (pb.__class__.__name__, field.name, field.type,)) 106 | if field.label == FD.LABEL_REPEATED: 107 | js_value = [] 108 | for v in value: 109 | js_value.append(ftype(v)) 110 | else: 111 | js_value = ftype(value) 112 | js[field.name] = js_value 113 | return js 114 | 115 | 116 | _ftype2js = { 117 | FD.TYPE_DOUBLE: float, 118 | FD.TYPE_FLOAT: float, 119 | FD.TYPE_INT64: long, 120 | FD.TYPE_UINT64: long, 121 | FD.TYPE_INT32: int, 122 | FD.TYPE_FIXED64: float, 123 | FD.TYPE_FIXED32: float, 124 | FD.TYPE_BOOL: bool, 125 | FD.TYPE_STRING: unicode, 126 | # FD.TYPE_MESSAGE: pb2json, #handled specially 127 | FD.TYPE_BYTES: base64.b64encode, 128 | FD.TYPE_UINT32: int, 129 | FD.TYPE_ENUM: int, 130 | FD.TYPE_SFIXED32: float, 131 | FD.TYPE_SFIXED64: float, 132 | FD.TYPE_SINT32: int, 133 | FD.TYPE_SINT64: long, 134 | } 135 | 136 | _js2ftype = { 137 | FD.TYPE_DOUBLE: float, 138 | FD.TYPE_FLOAT: float, 139 | FD.TYPE_INT64: long, 140 | FD.TYPE_UINT64: long, 141 | FD.TYPE_INT32: int, 142 | FD.TYPE_FIXED64: float, 143 | FD.TYPE_FIXED32: float, 144 | FD.TYPE_BOOL: bool, 145 | FD.TYPE_STRING: unicode, 146 | # FD.TYPE_MESSAGE: json2pb, #handled specially 147 | FD.TYPE_BYTES: base64.b64decode, 148 | FD.TYPE_UINT32: int, 149 | FD.TYPE_ENUM: int, # Never used because catch before 150 | FD.TYPE_SFIXED32: float, 151 | FD.TYPE_SFIXED64: float, 152 | FD.TYPE_SINT32: int, 153 | FD.TYPE_SINT64: long, 154 | } 155 | -------------------------------------------------------------------------------- /idasec/report_generator.py: -------------------------------------------------------------------------------- 1 | header = ''' 2 | 3 | 4 | 5 | 27 | 28 | 29 | ''' 30 | 31 | trailer = ''' 32 | 33 | 34 | ''' 35 | 36 | BLUE = '#143CCC' 37 | WHITE = '#FFFFFF' 38 | BLACK = '#000000' 39 | GREEN = "#079F00" 40 | RED = "#ff0000" 41 | PURPLE = "#6E15CC" 42 | ORANGE = "#ff5500" 43 | 44 | 45 | def make_cell(content, bold=False, color=None): 46 | if not bold and color is None: 47 | return "%s" % content 48 | else: 49 | bold = "" if not bold else "font-weight:bold;" 50 | color = "" if color is None else "color: %s;" % color 51 | return "%s" % (bold, color, content) 52 | 53 | 54 | class HTMLReport: 55 | def __init__(self): 56 | # TODO: detect IDA version to embed or not bootstrap 57 | self.datas = [] 58 | 59 | def add_title(self, title, size=1): 60 | self.datas.append("%s" % (size, title, size)) 61 | 62 | def add_table_header(self, elts): 63 | prelude = '''''' 64 | row = ''.join(['' % x for x in elts]) 65 | self.datas.append(prelude+row+"") 66 | 67 | def add_table_line(self, elts): 68 | self.datas.append(""+''.join(elts)+"") 69 | 70 | def end_table(self): 71 | self.datas.append("
%s
") 72 | 73 | def generate(self): 74 | return "%s%s%s" % (header, ''.join(self.datas), trailer) 75 | -------------------------------------------------------------------------------- /idasec/trace.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import struct 3 | 4 | from capstone import Cs, CS_ARCH_X86, CS_MODE_32 5 | 6 | from idasec.proto.trace_pb2 import * 7 | from idasec.proto.common_pb2 import * 8 | from idasec.exception import assert_ida_available 9 | 10 | 11 | md = Cs(CS_ARCH_X86, CS_MODE_32) 12 | 13 | 14 | def proto_size_t_to_int(a): 15 | return {INVALID_SIZE: -1, 16 | BIT8: 8, 17 | BIT16: 16, 18 | BIT32: 32, 19 | BIT64: 64, 20 | BIT80: 80, 21 | BIT128: 128, 22 | BIT256: 256}[a] 23 | 24 | 25 | class Instruction: 26 | def __init__(self): 27 | self.thread = None 28 | self.address = None 29 | self.opcode = "" 30 | self.opc_bytes = None 31 | self.decoded = False 32 | self.dbainstrs = [] 33 | 34 | # Concrete infos 35 | self.nextaddr = None 36 | self.registers = [] 37 | self.memories = [] 38 | self.comment = None 39 | self.wave = None 40 | self.syscall = None 41 | self.libcall = None 42 | self.not_retrieved = False 43 | 44 | def parse(self, pb_inst): 45 | self.thread = pb_inst.thread_id 46 | self.address = pb_inst.address 47 | self.opc_bytes = pb_inst.opcode 48 | for i in md.disasm(self.opc_bytes, self.address): 49 | self.opcode += i.mnemonic + " "+i.op_str 50 | if len(pb_inst.dba_instrs.instrs) != 0: 51 | self.decoded = True 52 | # TODO: Add the decoding of DBA instructions 53 | else: 54 | pass 55 | for info in pb_inst.concrete_infos: 56 | if info.typeid == ins_con_info_t.REGREAD: 57 | self.add_register(info.read_register, "R") 58 | elif info.typeid == ins_con_info_t.REGWRITE: 59 | self.add_register(info.write_register, "W") 60 | elif info.typeid == ins_con_info_t.MEMLOAD: 61 | self.memories.append(("R", info.load_memory.addr, info.load_memory.value)) 62 | elif info.typeid == ins_con_info_t.MEMSTORE: 63 | self.memories.append(("W", info.store_memory.addr, info.store_memory.value)) 64 | elif info.typeid == ins_con_info_t.CALL: 65 | self.libcall = info.call 66 | elif info.typeid == ins_con_info_t.SYSCALL: 67 | self.syscall = info.system_call 68 | elif info.typeid == ins_con_info_t.NEXT_ADDRESS: 69 | self.nextaddr = info.next_address 70 | elif info.typeid == ins_con_info_t.COMMENT: 71 | self.comment = info.reserved_comment 72 | elif info.typeid == ins_con_info_t.WAVE: 73 | self.wave = info.wave 74 | 75 | def add_register(self, reg, r_or_w): 76 | value = {BIT8: reg.value.value_8, 77 | BIT16: reg.value.value_16, 78 | BIT32: reg.value.value_32, 79 | BIT64: reg.value.value_64, 80 | BIT80: reg.value.value_80, 81 | BIT128: reg.value.value_128, 82 | BIT256: reg.value.value_256}[reg.value.typeid] 83 | if reg != "ss": 84 | self.registers.append((r_or_w, reg.name, value)) 85 | 86 | def to_string(self): 87 | address = hex(self.address)[:-1] 88 | raw_opc = to_hex_spaced(self.opc_bytes) 89 | concs = "" 90 | concs += "" if self.libcall is None else " Call:"+self.libcall.func_name 91 | concs += "" if self.syscall is None else " "+str(self.syscall.id) 92 | concs += "" if self.comment is None else " Comment:"+self.comment 93 | for r_w, reg, value in self.registers: 94 | val = hex(value) if isinstance(value, int) else to_hex(value) 95 | concs += " "+r_w+"["+reg+"]="+val 96 | for r_w, addr, value in self.memories: 97 | concs += " "+r_w+"@["+hex(addr)+"]="+to_hex(value) 98 | concs += " Next:"+hex(self.nextaddr) if self.nextaddr is not None else "" 99 | padding1 = " " * (25 - len(raw_opc)) 100 | padding2 = " " * (16 - len(self.opcode) + len(padding1) + len(raw_opc)) 101 | th = self.thread 102 | return "%s t[%d] %s%s%s%s %s" % (address, th, raw_opc, padding1, self.opcode, padding2, concs) 103 | 104 | 105 | class Trace: 106 | 107 | def __init__(self, fname): 108 | self.filename = fname 109 | self.addr_size = 32 110 | self.architecture = None 111 | self.instrs = {} 112 | self.metas = {} 113 | self.trace_index = 0 114 | self.length = lambda: self.trace_index - 1 115 | self.addr_covered = set() 116 | self.address_hit_count = {} 117 | 118 | def parse_file_generator(self, filename): 119 | self.filename = filename 120 | f = open(filename, "rb") 121 | size, = struct.unpack("I", f.read(4)) 122 | header = header_t() 123 | header.ParseFromString(f.read(size)) 124 | self.architecture = header.architecture 125 | self.addr_size = proto_size_t_to_int(header.address_size) 126 | chunk_nb = 0 127 | chunk = chunk_t() 128 | while True: 129 | tmp = f.read(4) 130 | if tmp == "": 131 | break 132 | else: 133 | chunk_nb += 1 134 | old_index = self.trace_index 135 | size, = struct.unpack("I", tmp) 136 | chunk.ParseFromString(f.read(size)) 137 | self.add_body(chunk.body) 138 | yield chunk_nb, len(chunk.body), old_index, self.trace_index, size 139 | f.close() 140 | 141 | def parse_file(self, filename): 142 | for _, _, _, _, _ in self.parse_file_generator(filename): 143 | pass 144 | 145 | def add_body(self, body): 146 | for elem in body: 147 | if elem.typeid == body_t.METADATA: 148 | m = {metadata_t.INVALID_METADATA: ("invalid", None, None), 149 | metadata_t.EXCEPTION_TYPE: ('exception', elem.metadata.exception_metadata.type_exception, 150 | elem.metadata.exception_metadata.handler), 151 | metadata_t.MODULE_TYPE: ('module', elem.metadata.module_metadata, None), 152 | metadata_t.WAVE_TYPE: ("wave", elem.metadata.wave_metadata, None)}[elem.metadata.typeid] 153 | if self.trace_index in self.metas: 154 | self.metas[self.trace_index].append(m) 155 | else: 156 | self.metas[self.trace_index] = [m] 157 | elif elem.typeid == body_t.INSTRUCTION: 158 | inst = Instruction() 159 | inst.parse(elem.instruction) 160 | self.instrs[self.trace_index] = inst 161 | self.addr_covered.add(inst.address) 162 | if inst.address in self.address_hit_count: 163 | self.address_hit_count[inst.address] += 1 164 | else: 165 | self.address_hit_count[inst.address] = 1 166 | self.trace_index += 1 167 | 168 | def to_string_generator(self): 169 | for i in xrange(self.trace_index): 170 | if i in self.metas: 171 | for m in self.metas[i]: 172 | name_id, arg1, arg2 = m 173 | if name_id == "invalid": 174 | print name_id 175 | elif name_id == "exception": 176 | print "Exception, type:"+str(arg1)+" handler:"+to_hex(arg2) 177 | elif name_id == "wave": 178 | print "====================== Wave "+str(arg1) + "======================" 179 | yield "%d %s" % (i, self.instrs[i].to_string()) 180 | 181 | def print_trace(self): 182 | for line in self.to_string_generator(): 183 | print line 184 | 185 | 186 | def make_header(): 187 | header = header_t() 188 | header.architecture = header.X86 189 | header.address_size = BIT32 190 | return header 191 | 192 | 193 | def chunk_from_path(path): 194 | assert_ida_available() 195 | import idc 196 | chunk = chunk_t() 197 | for i in xrange(len(path)): 198 | body = chunk.body.add() 199 | body.typeid = body.INSTRUCTION 200 | inst = body.instruction 201 | inst.thread_id = 0 202 | addr = path[i] 203 | inst.address = addr 204 | inst.opcode = idc.GetManyBytes(addr, idc.NextHead(addr)-addr) 205 | try: 206 | next_a = path[i+1] 207 | inf1 = inst.concrete_infos.add() 208 | inf1.next_address = next_a 209 | inf1.typeid = inf1.NEXT_ADDRESS 210 | except IndexError: 211 | pass 212 | inf2 = inst.concrete_infos.add() 213 | inf2.typeid = inf2.NOT_RETRIEVED 214 | return chunk 215 | 216 | 217 | def raw_parse_trace(filename): 218 | f = open(filename, "rb") 219 | size, = struct.unpack("I", f.read(4)) 220 | raw_header = f.read(size) 221 | yield "TRACE_HEADER", raw_header 222 | while True: 223 | tmp = f.read(4) 224 | if tmp == "": 225 | break 226 | else: 227 | size, = struct.unpack("I", tmp) 228 | raw = f.read(size) 229 | yield "TRACE_CHUNK", raw 230 | f.close() 231 | 232 | 233 | if __name__ == "__main__": 234 | from utils import to_hex_spaced, to_hex 235 | if len(sys.argv) < 2: 236 | print "Please provide a trace in parameter" 237 | sys.exit(1) 238 | 239 | name = sys.argv[1] 240 | 241 | trace = Trace(name) 242 | trace.parse_file(name) 243 | trace.print_trace() 244 | -------------------------------------------------------------------------------- /idasec/ui/Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | pyuic5 main.ui -o main_ui.py 3 | pyuic5 master.ui -o master_ui.py 4 | pyuic5 trace.ui -o trace_ui.py 5 | pyuic5 analysis.ui -o analysis_ui.py 6 | pyuic5 generic_analysis.ui -o generic_analysis_ui.py 7 | pyuic5 generic_analysis_result.ui -o generic_analysis_result_ui.py 8 | pyuic5 standard_params.ui -o standard_params_ui.py 9 | pyuic5 static_iteration_config.ui -o static_iteration_config_ui.py 10 | pyuic5 standard_result.ui -o standard_result_ui.py 11 | pyrcc5 resources.qrc -o resources_rc.py 12 | -------------------------------------------------------------------------------- /idasec/ui/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RobinDavid/idasec/e7d9328f9291dcadf34540a31608995b3697d047/idasec/ui/__init__.py -------------------------------------------------------------------------------- /idasec/ui/custom_widgets.py: -------------------------------------------------------------------------------- 1 | from PyQt5 import QtGui, QtCore, QtWidgets 2 | 3 | 4 | class ButtonLineEdit(QtWidgets.QLineEdit): 5 | 6 | def __init__(self, items, parent=None): 7 | super(ButtonLineEdit, self).__init__(parent) 8 | 9 | self.menu = QtWidgets.QMenu() 10 | for i in items: 11 | self.menu.addAction(i) 12 | 13 | self.button = QtWidgets.QToolButton(self) 14 | self.button.setStyleSheet('border: 0px; padding: 0px;') 15 | self.button.setCursor(QtCore.Qt.ArrowCursor) 16 | self.button.triggered.connect(self.menu_action_triggered) 17 | self.button.setPopupMode(QtWidgets.QToolButton.InstantPopup) 18 | self.button.setMenu(self.menu) 19 | 20 | frameWidth = self.style().pixelMetric(QtWidgets.QStyle.PM_DefaultFrameWidth) 21 | buttonSize = self.button.sizeHint() 22 | 23 | self.setAlignment(QtCore.Qt.Alignment(QtCore.Qt.AlignHCenter)) 24 | self.setStyleSheet('QLineEdit {padding-right: %dpx; }' % (buttonSize.width() + frameWidth + 1)) 25 | self.setMinimumSize(max(self.minimumSizeHint().width(), buttonSize.width() + frameWidth*2 + 2), 26 | max(self.minimumSizeHint().height(), buttonSize.height() + frameWidth*2 + 2)) 27 | self.setMaximumWidth(100) 28 | 29 | def resizeEvent(self, event): 30 | buttonSize = self.button.sizeHint() 31 | frameWidth = self.style().pixelMetric(QtWidgets.QStyle.PM_DefaultFrameWidth) 32 | self.button.move(self.rect().right() - frameWidth - buttonSize.width(), 33 | (self.rect().bottom() - buttonSize.height() + 1)/2) 34 | super(ButtonLineEdit, self).resizeEvent(event) 35 | 36 | def menu_action_triggered(self, action): 37 | self.setText(action.text()) 38 | -------------------------------------------------------------------------------- /idasec/ui/generic_analysis.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | generic_analysis_widget 4 | 5 | 6 | 7 | 0 8 | 0 9 | 292 10 | 196 11 | 12 | 13 | 14 | Form 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 0 24 | 0 25 | 26 | 27 | 28 | From: 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 25 40 | 25 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | :/icons/icons/open-iconic-master/png/3x/target-3x.png:/icons/icons/open-iconic-master/png/3x/target-3x.png 49 | 50 | 51 | 52 | 12 53 | 12 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 0 63 | 0 64 | 65 | 66 | 67 | To: 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 25 79 | 25 80 | 81 | 82 | 83 | 84 | 25 85 | 25 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | :/icons/icons/open-iconic-master/png/3x/target-3x.png:/icons/icons/open-iconic-master/png/3x/target-3x.png 94 | 95 | 96 | 97 | 12 98 | 12 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 0 112 | 0 113 | 114 | 115 | 116 | Target addr: 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 25 128 | 25 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | :/icons/icons/open-iconic-master/png/3x/target-3x.png:/icons/icons/open-iconic-master/png/3x/target-3x.png 137 | 138 | 139 | 140 | 12 141 | 12 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 0 155 | 0 156 | 157 | 158 | 159 | DBA Expr: 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 25 171 | 25 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | :/icons/icons/open-iconic-master/png/3x/question-mark-3x.png:/icons/icons/open-iconic-master/png/3x/question-mark-3x.png 180 | 181 | 182 | 183 | 12 184 | 12 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 0 198 | 0 199 | 200 | 201 | 202 | Query: 203 | 204 | 205 | 206 | 207 | 208 | 209 | Satisfiability 210 | 211 | 212 | 213 | 214 | 215 | 216 | Values 217 | 218 | 219 | 220 | 221 | 222 | 223 | 1 224 | 225 | 226 | 227 | 228 | 229 | 230 | Qt::Horizontal 231 | 232 | 233 | 234 | 40 235 | 20 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | Restrict values space: 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | From: 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 25 266 | 25 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | :/icons/icons/open-iconic-master/png/3x/target-3x.png:/icons/icons/open-iconic-master/png/3x/target-3x.png 275 | 276 | 277 | 278 | 12 279 | 12 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | To: 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 25 299 | 25 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | :/icons/icons/open-iconic-master/png/3x/target-3x.png:/icons/icons/open-iconic-master/png/3x/target-3x.png 308 | 309 | 310 | 311 | 12 312 | 12 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | Retrieve formula from Binsec 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | -------------------------------------------------------------------------------- /idasec/ui/generic_analysis_result.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | generic_analysis_result 4 | 5 | 6 | 7 | 0 8 | 0 9 | 526 10 | 441 11 | 12 | 13 | 14 | Form 15 | 16 | 17 | 18 | 19 | 20 | Qt::Vertical 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 10 29 | 75 30 | false 31 | true 32 | 33 | 34 | 35 | Result 36 | 37 | 38 | Qt::AlignCenter 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 75 54 | true 55 | 56 | 57 | 58 | SMT Formula 59 | 60 | 61 | Qt::AlignCenter 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 0 70 | 0 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 0 86 | 0 87 | 88 | 89 | 90 | Action: 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 0 102 | 0 103 | 104 | 105 | 106 | 107 | 70 108 | 0 109 | 110 | 111 | 112 | Go ! 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | -------------------------------------------------------------------------------- /idasec/ui/icons/idasec.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RobinDavid/idasec/e7d9328f9291dcadf34540a31608995b3697d047/idasec/ui/icons/idasec.png -------------------------------------------------------------------------------- /idasec/ui/icons/idasec_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RobinDavid/idasec/e7d9328f9291dcadf34540a31608995b3697d047/idasec/ui/icons/idasec_small.png -------------------------------------------------------------------------------- /idasec/ui/icons/open-iconic-master/png/3x/magnifying-glass-3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RobinDavid/idasec/e7d9328f9291dcadf34540a31608995b3697d047/idasec/ui/icons/open-iconic-master/png/3x/magnifying-glass-3x.png -------------------------------------------------------------------------------- /idasec/ui/icons/open-iconic-master/png/3x/minus-3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RobinDavid/idasec/e7d9328f9291dcadf34540a31608995b3697d047/idasec/ui/icons/open-iconic-master/png/3x/minus-3x.png -------------------------------------------------------------------------------- /idasec/ui/icons/open-iconic-master/png/3x/plus-3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RobinDavid/idasec/e7d9328f9291dcadf34540a31608995b3697d047/idasec/ui/icons/open-iconic-master/png/3x/plus-3x.png -------------------------------------------------------------------------------- /idasec/ui/icons/open-iconic-master/png/3x/question-mark-3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RobinDavid/idasec/e7d9328f9291dcadf34540a31608995b3697d047/idasec/ui/icons/open-iconic-master/png/3x/question-mark-3x.png -------------------------------------------------------------------------------- /idasec/ui/icons/open-iconic-master/png/3x/target-3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RobinDavid/idasec/e7d9328f9291dcadf34540a31608995b3697d047/idasec/ui/icons/open-iconic-master/png/3x/target-3x.png -------------------------------------------------------------------------------- /idasec/ui/icons/oxygen/22x22/ko.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RobinDavid/idasec/e7d9328f9291dcadf34540a31608995b3697d047/idasec/ui/icons/oxygen/22x22/ko.png -------------------------------------------------------------------------------- /idasec/ui/icons/oxygen/22x22/ok.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RobinDavid/idasec/e7d9328f9291dcadf34540a31608995b3697d047/idasec/ui/icons/oxygen/22x22/ok.png -------------------------------------------------------------------------------- /idasec/ui/main.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Main 4 | 5 | 6 | 7 | 0 8 | 0 9 | 467 10 | 358 11 | 12 | 13 | 14 | 15 | 16777215 16 | 16777215 17 | 18 | 19 | 20 | IDASec 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 14 30 | 75 31 | true 32 | 33 | 34 | 35 | 36 | 37 | 38 | :/icons/icons/idasec_small.png 39 | 40 | 41 | Qt::AlignCenter 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | Binsec IP: 51 | 52 | 53 | 54 | 55 | 56 | 57 | 127.0.0.1 58 | 59 | 60 | 61 | 62 | 63 | 64 | Port: 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 0 73 | 0 74 | 75 | 76 | 77 | 78 | 100 79 | 16777215 80 | 81 | 82 | 83 | 5570 84 | 85 | 86 | 87 | 88 | 89 | 90 | Connect 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | Pinsec IP: 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | Port: 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 100 127 | 16777215 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | Connect 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | false 152 | 153 | 154 | Infos: 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | Qt::Horizontal 169 | 170 | 171 | 172 | 173 | 174 | 175 | false 176 | 177 | 178 | 179 | 12 180 | 75 181 | false 182 | true 183 | true 184 | 185 | 186 | 187 | Utils 188 | 189 | 190 | Qt::AlignCenter 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | false 200 | 201 | 202 | IR Decode: 203 | 204 | 205 | 206 | 207 | 208 | 209 | false 210 | 211 | 212 | Instruction Hex eg:85db 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | false 223 | 224 | 225 | Decode 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 25 234 | 25 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | :/icons/icons/open-iconic-master/png/3x/target-3x.png:/icons/icons/open-iconic-master/png/3x/target-3x.png 243 | 244 | 245 | 246 | 12 247 | 12 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | false 258 | 259 | 260 | 261 | DejaVu Sans Mono 262 | 263 | 264 | 265 | 1 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | dba_decode_button 279 | clicked() 280 | ir_textarea 281 | clear() 282 | 283 | 284 | 453 285 | 199 286 | 287 | 288 | 248 289 | 283 290 | 291 | 292 | 293 | 294 | 295 | -------------------------------------------------------------------------------- /idasec/ui/master.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Master 4 | 5 | 6 | 7 | 0 8 | 0 9 | 718 10 | 477 11 | 12 | 13 | 14 | IDASec 15 | 16 | 17 | 18 | 19 | 20 | Qt::Vertical 21 | 22 | 23 | 24 | -1 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /idasec/ui/resources.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | icons/open-iconic-master/png/3x/magnifying-glass-3x.png 4 | icons/open-iconic-master/png/3x/minus-3x.png 5 | icons/open-iconic-master/png/3x/plus-3x.png 6 | icons/oxygen/22x22/ok.png 7 | icons/oxygen/22x22/ko.png 8 | icons/open-iconic-master/png/3x/question-mark-3x.png 9 | icons/open-iconic-master/png/3x/target-3x.png 10 | icons/idasec.png 11 | icons/idasec_small.png 12 | 13 | 14 | -------------------------------------------------------------------------------- /idasec/ui/standard_params.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | standard_params 4 | 5 | 6 | 7 | 0 8 | 0 9 | 293 10 | 82 11 | 12 | 13 | 14 | Form 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | Target: 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 25 34 | 25 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | :/icons/icons/open-iconic-master/png/3x/target-3x.png:/icons/icons/open-iconic-master/png/3x/target-3x.png 43 | 44 | 45 | 46 | 47 | 48 | 49 | Qt::Horizontal 50 | 51 | 52 | 53 | 40 54 | 20 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | Unique results (per target) 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /idasec/ui/standard_result.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | standard_result 4 | 5 | 6 | 7 | 0 8 | 0 9 | 495 10 | 416 11 | 12 | 13 | 14 | Form 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 0 27 | 0 28 | 29 | 30 | 31 | Action: 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 0 43 | 0 44 | 45 | 46 | 47 | 48 | 70 49 | 0 50 | 51 | 52 | 53 | Go ! 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /idasec/ui/static_iteration_config.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | static_iteration_config 4 | 5 | 6 | 7 | 0 8 | 0 9 | 294 10 | 231 11 | 12 | 13 | 14 | Form 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | Granularity: 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | Ins&truction 32 | 33 | 34 | group_granularity 35 | 36 | 37 | 38 | 39 | 40 | 41 | Routine 42 | 43 | 44 | group_granularity 45 | 46 | 47 | 48 | 49 | 50 | 51 | Progra&m 52 | 53 | 54 | group_granularity 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | Target: 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 25 79 | 25 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | :/icons/icons/open-iconic-master/png/3x/target-3x.png:/icons/icons/open-iconic-master/png/3x/target-3x.png 88 | 89 | 90 | 91 | 92 | 93 | 94 | Qt::Horizontal 95 | 96 | 97 | 98 | 40 99 | 20 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | Qt::Horizontal 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | Path: 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | Make the path to the predicate start at the begining of the routine 128 | 129 | 130 | Routine 131 | 132 | 133 | group_path 134 | 135 | 136 | 137 | 138 | 139 | 140 | Start the path at the basic block 141 | 142 | 143 | Basic Bloc&k 144 | 145 | 146 | group_path 147 | 148 | 149 | 150 | 151 | 152 | 153 | Start the path at the first last instruction with multiple predecessors 154 | 155 | 156 | Safe 157 | 158 | 159 | group_path 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | -------------------------------------------------------------------------------- /idasec/ui/trace.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | trace_form 4 | 5 | 6 | 7 | 0 8 | 0 9 | 799 10 | 556 11 | 12 | 13 | 14 | Trace 15 | 16 | 17 | 18 | 19 | 20 | Qt::Horizontal 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 400 29 | 0 30 | 31 | 32 | 33 | -1 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 0 44 | 0 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 24 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 0 64 | 30 65 | 66 | 67 | 68 | 69 | 30 70 | 30 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | :/icons/icons/open-iconic-master/png/3x/plus-3x.png:/icons/icons/open-iconic-master/png/3x/plus-3x.png 79 | 80 | 81 | 82 | 14 83 | 14 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 0 99 | 0 100 | 101 | 102 | 103 | 104 | 75 105 | false 106 | true 107 | false 108 | 109 | 110 | 111 | Trace infos 112 | 113 | 114 | Qt::AlignCenter 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 0 123 | 0 124 | 125 | 126 | 127 | 128 | 0 129 | 60 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | Qt::Horizontal 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 0 149 | 0 150 | 151 | 152 | 153 | 154 | 75 155 | true 156 | 157 | 158 | 159 | Instruction infos 160 | 161 | 162 | Qt::AlignCenter 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 0 173 | 0 174 | 175 | 176 | 177 | 178 | 16777215 179 | 20 180 | 181 | 182 | 183 | 184 | true 185 | 186 | 187 | 188 | Read: 189 | 190 | 191 | Qt::AlignCenter 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 0 200 | 0 201 | 202 | 203 | 204 | 205 | 16777215 206 | 20 207 | 208 | 209 | 210 | 211 | true 212 | 213 | 214 | 215 | Write: 216 | 217 | 218 | Qt::AlignCenter 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 0 231 | 0 232 | 233 | 234 | 235 | 236 | 0 237 | 0 238 | 239 | 240 | 241 | 242 | 16777215 243 | 16777215 244 | 245 | 246 | 247 | 248 | 1 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 0 258 | 0 259 | 260 | 261 | 262 | 263 | 16777215 264 | 16777215 265 | 266 | 267 | 268 | 269 | 1 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | Qt::Vertical 283 | 284 | 285 | 286 | 20 287 | 40 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | Qt::Horizontal 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 0 304 | 0 305 | 306 | 307 | 308 | 309 | 75 310 | true 311 | 312 | 313 | 314 | Trace actions 315 | 316 | 317 | Qt::AlignCenter 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | Save a dump of the trace to a file 327 | 328 | 329 | Dump to file 330 | 331 | 332 | 333 | 334 | 335 | 336 | Coloration: 337 | 338 | 339 | 340 | 341 | 342 | 343 | Refresh function information of the trace 344 | 345 | 346 | Refresh infos 347 | 348 | 349 | 350 | 351 | 352 | 353 | disassemble program following the trace (* stop at the first wave *) 354 | 355 | 356 | Disassemble 357 | 358 | 359 | 360 | 361 | 362 | 363 | Utils: 364 | 365 | 366 | 367 | 368 | 369 | 370 | Colorize the trace to vizualize code coverage 371 | 372 | 373 | Colorize trace 374 | 375 | 376 | 377 | 378 | 379 | 380 | Colorize the code by instruction occurence 381 | 382 | 383 | Heatmap 384 | 385 | 386 | 387 | 388 | 389 | 390 | Disassembly: 391 | 392 | 393 | 394 | 395 | 396 | 397 | Qt::Horizontal 398 | 399 | 400 | 401 | 40 402 | 20 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | -------------------------------------------------------------------------------- /idasec/utils.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | 4 | def register_name_to_size(reg): 5 | if reg in ["al", "ah", "bl", "bh", "cl", "ch", "dl", "dh"]: 6 | return 8 7 | elif reg in ["ax", "bx", "cx", "dx", "di", "si", "bp", "sp"]: 8 | return 16 9 | elif reg in ["eax", "ebx", "ecx", "edx", "edi", "esi", "ebp", "esp"]: 10 | return 32 11 | elif reg in ["x87"]: 12 | return 80 13 | else: 14 | return -1 15 | 16 | 17 | def to_hex(s): 18 | return "".join("{:02x}".format(ord(c)) for c in s) 19 | 20 | 21 | def to_hex_spaced(s): 22 | return " ".join("{:02x}".format(ord(c)) for c in s) 23 | 24 | 25 | def hex_to_bin(s): 26 | s = s[2:] if s.startswith("0x") else s 27 | return ''.join([chr(int(x, 16)) for x in hex_split(s)]) 28 | 29 | 30 | def hex_split(s): 31 | return [s[k:k+2] for k in xrange(0, len(s), 2)] 32 | 33 | 34 | def to_addr(s): # raise ValueError if the conversion fail 35 | s = s.replace(" ", "") 36 | if s.endswith("L"): 37 | s = s[:-1] 38 | if not re.match('^[0-9a-fA-F]+$', s if not s.startswith("0x") else s[2:]): 39 | raise ValueError 40 | return int(s, 16) if s.startswith("0x") else int(s) 41 | 42 | 43 | def nsplit(s, n): # Split a list into sublists of size "n" 44 | return [s[k:k+n] for k in xrange(0, len(s), n)] -------------------------------------------------------------------------------- /idasec/widgets/MainWidget.py: -------------------------------------------------------------------------------- 1 | # coding: utf8 2 | 3 | import re 4 | import sys 5 | import time 6 | from PyQt5 import QtGui, QtWidgets 7 | 8 | import idc 9 | from idasec.dba_printer import instr_to_string 10 | from idasec.network.commands import END, DECODE_INSTR_REPLY 11 | from idasec.network.message import MessageInfos, MessageDecodeInstr, MessageDecodeInstrReply 12 | from idasec.ui.main_ui import Ui_Main 13 | from idasec.utils import to_hex 14 | 15 | 16 | class MainWidget(QtWidgets.QWidget, Ui_Main): 17 | def __init__(self, parent): 18 | super(MainWidget, self).__init__() 19 | self.parent = parent 20 | self.name = "MainWidget" 21 | self.core = self.parent.core 22 | self.broker = self.core.broker 23 | self.icon = QtGui.QIcon("semantics.png") 24 | self.OnCreate(self) 25 | 26 | # class IDASecApp(PluginForm, Ui_Main): 27 | 28 | def OnCreate(self, _): 29 | self.setupUi(self) 30 | self.binsec_connect_button.clicked.connect(self.connect_binsec) 31 | self.dba_decode_button.clicked.connect(self.decode_button_clicked) 32 | self.here_decode_button.clicked.connect(self.decode_here_clicked) 33 | self.pinsec_ip_field.setText("192.168.56.101") 34 | self.pinsec_port_field.setText("5555") 35 | self.binsec_port_field.setValidator(QtGui.QIntValidator(0, 65535)) 36 | self.pinsec_port_field.setValidator(QtGui.QIntValidator(0, 65535)) 37 | self.ok = QtGui.QPixmap(":/icons/icons/oxygen/22x22/ok.png") 38 | self.ko = QtGui.QPixmap(":/icons/icons/oxygen/22x22/ko.png") 39 | self.prev_modules = sys.modules.keys() 40 | self.set_pinsec_visible(False) 41 | 42 | def connect_binsec(self): 43 | try: 44 | time.sleep(0.1) 45 | except Exception: 46 | print "Something went wrong (with Binsec connection)..." 47 | 48 | if self.core.binsec_connected: 49 | self.broker.disconnect_binsec() 50 | self.set_disconnected() 51 | else: 52 | ip = self.binsec_ip_field.text() 53 | port = self.binsec_port_field.text() 54 | self.broker.connect_binsec(ip, port) 55 | self.broker.send_binsec_message("GET_INFOS", "STUB", blocking=False) 56 | time.sleep(0.1) 57 | cmd, data = self.broker.receive_binsec_message(blocking=False) 58 | if cmd is not None and data is not None: 59 | print "Connected to Binsec" 60 | self.binsec_label_status.setPixmap(self.ok) 61 | message = MessageInfos() 62 | message.parse(data) 63 | nb_workers, analyses, solvers = message.get_infos() 64 | self.infos_label.setText( 65 | "CPU:%d\nAnalyses:%s\nSolvers:%s" % (nb_workers, " ".join(analyses), " ".join(solvers))) 66 | self.parent.add_solvers(solvers) 67 | self.parent.add_analyses([x.upper() for x in analyses]) 68 | self.core.nb_cpus = nb_workers 69 | self.set_connected() 70 | else: 71 | print "Not Connected to Binsec" 72 | self.set_disconnected() 73 | self.binsec_label_status.setPixmap(self.ko) 74 | 75 | def set_connected(self): 76 | self.core.binsec_connected = True 77 | self.infos_title_label.setEnabled(True) 78 | self.infos_label.setEnabled(True) 79 | self.utils_label.setEnabled(True) 80 | self.ir_label.setEnabled(True) 81 | self.dba_decode_field.setEnabled(True) 82 | self.dba_decode_button.setEnabled(True) 83 | self.ir_textarea.setEnabled(True) 84 | self.binsec_connect_button.setText("disconnect") 85 | self.binsec_port_field.setEnabled(False) 86 | self.binsec_ip_field.setEnabled(False) 87 | 88 | def set_disconnected(self): 89 | self.core.binsec_connected = False 90 | self.binsec_label_status.clear() 91 | self.infos_title_label.setEnabled(False) 92 | self.infos_label.setEnabled(False) 93 | self.utils_label.setEnabled(False) 94 | self.ir_label.setEnabled(False) 95 | self.dba_decode_field.setEnabled(False) 96 | self.dba_decode_button.setEnabled(False) 97 | self.ir_textarea.setEnabled(False) 98 | self.binsec_connect_button.setText("connect") 99 | self.binsec_port_field.setEnabled(True) 100 | self.binsec_ip_field.setEnabled(True) 101 | 102 | def set_pinsec_visible(self, value): 103 | self.label_2.setVisible(value) 104 | self.pinsec_connect_button.setVisible(value) 105 | self.pinsec_ip_field.setVisible(value) 106 | self.pinsec_label.setVisible(value) 107 | self.pinsec_port_field.setVisible(value) 108 | 109 | def decode_here_clicked(self): 110 | inst = idc.here() 111 | if not idc.isCode(idc.GetFlags(inst)): 112 | print "Not code instruction" 113 | else: 114 | raw = idc.GetManyBytes(inst, idc.NextHead(inst)-inst) 115 | s = to_hex(raw) 116 | self.decode_ir(s) 117 | 118 | def decode_button_clicked(self): 119 | opc = self.dba_decode_field.text().encode('ascii', 'ignore').replace(" ", "") 120 | if not re.match('^[0-9a-fA-F]+$', opc): 121 | print "Invalid input:"+opc 122 | return 123 | self.decode_ir(opc) 124 | 125 | def decode_ir(self, opc): 126 | if not self.core.binsec_connected: 127 | self.parent.log("ERROR", "Not connected to Binsec") 128 | return 129 | 130 | if opc != "": 131 | mess = MessageDecodeInstr(kind="hexa", instrs=opc, base_addrs=0) 132 | raw = mess.serialize() 133 | self.broker.send_binsec_message("DECODE_INSTR", raw, blocking=False) 134 | time.sleep(0.2) 135 | cmd, data = self.broker.receive_binsec_message(blocking=False) 136 | if cmd is not None and data is not None: 137 | if cmd == END: 138 | self.ir_textarea.setText("Error occured:"+data) 139 | elif cmd == DECODE_INSTR_REPLY: 140 | reply = MessageDecodeInstrReply() 141 | reply.parse(data) 142 | for opc, dbainsts in reply.instrs: 143 | self.ir_textarea.setText(opc+":") 144 | length = len(dbainsts)-1 145 | arr = ["⎧" if i == 0 else "⎩" if i == length - 1 else "⎨" if i == length / 2 else "⎪" 146 | for i in range(length)] 147 | arr = [""] if length == 1 else arr 148 | for i in range(len(dbainsts[:-1])): 149 | dba = dbainsts[i] 150 | self.ir_textarea.append(arr[i]+instr_to_string(dba)) 151 | else: 152 | print "Unknown cmd:"+cmd 153 | else: 154 | print "Timeout exceeded to receive a binsec reply" 155 | else: 156 | print "Invalid input :"+opc 157 | 158 | def OnClose(self, _): 159 | print("Closed invoked !") 160 | pass 161 | -------------------------------------------------------------------------------- /idasec/widgets/StandardParamWidget.py: -------------------------------------------------------------------------------- 1 | from PyQt5 import QtWidgets 2 | import idc 3 | 4 | from idasec.proto.analysis_config_pb2 import standard_analysis, specific_parameters_t 5 | import idasec.utils as utils 6 | from idasec.ui.standard_params_ui import Ui_standard_params 7 | 8 | 9 | class StandardParamConfigWidget(QtWidgets.QWidget, Ui_standard_params): 10 | 11 | def __init__(self): 12 | super(StandardParamConfigWidget, self).__init__() 13 | self.conf = standard_analysis() 14 | self.setupUi(self) 15 | self.target_button.clicked.connect(self.target_button_clicked) 16 | 17 | def set_fields(self, json_fields): 18 | gen = json_fields["standard_params"] 19 | if "target_addr" in gen: 20 | self.target_field.setText(hex(gen["target_addr"])) 21 | if "uniq" in gen: 22 | self.uniq_checkbox.setChecked(gen["uniq"]) 23 | 24 | def serialize(self): 25 | target_addr = self.target_field.text() 26 | try: 27 | if target_addr != "": 28 | self.conf.target_addr = utils.to_addr(target_addr) 29 | except ValueError: 30 | print "Invalid values for target address" 31 | 32 | self.conf.uniq = self.uniq_checkbox.isChecked() 33 | 34 | try: 35 | params = specific_parameters_t() 36 | params.typeid = params.STANDARD 37 | params.standard_params.CopyFrom(self.conf) 38 | return params 39 | except Exception: 40 | print "Analysis specific arguments serialization failed" 41 | return None 42 | 43 | def target_button_clicked(self): 44 | self.target_field.setText(hex(idc.here())) 45 | -------------------------------------------------------------------------------- /idasec/widgets/StandardResultWidget.py: -------------------------------------------------------------------------------- 1 | from PyQt5 import QtWidgets 2 | 3 | from idasec.ui.standard_result_ui import Ui_standard_result 4 | 5 | 6 | class StandardResultWidget(QtWidgets.QWidget, Ui_standard_result): 7 | 8 | def __init__(self, parent): 9 | QtWidgets.QWidget.__init__(self) 10 | self.setupUi(self) 11 | self.parent = parent 12 | self.action_selector.setEnabled(False) 13 | self.action_button.setEnabled(False) 14 | for k in self.parent.actions.keys(): 15 | 16 | self.action_selector.addItem(k) 17 | self.action_button.clicked.connect(self.action_clicked) 18 | self.action_selector.currentIndexChanged[str].connect(self.action_selector_changed) 19 | 20 | def action_selector_changed(self, s): 21 | _, enabled = self.parent.actions[s] 22 | if enabled: 23 | self.action_button.setText("Undo !") 24 | else: 25 | self.action_button.setText("Do !") 26 | 27 | def action_clicked(self): 28 | s = self.action_selector.currentText() 29 | fun, enabled = self.parent.actions[s] 30 | fun(enabled) 31 | 32 | def set_actions_visible_and_enabled(self, enable): 33 | self.action_label.setVisible(enable) 34 | self.action_selector.setVisible(enable) 35 | self.action_button.setVisible(enable) 36 | self.action_selector.setEnabled(enable) 37 | self.action_button.setEnabled(enable) 38 | -------------------------------------------------------------------------------- /idasec/widgets/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RobinDavid/idasec/e7d9328f9291dcadf34540a31608995b3697d047/idasec/widgets/__init__.py -------------------------------------------------------------------------------- /screenshot/idasec1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RobinDavid/idasec/e7d9328f9291dcadf34540a31608995b3697d047/screenshot/idasec1.png -------------------------------------------------------------------------------- /screenshot/idasec2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RobinDavid/idasec/e7d9328f9291dcadf34540a31608995b3697d047/screenshot/idasec2.png -------------------------------------------------------------------------------- /screenshot/idasec3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RobinDavid/idasec/e7d9328f9291dcadf34540a31608995b3697d047/screenshot/idasec3.png --------------------------------------------------------------------------------