Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update README to reflect new API #11

Merged
merged 6 commits into from
Nov 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@ jobs:
with:
python-version: '3.10'
- name: Build sdist
run: python setup.py sdist
run: |
python -m pip install -U pip build
python -m build --sdist
- uses: actions/upload-artifact@v3
with:
name: artifact
Expand Down
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
### Python template
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

build/
*.egg-info/
/venv/
11 changes: 6 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,12 @@ from binexport import ProgramBinExport

p = ProgramBinExport("myprogram.BinExport")
for fun_addr, fun in p.items():
for bb_addr, bb in fun.items():
for inst_addr, inst in bb.items():
for operand in inst.operands:
for exp in operand.expressions:
pass # Do whatever at such deep level
with fun: # Preload all the basic blocks
for bb_addr, bb in fun.items():
for inst_addr, inst in bb.instructions.items():
for operand in inst.operands:
for exp in operand.expressions:
pass # Do whatever at such deep level
```

Obviously ``ProgramBinExport``, ``FunctionBinExport``, ``InstructionBinExport`` and ``OperandBinExport``
Expand Down
30 changes: 30 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,36 @@
requires = ["setuptools"]
build-backend = "setuptools.build_meta"

[project]
name = "python-binexport"
version = "0.2.1"
description = "Python wrapper to manipulate binexport files (protobuf)"
readme = { file = "README.md", content-type = "text/markdown" }
authors = [{ name = "Quarkslab", email = "[email protected]" }]
dependencies = [
"python-magic",
"python-magic-bin; os_name=='nt'",
"click",
"protobuf",
"networkx",
"enum_tools",
"idascript",
]
classifiers = [
'Topic :: Security',
'Environment :: Console',
'Operating System :: OS Independent',
]

[project.urls]
Homepage = "https://github.com/quarkslab/python-binexport"
Repository = "https://github.com/quarkslab/python-binexport"
Documentation = "https://quarkslab.github.io/diffing-portal/exporter/binexport.html#python-binexport"
"Bug Tracker" = "https://github.com/quarkslab/python-binexport/issues"

[tools.setuptools]
script-files = ["bin/binexporter"]

[tool.black]
line-length = 100
target-version = ['py310']
File renamed without changes.
21 changes: 4 additions & 17 deletions binexport/basic_block.py → src/binexport/basic_block.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from .function import FunctionBinExport
from .binexport2_pb2 import BinExport2


class BasicBlockBinExport:
"""
Basic block class.
Expand Down Expand Up @@ -81,14 +82,12 @@ def function(self) -> "FunctionBinExport":
"""
return self._function()

@property
def uncached_instructions(self) -> dict[Addr, InstructionBinExport]:
@cached_property
def instructions(self) -> dict[Addr, InstructionBinExport]:
"""
Returns a dict which is used to reference all the instructions in this basic
block by their address.
The object returned is not cached, calling this function multiple times will
create the same object multiple times. If you want to cache the object you
should use `BasicBlockBinExport.instructions`.
The object returned is by default cached, to erase the cache delete the attribute.

:return: dictionary of addresses to instructions
"""
Expand All @@ -108,15 +107,3 @@ def uncached_instructions(self) -> dict[Addr, InstructionBinExport]:
)

return instructions

@cached_property
def instructions(self) -> dict[Addr, InstructionBinExport]:
"""
Returns a dict which is used to reference all the instructions in this basic
block by their address.
The object returned is by default cached, to erase the cache delete the attribute.

:return: dictionary of addresses to instructions
"""

return self.uncached_instructions
File renamed without changes.
File renamed without changes.
File renamed without changes.
60 changes: 45 additions & 15 deletions binexport/function.py → src/binexport/function.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ def __init__(
self._name = None # Set by the Program constructor
self._program = program
self._pb_fun = pb_fun
self._enable_unloading = False
self._basic_blocks = None

if is_import:
if self.addr is None:
Expand All @@ -70,6 +72,29 @@ def __hash__(self) -> int:
def __repr__(self) -> str:
return "<%s: 0x%x>" % (type(self).__name__, self.addr)

def __enter__(self) -> None:
"""Preload basic blocks and don't deallocate them until __exit__ is called"""

self._enable_unloading = False
self.preload()

def __exit__(self, exc_type, exc_value, traceback) -> None:
"""Deallocate all the basic blocks"""

self._enable_unloading = True
self.unload()

def preload(self) -> None:
"""Load in memory all the basic blocks"""

self._basic_blocks = self.blocks

def unload(self) -> None:
"""Unload from memory all the basic blocks"""

if self._enable_unloading:
self._basic_blocks = None

def items(self) -> abc.ItemsView[Addr, "BasicBlockBinExport"]:
"""
Each function is associated to a dictionary with key-value
Expand Down Expand Up @@ -117,17 +142,34 @@ def program(self) -> "ProgramBinExport":
return self._program()

@property
def uncached_blocks(self) -> dict[Addr, BasicBlockBinExport]:
def blocks(self) -> Dict[Addr, BasicBlockBinExport]:
"""
Returns a dict which is used to reference all basic blocks by their address.
Calling this function will also load the CFG.
The object returned is not cached, calling this function multiple times will
By default the object returned is not cached, calling this function multiple times will
create the same object multiple times. If you want to cache the object you
should use `FunctionBinExport.blocks`.
should use the context manager of the function or calling the function `FunctionBinExport.load`.
Ex:

.. code-block:: python
:linenos:

# func: FunctionBinExport
with func: # Loading all the basic blocks
for bb_addr, bb in func.blocks.items(): # Blocks are already loaded
pass
# The blocks are still loaded
for bb_addr, bb in func.blocks.items():
pass
# here the blocks have been unloaded

:return: dictionary of addresses to basic blocks
"""

# Check if the blocks are already loaded
if self._basic_blocks is not None:
return self._basic_blocks

# Fast return if it is a imported function
if self.is_import():
if self._graph is None:
Expand Down Expand Up @@ -177,18 +219,6 @@ def uncached_blocks(self) -> dict[Addr, BasicBlockBinExport]:

return bblocks

@cached_property
def blocks(self) -> Dict[Addr, BasicBlockBinExport]:
"""
Returns a dict which is used to reference all basic blocks by their address.
Calling this function will also load the CFG.
The dict is by default cached, to erase the cache delete the attribute.

:return: dictionary of addresses to basic blocks
"""

return self.uncached_blocks

@property
def graph(self) -> networkx.DiGraph:
"""
Expand Down
20 changes: 7 additions & 13 deletions binexport/instruction.py → src/binexport/instruction.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from .function import FunctionBinExport
from .binexport2_pb2 import BinExport2


class InstructionBinExport:
"""
Instruction class. It represents an instruction with its operands.
Expand Down Expand Up @@ -65,23 +66,16 @@ def mnemonic(self) -> str:
"""
return self.program.proto.mnemonic[self.pb_instr.mnemonic_index].name

@property
def uncached_operands(self) -> list[OperandBinExport]:
@cached_property
def operands(self) -> List[OperandBinExport]:
"""
Returns a list of the operands instanciated dynamically on-demand.
The object returned is not cached, calling this function multiple times will
create the same object multiple times. If you want to cache the object you
should use `InstructionBinExport.operands`.
The list is cached by default, to erase the cache delete the attribute.

:return: list of operands
"""

return [
OperandBinExport(self._program, self._function, weakref.ref(self), op_idx)
for op_idx in self.pb_instr.operand_index
]

@cached_property
def operands(self) -> List[OperandBinExport]:
"""
Returns a list of the operands instanciated dynamically on-demand.
The list is cached by default, to erase the cache delete the attribute.
"""
return self.uncached_operands
20 changes: 5 additions & 15 deletions binexport/operand.py → src/binexport/operand.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,14 +114,14 @@ def pb_operand(self) -> "BinExport2.Operand":
"""
return self.program.proto.operand[self._idx]

@property
def uncached_expressions(self) -> List[ExpressionBinExport]:
@cached_property
def expressions(self) -> List[ExpressionBinExport]:
"""
Iterates over all the operand expression in a pre-order manner
(binary operator first).
The object returned is not cached, calling this function multiple times will
create the same object multiple times. If you want to cache the object you
should use `OperandBinExport.expressions`.
The list is cached by default, to erase the cache delete the attribute

:return: list of expressions
"""

expr_dict = {} # {expression protobuf idx : ExpressionBinExport}
Expand All @@ -133,13 +133,3 @@ def uncached_expressions(self) -> List[ExpressionBinExport]:
self.program, self.function, self.instruction, exp_idx, parent
)
return list(expr_dict.values())

@cached_property
def expressions(self) -> List[ExpressionBinExport]:
"""
Iterates over all the operand expression in a pre-order manner
(binary operator first).
The list is cached by default, to erase the cache delete the attribute
"""

return self.uncached_expressions
File renamed without changes.
File renamed without changes.
File renamed without changes.