Skip to content

Commit

Permalink
Enforce black code formatting
Browse files Browse the repository at this point in the history
  • Loading branch information
patacca committed Jan 9, 2024
1 parent f0b8020 commit 4bd65d2
Show file tree
Hide file tree
Showing 4 changed files with 210 additions and 100 deletions.
63 changes: 41 additions & 22 deletions bin/bindiffer
Original file line number Diff line number Diff line change
Expand Up @@ -11,26 +11,39 @@ import sys
from bindiff import BinDiff
from binexport import ProgramBinExport

BINARY_FORMAT = {'application/x-dosexec',
'application/x-sharedlib',
'application/x-mach-binary',
'application/x-executable',
'application/x-object',
'application/x-pie-executable'}
BINARY_FORMAT = {
"application/x-dosexec",
"application/x-sharedlib",
"application/x-mach-binary",
"application/x-executable",
"application/x-object",
"application/x-pie-executable",
}

EXTENSIONS_WHITELIST = {'application/octet-stream': ['.dex']}
EXTENSIONS_WHITELIST = {"application/octet-stream": [".dex"]}


CONTEXT_SETTINGS = dict(help_option_names=['-h', '--help'],
max_content_width=300)
CONTEXT_SETTINGS = dict(help_option_names=["-h", "--help"], max_content_width=300)


@click.command(context_settings=CONTEXT_SETTINGS)
@click.option('-i', '--ida-path', type=click.Path(exists=True), default=None, help="IDA Pro installation directory")
@click.option('-b', '--bindiff-path', type=click.Path(exists=True), default=None, help="BinDiff differ directory")
@click.option('-o', '--output', type=click.Path(), default=None, help="Output file matching")
@click.option(
"-i",
"--ida-path",
type=click.Path(exists=True),
default=None,
help="IDA Pro installation directory",
)
@click.option(
"-b",
"--bindiff-path",
type=click.Path(exists=True),
default=None,
help="BinDiff differ directory",
)
@click.option("-o", "--output", type=click.Path(), default=None, help="Output file matching")
@click.argument("primary", type=click.Path(exists=True), metavar="<primary file>")
@click.argument('secondary', type=click.Path(exists=True), metavar="<secondary file>")
@click.argument("secondary", type=click.Path(exists=True), metavar="<secondary file>")
def main(ida_path: str, bindiff_path: str, output: str, primary: str, secondary: str) -> None:
"""
bindiffer is a very simple utility to diff two binary files using BinDiff
Expand All @@ -44,20 +57,22 @@ def main(ida_path: str, bindiff_path: str, output: str, primary: str, secondary:
:param secondary: Path to the secondary file
"""

logging.basicConfig(format='[%(levelname)s] %(message)s', level=logging.INFO)
logging.basicConfig(format="[%(levelname)s] %(message)s", level=logging.INFO)

if ida_path:
os.environ['IDA_PATH'] = Path(ida_path).resolve().as_posix()
os.environ["IDA_PATH"] = Path(ida_path).resolve().as_posix()

if bindiff_path:
os.environ['BINDIFF_PATH'] = Path(bindiff_path).resolve().as_posix()
os.environ["BINDIFF_PATH"] = Path(bindiff_path).resolve().as_posix()

if not BinDiff.is_installation_ok():
logging.error("can't find bindiff executable (make sure its available in $PATH or via --bindiff-path")
logging.error(
"can't find bindiff executable (make sure its available in $PATH or via --bindiff-path"
)
sys.exit(1)

if output is None:
output = '{}_vs_{}.BinDiff'.format(Path(primary).stem, Path(secondary).stem)
output = "{}_vs_{}.BinDiff".format(Path(primary).stem, Path(secondary).stem)

