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

Add an ability to install OFRAK from source #314

Open
wants to merge 55 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
c80ce01
Allow local installation from source tree without docker
ANogin May 23, 2023
8e2fd54
Fix unused import
ANogin May 23, 2023
2bbd207
Handle missing binja dependency
ANogin May 23, 2023
f8d6fa5
black formatting
ANogin May 23, 2023
f037711
Use OFRAK_INSTALL_PYTHON to run install.py
ANogin May 23, 2023
c71df7f
Ignore more files in .gitignore
ANogin May 23, 2023
b48474c
Pre-install pyyaml
ANogin May 23, 2023
4b1c79a
Fix some of the typos Jacob found
ANogin May 23, 2023
93b073a
Merge branch 'feature/install_from_source' of github.com:ANogin/ofrak…
ANogin May 23, 2023
bf465a8
Fix a typo noticed by Jacob
ANogin May 23, 2023
ce9cc7e
Use logging instead of raw priting, where appropriate
ANogin May 23, 2023
93afc76
Correct the formatting
ANogin May 23, 2023
08a828e
Merge remote-tracking branch 'origin/master' into feature/install_fro…
ANogin Jul 27, 2023
1ec4b3f
Use "npm install" to install rollup
ANogin Jul 27, 2023
3d50cf3
Make the black version requirement consistent
ANogin Jul 27, 2023
17ed5e8
Add an option to run tests after install
ANogin Jul 27, 2023
0d7ff95
Updated the instructions for installing from source
ANogin Jul 27, 2023
9c9c856
Add more detailed instructions to documentation
ANogin Jul 27, 2023
68c9bd6
Merge remote-tracking branch 'origin/master' into feature/install_fro…
ANogin Feb 9, 2024
597db82
Use $(PYTHON) to run mypy
ANogin Feb 10, 2024
ba347a9
Use correct version of python executable
ANogin Feb 10, 2024
ed59084
Add apt/brew package info for binwalk
ANogin Feb 10, 2024
df3b3ac
Ignore files generated by "make test"
ANogin Feb 10, 2024
cc02334
Add apt/brew package info for mksquashfs/unsquashfs
ANogin Feb 10, 2024
aa89083
Quiet mypy errors in `ofrak_binary_ninja`
ANogin Feb 10, 2024
0285823
Fix another `ofrak_binary_ninja` mypy issue
ANogin Feb 10, 2024
60011dc
Fix a weird `ofrak_binary_ninja` coverage issue
ANogin Feb 10, 2024
d5f4b87
Install `python-lzo`, `bincopy`, `binwalk` in `make install`/`make de…
ANogin Feb 10, 2024
2f85e98
`jupyter` is required for `ofrak_tutorial_test/ofrak_tutorial_test.py…
ANogin Feb 11, 2024
ad4561d
Revert "`jupyter` is required for `ofrak_tutorial_test/ofrak_tutorial…
ANogin Feb 11, 2024
4e0bb0f
Add a script to test all modules across all python versions
ANogin Feb 11, 2024
5d22975
Correct incomplete `ofrak_type` test requirements
ANogin Feb 11, 2024
fbb75a7
Force `legacy-editable` setuptools flag
ANogin Feb 11, 2024
38dcbd5
Ignore a couple of files generated by `make test`
ANogin Feb 12, 2024
76d5942
Use python3.8 in docker images (#416)
ANogin Feb 12, 2024
25fcc41
Dropping the .altinstr_replacement section from the toolchain (#414)
rbs-alexr Feb 13, 2024
d4ff0fa
Set the fallback font to monospace (#422)
rbs-jacob Feb 14, 2024
2880879
Display strings with numbers primarily as strings (#423)
rbs-jacob Feb 16, 2024
5505696
Add typing support to ofrak_ghidra package (#421)
paulnoalhyt Feb 17, 2024
0f513fe
Fix occasional spurious test failures. (#424)
ANogin Feb 18, 2024
b805ac1
Update to angr==9.2.77 (#417)
ANogin Feb 22, 2024
4f80d75
Move away from "$OFRAK_DIR" argument to $PACKAGE_PATH for Docker buil…
whyitfor Feb 23, 2024
af5bf50
Refactor the GUI hex view and pane components (#427)
dannyp303 Feb 28, 2024
db3abb1
Revert using SUBALIGN(0) for .bss sections (#431)
rbs-afflitto Feb 28, 2024
59c4387
move resourceTreeNode and dataLength to stores (#434)
dannyp303 Feb 29, 2024
57e4e8a
Add identify recursively to the GUI (#435)
rbs-jacob Feb 29, 2024
faea3d8
Feature/angr decompilation view (#436)
dannyp303 Mar 4, 2024
da36f79
Bump aiohttp to ~=3.9.3 (#440)
whyitfor Mar 10, 2024
cd89508
Update orjston to ~=3.9.15 to address security issue. (#442)
whyitfor Mar 12, 2024
a92cd61
add lief add/remove section modifier (#443)
dannyp303 Mar 12, 2024
bff6daa
change strings unpacker target to none (#438)
dannyp303 Mar 14, 2024
240686f
Fix Carve/Modify bug, handle an error in the server, vscode doesnt li…
dannyp303 Mar 15, 2024
091ebac
Support `make -k test` in docker root (#446)
ANogin Mar 21, 2024
16552c7
reduce chunking min limit from 64mb to 1mb (#449)
dannyp303 Apr 3, 2024
c0eb500
Explicitly set the downloaded package file name (#451)
rbs-afflitto Apr 4, 2024
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
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ docs/user-guide/gui/assets/* filter=lfs diff=lfs merge=lfs -text
ofrak_core/test_ofrak/components/assets/elf/* filter=lfs diff=lfs merge=lfs -text
ofrak_core/test_ofrak/components/assets/elf/edge-cases/* filter=lfs diff=lfs merge=lfs -text
frontend/public/themes/**/* filter=lfs diff=lfs merge=lfs -text
disassemblers/ofrak_angr/ofrak_angr_test/assets/* filter=lfs diff=lfs merge=lfs -text
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ venv/
.idea/
test/
.DS_Store
.coverage
*.iml
*.egg-info/
QUESTIONS
Expand All @@ -17,4 +18,10 @@ ofrak_core/ofrak/core/entropy/entropy.so.1
frontend/public/build
ofrak_core/ofrak/core/entropy/entropy_c.cpython*
ofrak_core/ofrak/gui/public
ofrak_core/replaced_hello.out
ofrak_core/replaced_simple_arm_gcc.o.elf
ofrak_core/build
ofrak_io/build/
ofrak_patch_maker/build/
ofrak_type/build/
disassemblers/ofrak_ghidra/ofrak_ghidra/config/ofrak_ghidra.conf.yml
19 changes: 19 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,22 @@ tutorial-image:

tutorial-run:
make -C ofrak_tutorial run

OFRAK_INSTALL_PYTHON=python3

.PHONY: install_tutorial install_core install_develop install_test_all
install_tutorial:
$(OFRAK_INSTALL_PYTHON) -m pip install pyyaml
$(OFRAK_INSTALL_PYTHON) install.py --config ofrak-tutorial.yml --target install

install_core:
$(OFRAK_INSTALL_PYTHON) -m pip install pyyaml
$(OFRAK_INSTALL_PYTHON) install.py --config ofrak-core-dev.yml --target install

install_develop:
$(OFRAK_INSTALL_PYTHON) -m pip install pyyaml
$(OFRAK_INSTALL_PYTHON) install.py --config ofrak-dev.yml --target develop

install_test_all:
$(OFRAK_INSTALL_PYTHON) -m pip install pyyaml
$(OFRAK_INSTALL_PYTHON) install.py --config ofrak-all.yml --target develop --test
23 changes: 17 additions & 6 deletions build_image.py
Original file line number Diff line number Diff line change
Expand Up @@ -192,10 +192,12 @@ def create_dockerfile_base(config: OfrakImageConfig) -> str:
continue
with open(dockerstage_path) as file_handle:
dockerstub = file_handle.read()
# Cannot use ENV here because of multi-stage build FROM, so replace direclty in Docerkstage contents
dockerstub = dockerstub.replace("$PACKAGE_DIR", package_path)
dockerfile_base_parts += [f"### {dockerstage_path}", dockerstub]

dockerfile_base_parts += [
"FROM python:3.7-bullseye@sha256:338ead05c1a0aa8bd8fcba8e4dbbe2afd0283b4732fd30cf9b3bfcfcbc4affab",
"FROM python:3.8-bullseye@sha256:e1cd369204123e89646f8c001db830eddfe3e381bd5c837df00141be3bd754cb",
"",
]

Expand All @@ -207,7 +209,11 @@ def create_dockerfile_base(config: OfrakImageConfig) -> str:
dockerstub_path = os.path.join(package_path, "Dockerstub")
with open(dockerstub_path) as file_handle:
dockerstub = file_handle.read()
dockerfile_base_parts += [f"### {dockerstub_path}", dockerstub]
dockerfile_base_parts += [
f"### {dockerstub_path}",
f"ENV PACKAGE_PATH={package_path}",
dockerstub,
]
# Collect python dependencies
python_reqs = []
for suff in requirement_suffixes:
Expand Down Expand Up @@ -247,19 +253,24 @@ def create_dockerfile_finish(config: OfrakImageConfig) -> str:
[
"$INSTALL_TARGET:",
"\\n\\\n".join(
[f"\tmake -C {package_name} $INSTALL_TARGET" for package_name in package_names]
[f"\t\\$(MAKE) -C {package_name} $INSTALL_TARGET" for package_name in package_names]
),
"\\n",
]
)
dockerfile_finish_parts.append(f'RUN printf "{develop_makefile}" >> Makefile\n')
dockerfile_finish_parts.append("RUN make $INSTALL_TARGET\n\n")
test_names = " ".join([f"test_{package_name}" for package_name in package_names])
finish_makefile = "\\n\\\n".join(
[
"test:",
"\\n\\\n".join([f"\tmake -C {package_name} test" for package_name in package_names]),
"\\n",
".PHONY: test " + test_names,
"test: " + test_names,
]
+ [
f"test_{package_name}:\\n\\\n\t\\$(MAKE) -C {package_name} test"
for package_name in package_names
]
+ ["\\n"]
)
dockerfile_finish_parts.append(f'RUN printf "{finish_makefile}" >> Makefile\n')
if config.entrypoint is not None:
Expand Down
3 changes: 3 additions & 0 deletions disassemblers/ofrak_angr/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)

## [Unreleased](https://github.com/redballoonsecurity/ofrak/tree/master)

### Changed
- Update to latest angr==9.2.77, which also necessitates Python >= 3.8.

### Fixed
- Add `importlib-resources` dependency as workaround for z3-solver dependency issue. ([#401](https://github.com/redballoonsecurity/ofrak/pull/401))

Expand Down
2 changes: 1 addition & 1 deletion disassemblers/ofrak_angr/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ develop:

.PHONY: inspect
inspect:
mypy
$(PYTHON) -m mypy

.PHONY: test
test: inspect
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
from dataclasses import dataclass
import angr
from angr.analyses.decompiler import Decompiler
from ofrak.component.analyzer import Analyzer
from ofrak.component.identifier import Identifier
from ofrak.resource_view import ResourceView

from ofrak.resource import Resource
from ofrak.core.complex_block import ComplexBlock
from ofrak.service.resource_service_i import ResourceFilter
from ofrak_angr.model import AngrAnalysis, AngrAnalysisResource


@dataclass
class AngrDecompilationAnalysis(ResourceView):
decompilation: str


class AngrDecompilationAnalysisIdentifier(Identifier):
id = b"AngrDecompilationAnalysisIdentifier"
targets = (ComplexBlock,)

async def identify(self, resource: Resource, config=None):
resource.add_tag(AngrDecompilationAnalysis)


class AngrDecompilatonAnalyzer(Analyzer[None, AngrDecompilationAnalysis]):
id = b"AngrDecompilationAnalyzer"
targets = (ComplexBlock,)
outputs = (AngrDecompilationAnalysis,)

async def analyze(self, resource: Resource, config: None) -> AngrDecompilationAnalysis:
# Run / fetch angr analyzer
try:
root_resource = await resource.get_only_ancestor(
ResourceFilter(tags=[AngrAnalysisResource], include_self=True)
)
complex_block = await resource.view_as(ComplexBlock)
angr_analysis = await root_resource.analyze(AngrAnalysis)

cfg = angr_analysis.project.analyses[angr.analyses.CFGFast].prep()(
data_references=True, normalize=True
)

function_s = [
func
for addr, func in angr_analysis.project.kb.functions.items()
if func.addr == complex_block.virtual_address
]
if len(function_s) == 0:
# Check for thumb
function_s = [
func
for addr, func in angr_analysis.project.kb.functions.items()
if func.addr == complex_block.virtual_address + 1
]
if len(function_s) != 1:
raise ValueError(
f"Could not find angr function for function at address {complex_block.virtual_address}"
)
function = function_s[0]
dec: Decompiler = angr_analysis.project.analyses[angr.analyses.Decompiler].prep()(
function, cfg=cfg.model, options=None
)
if dec.codegen is not None:
decomp = dec.codegen.text
else:
decomp = "No Decompilation available"
return AngrDecompilationAnalysis(decomp)
except Exception as e:
return AngrDecompilationAnalysis(
f"The decompilation for this Complex Block has failed with the error {e}"
)
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@ def _angr_get_dword_blocks(
if xref is None or not any(xref in bb_range for bb_range in valid_data_xref_ranges):
continue

LOGGER.debug(f"Creating DataWord for {cb_data_xref.content} @ {cb_data_xref_addr:#x}")
LOGGER.debug(f"Creating DataWord for {cb_data_xref.content!r} @ {cb_data_xref_addr:#x}")

format_string = endian_flag + dword_size_map[word_size]

Expand Down
3 changes: 3 additions & 0 deletions disassemblers/ofrak_angr/ofrak_angr_test/assets/hello.x64.elf
Git LFS file not shown
36 changes: 36 additions & 0 deletions disassemblers/ofrak_angr/ofrak_angr_test/test_decompilation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
from typing import List
import os
from ofrak_angr.components.angr_decompilation_analyzer import AngrDecompilationAnalysis
from ofrak.ofrak_context import OFRAKContext
from ofrak.core.complex_block import ComplexBlock
from ofrak.service.resource_service_i import ResourceFilter


async def test_angr_decompilation(ofrak_context: OFRAKContext):
root_resource = await ofrak_context.create_root_resource_from_file(
os.path.join(os.path.dirname(__file__), "assets/hello.x64.elf")
)
await root_resource.unpack_recursively(
do_not_unpack=[
ComplexBlock,
]
)
complex_blocks: List[ComplexBlock] = await root_resource.get_descendants_as_view(
ComplexBlock,
r_filter=ResourceFilter(
tags=[
ComplexBlock,
]
),
)
decomps = []
for complex_block in complex_blocks:
await complex_block.resource.identify()
angr_resource: AngrDecompilationAnalysis = await complex_block.resource.view_as(
AngrDecompilationAnalysis
)
decomps.append(angr_resource.decompilation)
assert len(decomps) == 11
assert "" not in decomps
assert "main" in " ".join(decomps)
assert "print" in " ".join(decomps)
19 changes: 18 additions & 1 deletion disassemblers/ofrak_angr/ofrak_angr_test/test_unpackers.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,14 +54,31 @@ async def expected_results(self, unpack_verify_test_case: ComplexBlockUnpackerTe
0x110,
0x110,
0x130,
keep_same_is_exit_point=True,
keep_same_is_exit_point=False,
)

return self._fixup_test_case_for_pie(
unpack_verify_test_case.expected_results,
pie_base_vaddr=0x400000,
)

elif unpack_verify_test_case.binary_md5_digest == "c79d1bea0398d7a9d0faa1ba68786f5e":
# Unlike angr 9.2.6, angr 9.2.77 and 9.2.91 miss this DataWord now
# = the ref to it does not appear in the list of xrefs

missing_data_words = {0x8030, 0x8060}

fixed_up_results = {
vaddr: [
block
for block in original_expected_blocks
if block.virtual_address not in missing_data_words
]
for vaddr, original_expected_blocks in unpack_verify_test_case.expected_results.items()
}

return fixed_up_results

return unpack_verify_test_case.expected_results

def _split_bb(
Expand Down
2 changes: 1 addition & 1 deletion disassemblers/ofrak_angr/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
angr==9.2.6
angr==9.2.77
importlib-resources # A workaround for https://github.com/redballoonsecurity/ofrak/issues/398
2 changes: 1 addition & 1 deletion disassemblers/ofrak_angr/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ def read_requirements(requirements_path):
"Topic :: Security",
"Typing :: Typed",
],
python_requires=">=3.7",
python_requires=">=3.8",
license="Proprietary",
license_files=["LICENSE"],
cmdclass={"egg_info": egg_info_ex},
Expand Down
5 changes: 2 additions & 3 deletions disassemblers/ofrak_binary_ninja/Dockerstub
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
RUN apt-get update && apt-get install -y libdbus-1-3
ARG OFRAK_DIR=.
COPY $OFRAK_DIR/disassemblers/ofrak_binary_ninja/install_binary_ninja_headless_linux.sh /tmp/
COPY $OFRAK_DIR/disassemblers/ofrak_binary_ninja/pin_version.py /tmp/
COPY $PACKAGE_PATH/install_binary_ninja_headless_linux.sh /tmp/
COPY $PACKAGE_PATH/pin_version.py /tmp/
RUN --mount=type=secret,id=serial --mount=type=secret,id=license.dat,dst=/root/.binaryninja/license.dat /tmp/install_binary_ninja_headless_linux.sh && \
python3 /tmp/pin_version.py "3.2.3814 Headless" && \
rm /tmp/install_binary_ninja_headless_linux.sh && \
Expand Down
2 changes: 1 addition & 1 deletion disassemblers/ofrak_binary_ninja/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ develop:

.PHONY: inspect
inspect:
mypy
$(PYTHON) -m mypy

.PHONY: test
test: inspect
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@ else
echo "Error: BinaryNinja license serial number not found." >&2
exit 1
fi
PACKAGE_NAME="binaryninja-headless.zip"
INSTALL_DIR=/opt/rbs
mkdir -p $INSTALL_DIR
cd $INSTALL_DIR
curl -O https://raw.githubusercontent.com/Vector35/binaryninja-api/dev/scripts/download_headless.py
python3 -m pip --no-input install requests
python3 download_headless.py --serial $SERIAL
unzip BinaryNinja-headless.zip
rm download_headless.py BinaryNinja-headless.zip
python3 download_headless.py --serial $SERIAL --output "$PACKAGE_NAME"
unzip "$PACKAGE_NAME"
rm download_headless.py "$PACKAGE_NAME"
python3 binaryninja/scripts/install_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,17 @@
import tempfile
from dataclasses import dataclass
from typing import Optional, List
from ofrak.component.abstract import ComponentMissingDependencyError

from binaryninja import open_view, BinaryViewType
try:
from binaryninja import open_view, BinaryViewType

BINJA_INSTALLED = True
except ImportError:
BINJA_INSTALLED = False

from ofrak.component.analyzer import Analyzer
from ofrak.model.component_model import ComponentConfig
from ofrak.model.component_model import ComponentConfig, ComponentExternalTool
from ofrak.model.resource_model import ResourceAttributeDependency
from ofrak_binary_ninja.components.identifiers import BinaryNinjaAnalysisResource
from ofrak_binary_ninja.model import BinaryNinjaAnalysis
Expand All @@ -15,6 +21,21 @@
LOGGER = logging.getLogger(__file__)


class _BinjaExternalTool(ComponentExternalTool):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Haven't yet had a chance to test, but I wonder if making Binary Ninja an "external tool" creates the possibility of a confusing situation for a user: they explicitly set the Binary Ninja back end by either discovering it or setting a command-line argument, they set exclude_components_missing_dependencies=True (also settable by a command-line argument), and there is an issue with their Binary Ninja installation. In this case, when they go to analyze their file, it won't unpack code regions with Binary Ninja, but also won't give the user any feedback as to why.

This may not be an issue, whether because this situation is sufficiently unlikely, because we don't deem this to be a problem, or because this issue doesn't actually manifest this way, but it's worth considering this situation.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One other concern I have about making Binary Ninja an external tool like this is that it's not consistent with the other analysis back ends. We only show information about when Binary Ninja is missing, but don't show any of the respective information about which components won't be loaded when we don't have Ghidra installed, , for example.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that this is here because the binja backend is the one that actually prevents OFRAK from working at all if binja is not installed. Other backends do not have the issue.

def __init__(self):
super().__init__(
"binary_ninja",
"https://ofrak.com/docs/user-guide/disassembler-backends/binary_ninja.html",
install_check_arg="",
)

async def is_tool_installed(self) -> bool:
return BINJA_INSTALLED


BINJA_TOOL = _BinjaExternalTool()


@dataclass
class BinaryNinjaAnalyzerConfig(ComponentConfig):
bndb_file: str # Path to BinaryNinja DB pre-analyzed file
Expand All @@ -24,10 +45,13 @@ class BinaryNinjaAnalyzer(Analyzer[Optional[BinaryNinjaAnalyzerConfig], BinaryNi
id = b"BinaryNinjaAnalyzer"
targets = (BinaryNinjaAnalysisResource,)
outputs = (BinaryNinjaAnalysis,)
external_dependencies = (BINJA_TOOL,)

async def analyze(
self, resource: Resource, config: Optional[BinaryNinjaAnalyzerConfig] = None
) -> BinaryNinjaAnalysis:
if not BINJA_INSTALLED:
raise ComponentMissingDependencyError(self, BINJA_TOOL)
if not config:
resource_data = await resource.get_data()
temp_file = tempfile.NamedTemporaryFile()
Expand All @@ -36,9 +60,9 @@ async def analyze(
bv = open_view(temp_file.name)
return BinaryNinjaAnalysis(bv)
else:
bv = BinaryViewType.get_view_of_file(config.bndb_file)
assert bv is not None
return BinaryNinjaAnalysis(bv)
opt_bv = BinaryViewType.get_view_of_file(config.bndb_file)
assert opt_bv is not None
return BinaryNinjaAnalysis(opt_bv)

def _create_dependencies(
self,
Expand Down
Loading