Skip to content

Commit 6b10560

Browse files
committed
Fixed plots.graph
1 parent 69cf37a commit 6b10560

File tree

10 files changed

+42
-19
lines changed

10 files changed

+42
-19
lines changed

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ dynamic = ["version"]
4141
graph = [
4242
"angr>=9.2",
4343
"networkx>=3.4.2",
44-
"numpy<2", # required until angr gets compatible with numpy>=2
44+
"numpy",
4545
"pygraphviz>=1.14",
4646
]
4747

pytest.ini

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
[pytest]
2-
python_paths = src
2+
pythonpath = src

src/exeplot/VERSION.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
0.3.3
1+
0.4.0

src/exeplot/__conf__.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,19 @@
2222
numpy.int = numpy.int_ # dirty fix to "AttributeError: module 'numpy' has no attribute 'int'."
2323

2424

25+
def check_imports(*names):
26+
import warnings
27+
from inspect import currentframe
28+
glob = currentframe().f_back.f_globals
29+
for name in names:
30+
try:
31+
__import__(name)
32+
glob['_IMP'] = True & glob.get('_IMP', True)
33+
except Exception as e: # pragma: no cover
34+
warnings.warn(f"{name} import failed: {e} ({type(e).__name__})", ImportWarning)
35+
glob['_IMP'] = False
36+
37+
2538
def configure(): # pragma: no cover
2639
from configparser import ConfigParser
2740
from os.path import exists, expanduser
@@ -89,6 +102,8 @@ def _wrapper(*a, **kw):
89102
plt.savefig(img, **kw_plot)
90103
logger.debug(f"> saved to {img}...")
91104
r.append(img)
105+
plt.clf()
106+
plt.close()
92107
return r
93108
return _wrapper
94109

src/exeplot/plots/__common__.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ def __init__(self, path, **kwargs):
7878
if self.__binary is None:
7979
raise TypeError("Not an executable")
8080
self.type = str(type(self.__binary)).split(".")[2]
81-
if self.type not in ["ELF", "MachO", "PE"]:
81+
if self.type not in ["ELF", "MachO", "PE"]: # pragma: no cover
8282
raise OSError("Unknown format")
8383

8484
def __getattr__(self, name):
@@ -103,7 +103,7 @@ def __get_ep_and_section(self):
103103
elif self.type == "PE":
104104
self.__ep = b.rva_to_offset(b.optional_header.addressof_entrypoint)
105105
self.__ep_section = b.section_from_rva(b.optional_header.addressof_entrypoint)
106-
except (AttributeError, TypeError):
106+
except (AttributeError, TypeError): # pragma: no cover
107107
self.__ep, self.__ep_section = None, None
108108

109109
def __sections_data(self):
@@ -244,7 +244,7 @@ def section_names(self):
244244
return names
245245
# start parsing section names
246246
from re import match
247-
if all(match(r"/\d+$", n) is None for n in names.keys()):
247+
if all(match(r"/\d+$", n) is None for n in names.keys()): # pragma: no cover
248248
return names
249249
real_names = {}
250250
str_table_offset = self.__binary.header.pointerto_symbol_table + self.__binary.header.numberof_symbols * 18

src/exeplot/plots/byte.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,5 +96,4 @@ def plot(executable, height=600, **kwargs):
9696
plt.suptitle(binary.hash, x=(fsp.right+fsp.left)/2, y=.88, **kwargs['annotation-font']) # y=fsp.top*1.02
9797
plt.axis("off")
9898
plt.imshow(img)
99-
plot.__args__ = arguments
10099

