From 76d9b336ec65eb2eeff87882926967f0bba629c6 Mon Sep 17 00:00:00 2001 From: doronz88 Date: Sun, 8 Sep 2024 10:32:58 +0300 Subject: [PATCH] add ida 9.0 support --- .github/workflows/python-app.yml | 2 +- .github/workflows/python-publish.yml | 4 +- .gitignore | 3 + README.md | 2 +- elf_loader.py | 2 +- fa/commands/add.py | 1 + fa/commands/add_offset_range.py | 2 +- fa/commands/align.py | 1 + fa/commands/argument.py | 3 +- fa/commands/b.py | 1 + fa/commands/clear.py | 1 + fa/commands/find_bytes.py | 2 +- fa/commands/find_bytes_ida.py | 2 +- fa/commands/find_immediate.py | 9 +- fa/commands/find_str.py | 3 +- fa/commands/function_end.py | 4 +- fa/commands/function_lines.py | 3 +- fa/commands/function_start.py | 3 +- fa/commands/goto_ref.py | 3 +- fa/commands/if.py | 1 + fa/commands/intersect.py | 1 + fa/commands/keystone_find_opcodes.py | 3 +- fa/commands/keystone_verify_opcodes.py | 3 +- fa/commands/load.py | 1 + fa/commands/locate.py | 3 +- fa/commands/make_code.py | 2 +- fa/commands/make_comment.py | 2 +- fa/commands/make_function.py | 2 +- fa/commands/make_literal.py | 2 +- fa/commands/make_unknown.py | 2 +- fa/commands/max_xrefs.py | 2 +- fa/commands/min_xrefs.py | 2 +- fa/commands/most_common.py | 1 + fa/commands/offset.py | 1 + fa/commands/operand.py | 3 +- fa/commands/python_if.py | 1 + fa/commands/set_enum.py | 2 +- fa/commands/set_struct_member.py | 2 +- fa/commands/set_type.py | 4 +- fa/commands/single.py | 1 + fa/commands/sort.py | 1 + fa/commands/stop_if_empty.py | 1 + fa/commands/store.py | 1 + fa/commands/symdiff.py | 1 + fa/commands/unique.py | 1 + fa/commands/verify_aligned.py | 1 + fa/commands/verify_bytes.py | 2 +- fa/commands/verify_name.py | 2 +- fa/commands/verify_operand.py | 3 +- fa/commands/verify_ref.py | 2 +- fa/commands/verify_segment.py | 2 +- fa/commands/verify_single.py | 1 + fa/commands/verify_str.py | 2 +- fa/commands/xref.py | 2 +- fa/commands/xrefs_to.py | 4 +- fa/fa_types.py | 51 ++++++----- fa/fainterp.py | 32 +++---- fa/ida_launcher.py | 2 +- fa/ida_plugin.py | 51 +++++------ fa/utils.py | 30 +++--- pyproject.toml | 55 +++++++++++ requirements_testing.txt | 1 - scripts/git/pre-commit | 4 +- setup.py | 40 -------- tests/conftest.py | 8 +- tests/test_commands/test_elf.py | 1 + tests/test_commands/test_idalink.py | 121 ------------------------- 67 files changed, 219 insertions(+), 295 deletions(-) create mode 100644 pyproject.toml delete mode 100644 setup.py delete mode 100644 tests/test_commands/test_idalink.py diff --git a/.github/workflows/python-app.yml b/.github/workflows/python-app.yml index 5111263..f3e0cff 100644 --- a/.github/workflows/python-app.yml +++ b/.github/workflows/python-app.yml @@ -16,7 +16,7 @@ jobs: strategy: matrix: - python-version: [2.7, 3.5, 3.6, 3.7, 3.8] + python-version: [3.9, "3.10", 3.11, 3.12] steps: - uses: actions/checkout@v2 diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml index 4e1ef42..2d07178 100644 --- a/.github/workflows/python-publish.yml +++ b/.github/workflows/python-publish.yml @@ -21,11 +21,11 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - pip install setuptools wheel twine + pip install setuptools wheel twine build - name: Build and publish env: TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} run: | - python setup.py sdist bdist_wheel + python -m build twine upload dist/* diff --git a/.gitignore b/.gitignore index 01c886e..374512e 100644 --- a/.gitignore +++ b/.gitignore @@ -108,3 +108,6 @@ venv.bak/ # config config.ini + +# setuptools-scm +fa/_version.py \ No newline at end of file diff --git a/README.md b/README.md index f38d5d7..9f0e574 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ Pull Requests are of course more than welcome :smirk:. ## Requirements -Supported IDA 7.x. +Supported IDA 8.0+. In your IDA's python directory, run: diff --git a/elf_loader.py b/elf_loader.py index 517541e..13515c1 100644 --- a/elf_loader.py +++ b/elf_loader.py @@ -1,5 +1,5 @@ -from elftools.elf import elffile import click +from elftools.elf import elffile from fa import fainterp diff --git a/fa/commands/add.py b/fa/commands/add.py index 453f417..daa1089 100644 --- a/fa/commands/add.py +++ b/fa/commands/add.py @@ -1,4 +1,5 @@ from argparse import RawTextHelpFormatter + from fa import utils DESCRIPTION = '''add an hard-coded value into resultset diff --git a/fa/commands/add_offset_range.py b/fa/commands/add_offset_range.py index bd832ec..4650be6 100644 --- a/fa/commands/add_offset_range.py +++ b/fa/commands/add_offset_range.py @@ -1,6 +1,6 @@ from argparse import RawTextHelpFormatter -from fa import utils +from fa import utils DESCRIPTION = '''adds a python-range to resultset diff --git a/fa/commands/align.py b/fa/commands/align.py index 656398e..e698166 100644 --- a/fa/commands/align.py +++ b/fa/commands/align.py @@ -1,4 +1,5 @@ from argparse import RawTextHelpFormatter + from fa import utils DESCRIPTION = '''align results to given base (round-up) diff --git a/fa/commands/argument.py b/fa/commands/argument.py index f4f34be..92411b5 100644 --- a/fa/commands/argument.py +++ b/fa/commands/argument.py @@ -1,5 +1,6 @@ from argparse import RawTextHelpFormatter -from fa import utils, context + +from fa import context, utils try: import ida_typeinf diff --git a/fa/commands/b.py b/fa/commands/b.py index b275007..281fed1 100644 --- a/fa/commands/b.py +++ b/fa/commands/b.py @@ -1,4 +1,5 @@ from argparse import RawTextHelpFormatter + from fa import utils DESCRIPTION = '''branch unconditionally to label diff --git a/fa/commands/clear.py b/fa/commands/clear.py index b650569..c5063a1 100644 --- a/fa/commands/clear.py +++ b/fa/commands/clear.py @@ -1,4 +1,5 @@ from argparse import RawTextHelpFormatter + from fa import utils DESCRIPTION = '''clears the current result-set diff --git a/fa/commands/find_bytes.py b/fa/commands/find_bytes.py index 840f156..3d16df6 100644 --- a/fa/commands/find_bytes.py +++ b/fa/commands/find_bytes.py @@ -1,5 +1,5 @@ -from argparse import RawTextHelpFormatter import binascii +from argparse import RawTextHelpFormatter from fa import utils diff --git a/fa/commands/find_bytes_ida.py b/fa/commands/find_bytes_ida.py index 541a5f9..f53f062 100644 --- a/fa/commands/find_bytes_ida.py +++ b/fa/commands/find_bytes_ida.py @@ -1,6 +1,6 @@ from argparse import RawTextHelpFormatter -from fa import utils, context +from fa import context, utils DESCRIPTION = '''expands the result-set with the occurrences of the given bytes expression in "ida bytes syntax" diff --git a/fa/commands/find_immediate.py b/fa/commands/find_immediate.py index 33aa444..e70eb1c 100644 --- a/fa/commands/find_immediate.py +++ b/fa/commands/find_immediate.py @@ -1,10 +1,10 @@ from argparse import RawTextHelpFormatter -from fa import utils, context +from fa import context, utils try: - import idc import ida_search + import idc except ImportError: pass @@ -34,11 +34,10 @@ def find_immediate(expression): if isinstance(expression, str): expression = eval(expression) - ea, imm = ida_search.find_imm(0, idc.SEARCH_DOWN, expression) + ea, imm = ida_search.find_imm(0, ida_search.SEARCH_DOWN, expression) while ea != idc.BADADDR: yield ea - ea, imm = idc.find_imm(ea + 1, idc.SEARCH_DOWN, - expression) + ea, imm = idc.find_imm(ea + 1, ida_search.SEARCH_DOWN, expression) def run(segments, args, addresses, interpreter=None, **kwargs): diff --git a/fa/commands/find_str.py b/fa/commands/find_str.py index 5efca72..9f2dbca 100644 --- a/fa/commands/find_str.py +++ b/fa/commands/find_str.py @@ -1,6 +1,7 @@ +import binascii import sys from argparse import RawTextHelpFormatter -import binascii + import six from fa.commands import find_bytes diff --git a/fa/commands/function_end.py b/fa/commands/function_end.py index b7173d3..9ce896e 100644 --- a/fa/commands/function_end.py +++ b/fa/commands/function_end.py @@ -1,6 +1,6 @@ from argparse import RawTextHelpFormatter -from fa import utils -from fa import context + +from fa import context, utils try: import idc diff --git a/fa/commands/function_lines.py b/fa/commands/function_lines.py index e161387..2980cb1 100644 --- a/fa/commands/function_lines.py +++ b/fa/commands/function_lines.py @@ -1,5 +1,6 @@ from argparse import RawTextHelpFormatter -from fa import utils, context + +from fa import context, utils try: import idautils diff --git a/fa/commands/function_start.py b/fa/commands/function_start.py index c7cbb66..b48b4a8 100644 --- a/fa/commands/function_start.py +++ b/fa/commands/function_start.py @@ -1,5 +1,6 @@ from argparse import RawTextHelpFormatter -from fa import utils, context + +from fa import context, utils try: import idc diff --git a/fa/commands/goto_ref.py b/fa/commands/goto_ref.py index fe6e565..37cb327 100644 --- a/fa/commands/goto_ref.py +++ b/fa/commands/goto_ref.py @@ -1,5 +1,6 @@ from argparse import RawTextHelpFormatter -from fa import utils, context + +from fa import context, utils try: import idautils diff --git a/fa/commands/if.py b/fa/commands/if.py index 4be8422..b5b318c 100644 --- a/fa/commands/if.py +++ b/fa/commands/if.py @@ -1,4 +1,5 @@ from argparse import RawTextHelpFormatter + from fa import utils DESCRIPTION = '''perform an 'if' statement to create conditional branches diff --git a/fa/commands/intersect.py b/fa/commands/intersect.py index 4306c38..9a62231 100644 --- a/fa/commands/intersect.py +++ b/fa/commands/intersect.py @@ -1,4 +1,5 @@ from argparse import RawTextHelpFormatter + from fa import utils DESCRIPTION = '''intersect two or more variables diff --git a/fa/commands/keystone_find_opcodes.py b/fa/commands/keystone_find_opcodes.py index c1ab397..2f88127 100644 --- a/fa/commands/keystone_find_opcodes.py +++ b/fa/commands/keystone_find_opcodes.py @@ -1,4 +1,5 @@ from argparse import RawTextHelpFormatter + try: # flake8: noqa from keystone import * @@ -7,8 +8,8 @@ import binascii -from fa.commands import find_bytes from fa import utils +from fa.commands import find_bytes DESCRIPTION = '''use keystone to search for the supplied opcodes diff --git a/fa/commands/keystone_verify_opcodes.py b/fa/commands/keystone_verify_opcodes.py index a2c3ad7..6e1053f 100644 --- a/fa/commands/keystone_verify_opcodes.py +++ b/fa/commands/keystone_verify_opcodes.py @@ -1,4 +1,5 @@ from argparse import RawTextHelpFormatter + try: # flake8: noqa from keystone import * @@ -8,8 +9,8 @@ import binascii -from fa.commands import verify_bytes from fa import utils +from fa.commands import verify_bytes DESCRIPTION = '''use keystone to verify the result-set matches the given opcodes diff --git a/fa/commands/load.py b/fa/commands/load.py index 97b89f3..f66f331 100644 --- a/fa/commands/load.py +++ b/fa/commands/load.py @@ -1,4 +1,5 @@ from argparse import RawTextHelpFormatter + from fa import utils DESCRIPTION = '''go back to previous result-set saved by 'store' command. diff --git a/fa/commands/locate.py b/fa/commands/locate.py index df933b6..f0faf0b 100644 --- a/fa/commands/locate.py +++ b/fa/commands/locate.py @@ -1,5 +1,6 @@ from argparse import RawTextHelpFormatter -from fa import utils, context + +from fa import context, utils try: import idc diff --git a/fa/commands/make_code.py b/fa/commands/make_code.py index 2f41665..110b4d6 100644 --- a/fa/commands/make_code.py +++ b/fa/commands/make_code.py @@ -1,4 +1,4 @@ -from fa import utils, context +from fa import context, utils try: import idc diff --git a/fa/commands/make_comment.py b/fa/commands/make_comment.py index 192f6ed..6b4d7b6 100644 --- a/fa/commands/make_comment.py +++ b/fa/commands/make_comment.py @@ -5,7 +5,7 @@ except ImportError: pass -from fa import utils, context +from fa import context, utils DESCRIPTION = '''add comment for given addresses diff --git a/fa/commands/make_function.py b/fa/commands/make_function.py index 86d1d2a..da379b3 100644 --- a/fa/commands/make_function.py +++ b/fa/commands/make_function.py @@ -1,4 +1,4 @@ -from fa import utils, context +from fa import context, utils try: import ida_funcs diff --git a/fa/commands/make_literal.py b/fa/commands/make_literal.py index d857cdd..8c4f4ac 100644 --- a/fa/commands/make_literal.py +++ b/fa/commands/make_literal.py @@ -1,4 +1,4 @@ -from fa import utils, context +from fa import context, utils try: import idc diff --git a/fa/commands/make_unknown.py b/fa/commands/make_unknown.py index f5a56e1..8d3bc15 100644 --- a/fa/commands/make_unknown.py +++ b/fa/commands/make_unknown.py @@ -1,4 +1,4 @@ -from fa import utils, context +from fa import context, utils try: import ida_bytes diff --git a/fa/commands/max_xrefs.py b/fa/commands/max_xrefs.py index 95ea61a..6b1cd89 100644 --- a/fa/commands/max_xrefs.py +++ b/fa/commands/max_xrefs.py @@ -1,4 +1,4 @@ -from fa import utils, context +from fa import context, utils try: import idautils diff --git a/fa/commands/min_xrefs.py b/fa/commands/min_xrefs.py index e8ba0e5..dc2a8bb 100644 --- a/fa/commands/min_xrefs.py +++ b/fa/commands/min_xrefs.py @@ -1,4 +1,4 @@ -from fa import utils, context +from fa import context, utils try: import idautils diff --git a/fa/commands/most_common.py b/fa/commands/most_common.py index e3dd7ac..070f121 100644 --- a/fa/commands/most_common.py +++ b/fa/commands/most_common.py @@ -1,4 +1,5 @@ from argparse import RawTextHelpFormatter + from fa import utils DESCRIPTION = '''get the result appearing the most in the result-set diff --git a/fa/commands/offset.py b/fa/commands/offset.py index ef9e6df..8a4678d 100644 --- a/fa/commands/offset.py +++ b/fa/commands/offset.py @@ -1,4 +1,5 @@ from argparse import RawTextHelpFormatter + from fa import utils DESCRIPTION = '''advance the result-set by a given offset diff --git a/fa/commands/operand.py b/fa/commands/operand.py index 474252f..3fb3b5c 100644 --- a/fa/commands/operand.py +++ b/fa/commands/operand.py @@ -1,5 +1,6 @@ from argparse import RawTextHelpFormatter -from fa import utils, context + +from fa import context, utils try: import idc diff --git a/fa/commands/python_if.py b/fa/commands/python_if.py index e499acf..6850014 100644 --- a/fa/commands/python_if.py +++ b/fa/commands/python_if.py @@ -1,4 +1,5 @@ from argparse import RawTextHelpFormatter + from fa import utils DESCRIPTION = '''perform an 'if' statement to create conditional branches diff --git a/fa/commands/set_enum.py b/fa/commands/set_enum.py index e7b5d1a..bc55ff9 100644 --- a/fa/commands/set_enum.py +++ b/fa/commands/set_enum.py @@ -1,4 +1,4 @@ -from fa import utils, fa_types +from fa import fa_types, utils def get_parser(): diff --git a/fa/commands/set_struct_member.py b/fa/commands/set_struct_member.py index a09dfcc..b808a0a 100644 --- a/fa/commands/set_struct_member.py +++ b/fa/commands/set_struct_member.py @@ -1,4 +1,4 @@ -from fa import utils, fa_types +from fa import fa_types, utils def get_parser(): diff --git a/fa/commands/set_type.py b/fa/commands/set_type.py index e5a1867..ac29304 100644 --- a/fa/commands/set_type.py +++ b/fa/commands/set_type.py @@ -1,8 +1,8 @@ -from fa import utils, fa_types, context +from fa import context, fa_types, utils try: - import idc import ida_auto + import idc except ImportError: pass diff --git a/fa/commands/single.py b/fa/commands/single.py index 51bd2da..b1a921b 100644 --- a/fa/commands/single.py +++ b/fa/commands/single.py @@ -1,4 +1,5 @@ from argparse import RawTextHelpFormatter + from fa import utils DESCRIPTION = '''peek a single result from the result-set (zero-based) diff --git a/fa/commands/sort.py b/fa/commands/sort.py index 8906332..d837dbc 100644 --- a/fa/commands/sort.py +++ b/fa/commands/sort.py @@ -1,4 +1,5 @@ from argparse import RawTextHelpFormatter + from fa import utils DESCRIPTION = '''performs a sort on the current result-set diff --git a/fa/commands/stop_if_empty.py b/fa/commands/stop_if_empty.py index 9d18a00..4353373 100644 --- a/fa/commands/stop_if_empty.py +++ b/fa/commands/stop_if_empty.py @@ -1,4 +1,5 @@ from argparse import RawTextHelpFormatter + from fa import utils DESCRIPTION = '''exit if current resultset is empty diff --git a/fa/commands/store.py b/fa/commands/store.py index a3994f4..a2c3eae 100644 --- a/fa/commands/store.py +++ b/fa/commands/store.py @@ -1,4 +1,5 @@ from argparse import RawTextHelpFormatter + from fa import utils DESCRIPTION = '''save current result-set in a variable. diff --git a/fa/commands/symdiff.py b/fa/commands/symdiff.py index 02d9228..078fa82 100644 --- a/fa/commands/symdiff.py +++ b/fa/commands/symdiff.py @@ -1,4 +1,5 @@ from argparse import RawTextHelpFormatter + from fa import utils DESCRIPTION = '''symmetric difference between two or more variables diff --git a/fa/commands/unique.py b/fa/commands/unique.py index 91ddfef..6a8de94 100644 --- a/fa/commands/unique.py +++ b/fa/commands/unique.py @@ -1,4 +1,5 @@ from argparse import RawTextHelpFormatter + from fa import utils DESCRIPTION = '''make the resultset unique diff --git a/fa/commands/verify_aligned.py b/fa/commands/verify_aligned.py index e746053..6b56741 100644 --- a/fa/commands/verify_aligned.py +++ b/fa/commands/verify_aligned.py @@ -1,4 +1,5 @@ from argparse import RawTextHelpFormatter + from fa import utils DESCRIPTION = '''leave only results fitting required alignment diff --git a/fa/commands/verify_bytes.py b/fa/commands/verify_bytes.py index eec4a7e..3203897 100644 --- a/fa/commands/verify_bytes.py +++ b/fa/commands/verify_bytes.py @@ -1,5 +1,5 @@ -from argparse import RawTextHelpFormatter import binascii +from argparse import RawTextHelpFormatter from fa import utils diff --git a/fa/commands/verify_name.py b/fa/commands/verify_name.py index d7a46af..742fdf7 100644 --- a/fa/commands/verify_name.py +++ b/fa/commands/verify_name.py @@ -1,4 +1,4 @@ -from fa import utils, context +from fa import context, utils from fa.commands.locate import locate diff --git a/fa/commands/verify_operand.py b/fa/commands/verify_operand.py index 8a01842..30ecca9 100644 --- a/fa/commands/verify_operand.py +++ b/fa/commands/verify_operand.py @@ -1,5 +1,6 @@ from argparse import RawTextHelpFormatter -from fa import utils, context + +from fa import context, utils try: import idc diff --git a/fa/commands/verify_ref.py b/fa/commands/verify_ref.py index 5f1114f..8eecfe5 100644 --- a/fa/commands/verify_ref.py +++ b/fa/commands/verify_ref.py @@ -1,5 +1,5 @@ +from fa import context, utils from fa.commands.locate import locate -from fa import utils, context try: import idautils diff --git a/fa/commands/verify_segment.py b/fa/commands/verify_segment.py index c63c887..e711f0d 100644 --- a/fa/commands/verify_segment.py +++ b/fa/commands/verify_segment.py @@ -5,7 +5,7 @@ except ImportError: pass -from fa import utils, context +from fa import context, utils DESCRIPTION = '''reduce the result-set to those in the given segment name diff --git a/fa/commands/verify_single.py b/fa/commands/verify_single.py index cbe52a5..e104329 100644 --- a/fa/commands/verify_single.py +++ b/fa/commands/verify_single.py @@ -1,4 +1,5 @@ from argparse import RawTextHelpFormatter + from fa import utils DESCRIPTION = '''verifies the result-list contains a single value diff --git a/fa/commands/verify_str.py b/fa/commands/verify_str.py index 1090562..cbafcf2 100644 --- a/fa/commands/verify_str.py +++ b/fa/commands/verify_str.py @@ -1,5 +1,5 @@ -from argparse import RawTextHelpFormatter import binascii +from argparse import RawTextHelpFormatter from fa.commands import verify_bytes diff --git a/fa/commands/xref.py b/fa/commands/xref.py index bc00272..32a7fe3 100644 --- a/fa/commands/xref.py +++ b/fa/commands/xref.py @@ -1,4 +1,4 @@ -from fa import utils, context +from fa import context, utils try: import idautils diff --git a/fa/commands/xrefs_to.py b/fa/commands/xrefs_to.py index 60c4ac4..1b48673 100644 --- a/fa/commands/xrefs_to.py +++ b/fa/commands/xrefs_to.py @@ -1,9 +1,9 @@ -from fa import utils, context +from fa import context, utils from fa.commands import function_start try: - import idc import idautils + import idc except ImportError: pass diff --git a/fa/fa_types.py b/fa/fa_types.py index 8eed87f..df33b5c 100644 --- a/fa/fa_types.py +++ b/fa/fa_types.py @@ -1,20 +1,32 @@ from abc import abstractmethod from collections import namedtuple - try: - import idc - import idaapi import ida_auto import ida_bytes - import ida_enum - import ida_struct + import ida_typeinf + import idaapi + import idc IDA_MODULE = True except ImportError: pass +def del_struct_members(sid: int, offset1: int, offset2: int) -> None: + tif = ida_typeinf.tinfo_t() + if tif.get_type_by_tid(sid) and tif.is_udt(): + udm = ida_typeinf.udm_t() + udm.offset = offset1 * 8 + idx1 = tif.find_udm(udm, ida_typeinf.STRMEM_OFFSET) + udm = ida_typeinf.udm_t() + udm.offset = offset2 * 8 + idx2 = tif.find_udm(udm, ida_typeinf.STRMEM_OFFSET) + idx1 &= 0xffffffff + idx2 &= 0xffffffff + tif.del_udms(idx1, idx2) + + class FaType(object): def __init__(self, name): self._name = name @@ -23,7 +35,7 @@ def get_name(self): return self._name def exists(self): - return -1 != ida_struct.get_struc_id(self._name) + return -1 != idc.get_struc_id(self._name) @abstractmethod def update_idb(self): @@ -39,15 +51,15 @@ def add_value(self, name, value): self._values[value] = name def update_idb(self): - id = ida_enum.get_enum(self._name) + id = idc.get_enum(self._name) if idc.BADADDR == id: - id = ida_enum.add_enum(idc.BADADDR, self._name, ida_bytes.dec_flag()) + id = idc.add_enum(idc.BADADDR, self._name, ida_bytes.dec_flag()) keys = self._values.keys() sorted(keys) for k in keys: - ida_enum.add_enum_member(id, self._values[k], k) + idc.add_enum_member(id, self._values[k], k, 0xffffffff) class FaStruct(FaType): @@ -60,25 +72,20 @@ def __init__(self, name): def add_field(self, name, type_, offset=0xffffffff): self._fields.append(self.Field(name, type_, offset)) - def update_idb(self, delete_existing_members=True): - sid = ida_struct.get_struc_id(self._name) - sptr = ida_struct.get_struc(sid) + def update_idb(self, delete_existing_members: bool = True) -> None: + sid = idc.get_struc_id(self._name) if sid == idc.BADADDR: - sid = ida_struct.add_struc(idc.BADADDR, self._name, 0) - sptr = ida_struct.get_struc(sid) + sid = idc.add_struc(idc.BADADDR, self._name, 0) else: if delete_existing_members: - ida_struct.del_struc_members(sptr, 0, 0xffffffff) + del_struct_members(sid, 0, 0xffffffff) for f in self._fields: - ida_struct.add_struc_member(sptr, f.name, f.offset, - (idc.FF_BYTE | idc.FF_DATA) - & 0xFFFFFFFF, - None, 1) - member_name = "{}.{}".format(self._name, f.name) - idc.SetType(idaapi.get_member_by_fullname(member_name)[0].id, - f.type) + idc.add_struc_member(sid, f.name, f.offset, (idc.FF_BYTE | idc.FF_DATA) & 0xFFFFFFFF, 0xFFFFFFFF, 1) + member_name = f'{self._name}.{f.name}' + member_struct_id = idc.get_struc_id(member_name) + idc.SetType(member_struct_id, f.type) ida_auto.auto_wait() diff --git a/fa/fainterp.py b/fa/fainterp.py index 05da151..1e6a5d6 100644 --- a/fa/fainterp.py +++ b/fa/fainterp.py @@ -1,15 +1,14 @@ +import importlib.resources +import importlib.util +import os +import re +import shlex +import sys import time - -import pkg_resources -from tkinter import ttk, Tk -from configparser import ConfigParser - from abc import ABCMeta, abstractmethod from collections import OrderedDict -import shlex -import sys -import os -import re +from configparser import ConfigParser +from tkinter import Tk, ttk import hjson @@ -276,15 +275,9 @@ def get_module(name, filename): if not os.path.exists(filename): raise NotImplementedError("no such filename: {}".format(filename)) - if sys.version == '3': - # TODO: support python 3.0-3.4 - import importlib.util - spec = importlib.util.spec_from_file_location(name, filename) - module = importlib.util.module_from_spec(spec) - spec.loader.exec_module(module) - else: - import imp - module = imp.load_source(name, filename) + spec = importlib.util.spec_from_file_location(name, filename) + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) return module @@ -327,8 +320,7 @@ def get_alias(self): retval = {} alias_res_path = os.path.join('commands', 'alias') - alias_filename = pkg_resources.resource_filename('fa', alias_res_path) - with open(alias_filename) as f: + with importlib.resources.files('fa').joinpath(alias_res_path).open('rt') as f: for line in f.readlines(): line = line.strip() k, v = line.split('=') diff --git a/fa/ida_launcher.py b/fa/ida_launcher.py index 8beb854..473e9b0 100644 --- a/fa/ida_launcher.py +++ b/fa/ida_launcher.py @@ -4,8 +4,8 @@ import subprocess from collections import namedtuple -import IPython import click +import IPython import rpyc from termcolor import cprint diff --git a/fa/ida_plugin.py b/fa/ida_plugin.py index a9b3fa7..0de4578 100644 --- a/fa/ida_plugin.py +++ b/fa/ida_plugin.py @@ -1,34 +1,33 @@ -from collections import namedtuple -import pkg_resources +import binascii +import importlib.resources +import os +import re import subprocess +import sys import tempfile import traceback -import binascii -import sys -import re -import os +from collections import namedtuple import rpyc from rpyc import OneShotServer sys.path.append('.') # noqa: E402 -import hjson import click - -from ida_kernwin import Form -import ida_segment -import ida_kernwin -import ida_typeinf -import ida_struct -import ida_bytes -import idautils +import hjson import ida_auto +import ida_bytes +import ida_ida +import ida_kernwin import ida_pro +import ida_segment +import ida_typeinf import idaapi +import idautils import idc +from ida_kernwin import Form -from fa import fainterp, fa_types +from fa import fa_types, fainterp # Filename for the temporary created signature TEMP_SIG_FILENAME = os.path.join(tempfile.gettempdir(), 'fa_tmp_sig.sig') @@ -172,7 +171,7 @@ def verify_project(self): try: super(IdaLoader, self).verify_project() except IOError as e: - ida_kernwin.warning(e.message) + ida_kernwin.warning(str(e)) raise e def prompt_save_signature(self): @@ -372,8 +371,8 @@ def OnFormChange(self, fid): structs_buf): f.write( 'typedef {struct_type} {struct_name} {struct_name};\n' - .format(struct_type=struct_type, - struct_name=struct_name)) + .format(struct_type=struct_type, + struct_name=struct_name)) structs_buf = structs_buf.replace('__fastcall', '') f.write('\n') @@ -392,7 +391,7 @@ def set_input(self, input_): :param input_: doesn't matter :return: None """ - self.endianity = '>' if idaapi.get_inf_structure().is_be() else '<' + self.endianity = '>' if ida_ida.inf_is_be() else '<' self._input = input_ self.reload_segments() @@ -538,14 +537,8 @@ def update(self, ctx): act_icon = -1 if action.icon_filename: - icon_full_filename = \ - pkg_resources.resource_filename('fa', - os.path.join( - 'res', - 'icons', - action.icon_filename)) - with open(icon_full_filename, 'rb') as f: - icon_data = f.read() + icon_data = \ + importlib.resources.files('fa').joinpath(f'res/icons/{action.icon_filename}').read_bytes() act_icon = ida_kernwin.load_custom_icon(data=icon_data, format="png") act_name = action.name @@ -680,11 +673,11 @@ class FaService(rpyc.Service): ida_segment = ida_segment ida_kernwin = ida_kernwin ida_typeinf = ida_typeinf - ida_struct = ida_struct ida_bytes = ida_bytes idautils = idautils ida_auto = ida_auto ida_pro = ida_pro + ida_ida = ida_ida idaapi = idaapi idc = idc diff --git a/fa/utils.py b/fa/utils.py index e600a33..058170e 100644 --- a/fa/utils.py +++ b/fa/utils.py @@ -2,12 +2,14 @@ import inspect import os import warnings +from typing import Generator, Union IDA_MODULE = False try: + import ida_bytes + import idaapi import idc - import ida_struct IDA_MODULE = True except ImportError: @@ -41,18 +43,23 @@ def find_raw(needle, segments=None): address = segment_ea + offset + extra_offset yield address - extra_offset += offset+1 - data = data[offset+1:] + extra_offset += offset + 1 + data = data[offset + 1:] offset = index_of(needle, data) -def ida_find_all(payload): - ea = idc.find_binary(0, idc.SEARCH_DOWN | idc.SEARCH_REGEX, payload) - while ea != idc.BADADDR: - yield ea - ea = idc.find_binary(ea + 1, idc.SEARCH_DOWN | idc.SEARCH_REGEX, - payload) +def ida_find_all(payload: Union[bytes, bytearray, str]) -> Generator[int, None, None]: + if float(idaapi.get_kernel_version()) < 9: + ea = idc.find_binary(0, idc.SEARCH_DOWN | idc.SEARCH_REGEX, payload) + while ea != idc.BADADDR: + yield ea + ea = idc.find_binary(ea + 1, idc.SEARCH_DOWN | idc.SEARCH_REGEX, payload) + else: + ea = ida_bytes.find_bytes(payload, 0) + while ea != idc.BADADDR: + yield ea + ea = ida_bytes.find_bytes(payload, ea + 1) def read_memory(segments, ea, size): @@ -62,7 +69,7 @@ def read_memory(segments, ea, size): for segment_ea, data in segments.items(): if (ea <= segment_ea + len(data)) and (ea >= segment_ea): offset = ea - segment_ea - return data[offset:offset+size] + return data[offset:offset + size] def yield_unique(func): @@ -72,6 +79,7 @@ def wrapper(*args, **kwargs): if i not in results: yield i results.add(i) + return wrapper @@ -96,7 +104,7 @@ def add_struct_to_idb(name): def find_or_create_struct(name): - sid = ida_struct.get_struc_id(name) + sid = idc.get_struc_id(name) if sid == idc.BADADDR: sid = idc.add_struc(-1, name, 0) print("added struct \"{0}\", id: {1}".format(name, sid)) diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..30bcbb8 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,55 @@ +[project] +name = "pymobiledevice3" +description = "Automation tool for locating symbols & structs in binary (primarily IDA focused)" +readme = "README.md" +requires-python = ">=3.9" +license = { text = "GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007" } +keywords = ["reverse-engineering", "ida", "automation", "signatures", "symbols"] +authors = [ + { name = "doronz88", email = "doron88@gmail.com" } +] +maintainers = [ + { name = "doronz88", email = "doron88@gmail.com" } +] +classifiers = [ + "Development Status :: 5 - Production/Stable", + "License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3 :: Only", +] +dynamic = ["dependencies", "version"] + +[project.optional-dependencies] +test = ["pytest"] + +[project.urls] +"Homepage" = "https://github.com/doronz88/fa" +"Bug Reports" = "https://github.com/doronz88/fa/issues" + +[tool.setuptools] +package-data = { "fa" = ["res/icons/create_sig.png", + "res/icons/export.png", + "res/icons/find.png", + "res/icons/find_all.png", + "res/icons/save.png", + "res/icons/settings.png", + "res/icons/suitcase.png", + "commands/alias"] } + +[tool.setuptools.packages.find] +exclude = ["docs*", "tests*"] + +[tool.setuptools.dynamic] +dependencies = { file = ["requirements.txt"] } +version = { attr = "fa._version.__version__" } + +[tool.setuptools_scm] +version_file = "fa/_version.py" + +[build-system] +requires = ["setuptools>=43.0.0", "setuptools_scm>=8", "wheel"] +build-backend = "setuptools.build_meta" diff --git a/requirements_testing.txt b/requirements_testing.txt index eb95f01..fec1147 100644 --- a/requirements_testing.txt +++ b/requirements_testing.txt @@ -4,7 +4,6 @@ click hjson future configparser -idalink pytest simpleelf pyelftools diff --git a/scripts/git/pre-commit b/scripts/git/pre-commit index 753faee..3f091ef 100644 --- a/scripts/git/pre-commit +++ b/scripts/git/pre-commit @@ -1,9 +1,9 @@ #!/usr/bin/python3 import json -import re -from collections import OrderedDict import os +import re import sys +from collections import OrderedDict sys.path.append('.') # noqa: E402 diff --git a/setup.py b/setup.py deleted file mode 100644 index 792424c..0000000 --- a/setup.py +++ /dev/null @@ -1,40 +0,0 @@ -from pathlib import Path - -from setuptools import setup - -BASE_DIR = Path(__file__).parent.resolve(strict=True) - - -def parse_requirements(): - reqs = [] - with open(BASE_DIR / 'requirements.txt', 'r') as fd: - for line in fd.readlines(): - line = line.strip() - if line: - reqs.append(line) - return reqs - - -setup( - name='fa', - version='0.3.0', - description='FA Plugin', - author='DoronZ', - author_email='doron88@gmail.com', - url='https://github.com/doronz88/fa', - packages=['fa', 'fa.commands'], - package_dir={'fa': 'fa'}, - package_data={'': ['*.png', '*'], }, - include_package_data=True, - data_files=[(r'fa/res/icons', [r'fa/res/icons/create_sig.png', - r'fa/res/icons/export.png', - r'fa/res/icons/find.png', - r'fa/res/icons/find_all.png', - r'fa/res/icons/save.png', - r'fa/res/icons/settings.png', - r'fa/res/icons/suitcase.png']), - (r'fa/commands', ['fa/commands/alias']), - ], - install_requires=parse_requirements(), - python_requires='>=2.7', -) diff --git a/tests/conftest.py b/tests/conftest.py index cb8f86c..ea83a4c 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,9 +1,9 @@ import tempfile -from keystone import KS_MODE_BIG_ENDIAN, KS_MODE_ARM, KS_ARCH_ARM, Ks -from simpleelf.elf_builder import ElfBuilder -from simpleelf import elf_consts import pytest +from keystone import KS_ARCH_ARM, KS_MODE_ARM, KS_MODE_BIG_ENDIAN, Ks +from simpleelf import elf_consts +from simpleelf.elf_builder import ElfBuilder def pytest_addoption(parser): @@ -51,7 +51,7 @@ def sample_elf(request): e.add_segment(text_address, text_buffer, elf_consts.PF_R | elf_consts.PF_W | elf_consts.PF_X) - e.add_code_section('.text', text_address, len(text_buffer)) + e.add_code_section(text_address, len(text_buffer), name='.text') f.write(e.build()) yield f diff --git a/tests/test_commands/test_elf.py b/tests/test_commands/test_elf.py index b70d121..02b5cf3 100644 --- a/tests/test_commands/test_elf.py +++ b/tests/test_commands/test_elf.py @@ -1,4 +1,5 @@ import pytest + import elf_loader diff --git a/tests/test_commands/test_idalink.py b/tests/test_commands/test_idalink.py deleted file mode 100644 index 29ff5b2..0000000 --- a/tests/test_commands/test_idalink.py +++ /dev/null @@ -1,121 +0,0 @@ -from idalink import IDALink -import pytest - -import sys -import importlib - - -class ImportInterceptor(object): - def __init__(self, package_permissions): - self.package_permissions = package_permissions - - def find_module(self, fullname, path=None): - last = fullname.split('.')[-1] - if last in dir(ida_namespace): - return self - if fullname in self.package_permissions: - if self.package_permissions[fullname]: - return self - else: - raise ImportError("Package import was not allowed") - - def load_module(self, fullname): - last = fullname.split('.')[-1] - if last in dir(ida_namespace): - return getattr(ida_namespace, last) - sys.meta_path = [x for x in sys.meta_path[1:] if x is not self] - module = importlib.import_module(fullname) - sys.meta_path = [self] + sys.meta_path - return module - - -sys.meta_path.append(ImportInterceptor({'textwrap': True, 'Pathlib': False})) - -ida_namespace = None - - -def test_ida_symbols(ida, sample_elf): - sample_elf.close() - - if sys.version[0] == '3': - pytest.skip('not supported for python3') - - global ida_namespace - if None in (ida, ): - pytest.skip("--ida param must be passed for this test") - - with IDALink(ida, sample_elf.name) as s: - ida_namespace = s - - from fa import utils, context - - # hack to fix imports - # flake8: noqa - reload(utils) - reload(context) - - s.ida_bytes.del_items(0x1240) - s.ida_funcs.add_func(0x1248) - s.ida_auto.auto_wait() - - from fa import ida_plugin - fa_instance = ida_plugin.IdaLoader() - fa_instance.set_input('ida') - fa_instance.set_project('test-project-ida') - symbols = fa_instance.symbols() - - for k, v in symbols.items(): - if isinstance(v, list) or isinstance(v, set): - assert len(v) == 1 - symbols[k] = v.pop() - - consts = fa_instance.get_consts() - - # from test-basic - assert symbols['test_add'] == 80 - assert symbols['test_pos_offset'] == 81 - assert symbols['test_neg_offset'] == 80 - assert symbols['test_add_offset_range'] == 100 - assert symbols['test_load'] == 80 - assert symbols['test_align'] == 84 - assert symbols['test_most_common'] == 2 - assert symbols['test_sort'] == 3 - assert symbols['test_verify_single_success'] == 1 - assert 'test_verify_single_fail' not in symbols - assert symbols['test_run'] == 67 - assert symbols['test_alias'] == 0x123c - assert symbols['test_keystone_find_opcodes'] == 0x123c - assert symbols['test_keystone_verify_opcodes'] == 0x123c - assert symbols['test_find_bytes'] == 0x1240 - assert symbols['test_find_str'] == 0x1242 - assert symbols['test_find'] == 76 - assert symbols['test_intersect_ab'] == 2 - assert 'test_intersect_abc' not in symbols - - # test for branches - assert 'test_is_single_false1' in symbols - assert 'test_is_single_true1' not in symbols - - assert 'test_is_single_false2' not in symbols - assert 'test_is_single_true2' in symbols - - assert 'test_else3' not in symbols - assert 'test_if3' in symbols - - # from test-ida-context - assert symbols['test_find_bytes_ida'] == 0x1240 - assert symbols['test_xref'] == 0x125c - assert symbols['test_function_start'] == 0x1248 - assert symbols['test_function_end'] == 0x125c - assert symbols['test_function_lines'] == 0x1248 - assert symbols['test_verify_operand'] == 0x1250 - assert symbols['test_verify_ref_no_name'] == 0x1250 - assert symbols['test_verify_goto_ref'] == 0x125c - assert symbols['test_verify_ref_name'] == 0x1250 - assert symbols['test_locate'] == symbols['test_function_lines'] - assert symbols['test_find_immediate'] == 0x1240 - assert symbols['test_find_immediate'] == 0x1240 - assert symbols['test_operand'] == 1 - assert symbols['test_argument'] == 0x00001250 - assert 'test_branch1_false' not in symbols - assert 'test_branch1_true' in symbols