# Check that the output name is not too long
if len(output) > 255:
Expand All @@ -70,14 +85,18 @@ def main(ida_path: str, bindiff_path: str, output: str, primary: str, secondary:
if not (primary.suffix == ".BinExport" and secondary.suffix == ".BinExport"):
for file in [primary, secondary]:
mime_type = magic.from_file(file, mime=True)
if mime_type not in BINARY_FORMAT and Path(file).suffix not in EXTENSIONS_WHITELIST.get(mime_type, []):
logging.error(f"file {file} mimetype ({mime_type}) not supported (not an executable file)")
if mime_type not in BINARY_FORMAT and Path(file).suffix not in EXTENSIONS_WHITELIST.get(
mime_type, []
):
logging.error(
f"file {file} mimetype ({mime_type}) not supported (not an executable file)"
)
exit(1)

# Export each binary separately (and then diff to be able to print it)
logging.info(f"export primary: {primary}.BinExport")
ProgramBinExport.from_binary_file(primary, open_export=False, override=True)
primary = Path(str(primary)+".BinExport")
primary = Path(str(primary) + ".BinExport")

logging.info(f"export secondary: {secondary}.BinExport")
ProgramBinExport.from_binary_file(secondary, open_export=False, override=True)
Expand All @@ -92,5 +111,5 @@ def main(ida_path: str, bindiff_path: str, output: str, primary: str, secondary:
sys.exit(1)


if __name__ == '__main__':
if __name__ == "__main__":
main()
98 changes: 63 additions & 35 deletions src/bindiff/bindiff.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

BINDIFF_BINARY = None
BINDIFF_PATH_ENV = "BINDIFF_PATH"
BIN_NAMES = ['bindiff', 'bindiff.exe', 'differ']
BIN_NAMES = ["bindiff", "bindiff.exe", "differ"]


def _check_bin_names(path: Path) -> bool:
Expand Down Expand Up @@ -48,7 +48,7 @@ def _check_environ() -> bool:
def _check_default_path() -> bool:
"""
Check if BinDiff is installed at its default location
:return: bool
"""
return _check_bin_names(Path("/opt/zynamics/BinDiff/bin"))
Expand Down Expand Up @@ -78,7 +78,12 @@ class BinDiff(BindiffFile):
additional attributes and method to the class.
"""

def __init__(self, primary: Union[ProgramBinExport, str], secondary: Union[ProgramBinExport, str], diff_file: str):
def __init__(
self,
primary: Union[ProgramBinExport, str],
secondary: Union[ProgramBinExport, str],
diff_file: str,
):
"""
:param primary: first program diffed
:param secondary: second program diffed
Expand All @@ -94,7 +99,7 @@ def __init__(self, primary: Union[ProgramBinExport, str], secondary: Union[Progr
def primary_unmatched_function(self) -> list[FunctionBinExport]:
"""
Return a list of the unmatched functions in the primary program.
:return: list of unmatched functions in primary
"""
funs = []
Expand All @@ -106,7 +111,7 @@ def primary_unmatched_function(self) -> list[FunctionBinExport]:
def secondary_unmatched_function(self) -> list[FunctionBinExport]:
"""
Return a list of the unmatched functions in the secondary program.
:return: list of unmatched functions in secondary
"""
funs = []
Expand All @@ -115,7 +120,9 @@ def secondary_unmatched_function(self) -> list[FunctionBinExport]:
funs.append(fun)
return funs

def iter_function_matches(self) -> list[tuple[FunctionBinExport, FunctionBinExport, FunctionMatch]]:
def iter_function_matches(
self,
) -> list[tuple[FunctionBinExport, FunctionBinExport, FunctionMatch]]:
"""
Return a list of all the matched functions. Each element of the list is a tuple containing
the function in the primary program, the matched function in the secondary program and the
Expand All @@ -124,50 +131,58 @@ def iter_function_matches(self) -> list[tuple[FunctionBinExport, FunctionBinExpo
:return: list of tuple, each containing the primary function, the secondary function and
the FunctionMatch object
"""
return [(self.primary[match.address1], self.secondary[match.address2], match) \
for match in self.primary_functions_match.values()]
return [
(self.primary[match.address1], self.secondary[match.address2], match)
for match in self.primary_functions_match.values()
]

def _unmatched_bbs(self, function: FunctionBinExport, map: dict[int, dict[int, BasicBlockMatch]]) -> list[BasicBlockBinExport]:
def _unmatched_bbs(
self, function: FunctionBinExport, map: dict[int, dict[int, BasicBlockMatch]]
) -> list[BasicBlockBinExport]:
bbs = []
for bb_addr, bb in function.items():
if maps := map.get(bb_addr):
if function.addr not in maps: # The block has been match but in another function thus unmatched here
# The block has been match but in another function thus unmatched here
if function.addr not in maps:
bbs.append(bb)
else:
bbs.append(bb)
return bbs

def primary_unmatched_basic_block(self, function: FunctionBinExport) -> list[BasicBlockBinExport]:
def primary_unmatched_basic_block(
self, function: FunctionBinExport
) -> list[BasicBlockBinExport]:
"""
Return a list of the unmatched basic blocks in the provided function.
The function must be part of the primary program.
:param function: A function of the primary program
:return: list of unmatched basic blocks
"""
return self._unmatched_bbs(function, self.primary_basicblock_match)

def secondary_unmatched_basic_block(self, function: FunctionBinExport) -> list[BasicBlockBinExport]:
def secondary_unmatched_basic_block(
self, function: FunctionBinExport
) -> list[BasicBlockBinExport]:
"""
Return a list of the unmatched basic blocks in the provided function.
The function must be part of the secondary program.
:param function: A function of the secondary program
:return: list of unmatched basic blocks
"""
return self._unmatched_bbs(function, self.secondary_basicblock_match)

def iter_basicblock_matches(self,
function1: FunctionBinExport,
function2: FunctionBinExport
def iter_basicblock_matches(
self, function1: FunctionBinExport, function2: FunctionBinExport
) -> list[tuple[BasicBlockBinExport, BasicBlockBinExport, BasicBlockMatch]]:
"""
Return a list of all the matched basic blocks between the two provided functions.
Each element of the list is a tuple containing the basic blocks of the primary and secondary
functions and the BasicBlockMatch object describing the match.
The first function must be part of the primary program while the second function must be
part of the secondary program.
:param function1: A function of the primary program
:param function2: A function of the secondary program
:return: list of tuple, each containing the primary basic block, the secondary basic block
Expand All @@ -180,7 +195,9 @@ def iter_basicblock_matches(self,
items.append((bb, function2[match.address2], match))
return items

def _unmatched_instrs(self, bb: BasicBlockBinExport, map: dict[int, dict[int, int]]) -> list[InstructionBinExport]:
def _unmatched_instrs(
self, bb: BasicBlockBinExport, map: dict[int, dict[int, int]]
) -> list[InstructionBinExport]:
instrs = []
for addr, instr in bb.instructions.items():
if addr not in map:
Expand All @@ -191,31 +208,34 @@ def primary_unmatched_instruction(self, bb: BasicBlockBinExport) -> list[Instruc
"""
Return a list of the unmatched instructions in the provided basic block.
The basic block must be part of the primary program.
:param bb: A basic block belonging to the primary program
:return: list of unmatched instructions
"""
return self._unmatched_instrs(bb, self.primary_instruction_match)

def secondary_unmatched_instruction(self, bb: BasicBlockBinExport) -> list[InstructionBinExport]:
def secondary_unmatched_instruction(
self, bb: BasicBlockBinExport
) -> list[InstructionBinExport]:
"""
Return a list of the unmatched instructions in the provided basic block.
The basic block must be part of the secondary program.
:param bb: A basic block belonging to the secondary program
:return: list of unmatched instructions
"""
return self._unmatched_instrs(bb, self.secondary_instruction_match)

def iter_instruction_matches(self, block1: BasicBlockBinExport,
block2: BasicBlockBinExport) -> list[tuple[InstructionBinExport, InstructionBinExport]]:
def iter_instruction_matches(
self, block1: BasicBlockBinExport, block2: BasicBlockBinExport
) -> list[tuple[InstructionBinExport, InstructionBinExport]]:
"""
Return a list of all the matched instructions between the two provided basic blocks.
Each element of the list is a tuple containing the instructions of the primary and secondary
basic blocks.
The first basic block must belong to the primary program while the second one must be
part of the secondary program.
:param block1: A basic block belonging to the primary program
:param block2: A basic block belonging to the secondary program
:return: list of tuple, each containing the primary instruction and the secondary instruction
Expand All @@ -226,10 +246,12 @@ def iter_instruction_matches(self, block1: BasicBlockBinExport,
insts.append((instr, block2.instructions[addr2]))
return insts

def get_match(self, function: FunctionBinExport) -> tuple[FunctionBinExport, FunctionMatch] | None:
def get_match(
self, function: FunctionBinExport
) -> tuple[FunctionBinExport, FunctionMatch] | None:
"""
Get the function that matches the provided one.
:param function: A function that belongs either to primary or secondary
:return: A tuple with the matched function and the match object if there is a match for
the provided function, otherwise None
Expand Down Expand Up @@ -268,10 +290,12 @@ def raw_diffing(p1_path: Union[Path, str], p2_path: Union[Path, str], out_diff:
f1 = Path(p1_path)
f2 = Path(p2_path)

cmd_line = [BINDIFF_BINARY.as_posix(),
f"--primary={p1_path}",
f"--secondary={p2_path}",
f"--output_dir={tmp_dir.as_posix()}"]
cmd_line = [
BINDIFF_BINARY.as_posix(),
f"--primary={p1_path}",
f"--secondary={p2_path}",
f"--output_dir={tmp_dir.as_posix()}",
]

logging.debug(f"run diffing: {' '.join(cmd_line)}")
process = subprocess.Popen(cmd_line, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Expand Down Expand Up @@ -301,7 +325,7 @@ def raw_diffing(p1_path: Union[Path, str], p2_path: Union[Path, str], out_diff:
return True

@staticmethod
def from_binary_files(p1_path: str, p2_path: str, diff_out: str) -> Optional['BinDiff']:
def from_binary_files(p1_path: str, p2_path: str, diff_out: str) -> Optional["BinDiff"]:
"""
Diff two executable files. Thus it export .BinExport files from IDA
and then diff the two resulting files in BinDiff.
Expand All @@ -324,7 +348,9 @@ def from_binary_files(p1_path: str, p2_path: str, diff_out: str) -> Optional['Bi
return None

@staticmethod
def from_binexport_files(p1_binexport: str, p2_binexport: str, diff_out: str) -> Optional['BinDiff']:
def from_binexport_files(
p1_binexport: str, p2_binexport: str, diff_out: str
) -> Optional["BinDiff"]:
"""
Diff two binexport files. Diff the two binexport files with bindiff
and then load a BinDiff instance.
Expand All @@ -346,8 +372,10 @@ def _configure_bindiff_path() -> None:
if not _check_environ():
if not _check_default_path():
if not _check_path():
logging.warning(f"Can't find a valid bindiff executable. (should be available in PATH or"
f"as ${BINDIFF_PATH_ENV} env variable")
logging.warning(
f"Can't find a valid bindiff executable. (should be available in PATH or"
f"as ${BINDIFF_PATH_ENV} env variable"
)

@staticmethod
def assert_installation_ok() -> None:
Expand Down
Loading

0 comments on commit 4bd65d2

Please sign in to comment.