Skip to content

Commit

Permalink
1.0.0 release
Browse files Browse the repository at this point in the history
  • Loading branch information
dc3-tsd committed Jun 17, 2024
1 parent 9ddb651 commit 734874f
Show file tree
Hide file tree
Showing 19 changed files with 66 additions and 30 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/workflow.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ jobs:
strategy:
matrix:
# This workflow can be matrixed against multiple Python versions if desired. eg. [3.7, 3.8, 3.9, "3.10"]
python-version: [ 3.8 ]
python-version: [ "3.11" ]

steps:
# Get the code from the repository to be linted, packaged, and pushed
Expand Down
9 changes: 8 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## [1.0.0] - 2024-05-23
- Dropped support for Python 3.8.
- Fixed issue with default not being respected in `get_function_by_name()`.
- Fixed handling of imports of variables/macros.


## [0.8.0] - 2023-11-29
- Updated Ghidra support to 10.3.2 and 10.4
- Added automatic activation of virtualenv within IDA process if one is detected.
Expand Down Expand Up @@ -135,7 +141,8 @@
- Initial release


[Unreleased]: https://github.com/dod-cyber-crime-center/dragodis/compare/0.8.0...HEAD
[Unreleased]: https://github.com/dod-cyber-crime-center/dragodis/compare/1.0.0...HEAD
[1.0.0]: https://github.com/dod-cyber-crime-center/dragodis/compare/0.8.0...1.0.0
[0.8.0]: https://github.com/dod-cyber-crime-center/dragodis/compare/0.7.2...0.8.0
[0.7.2]: https://github.com/dod-cyber-crime-center/dragodis/compare/0.7.1...0.7.2
[0.7.1]: https://github.com/dod-cyber-crime-center/dragodis/compare/0.7.0...0.7.1
Expand Down
2 changes: 1 addition & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
# -- Project information -----------------------------------------------------

project = 'Dragodis'
copyright = '2022, DC3'
copyright = '2024, DC3'
author = 'DC3'

# The full version, including alpha/beta/rc tags
Expand Down
4 changes: 2 additions & 2 deletions docs/examples/flowcharts.rst
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ flow type, the lines within, as well as other basic blocks that come into or out
0x401003
>>> print(hex(block.end))
0x40100d
>>> print(block.flow_type)
FlowType.conditional_jump
>>> print(block.flow_type.name)
conditional_jump
>>> print("\n".join(map(str, block.lines())))
0x00401003: mov eax, [ebp+arg_0]
0x00401006: movsx ecx, byte ptr [eax]
Expand Down
2 changes: 1 addition & 1 deletion docs/examples/segments.rst
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ A *Memory* object for the underlying data can be obtained using the ``.open()``
b']\xc3\xcc\xcc'
>>> segment.permissions
<SegmentPermission.read|execute: 5>
<SegmentPermission.execute|read: 5>
>>> with segment.open() as stream:
... stream.seek(4)
Expand Down
4 changes: 2 additions & 2 deletions docs/install.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,13 @@ Then follow one or more of the following instructions to setup your favorite dis
environment than IDA, you can manually install the library in the IDA environment using the `--target` flag.

```bash
py -3.8 -m pip install rpyc --target="%IDA_INSTALL_DIR%\python\3"
py -3.11 -m pip install rpyc --target="%IDA_INSTALL_DIR%\python\3"
```

4. **WINDOWS**: If you are on Windows, you'll also need to install `pywin32` in the IDA interpreter.
```bash
py -3.8 -m pip install pywin32 --target="%IDA_INSTALL_DIR%\python\3"
py -3.11 -m pip install pywin32 --target="%IDA_INSTALL_DIR%\python\3"
```
Expand Down
2 changes: 1 addition & 1 deletion dragodis/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@
# Import types from interface that may needed by users.
from dragodis.interface.types import *