src/exeplot/plots/entropy.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,5 +194,4 @@ def plot(*filenames, labels=None, sublabel=None, scale=False, target=None, **kwa
194194
h.append(Patch(facecolor="lightgray")), l.append("Overlay")
195195
if len(h) > 0:
196196
plt.figlegend(h, l, loc=lloc, ncol=1 if lloc_side else len(l), prop={'size': fs_ref*.7})
197-
plot.__args__ = arguments
198197

src/exeplot/plots/graph.py

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
# -*- coding: UTF-8 -*-
2-
try:
3-
import angr
4-
import pygraphviz as pgv
5-
import networkx as nx
6-
_IMP = True
7-
except ImportError:
8-
_IMP = False
92
import matplotlib.pyplot as plt
103

114
from .__common__ import Binary, CACHE_DIR, COLORS, MIN_ZONE_WIDTH
12-
from ..__conf__ import save_figure
5+
from ..__conf__ import check_imports, save_figure
136

7+
try:
8+
# dirty fix to known issue with angr: AttributeError: module 'unicorn' has no attribute 'UC_ARCH_RISCV'.
9+
__import__("unicorn").UC_ARCH_RISCV = 8
10+
except ModuleNotFoundError: # pragma: no cover
11+
pass # 'unicorn' is an optional dependency of 'angr' ; fix it only if it is already installed
12+
13+
check_imports("angr", "networkx", "pygraphviz")
1414

1515
_DEFAULT_ALGORITHM, _DEFAULT_ENGINE = "fast", "default"
1616
_ENGINES = ["default", "pcode", "vex"]
@@ -28,6 +28,9 @@ def arguments(parser):
2828
@save_figure
2929
def plot(executable, algorithm=_DEFAULT_ALGORITHM, engine=_DEFAULT_ENGINE, **kwargs):
3030
""" plot the Control Flow Graph (CFG) of an executable """
31+
import angr
32+
import networkx as nx
33+
import pygraphviz as pgv
3134
from math import ceil, log2
3235
engine = {k: getattr(angr.engines, "UberEngine" if k != "pcode" else f"UberEngine{k.capitalize()}") \
3336
for k in _ENGINES}[engine]
@@ -38,7 +41,7 @@ def plot(executable, algorithm=_DEFAULT_ALGORITHM, engine=_DEFAULT_ENGINE, **kwa
3841
labels[node] = f"{node.name}\n0x{node.addr:x}" if hasattr(node, "name") and node.name else f"0x{node.addr:x}"
3942
node_colors.append("red" if node.function_address == node.addr else "lightblue")
4043
n = max(10, min(30, ceil(log2(n_nodes := len(cfg.graph.nodes()) + 1) * 2)))
41-
plt.figure(figsize=(n, n))
42-
nx.draw(cfg.graph, nx.kamada_kawai_layout(cfg.graph), font_size=8, with_labels=True, labels=labels,
44+
fig = plt.figure(figsize=(n, n))
45+
nx.draw(cfg.graph, nx.kamada_kawai_layout(cfg.graph), fig.gca(), font_size=8, with_labels=True, labels=labels,
4346
node_size=max(300, 15000 // n_nodes), node_color=node_colors)
4447

tests/test_others.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ def test_miscellaneous(self):
2020

2121
class TestUtils(TestCase):
2222
def test_ngrams_functions(self):
23+
self.assertRaises(TypeError, ngrams_counts, 123)
2324
self.assertTrue(isinstance(ngrams_counts(seq := b"\x00" * 4 + os.urandom(120) + b"\xff" * 4), Counter))
2425
class Test:
2526
bytes = seq

tests/test_plots.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,15 @@ def test_plot_functions(self):
4141
plt.clf()
4242
ok = True
4343
break
44-
except TypeError:
44+
except TypeError as e:
45+
# known issue: macho CFG generation is not well supported
46+
if "NoneType" in str(e) and path.endswith(".macho"):
47+
ok = True
48+
break
4549
pass
4650
self.assertTrue(ok)
51+
NOT_EXE = os.path.join(os.path.dirname(__file__), "__init__.py")
52+
self.assertRaises(TypeError, plot_func, NOT_EXE)
4753

4854

4955
class TestPlotOptions(TestCase):

0 commit comments

Comments
 (0)