├── .gitignore
├── .travis.yml
├── LICENSE
├── Makefile
├── README.md
├── codecov.yml
├── cutterdrcov_plugin
├── __init__.py
├── autogen
│ ├── __init__.py
│ ├── dockui.py
│ └── icon_rc.py
├── covtable.py
├── cutterplugin.py
├── drcov.py
├── extras.py
├── plugin_interface.py
└── sortable_table_item.py
├── resources
├── ICON-LICENSE
├── icon.qrc
└── icon
│ ├── brush.svg
│ ├── data-transfer-download.svg
│ ├── reload.svg
│ └── x.svg
├── screanshots
├── overview.jpg
├── path.jpg
└── usage.gif
├── scripts
├── lint.sh
├── local.sh
└── travis.sh
├── test_files
├── drcov.test2.log
├── drcov.test3.log
├── drcov2.2.log
├── drcov2.3.log
├── drcov2.4.log
├── drcov_orphan.log
├── test1.asm
├── test1.bin
├── test1.o
├── test2.asm
├── test2.bin
├── test2.o
├── test3.asm
├── test3.bin
└── test3.o
├── ui
└── dock.ui
└── unittest
├── __main__.py
├── test_covtable.py
├── test_drcov.py
├── test_extras.py
└── test_sortable_table_item.py
/.gitignore:
--------------------------------------------------------------------------------
1 | __pycache__
2 | *.swp
3 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: python
2 | python:
3 | - "3.6"
4 | install:
5 | pip install Pyside2 codecov pylint
6 | script:
7 | ./scripts/travis.sh
8 | after_success:
9 | - codecov
10 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Ahmed Abd El Mawgood
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | all:
2 | pyside2-uic ui/dock.ui --from-imports > cutterDRcovPlugin/autogen/dockui.py
3 | pyside2-rcc resources/icon.qrc > cutterDRcovPlugin/autogen/icon_rc.py
4 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # CutterDRcov - DynamoRIO Coverage Visualizer for Cutter
2 |
3 | 
4 |
5 | [](https://travis-ci.org/oddcoder/CutterDRcov)
6 | [](https://codecov.io/gh/oddcoder/CutterDRcov)
7 | [](LICENSE)
8 | [](https://lgtm.com/projects/g/oddcoder/CutterDRcov/context:python)
9 |
10 | ## Introduction
11 |
12 | CutterDrcov is code coverage plugin that visualize DynamoRIO
13 | [drcov](http://dynamorio.org/docs/page_drcov.html) into [Cutter](https://cutter.re)
14 | static analysis.
15 |
16 | IDA or Binary ninja user? checkout
17 | [lighthouse](https://github.com/gaasedelen/lighthouse)
18 |
19 | ## Installation
20 | First, locate the folder used by cutter for loading plugins, you can find it
21 | from inside cutter by going to *Edit menu* → *preferences* and finally
22 | selecting *Plugins*.
23 | 
24 |
25 | Inside that folder you will want to go *python* folder,
26 | [download](https://github.com/oddcoder/CutterDRcov/archive/master.zip) this
27 | repository, and move *cutterdrcov_plugin* folder there.
28 |
29 | ## Usage
30 |
31 | First you will need to get some drcov trace, you can get DynamoRIO from their
32 | official repository [here](https://github.com/DynamoRIO/dynamorio/releases).
33 |
34 | For example, on 64Bit linux to get coverage trace by issuing this shell command:
35 |
36 | ```sh
37 | $ dynamoRIO/bin64/drrun -t drcov -- [program name] [arguments]
38 | ```
39 | Finally you will need to load that trace into CutterDRcov!
40 |
41 | 
42 |
--------------------------------------------------------------------------------
/codecov.yml:
--------------------------------------------------------------------------------
1 | ignore:
2 | - "unittest"
3 | - "cutterdrcov_plugin/autogen"
4 | - "cutterdrcov_plugin/plugin_interface.py"
5 | - "cutterdrcov_plugin/cutterplugin.py"
6 |
--------------------------------------------------------------------------------
/cutterdrcov_plugin/__init__.py:
--------------------------------------------------------------------------------
1 | try:
2 | from .plugin_interface import create_cutter_plugin
3 | except Exception:
4 | pass
5 |
--------------------------------------------------------------------------------
/cutterdrcov_plugin/autogen/__init__.py:
--------------------------------------------------------------------------------
1 | from .dockui import Ui_DockWidget
2 |
--------------------------------------------------------------------------------
/cutterdrcov_plugin/autogen/dockui.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | # Form implementation generated from reading ui file 'ui/dock.ui',
4 | # licensing of 'ui/dock.ui' applies.
5 | #
6 | # Created: Tue Mar 26 18:55:32 2019
7 | # by: pyside2-uic running on PySide2 5.12.1
8 | #
9 | # WARNING! All changes made in this file will be lost!
10 |
11 | from PySide2 import QtCore, QtGui, QtWidgets
12 |
13 | class Ui_DockWidget(object):
14 | def setupUi(self, DockWidget):
15 | DockWidget.setObjectName("DockWidget")
16 | DockWidget.resize(687, 455)
17 | self.contents = QtWidgets.QWidget()
18 | self.contents.setObjectName("contents")
19 | self.gridLayout_5 = QtWidgets.QGridLayout(self.contents)
20 | self.gridLayout_5.setObjectName("gridLayout_5")
21 | self.stackedWidget = QtWidgets.QStackedWidget(self.contents)
22 | self.stackedWidget.setAcceptDrops(False)
23 | self.stackedWidget.setObjectName("stackedWidget")
24 | self.LoaderPage = QtWidgets.QWidget()
25 | self.LoaderPage.setObjectName("LoaderPage")
26 | self.gridLayout_2 = QtWidgets.QGridLayout(self.LoaderPage)
27 | self.gridLayout_2.setObjectName("gridLayout_2")
28 | self.loader = QtWidgets.QLabel(self.LoaderPage)
29 | self.loader.setCursor(QtCore.Qt.PointingHandCursor)
30 | self.loader.setAcceptDrops(True)
31 | self.loader.setStyleSheet("")
32 | self.loader.setObjectName("loader")
33 | self.gridLayout_2.addWidget(self.loader, 0, 0, 1, 1)
34 | self.stackedWidget.addWidget(self.LoaderPage)
35 | self.CovTablePage = QtWidgets.QWidget()
36 | self.CovTablePage.setObjectName("CovTablePage")
37 | self.gridLayout = QtWidgets.QGridLayout(self.CovTablePage)
38 | self.gridLayout.setObjectName("gridLayout")
39 | self.horizontalLayout = QtWidgets.QHBoxLayout()
40 | self.horizontalLayout.setObjectName("horizontalLayout")
41 | self.close = QtWidgets.QToolButton(self.CovTablePage)
42 | self.close.setText("")
43 | icon = QtGui.QIcon()
44 | icon.addPixmap(QtGui.QPixmap(":/icons/icon/x.svg"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
45 | self.close.setIcon(icon)
46 | self.close.setObjectName("close")
47 | self.horizontalLayout.addWidget(self.close)
48 | self.reload = QtWidgets.QToolButton(self.CovTablePage)
49 | self.reload.setText("")
50 | icon1 = QtGui.QIcon()
51 | icon1.addPixmap(QtGui.QPixmap(":/icons/icon/reload.svg"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
52 | self.reload.setIcon(icon1)
53 | self.reload.setObjectName("reload")
54 | self.horizontalLayout.addWidget(self.reload)
55 | self.selectColor = QtWidgets.QToolButton(self.CovTablePage)
56 | self.selectColor.setText("")
57 | icon2 = QtGui.QIcon()
58 | icon2.addPixmap(QtGui.QPixmap(":/icons/icon/brush.svg"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
59 | self.selectColor.setIcon(icon2)
60 | self.selectColor.setObjectName("selectColor")
61 | self.horizontalLayout.addWidget(self.selectColor)
62 | self.gridLayout.addLayout(self.horizontalLayout, 0, 0, 1, 1)
63 | spacerItem = QtWidgets.QSpacerItem(540, 38, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
64 | self.gridLayout.addItem(spacerItem, 0, 1, 1, 1)
65 | self.colorize = QtWidgets.QCheckBox(self.CovTablePage)
66 | self.colorize.setChecked(True)
67 | self.colorize.setObjectName("colorize")
68 | self.gridLayout.addWidget(self.colorize, 0, 2, 1, 1)
69 | self.covtable = QtWidgets.QTableWidget(self.CovTablePage)
70 | self.covtable.setFrameShape(QtWidgets.QFrame.NoFrame)
71 | self.covtable.setFrameShadow(QtWidgets.QFrame.Plain)
72 | self.covtable.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
73 | self.covtable.setAlternatingRowColors(True)
74 | self.covtable.setSelectionMode(QtWidgets.QAbstractItemView.ContiguousSelection)
75 | self.covtable.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
76 | self.covtable.setShowGrid(False)
77 | self.covtable.setObjectName("covtable")
78 | self.covtable.setColumnCount(5)
79 | self.covtable.setRowCount(0)
80 | item = QtWidgets.QTableWidgetItem()
81 | self.covtable.setHorizontalHeaderItem(0, item)
82 | item = QtWidgets.QTableWidgetItem()
83 | self.covtable.setHorizontalHeaderItem(1, item)
84 | item = QtWidgets.QTableWidgetItem()
85 | self.covtable.setHorizontalHeaderItem(2, item)
86 | item = QtWidgets.QTableWidgetItem()
87 | self.covtable.setHorizontalHeaderItem(3, item)
88 | item = QtWidgets.QTableWidgetItem()
89 | self.covtable.setHorizontalHeaderItem(4, item)
90 | self.covtable.horizontalHeader().setDefaultSectionSize(118)
91 | self.covtable.horizontalHeader().setStretchLastSection(True)
92 | self.covtable.verticalHeader().setVisible(False)
93 | self.gridLayout.addWidget(self.covtable, 1, 0, 1, 3)
94 | self.stackedWidget.addWidget(self.CovTablePage)
95 | self.gridLayout_5.addWidget(self.stackedWidget, 0, 0, 1, 1)
96 | DockWidget.setWidget(self.contents)
97 |
98 | self.retranslateUi(DockWidget)
99 | self.stackedWidget.setCurrentIndex(1)
100 | QtCore.QMetaObject.connectSlotsByName(DockWidget)
101 |
102 | def retranslateUi(self, DockWidget):
103 | DockWidget.setWindowTitle(QtWidgets.QApplication.translate("DockWidget", "Cutter DynamoRIO Coverage", None, -1))
104 | self.loader.setText(QtWidgets.QApplication.translate("DockWidget", "

Click to Open drcov file or drag it here.
", None, -1))
105 | self.colorize.setText(QtWidgets.QApplication.translate("DockWidget", "Colorize (On / OFF)", None, -1))
106 | self.covtable.setSortingEnabled(True)
107 | self.covtable.horizontalHeaderItem(0).setText(QtWidgets.QApplication.translate("DockWidget", "Coverage", None, -1))
108 | self.covtable.horizontalHeaderItem(1).setText(QtWidgets.QApplication.translate("DockWidget", "Function Name", None, -1))
109 | self.covtable.horizontalHeaderItem(2).setText(QtWidgets.QApplication.translate("DockWidget", "Address", None, -1))
110 | self.covtable.horizontalHeaderItem(3).setText(QtWidgets.QApplication.translate("DockWidget", "Instructions Hits", None, -1))
111 | self.covtable.horizontalHeaderItem(4).setText(QtWidgets.QApplication.translate("DockWidget", "Basic Block Hits", None, -1))
112 |
113 | from . import icon_rc
114 |
--------------------------------------------------------------------------------
/cutterdrcov_plugin/autogen/icon_rc.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | # Resource object code
4 | #
5 | # Created: Tue Mar 26 18:55:33 2019
6 | # by: The Resource Compiler for PySide2 (Qt v5.12.1)
7 | #
8 | # WARNING! All changes made in this file will be lost!
9 |
10 | from PySide2 import QtCore
11 |
12 | qt_resource_data = b"\
13 | \x00\x00\x011\
14 | <\
15 | svg fill=\x22#92b0b\
16 | 3\x22 xmlns=\x22http:/\
17 | /www.w3.org/2000\
18 | /svg\x22 width=\x2232\x22\
19 | height=\x2232\x22 vie\
20 | wBox=\x220 0 8 8\x22>\x0a\
21 | \x0a\x0a\
34 | \
35 | \x00\x00\x01=\
36 | <\
37 | svg fill=\x22#92b0b\
38 | 3\x22 xmlns=\x22http:/\
39 | /www.w3.org/2000\
40 | /svg\x22 width=\x2232\x22\
41 | height=\x2232\x22 vie\
42 | wBox=\x220 0 8 8\x22>\x0a\
43 | \x0a\x0a\
57 | \x00\x00\x01\xbc\
58 | <\
59 | svg fill=\x22#92b0b\
60 | 3\x22 xmlns=\x22http:/\
61 | /www.w3.org/2000\
62 | /svg\x22 width=\x2232\x22\
63 | height=\x2232\x22 vie\
64 | wBox=\x220 0 8 8\x22>\x0a\
65 | \x0a\x0a\
87 | \x00\x00\x00\xa3\
88 | <\
89 | svg fill=\x22#92b0b\
90 | 3\x22 xmlns=\x22http:/\
91 | /www.w3.org/2000\
92 | /svg\x22 width=\x2264\x22\
93 | height=\x2264\x22 vie\
94 | wBox=\x220 0 8 8\x22>\x0a\
95 | \x0a\x0a\
100 | "
101 |
102 | qt_resource_name = b"\
103 | \x00\x05\
104 | \x00o\xa6S\
105 | \x00i\
106 | \x00c\x00o\x00n\x00s\
107 | \x00\x04\
108 | \x00\x06\xfa^\
109 | \x00i\
110 | \x00c\x00o\x00n\
111 | \x00\x05\
112 | \x00{Z\xc7\
113 | \x00x\
114 | \x00.\x00s\x00v\x00g\
115 | \x00\x0a\
116 | \x05xB\xa7\
117 | \x00r\
118 | \x00e\x00l\x00o\x00a\x00d\x00.\x00s\x00v\x00g\
119 | \x00\x09\
120 | \x0c\x9b\x89\xe7\
121 | \x00b\
122 | \x00r\x00u\x00s\x00h\x00.\x00s\x00v\x00g\
123 | \x00\x1a\
124 | \x06_\x97'\
125 | \x00d\
126 | \x00a\x00t\x00a\x00-\x00t\x00r\x00a\x00n\x00s\x00f\x00e\x00r\x00-\x00d\x00o\x00w\
127 | \x00n\x00l\x00o\x00a\x00d\x00.\x00s\x00v\x00g\
128 | "
129 |
130 | qt_resource_struct = b"\
131 | \x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\
132 | \x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x02\
133 | \x00\x00\x00\x10\x00\x02\x00\x00\x00\x04\x00\x00\x00\x03\
134 | \x00\x00\x00\x1e\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\
135 | \x00\x00\x00.\x00\x00\x00\x00\x00\x01\x00\x00\x015\
136 | \x00\x00\x00`\x00\x00\x00\x00\x00\x01\x00\x00\x046\
137 | \x00\x00\x00H\x00\x00\x00\x00\x00\x01\x00\x00\x02v\
138 | "
139 |
140 | def qInitResources():
141 | QtCore.qRegisterResourceData(0x01, qt_resource_struct, qt_resource_name, qt_resource_data)
142 |
143 | def qCleanupResources():
144 | QtCore.qUnregisterResourceData(0x01, qt_resource_struct, qt_resource_name, qt_resource_data)
145 |
146 | qInitResources()
147 |
--------------------------------------------------------------------------------
/cutterdrcov_plugin/covtable.py:
--------------------------------------------------------------------------------
1 | import cutter
2 | from .extras import hex_pad, file_name
3 |
4 | def get_module_idx(modules, module):
5 | for i in range(len(modules)):
6 | if modules[i]['name'] == module:
7 | return i
8 | return -1
9 |
10 |
11 |
12 | def analyse_function(function, base, covbbs):
13 | entry = ["", function['name'], hex_pad(function['offset'], 8), "", ""]
14 | bbs = cutter.cmdj("afbj @ " + function['name'])
15 | inst_count = 0
16 | inst_hits = 0
17 | bbs_count = 0
18 | bbs_hits = 0
19 | hit_set = set()
20 | to_be_added = {}
21 | for bblock in bbs:
22 | # radare2's basic block can't have jump inside it while that is
23 | # possible in DynamoRIO, for this reason we first need to check
24 | # if the size of the 2 basic block matches, if radare2's basic block
25 | # size is smaller thant we would add the next block untill the sizes
26 | # match. If dynamoRIO size is smaller then something is wrong with
27 | # r2 analysis or dynamoRIO coverage.
28 | bbs_count += 1
29 | inst_count += bblock['ninstr']
30 | dynamorio_size = 0
31 | covbbaddr = bblock['addr'] - base
32 | if covbbaddr in covbbs:
33 | dynamorio_size = covbbs[covbbaddr]
34 | if bblock['addr'] in to_be_added:
35 | dynamorio_size = to_be_added[bblock['addr']]
36 | if dynamorio_size == 0:
37 | continue
38 | bbs_hits += 1
39 | inst_hits += bblock['ninstr']
40 | hit_set.add(bblock['addr'])
41 | r2_size = bblock['size']
42 | if dynamorio_size > r2_size:
43 | to_be_added[bblock['addr'] + r2_size] = dynamorio_size - r2_size
44 | if bbs_hits == 0:
45 | return (None, hit_set)
46 | entry[3] = str(inst_hits) + "/" + str(inst_count)
47 | entry[4] = str(bbs_hits) + "/" + str(bbs_count)
48 | entry[0] = str(round(inst_hits * 100 / inst_count, 3)) + "%"
49 | return (entry, hit_set)
50 |
51 | def analyse(config):
52 | config['bb_hits'] = set()
53 | config['table'] = []
54 | functions = cutter.cmdj("aflj")
55 | info = cutter.cmdj("ij")
56 | module = file_name(info['core']['file'])
57 | base = info["bin"]["baddr"]
58 | idx = get_module_idx(config['modules'], module)
59 | if idx == -1:
60 | return
61 | # [coverage, name, address, instruction hits, basic block hits]
62 | for function in functions:
63 | entry, hits = analyse_function(function, base, config['bbs'][idx])
64 | if entry is None:
65 | continue
66 | config['table'].append(entry)
67 | config['bb_hits'].update(hits)
68 |
--------------------------------------------------------------------------------
/cutterdrcov_plugin/cutterplugin.py:
--------------------------------------------------------------------------------
1 | from PySide2 import QtCore
2 | from PySide2.QtGui import QColor
3 | from PySide2.QtWidgets import QFileDialog, QColorDialog
4 | import cutter
5 | from .autogen import Ui_DockWidget
6 | from . import drcov
7 | from .covtable import analyse
8 | from . import sortable_table_item as sit
9 |
10 | class MyDockWidget(cutter.CutterDockWidget, Ui_DockWidget):
11 | def __init__(self, parent, action):
12 | super(MyDockWidget, self).__init__(parent, action)
13 | self.setupUi(self)
14 | self.new_config()
15 | self.stackedWidget.setCurrentIndex(0)
16 | self.loader.dragEnterEvent = self.loader_drag_event
17 | self.loader.dropEvent = self.drop_file
18 | self.loader.mousePressEvent = self.open_file
19 | self.colorize.clicked.connect(self.toggle_colorize)
20 | self.selectColor.clicked.connect(self.set_color)
21 | self.close.clicked.connect(self.close_callback)
22 | self.reload.clicked.connect(self.reload_callback)
23 | self.covtable.doubleClicked.connect(self.seek)
24 |
25 | def seek(self, idx):
26 | row = idx.row()
27 | addr = int(self.covtable.item(row, 2).text(), 16)
28 | cutter.core().seek(addr)
29 |
30 | def toggle_colorize(self):
31 | self.config['colorize'] = not self.config['colorize']
32 | self.paint()
33 |
34 | def close_callback(self):
35 | self.clear_highlight()
36 | self.new_config()
37 | self.stackedWidget.setCurrentIndex(0)
38 |
39 | def reload_callback(self):
40 | self.clear_highlight()
41 | analyse(self.config)
42 | self.paint()
43 |
44 | def new_config(self):
45 | self.config = {
46 | 'color': 0x800000,
47 | 'colorize' : self.colorize.isChecked()
48 | }
49 |
50 | def loader_drag_event(self, event):
51 | if event.mimeData().hasUrls():
52 | self.loader.setCursor(QtCore.Qt.DragMoveCursor)
53 | event.acceptProposedAction()
54 |
55 | def drop_file(self, event):
56 | self.loader.setCursor(QtCore.Qt.PointingHandCursor)
57 | files = event.mimeData().urls()
58 | file_name = files[0].toLocalFile()
59 | self.loadcov(file_name)
60 |
61 | def open_file(self, _):
62 | file_name = QFileDialog.getOpenFileName(self)
63 | if not file_name[0]:
64 | return
65 | self.loadcov(file_name[0])
66 |
67 | def loadcov(self, file_name):
68 | try:
69 | modules, bbs = drcov.load(file_name)
70 | except Exception:
71 | self.parent().messageBoxWarning("", "Invalid Coverage File")
72 | return
73 | self.config['modules'] = modules
74 | self.config['bbs'] = bbs
75 | self.stackedWidget.setCurrentIndex(1)
76 | analyse(self.config)
77 | self.display()
78 | self.paint()
79 |
80 | def display(self):
81 | """
82 | This function displays analysed coverage entires (1 entry per function)
83 | """
84 | covtable = self.covtable
85 | covtable.clearContents()
86 | covtable.setRowCount(0)
87 | for entry in self.config['table']:
88 | row_position = covtable.rowCount()
89 | covtable.insertRow(row_position)
90 | covtable.setItem(row_position, 0, sit.PercentWidgetItem(entry[0]))
91 | covtable.setItem(row_position, 1, sit.QTableWidgetItem(entry[1]))
92 | covtable.setItem(row_position, 2, sit.HexWidgetItem(entry[2]))
93 | covtable.setItem(row_position, 3, sit.RatioWidgetItem(entry[3]))
94 | covtable.setItem(row_position, 4, sit.centered_text(entry[4]))
95 |
96 | def set_color(self):
97 | """
98 | This function changes color of drcoved bbs and reflects
99 | the change to gui.
100 | """
101 | new_color = QColorDialog.getColor(QColor(self.config['color']))
102 | if not new_color.isValid():
103 | return
104 | self.config['color'] = new_color.rgb()
105 | self.paint()
106 |
107 | def paint(self):
108 | """
109 | This function checks if we are allowed to highlight basic block,
110 | if yes it will highlight all drcov-ed basic blocks, otherwise it
111 | will clear remove highlights from all drcov-ed bbs.
112 | """
113 | if self.config['colorize']:
114 | self.highlight()
115 | else:
116 | self.clear_highlight()
117 |
118 | def highlight(self):
119 | """
120 | This function highlights all drcov-ed basic blocks
121 | """
122 | core = cutter.core()
123 | highlighter = core.getBBHighlighter()
124 | for bblock in self.config['bb_hits']:
125 | highlighter.highlight(bblock, self.config['color'])
126 |
127 | def clear_highlight(self):
128 | """
129 | This function removes highlights from all drcov-ed basic blocks
130 | """
131 | core = cutter.core()
132 | highlighter = core.getBBHighlighter()
133 | for bblock in self.config['bb_hits']:
134 | highlighter.clear(bblock)
135 |
--------------------------------------------------------------------------------
/cutterdrcov_plugin/drcov.py:
--------------------------------------------------------------------------------
1 | import re
2 | import struct
3 | from .extras import file_name
4 |
5 | MIN_DRCOV_FILE_SIZE = 20
6 | DRCOV_VERSION = 3
7 |
8 | DRCOV_HEADER_RE = r"DRCOV VERSION: (?P\d+)\n"
9 | MODULE_HEADER_V2_RE = r"Module Table: version (?P\d+), count (?P\d+)\n"
10 | BB_HEADER_RE = r"BB Table: (?P\d+) bbs\n"
11 |
12 | class DRCovVersionMisMatch(Exception):
13 | pass
14 |
15 | def check_module_header(drcov_file):
16 | header = drcov_file.readline().decode('utf-8')
17 | pattern = re.match(DRCOV_HEADER_RE, header)
18 | version = int(pattern.group('version'))
19 | if version != DRCOV_VERSION:
20 | raise DRCovVersionMisMatch
21 | # "DRCOV FLAVOR" doesn't really matter
22 | drcov_file.readline()
23 |
24 | def get_module_header_info(drcov_file):
25 | header = drcov_file.readline().decode('utf-8')
26 | pattern = re.match(MODULE_HEADER_V2_RE, header)
27 | # skip "Columns: id, containing_id, start, end, entry, offset, path"
28 | drcov_file.readline()
29 | return (int(pattern.group("mod_num")), int(pattern.group("version")))
30 |
31 | def parse_module_entry(drcov_file, version):
32 | entry = drcov_file.readline().decode('utf-8')[:-1]
33 | #XXX now put commas and spaces in the file path and this gets fucked up
34 | entry = re.split(r",\s+", entry)
35 | if version == 2:
36 | return {"start": int(entry[1], 16), "name": file_name(entry[-1])}
37 | return {"start": int(entry[2], 16), "name": file_name(entry[-1])}
38 |
39 | def read_module_list(drcov_file):
40 | modules = []
41 | check_module_header(drcov_file)
42 | mod_num, mod_version = get_module_header_info(drcov_file)
43 | for _ in range(mod_num):
44 | modules.append(parse_module_entry(drcov_file, mod_version))
45 | return modules
46 |
47 | def parse_bb_header(drcov_file):
48 | header = drcov_file.readline().decode('utf-8')
49 | pattern = re.match(BB_HEADER_RE, header)
50 | return int(pattern.group("bbcount"))
51 |
52 | def read_bb_list(drcov_file, module_count):
53 | bblist = [{} for i in range(module_count)]
54 | bb_count = parse_bb_header(drcov_file)
55 | struct_fmt = ' module_count:
63 | # we have a case where dynamocov failed to capture which modules
64 | # does this basic block belongs to
65 | # print("Warning: we have unknown module number:", mod_num)
66 | continue
67 | bblist[mod_num][offset] = size
68 | return bblist
69 |
70 | def dead_module_elimination(modules, bbs):
71 | delete = []
72 | for i in range(len(bbs)):
73 | if not bbs[i]:
74 | delete.insert(0, i)
75 | for i in delete:
76 | del bbs[i]
77 | del modules[i]
78 |
79 | def load(path):
80 | drcov_file = open(path, "rb")
81 | modules = read_module_list(drcov_file)
82 | bbs = read_bb_list(drcov_file, len(modules))
83 | drcov_file.close()
84 | dead_module_elimination(modules, bbs)
85 | return [modules, bbs]
86 |
--------------------------------------------------------------------------------
/cutterdrcov_plugin/extras.py:
--------------------------------------------------------------------------------
1 | import ntpath
2 |
3 | def hex_pad(num, pad):
4 | return "{0:#0{1}x}".format(num, pad + 2)
5 |
6 | # https://stackoverflow.com/questions/8384737/extract-file-name-from-path-no-matter-what-the-os-path-format
7 | # cuz windows sucks :( .. hard
8 | def file_name(path):
9 | # There's one caveat: Linux filenames may contain backslashes. So on linux,
10 | # r'a/b\c' always refers to the file b\c in the a folder, while on Windows,
11 | # it always refers to the c file in the b subfolder of the a folder. So when
12 | # both forward and backward slashes are used in a path, you need to know the
13 | # associated platform to be able to interpret it correctly. In practice it's
14 | # usually safe to assume it's a windows path since backslashes are seldom
15 | # used in Linux filenames, but keep this in mind when you code so you don't
16 | # create accidental security holes.
17 |
18 | head, tail = ntpath.split(path)
19 | return tail or ntpath.basename(head)
20 |
--------------------------------------------------------------------------------
/cutterdrcov_plugin/plugin_interface.py:
--------------------------------------------------------------------------------
1 | from PySide2.QtWidgets import QAction
2 | import cutter
3 |
4 | from .cutterplugin import MyDockWidget
5 |
6 | class CutterCovPlugin(cutter.CutterPlugin):
7 | name = "CutterCov"
8 | description = "Visualize DynamoRIOCov data into Cutter"
9 | version = "0.0.1"
10 | author = "oddcoder"
11 |
12 | def setupPlugin(self):
13 | pass
14 |
15 | def setupInterface(self, main):
16 | action = QAction("CutterCov", main)
17 | action.setCheckable(True)
18 | widget = MyDockWidget(main, action)
19 | main.addPluginDockWidget(widget, action)
20 |
21 | def create_cutter_plugin():
22 | """
23 | This function is the only api accessed by cutter, it
24 | would return object of cutter.CutterPlugin for cutter to work with
25 | """
26 | return CutterCovPlugin()
27 |
28 |
--------------------------------------------------------------------------------
/cutterdrcov_plugin/sortable_table_item.py:
--------------------------------------------------------------------------------
1 | from PySide2.QtWidgets import QTableWidgetItem
2 | from PySide2.QtCore import Qt
3 |
4 | class PercentWidgetItem(QTableWidgetItem):
5 | def __lt__(self, other):
6 | return float(self.text()[:-1]) < float(other.text()[:-1])
7 |
8 | class HexWidgetItem(QTableWidgetItem):
9 | def __lt__(self, other):
10 | return int(self.text(), 16) < int(other.text(), 16)
11 |
12 | class RatioWidgetItem(QTableWidgetItem):
13 | def get_ratio(self):
14 | splt = self.text().split("/")
15 | return int(splt[0]) / int(splt[1])
16 | def __lt__(self, other):
17 | return self.get_ratio() < other.get_ratio()
18 |
19 | def centered_text(txt):
20 | item = RatioWidgetItem(txt)
21 | item.setTextAlignment(Qt.AlignCenter)
22 | return item
23 |
--------------------------------------------------------------------------------
/resources/ICON-LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014 Waybury
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
--------------------------------------------------------------------------------
/resources/icon.qrc:
--------------------------------------------------------------------------------
1 |
2 |
3 | icon/brush.svg
4 | icon/reload.svg
5 | icon/x.svg
6 | icon/data-transfer-download.svg
7 |
8 |
9 |
--------------------------------------------------------------------------------
/resources/icon/brush.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/resources/icon/data-transfer-download.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/resources/icon/reload.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/resources/icon/x.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/screanshots/overview.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rizinorg/CutterDRcov/7735cafd44ffa2cdf4dca950d6d13d74a3d2fb25/screanshots/overview.jpg
--------------------------------------------------------------------------------
/screanshots/path.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rizinorg/CutterDRcov/7735cafd44ffa2cdf4dca950d6d13d74a3d2fb25/screanshots/path.jpg
--------------------------------------------------------------------------------
/screanshots/usage.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rizinorg/CutterDRcov/7735cafd44ffa2cdf4dca950d6d13d74a3d2fb25/screanshots/usage.gif
--------------------------------------------------------------------------------
/scripts/lint.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | set -x
3 | # W0703: Catching too general exception
4 | # C0111: missing-docstring
5 | # E0401: import-error (we hook import cutter)
6 | # E0611: No name X in module Y
7 | # C0200: Consider using enumerate instead of iterating with range and len
8 | # R0903: too-few-public-methods
9 | # W0511: fixme
10 | pylint --disable=W0511,R0903,C0200,E0611,W0703,C0111,I1101,E0401 \
11 | --ignore=autogen,plugin_interface.py cutterdrcov_plugin
12 | lint1=$?
13 | # C0413: wrong-import-position
14 | # R0201: no-self-use (use method as function)
15 | pylint --disable=C0413,C0111,R0201,R0903,E0401 unittest/*.py
16 | lint2=$?
17 | if [ $lint1 -ne 0 ]; then
18 | exit $lint1
19 | fi
20 | if [ $lint2 -ne 0 ]; then
21 | exit $lint2
22 | fi
23 |
--------------------------------------------------------------------------------
/scripts/local.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | set -x
3 |
4 | ./scripts/lint.sh
5 | python unittest
6 |
--------------------------------------------------------------------------------
/scripts/travis.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | set -x
3 |
4 | ./scripts/lint.sh
5 | lint_result=$?
6 | coverage run -m unittest discover unittest
7 | if [ $lint_result -ne 0 ]; then
8 | exit $lint_result
9 | fi
10 |
11 |
--------------------------------------------------------------------------------
/test_files/drcov.test2.log:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rizinorg/CutterDRcov/7735cafd44ffa2cdf4dca950d6d13d74a3d2fb25/test_files/drcov.test2.log
--------------------------------------------------------------------------------
/test_files/drcov.test3.log:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rizinorg/CutterDRcov/7735cafd44ffa2cdf4dca950d6d13d74a3d2fb25/test_files/drcov.test3.log
--------------------------------------------------------------------------------
/test_files/drcov2.2.log:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rizinorg/CutterDRcov/7735cafd44ffa2cdf4dca950d6d13d74a3d2fb25/test_files/drcov2.2.log
--------------------------------------------------------------------------------
/test_files/drcov2.3.log:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rizinorg/CutterDRcov/7735cafd44ffa2cdf4dca950d6d13d74a3d2fb25/test_files/drcov2.3.log
--------------------------------------------------------------------------------
/test_files/drcov2.4.log:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rizinorg/CutterDRcov/7735cafd44ffa2cdf4dca950d6d13d74a3d2fb25/test_files/drcov2.4.log
--------------------------------------------------------------------------------
/test_files/drcov_orphan.log:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rizinorg/CutterDRcov/7735cafd44ffa2cdf4dca950d6d13d74a3d2fb25/test_files/drcov_orphan.log
--------------------------------------------------------------------------------
/test_files/test1.asm:
--------------------------------------------------------------------------------
1 | SECTION .text
2 | GLOBAL _start
3 | _start:
4 | mov eax, 5
5 | mov ebx, 6
6 | mov ecx, 7
7 | cmp ecx, 6
8 | je BB2
9 | mov eax, 7
10 | jmp BB3
11 |
12 | BB2: mov eax, 5
13 | mov ebx, 6
14 | mov ecx, 7
15 |
16 | BB3: mov eax,1 ; 'exit' system call
17 | mov ebx,0 ; exit with error code 0
18 | int 80h ; call the kernel
19 |
20 |
--------------------------------------------------------------------------------
/test_files/test1.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rizinorg/CutterDRcov/7735cafd44ffa2cdf4dca950d6d13d74a3d2fb25/test_files/test1.bin
--------------------------------------------------------------------------------
/test_files/test1.o:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rizinorg/CutterDRcov/7735cafd44ffa2cdf4dca950d6d13d74a3d2fb25/test_files/test1.o
--------------------------------------------------------------------------------
/test_files/test2.asm:
--------------------------------------------------------------------------------
1 | SECTION .text
2 | GLOBAL _start
3 | GLOBAL uncalled
4 |
5 | uncalled:
6 | push ebp
7 | mov ebp, esp
8 | mov eax, 1
9 | pop ebp
10 | ret
11 |
12 | _start:
13 | mov eax, 5
14 | mov ebx, 6
15 | mov ecx, 7
16 | cmp ecx, 6
17 | je BB2
18 | mov eax, 7
19 | jmp BB3
20 |
21 | BB2: mov eax, 5
22 | mov ebx, 6
23 | mov ecx, 7
24 |
25 | BB3: mov eax,1 ; 'exit' system call
26 | mov ebx,0 ; exit with error code 0
27 | int 80h ; call the kernel
28 | call uncalled
29 |
--------------------------------------------------------------------------------
/test_files/test2.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rizinorg/CutterDRcov/7735cafd44ffa2cdf4dca950d6d13d74a3d2fb25/test_files/test2.bin
--------------------------------------------------------------------------------
/test_files/test2.o:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rizinorg/CutterDRcov/7735cafd44ffa2cdf4dca950d6d13d74a3d2fb25/test_files/test2.o
--------------------------------------------------------------------------------
/test_files/test3.asm:
--------------------------------------------------------------------------------
1 | SECTION .text
2 | GLOBAL _start
3 | _start:
4 | mov eax, 5
5 | mov ebx, 6
6 | mov ecx, 7
7 | cmp ecx, 1
8 | jne BB2
9 | cmp eax, 2
10 | je BB3
11 | cmp eax, 3
12 | je BB4
13 | cmp eax, 4
14 | je BB5
15 |
16 | BB2: mov eax, 5
17 | mov ebx, 6
18 | mov ecx, 7
19 | BB3: mov eax, 8
20 | mov ebx, 9
21 | mov ecx, 10
22 | BB4: mov eax, 11
23 | mov ebx, 12
24 | mov ecx, 13
25 | BB5: mov eax,1 ; 'exit' system call
26 | mov ebx,0 ; exit with error code 0
27 | int 80h ; call the kernel
28 |
--------------------------------------------------------------------------------
/test_files/test3.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rizinorg/CutterDRcov/7735cafd44ffa2cdf4dca950d6d13d74a3d2fb25/test_files/test3.bin
--------------------------------------------------------------------------------
/test_files/test3.o:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rizinorg/CutterDRcov/7735cafd44ffa2cdf4dca950d6d13d74a3d2fb25/test_files/test3.o
--------------------------------------------------------------------------------
/ui/dock.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 | DockWidget
4 |
5 |
6 |
7 | 0
8 | 0
9 | 687
10 | 455
11 |
12 |
13 |
14 | Cutter DynamoRIO Coverage
15 |
16 |
17 |
18 | -
19 |
20 |
21 | false
22 |
23 |
24 | 1
25 |
26 |
27 |
28 |
-
29 |
30 |
31 | PointingHandCursor
32 |
33 |
34 | true
35 |
36 |
37 |
38 |
39 |
40 | <html><head/><body><p align="center"><img src=":/icons/icon/data-transfer-download.svg"/></p><p align="center">Click to <span style=" font-weight:600;">Open drcov file</span> or drag it here.</p></body></html>
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 | -
49 |
50 |
-
51 |
52 |
53 |
54 |
55 |
56 |
57 | :/icons/icon/x.svg:/icons/icon/x.svg
58 |
59 |
60 |
61 | -
62 |
63 |
64 |
65 |
66 |
67 |
68 | :/icons/icon/reload.svg:/icons/icon/reload.svg
69 |
70 |
71 |
72 | -
73 |
74 |
75 |
76 |
77 |
78 |
79 | :/icons/icon/brush.svg:/icons/icon/brush.svg
80 |
81 |
82 |
83 |
84 |
85 | -
86 |
87 |
88 | Qt::Horizontal
89 |
90 |
91 |
92 | 540
93 | 38
94 |
95 |
96 |
97 |
98 | -
99 |
100 |
101 | Colorize (On / OFF)
102 |
103 |
104 | true
105 |
106 |
107 |
108 | -
109 |
110 |
111 | QFrame::NoFrame
112 |
113 |
114 | QFrame::Plain
115 |
116 |
117 | QAbstractItemView::NoEditTriggers
118 |
119 |
120 | true
121 |
122 |
123 | QAbstractItemView::ContiguousSelection
124 |
125 |
126 | QAbstractItemView::SelectRows
127 |
128 |
129 | false
130 |
131 |
132 | true
133 |
134 |
135 | 118
136 |
137 |
138 | true
139 |
140 |
141 | false
142 |
143 |
144 |
145 | Coverage
146 |
147 |
148 |
149 |
150 | Function Name
151 |
152 |
153 |
154 |
155 | Address
156 |
157 |
158 |
159 |
160 | Instructions Hits
161 |
162 |
163 |
164 |
165 | Basic Block Hits
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
--------------------------------------------------------------------------------
/unittest/__main__.py:
--------------------------------------------------------------------------------
1 | import unittest
2 | import os
3 | def main():
4 | loader = unittest.TestLoader()
5 | current_dir = os.path.dirname(os.path.realpath(__file__))
6 | test_suite = loader.discover(current_dir)
7 | runner = unittest.TextTestRunner(verbosity=2)
8 | runner.run(test_suite)
9 |
10 | if __name__ == "__main__":
11 | main()
12 |
--------------------------------------------------------------------------------
/unittest/test_covtable.py:
--------------------------------------------------------------------------------
1 | import unittest
2 | import sys
3 | from mock import patch
4 | sys.path.append(".")
5 |
6 | class FakeCutter():
7 | def cmdj(self, _):
8 | return ""
9 |
10 | sys.modules['cutter'] = FakeCutter()
11 |
12 | import cutter
13 | from cutterdrcov_plugin import covtable, drcov
14 |
15 | def test_1_cmdj(cmd):
16 | if cmd == "ij":
17 | return {
18 | 'core': {'file': 'test1.bin'},
19 | 'bin': {'baddr': 0x8048000}
20 | }
21 | if cmd == "aflj":
22 | return [{'offset': 0x8048060, 'name': 'entry0'}]
23 | if cmd == "afbj @entry0":
24 | return [
25 | {'addr': 0x8048060, 'size': 20, 'ninstr': 5},
26 | {'addr': 0x8048074, 'size': 7, 'ninstr': 2},
27 | {'addr': 0x804807b, 'size': 15, 'ninstr': 3},
28 | {'addr': 0x804808a, 'size': 12, 'ninstr': 3}
29 | ]
30 | return None
31 |
32 | def test_2_cmdj(cmd):
33 | if cmd == "ij":
34 | return {
35 | 'core': {'file': 'test2.bin'},
36 | 'bin': {'baddr': 0x8048000}
37 | }
38 | if cmd == "aflj":
39 | return [
40 | {'offset': 0x8048060, 'name': 'loc.uncalled'},
41 | {'offset': 0x804806a, 'name': 'entry0'}
42 | ]
43 | if cmd == "afbj @entry0":
44 | return [
45 | {'addr': 0x804806a, 'size': 20, 'ninstr': 5},
46 | {'addr': 0x804807e, 'size': 7, 'ninstr': 2},
47 | {'addr': 0x8048085, 'size': 15, 'ninstr': 3},
48 | {'addr': 0x8048094, 'size': 17, 'ninstr': 4}
49 | ]
50 | if cmd == "afbj @loc.uncalled":
51 | return [
52 | {"addr": 0x8048060, "size": 10, "ninstr": 5}
53 | ]
54 | return None
55 |
56 | def test_3_cmdj(cmd):
57 | if cmd == "ij":
58 | return {
59 | "core": {"file": "/some/crazy/path/test3.bin",},
60 | "bin":{"baddr": 0x8048000}
61 | }
62 | if cmd == "aflj":
63 | return [{"offset": 0x8048060, "name": "entry0"}]
64 | if cmd == "afbj @entry0":
65 | return [
66 | {"addr":134512736, "size":20, "ninstr":5},
67 | {"addr":134512756, "size":5, "ninstr":2},
68 | {"addr":134512761, "size":5, "ninstr":2},
69 | {"addr":134512766, "size":5, "ninstr":2},
70 | {"addr":134512771, "size":15, "ninstr":3},
71 | {"addr":134512786, "size":15, "ninstr":3},
72 | {"addr":134512801, "size":15, "ninstr":3},
73 | {"addr":134512816, "size":12, "ninstr":3}
74 | ]
75 | return None
76 |
77 |
78 |
79 | class TestcovTable(unittest.TestCase):
80 |
81 | @patch("cutter.cmdj", side_effect=test_1_cmdj)
82 | def test_analyse_function(self, _):
83 | # Here, checking if cutter works as expected is out of scope but rather
84 | # what I am checking is given that cutter works as expected does my code
85 | # work as expected as well or not
86 | _, bbs = drcov.load("test_files/drcov2.4.log")
87 | function = cutter.cmdj("aflj")[0]
88 | base = cutter.cmdj("ij")['bin']['baddr']
89 | entry, hits = covtable.analyse_function(function, base, bbs[0])
90 | hardcoded_entry = ['76.923%', 'entry0', '0x08048060', '10/13', '3/4']
91 | self.assertEqual(entry, hardcoded_entry)
92 | self.assertEqual(hits, {0x8048060, 0x804808a, 0x8048074})
93 |
94 | def do_analyse(self, modules, bbs, table, hits):
95 | config = {}
96 | config['modules'] = modules
97 | config['bbs'] = bbs
98 | covtable.analyse(config)
99 | self.assertEqual(config['table'], table)
100 | self.assertEqual(config['bb_hits'], hits)
101 |
102 | @patch("cutter.cmdj", side_effect=test_1_cmdj)
103 | def test_analyse1(self, _):
104 | modules, bbs = drcov.load("test_files/drcov2.4.log")
105 | table = [['76.923%', 'entry0', '0x08048060', '10/13', '3/4']]
106 | hits = {0x8048060, 0x804808a, 0x8048074}
107 | self.do_analyse(modules, bbs, table, hits)
108 |
109 | @patch("cutter.cmdj", side_effect=test_2_cmdj)
110 | def test_analyse2(self, _):
111 | """
112 | This test case where not all functions gets covered,
113 | so they needed be reported
114 | """
115 | modules, bbs = drcov.load("test_files/drcov.test2.log")
116 | # technically speaking only 10 instructions can get executed
117 | # but what can I do ... any way it wouldn't matter
118 | table = [["78.571%", "entry0", "0x0804806a", "11/14", "3/4"]]
119 | hits = {0x0804806a, 0x0804807e, 0x08048094}
120 | self.do_analyse(modules, bbs, table, hits)
121 |
122 | @patch("cutter.cmdj", side_effect=test_3_cmdj)
123 | def test_analyse3(self, _):
124 | """
125 | This tests the case when dynamoRIO block is equivalent to more
126 | than one radare2 block
127 | """
128 | modules, bbs = drcov.load("test_files/drcov.test3.log")
129 | table = [["73.913%", "entry0", "0x08048060", "17/23", "5/8"]]
130 | hits = {0x08048060, 0x08048083, 0x08048092, 0x080480a1, 0x080480b0}
131 | self.do_analyse(modules, bbs, table, hits)
132 |
133 | @patch("cutter.cmdj", side_effect=test_1_cmdj)
134 | def test_bad_analysis(self, _):
135 | """
136 | I found this by mistake, it triggered exception while it shouldn't
137 | """
138 | modules, bbs = drcov.load("test_files/drcov.test2.log")
139 | self.do_analyse(modules, bbs, [], set())
140 |
141 | if __name__ == '__main__':
142 | unittest.main()
143 |
--------------------------------------------------------------------------------
/unittest/test_drcov.py:
--------------------------------------------------------------------------------
1 | import unittest
2 | import sys
3 | sys.path.append(".")
4 | from cutterdrcov_plugin import drcov
5 |
6 | class TestDRcov(unittest.TestCase):
7 | def verify_test1_asm(self, covfile):
8 | """
9 | covfile: file name
10 | This function tests that given coverage file represents
11 | running test_files/test1.asm
12 | """
13 | modules, bbs = drcov.load(covfile)
14 | self.assertEqual(len(modules), 1)
15 | self.assertEqual(modules[0]['start'], 0x8048000)
16 | self.assertEqual(modules[0]['name'], 'test1.bin')
17 | self.assertEqual(len(bbs), 1)
18 | self.assertEqual(len(bbs[0]), 3)
19 | bbs = bbs[0]
20 | self.assertEqual(bbs[0x60], 20)
21 | self.assertEqual(bbs[0x74], 7)
22 | self.assertEqual(bbs[0x8a], 12)
23 |
24 | def test_drcov_2_4_linux(self):
25 | """
26 | DRCOV VERSION: 2
27 | Module Table: version 4
28 | dynamoRIO 7.1.0-1 linux 32
29 | test1.asm
30 | """
31 | self.verify_test1_asm("test_files/drcov2.4.log")
32 |
33 | def test_drcov_2_3_linux(self):
34 | """
35 | DRCOV VERSION: 2
36 | Module Table: version 3
37 | dynamoRIO 7.0.17595-0 linux 32
38 | test1.asm
39 | """
40 | self.verify_test1_asm("test_files/drcov2.3.log")
41 |
42 | def test_drcov_2_2_linux(self):
43 | """
44 | DRCOV VERSION: 2
45 | Module Table: version 2
46 | dynamoRIO 7.0.0-RC1 linux 32
47 | test1.asm
48 | """
49 | self.verify_test1_asm("test_files/drcov2.2.log")
50 |
51 | def test_drcov_orphan(self):
52 | """
53 | Some times a block structure will reference module with
54 | numbers that wasn't previously declared typically 0xffff
55 | I don't know what would cause this to happen, but I have
56 | seen it inside some complex application. I couldn't write
57 | code that would trigger this behavior so instead I wrote a
58 | test drcov file that would have this trait.
59 | """
60 | self.verify_test1_asm("test_files/drcov_orphan.log")
61 | if __name__ == '__main__':
62 | unittest.main()
63 |
--------------------------------------------------------------------------------
/unittest/test_extras.py:
--------------------------------------------------------------------------------
1 | import unittest
2 | import sys
3 | sys.path.append(".")
4 | from cutterdrcov_plugin import extras
5 |
6 | class TestExtras(unittest.TestCase):
7 |
8 | def test_hex_pad(self):
9 | self.assertEqual(extras.hex_pad(0x123, 0), "0x123")
10 | self.assertEqual(extras.hex_pad(0x123, 1), "0x123")
11 | self.assertEqual(extras.hex_pad(0x123, 2), "0x123")
12 | self.assertEqual(extras.hex_pad(0x123, 3), "0x123")
13 | self.assertEqual(extras.hex_pad(0x123, 4), "0x0123")
14 | self.assertEqual(extras.hex_pad(0x123, 5), "0x00123")
15 | self.assertEqual(extras.hex_pad(0x123, 6), "0x000123")
16 | self.assertEqual(extras.hex_pad(0x123, 7), "0x0000123")
17 | self.assertEqual(extras.hex_pad(0x123, 8), "0x00000123")
18 |
19 | def test_file_name(self):
20 | self.assertEqual(extras.file_name(r"C:\Windows\notepad.exe"), "notepad.exe")
21 | self.assertEqual(extras.file_name(r"C:/Windows/notepad.exe"), "notepad.exe")
22 | self.assertEqual(extras.file_name(r"/bin/ls"), "ls")
23 | self.assertEqual(extras.file_name(r"./test"), "test")
24 | if __name__ == '__main__':
25 | unittest.main()
26 |
--------------------------------------------------------------------------------
/unittest/test_sortable_table_item.py:
--------------------------------------------------------------------------------
1 | import unittest
2 | import sys
3 | sys.path.append(".")
4 | import cutterdrcov_plugin.sortable_table_item as sti
5 |
6 | class TestSortableTableItem(unittest.TestCase):
7 |
8 | def check_lt(self, objs):
9 | self.assertEqual(objs[0] < objs[1], True)
10 | self.assertEqual(objs[1] < objs[2], False)
11 | self.assertEqual(objs[2] < objs[0], False)
12 |
13 | def test_percent_widget_item(self):
14 | objs = [
15 | sti.PercentWidgetItem("50%"),
16 | sti.PercentWidgetItem("100%"),
17 | sti.PercentWidgetItem("50%")
18 | ]
19 | self.check_lt(objs)
20 |
21 | def test_hex_widget_item(self):
22 | objs = [
23 | sti.HexWidgetItem("0x5"),
24 | sti.HexWidgetItem("0X07"),
25 | sti.HexWidgetItem("0x5")
26 | ]
27 | self.check_lt(objs)
28 |
29 | def test_ratio_widget_item(self):
30 | objs = [
31 | sti.RatioWidgetItem("1/2"),
32 | sti.RatioWidgetItem("3/4"),
33 | sti.RatioWidgetItem("1/2")
34 | ]
35 | self.check_lt(objs)
36 |
37 | if __name__ == '__main__':
38 | unittest.main()
39 |
--------------------------------------------------------------------------------