__version__ = "0.8.0"
__version__ = "1.0.0"
10 changes: 6 additions & 4 deletions dragodis/ghidra/flat.py
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,10 @@ def get_string_bytes(self, addr: int, length: int = None, bit_width: int = None,
else:
raise ValueError(f"Invalid bit width: {bit_width}")
except Exception as e:
raise RuntimeError(f"Failed to create a string at {hex(addr)} with error: {e}")
if default is MISSING:
raise RuntimeError(f"Failed to create a string at {hex(addr)} with error: {e}")
else:
return default

def strings(self, min_length=3) -> Iterable[GhidraString]:
# NOTE: Not using findStrings() because Ghidra has issues getting the right starting address for unicode strings.
Expand Down Expand Up @@ -420,9 +423,8 @@ def undefine(self, start: int, end: int = None) -> bool:
if end:
if end <= start:
raise ValueError(f"End address {hex(end)} is smaller than starting address {hex(start)}")
# Ghidra is inclusive so jump to previous address for end.
end = self._listing.getCodeUnitBefore(self._to_addr(end)).getAddress()
self._flatapi.clearListing(self._to_addr(start), end)
# Ghidra is inclusive
self._flatapi.clearListing(self._to_addr(start), self._to_addr(end-1))
else:
self._flatapi.clearListing(self._to_addr(start))
return True
Expand Down
1 change: 0 additions & 1 deletion dragodis/ghidra/function_signature.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,6 @@ def _generate_parameter(self, data_type: str) -> "ghidra.program.model.listing.P
return ParameterImpl(
"",
data_type,
self._function.getEntryPoint(),
self._ghidra._program,
SourceType.USER_DEFINED
)
Expand Down
9 changes: 7 additions & 2 deletions dragodis/ghidra/line.py
Original file line number Diff line number Diff line change
Expand Up @@ -179,8 +179,9 @@ def type(self) -> LineType:
return LineType.code

data_type = self._code_unit.getDataType().getName()
data_type_class_name = str(self._code_unit.getDataType().getClass())

if "struct" in data_type: # FIXME
if "struct" in data_type or "Structure" in data_type_class_name: # FIXME: Still need proper support for structures.
return LineType.struct

# If data type is undefined, check if data is unloaded by checking if it has a value.
Expand All @@ -193,6 +194,10 @@ def type(self) -> LineType:
try:
return self._data_type_map[data_type]
except KeyError:
# Check if a dynamic type.
from ghidra.program.model.data import DynamicDataType
if isinstance(self._code_unit.getDataType(), DynamicDataType):
return LineType.dynamic
raise RuntimeError(f"Unexpected line type at {hex(self.address)}")

@type.setter
Expand Down Expand Up @@ -361,7 +366,7 @@ def _set_value(self, new_value: Any):
# For strings, we are going to undefine the value first so ghidra appropriately
# resets the code unit.
self.undefine()
self._ghidra._flatapi.setBytes(self._addr_obj, list(new_value))
self._ghidra._flatapi.setBytes(self._addr_obj, new_value)
self.type = type_
return

Expand Down
4 changes: 3 additions & 1 deletion dragodis/ghidra/symbol.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,12 +77,14 @@ def references_to(self) -> Iterable[GhidraReference]:
else:
thunk_function = None

linkage_address = self._linkage_address

# First pull references from original external symbol.
from ghidra.program.model.symbol import RefType
for ref in self._symbol.getReferences():
if (
ref.getReferenceType() != RefType.THUNK
and ref.getFromAddress() != self._linkage_address
and ref.getFromAddress() != linkage_address
and not (thunk_function and ref.getFromAddress().getOffset() in thunk_function)
):
yield GhidraReference(self._ghidra, ref)
Expand Down
12 changes: 9 additions & 3 deletions dragodis/ida/disassembler.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import pathlib

import atexit
import gc
import re
import logging
import os
Expand Down Expand Up @@ -185,7 +184,7 @@ class IDARemoteDisassembler(IDADisassembler):
"sync_request_timeout": 60
}

def __init__(self, input_path, is_64_bit=None, ida_path=None, timeout=None, processor=None, **unused):
def __init__(self, input_path, is_64_bit=None, ida_path=None, timeout=None, processor=None, detach=False, **unused):
"""
Initializes IDA disassembler.
Expand All @@ -197,6 +196,9 @@ def __init__(self, input_path, is_64_bit=None, ida_path=None, timeout=None, proc
:param timeout: Number of seconds to wait for remote results. (defaults to 60)
:param processor: Processor type (defaults to auto detected)
(https://hex-rays.com/products/ida/support/idadoc/618.shtml)
:param detach: Detach the IDA subprocess from the parent process group.
This will cause signals to no longer propagate.
(Linux only)
"""
super().__init__(input_path, processor=processor)
self._ida_path = ida_path or os.environ.get("IDA_INSTALL_DIR", os.environ.get("IDA_DIR"))
Expand Down Expand Up @@ -237,6 +239,7 @@ def __init__(self, input_path, is_64_bit=None, ida_path=None, timeout=None, proc

self._running = False

self._detach = detach and sys.platform != "win32"
self._socket_path = None
self._process = None
self._bridge = None
Expand Down Expand Up @@ -419,7 +422,10 @@ def start(self):

# TODO: Clean up ida temp files if we fail.
self._process = subprocess.Popen(
command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=sys.platform != "win32")
command,
shell=sys.platform != "win32",
preexec_fn=os.setpgrp if self._detach else None
)
atexit.register(self._process.kill)
finally:
os.chdir(orig_cwd)
Expand Down
2 changes: 1 addition & 1 deletion dragodis/ida/flat.py
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,7 @@ def get_string_bytes(self, addr: int, length: int = None, bit_width: int = None,
if default is MISSING:
raise NotExistError(f"Unable to obtain string bytes at 0x{addr:08x}")
return default
return self._ida_bytes.get_strlit_contents(addr, length, str_type)
return ret

def strings(self, min_length=3) -> Iterable[IDAString]:
sc = self._idautils.Strings()
Expand Down
11 changes: 9 additions & 2 deletions dragodis/ida/sdk/ida_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,8 @@ def callback(addr, name, ordinal):
name = ida_funcs.get_func_name(thunk_addr)
break
else:
raise RuntimeError(f"Failed to find a thunk for {name} at 0x{addr:08X}")
# If thunk not found, this is most likely an imported variable/macro.
name = raw_name[len("__imp_"):]

if _target and name != _target:
return True # continue enumeration
Expand Down Expand Up @@ -195,6 +196,12 @@ def get_operands(address: int) -> List[Tuple[int, ida_ua.op_t]]:
for index, op in enumerate(ops):
if op.type == o_void:
break # no more operands
# The op_t objects within the insn_t object become a dangling pointer when the insn_t
# object goes out of scope.
# Creating our own copy to avoid this.
op_copy = ida_ua.op_t()
op_copy.assign(op)
op = op_copy

ret.append((index, op))

Expand Down Expand Up @@ -387,7 +394,7 @@ def decompiled_code(address: int, _visited=None) -> Optional[ida_hexrays.cfuncpt
failed_address = fail_obj.errea
if ida_ua.ua_mnem(failed_address) == "call":
call_address = idc.get_operand_value(failed_address, 0)
if decompiled_code(_visited=_visited) is not None:
if decompiled_code(call_address, _visited=_visited) is not None:
return decompiled_code(address, visited=_visited)

# TODO: Observed this message pops up with fail_obj.code == 0... unsure if that is actually an error.
Expand Down
4 changes: 3 additions & 1 deletion dragodis/interface/flat.py
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,9 @@ def get_function_by_name(self, name: str, ignore_underscore: bool = True, defaul
func_name = func_name.strip("_")
if func_name == name:
return func
raise NotExistError(f"Unable to find function with name: {name}")
if default is MISSING:
raise NotExistError(f"Unable to find function with name: {name}")
return default

@abc.abstractmethod
def create_function(self, start: int, end: int = None, *, default=MISSING) -> Function:
Expand Down
5 changes: 3 additions & 2 deletions dragodis/interface/stack.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,11 @@ class StackFrame(MutableMapping, metaclass=abc.ABCMeta):
"""Function Stack Frame"""

def __str__(self) -> str:
return str(dict(self))
return "\n".join(map(str, self))

def __repr__(self):
return f"<StackFrame {self}>"
vars = ", ".join(map(repr, self))
return f"<StackFrame {vars}>"

@abc.abstractmethod
def __getitem__(self, name_or_offset: Union[str, int]) -> StackVariable:
Expand Down
1 change: 1 addition & 0 deletions dragodis/interface/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ class LineType(IntEnum):
tail = auto()
undefined = auto()
unloaded = auto()
dynamic = auto()

@classmethod
def match_type(cls, value: Any) -> List["LineType"]:
Expand Down
4 changes: 2 additions & 2 deletions noxfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,15 @@ def _install_local_deps(session):
session.install(path)


@nox.session(python="3.8")
@nox.session(python="3.11")
def test(session):
"""Run pytests"""
_install_local_deps(session)
session.install("-e", ".[testing]")
session.run("pytest")


@nox.session(python="3.8")
@nox.session(python="3.11")
def build(session):
"""Build source and wheel distribution"""
session.run("python", "setup.py", "sdist")
Expand Down
8 changes: 6 additions & 2 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,16 @@ classifiers =
Intended Audience :: Developers
License :: OSI Approved :: MIT License
Programming Language :: Python :: 3
Programming Language :: Python :: 3.8
Programming Language :: Python :: 3.9
Programming Language :: Python :: 3.10
Programming Language :: Python :: 3.11
Programming Language :: Python :: 3.12


[options]
include_package_data = True
packages = find:
python_requires = >=3.8
python_requires = >=3.9
install_requires =
bytesparse
capstone
Expand Down

0 comments on commit 734874f

Please sign in to comment.