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

Support loading CaRT files. #1267

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
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
110 changes: 82 additions & 28 deletions angrmanagement/data/jobs/loading.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from __future__ import annotations

import logging
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, Any

import angr
import archinfo
Expand All @@ -11,6 +11,7 @@

from angrmanagement.logic.threads import gui_thread_schedule
from angrmanagement.ui.dialogs import LoadBinary
from angrmanagement.ui.dialogs.set_encryption_key import SetEncryptionKeyDialog

from .job import Job

Expand Down Expand Up @@ -44,7 +45,7 @@ def _run(self, inst) -> None:
apb = archr.arsenal.angrProjectBow(t, dsb)
partial_ld = apb.fire(return_loader=True, perform_relocations=False, load_debug_info=False)
self._progress_callback(50)
load_options = gui_thread_schedule(LoadBinary.run, (partial_ld,))
load_options, _ = gui_thread_schedule(LoadBinary.run, (partial_ld,))
if load_options is None:
return

Expand All @@ -69,20 +70,94 @@ def __init__(self, fname, load_options=None, on_finish=None) -> None:
def _run(self, inst) -> None:
self._progress_callback(5)

retry = True
partial_ld = None
main_opts = {"ignore_missing_arch": True}
while partial_ld is None and retry:
retry, partial_ld, main_opts = self._load_binary_as_partial_loader(main_opts)
if main_opts:
if "ignore_missing_arch" in main_opts:
del main_opts["ignore_missing_arch"]

self._progress_callback(50)
new_load_options, simos = gui_thread_schedule(
LoadBinary.run,
(partial_ld,),
kwargs={
"suggested_backend": partial_ld.main_object.__class__,
"suggested_os_name": partial_ld.main_object.os,
"suggested_main_opts": main_opts,
"suggested_original_backend": (
None if partial_ld.original_main_object is None else partial_ld.original_main_object.__class__
),
"suggested_main_filename": (
None if partial_ld.original_main_object is None else partial_ld.original_main_object.unpacked_name
),
},
)
if new_load_options is None:
return

engine = None
if hasattr(new_load_options["arch"], "pcode_arch"):
engine = angr.engines.UberEnginePcode

self.load_options.update(new_load_options)

proj = angr.Project(self.fname, load_options=self.load_options, engine=engine, simos=simos)
self._progress_callback(95)

def callback() -> None:
inst._reset_containers()
inst.project.am_obj = proj
inst.project.am_event()

gui_thread_schedule(callback, ())

def _load_binary_as_partial_loader(self, main_opts: dict[str, Any]) -> tuple[bool, Any, dict[str, Any]]:
load_as_blob = False
load_with_libraries = True

partial_ld = None

def _load_user_passphrase(backend_cls) -> bytes | None:
dialog = SetEncryptionKeyDialog(
prompt_msg=f"The encryption key does not work or does not exist for "
f"the CLE backend {backend_cls.__name__}."
)
dialog.exec_()
return dialog.result

try:
# Try automatic loading
partial_ld = cle.Loader(
self.fname, perform_relocations=False, load_debug_info=False, main_opts={"ignore_missing_arch": True}
self.fname,
perform_relocations=False,
load_debug_info=False,
main_opts=main_opts,
)
except archinfo.arch.ArchNotFound:
_l.warning("Could not identify binary architecture.")
partial_ld = None
load_as_blob = True
except cle.CLECompatibilityError:
except cle.CLEInvalidEncryptionError as ex:
# it needs a password, but the default password does not work
if ex.backend is not None and ex.enckey_argname is not None:
# load a new password
enc_key: bytes | None = gui_thread_schedule(_load_user_passphrase, (ex.backend,))
if enc_key:
# Update main opts
main_opts[ex.enckey_argname] = enc_key
return True, None, main_opts

_l.warning(
"Failed to load binary with user-specified encryption key or the encryption key is missing. "
"Attempted to use backend %s (and failed). "
"Loading it as a blob instead.",
ex.backend,
)
# go load it as a blob
load_as_blob = True
except (cle.CLEInvalidFileFormatError, cle.CLECompatibilityError):
# Continue loading as blob
load_as_blob = True
except cle.CLEError:
Expand Down Expand Up @@ -110,30 +185,9 @@ def _run(self, inst) -> None:
except cle.CLECompatibilityError:
# Failed to load executable, even as blob!
gui_thread_schedule(LoadBinary.binary_loading_failed, (self.fname,))
return

self._progress_callback(50)
new_load_options, simos = gui_thread_schedule(
LoadBinary.run, (partial_ld, partial_ld.main_object.__class__, partial_ld.main_object.os)
)
if new_load_options is None:
return

engine = None
if hasattr(new_load_options["arch"], "pcode_arch"):
engine = angr.engines.UberEnginePcode

self.load_options.update(new_load_options)

proj = angr.Project(self.fname, load_options=self.load_options, engine=engine, simos=simos)
self._progress_callback(95)
return False, None, main_opts

def callback() -> None:
inst._reset_containers()
inst.project.am_obj = proj
inst.project.am_event()

gui_thread_schedule(callback, ())
return False, partial_ld, main_opts


class LoadAngrDBJob(Job):
Expand Down
6 changes: 2 additions & 4 deletions angrmanagement/logic/threads.py
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ def is_gui_thread() -> bool:


def gui_thread_schedule(
callable: Callable[..., T], args: tuple[Any] = None, timeout: int = None, kwargs: dict[str, Any] | None = None
callable: Callable[..., T], args: tuple = None, timeout: int = None, kwargs: dict[str, Any] | None = None
) -> T:
"""
Schedules the given callable to be executed on the GUI thread. If the current thread is the GUI thread, the callable
Expand Down Expand Up @@ -241,9 +241,7 @@ def gui_thread_schedule(
return event.result


def gui_thread_schedule_async(
callable: Callable[..., T], args: tuple[Any] = None, kwargs: dict[str, Any] = None
) -> None:
def gui_thread_schedule_async(callable: Callable[..., T], args: tuple = None, kwargs: dict[str, Any] = None) -> None:
"""
Schedules the given callable to be executed on the GUI thread. If the current thread is the GUI thread, the callable
is executed immediately. Otherwise, the callable is executed as an event on the GUI thread.
Expand Down
89 changes: 77 additions & 12 deletions angrmanagement/ui/dialogs/load_binary.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,17 +67,25 @@ def __init__(
partial_ld,
suggested_backend: cle.Backend | None = None,
suggested_os_name: str | None = None,
suggested_main_opts: dict | None = None,
suggested_original_backend: cle.Backend | None = None,
suggested_main_filename: str | None = None,
parent=None,
) -> None:
super().__init__(parent)

# initialization
self.file_path = partial_ld.main_object.binary
self.file_path: str | None = (
partial_ld.main_object.binary if suggested_main_filename is None else suggested_main_filename
)
self.md5 = None
self.sha256 = None
self.option_widgets = {}
self.suggested_backend = suggested_backend
self.suggested_os_name = suggested_os_name
self.suggested_main_opts = suggested_main_opts or {}
self.suggested_original_backend = suggested_original_backend
self.suggested_main_filename = suggested_main_filename
self.available_backends: dict[str, cle.Backend] = cle.ALL_BACKENDS
self.available_simos = {}
self.arch = partial_ld.main_object.arch
Expand Down Expand Up @@ -121,7 +129,7 @@ def __init__(

@property
def filename(self):
return os.path.basename(self.file_path)
return os.path.basename(self.file_path) if self.file_path else ""

#
# Private methods
Expand Down Expand Up @@ -184,7 +192,10 @@ def _init_widgets(self) -> None:
filename_caption.setText("File name:")

filename = QLabel(self)
filename.setText(self.filename)
if self.filename:
filename.setText(self.filename)
else:
filename.setText("<Unknown>")

layout.addWidget(filename_caption, 0, 0, Qt.AlignRight)
layout.addWidget(filename, 0, 1)
Expand Down Expand Up @@ -232,9 +243,36 @@ def _init_central_tab(self, tab) -> None:
self._init_load_options_tab(tab)

def _init_load_options_tab(self, tab):
#
# Outer backend selection
#

outer_backend_layout = QHBoxLayout()
outer_backend_caption = QLabel()
outer_backend_caption.setText("Outer backend:")
outer_backend_caption.setSizePolicy(QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed))
outer_backend_layout.addWidget(outer_backend_caption)

outer_backend_dropdown = QComboBox()
suggested_outer_backend_name = None
outer_backend_dropdown.addItem("<None>")
for backend_name, backend in self.available_backends.items():
if getattr(backend, "is_outer", False) is True:
outer_backend_dropdown.addItem(backend_name)
if backend is self.suggested_original_backend:
suggested_outer_backend_name = backend_name
if suggested_outer_backend_name is not None:
outer_backend_dropdown.setCurrentText(suggested_outer_backend_name)
else:
outer_backend_dropdown.setCurrentText("<None>")
outer_backend_layout.addWidget(outer_backend_dropdown)

self.option_widgets["outer_backend"] = outer_backend_dropdown

#
# Backend selection
#

backend_layout = QHBoxLayout()
backend_caption = QLabel()
backend_caption.setText("Backend:")
Expand All @@ -244,9 +282,10 @@ def _init_load_options_tab(self, tab):
backend_dropdown = QComboBox()
suggested_backend_name = None
for backend_name, backend in self.available_backends.items():
backend_dropdown.addItem(backend_name)
if backend is self.suggested_backend:
suggested_backend_name = backend_name
if getattr(backend, "is_outer", False) is False:
backend_dropdown.addItem(backend_name)
if backend is self.suggested_backend:
suggested_backend_name = backend_name
if suggested_backend_name is not None:
backend_dropdown.setCurrentText(suggested_backend_name)
backend_layout.addWidget(backend_dropdown)
Expand Down Expand Up @@ -368,6 +407,7 @@ def _init_load_options_tab(self, tab):
self.option_widgets["load_debug_info"] = load_debug_info

layout = QVBoxLayout()
layout.addLayout(outer_backend_layout)
layout.addLayout(backend_layout)
layout.addLayout(os_layout)
layout.addLayout(blob_layout)
Expand Down Expand Up @@ -465,6 +505,14 @@ def _on_ok_clicked(self) -> None:
self.load_options["auto_load_libs"] = self.option_widgets["auto_load_libs"].isChecked()
self.load_options["load_debug_info"] = self.option_widgets["load_debug_info"].isChecked()

outer_backend_dropdown: QComboBox = self.option_widgets["outer_backend"]
outer_backend: str | None = outer_backend_dropdown.currentText()
if outer_backend == "<None>":
outer_backend = None
elif not outer_backend or outer_backend not in self.available_backends:
QMessageBox.critical(None, "Incorrect backend selection", "Please select a backend before continue.")
return

backend_dropdown: QComboBox = self.option_widgets["backend"]
backend: str = backend_dropdown.currentText()
if not backend or backend not in self.available_backends:
Expand All @@ -490,7 +538,7 @@ def _on_ok_clicked(self) -> None:
arch = archinfo.ArchPcode(arch.id)
self.load_options["arch"] = arch

self.load_options["main_opts"] = {
main_opts = {
"backend": backend,
}

Expand All @@ -502,43 +550,60 @@ def _on_ok_clicked(self) -> None:
except ValueError:
QMessageBox.critical(None, "Incorrect base address", "Please input a valid base address.")
return
self.load_options["main_opts"]["base_addr"] = base_addr
main_opts["base_addr"] = base_addr

if self._entry_addr_checkbox.isChecked():
try:
entry_addr = int(self.option_widgets["entry_addr"].text(), 16)
except ValueError:
QMessageBox.critical(None, "Incorrect entry point address", "Please input a valid entry point address.")
return
self.load_options["main_opts"]["entry_point"] = entry_addr
main_opts["entry_point"] = entry_addr

if force_load_libs:
self.load_options["force_load_libs"] = force_load_libs
if skip_libs:
self.load_options["skip_libs"] = skip_libs

if outer_backend is not None:
self.load_options["main_opts"] = {
"backend": outer_backend,
} | self.suggested_main_opts
if self.suggested_main_filename is not None:
self.load_options["lib_opts"] = {
self.suggested_main_filename: main_opts,
}
else:
self.load_options["main_opts"] = main_opts | self.suggested_main_opts

self.close()

def _on_cancel_clicked(self) -> None:
self.close()

@staticmethod
def run(
partial_ld, suggested_backend=None, suggested_os_name: str | None = None
) -> tuple[dict | None, dict | None, dict | None]:
partial_ld,
suggested_backend=None,
suggested_os_name: str | None = None,
suggested_main_opts: dict | None = None,
**kwargs,
) -> tuple[dict | None, dict | None]:
try:
dialog = LoadBinary(
partial_ld,
suggested_backend=suggested_backend,
suggested_os_name=suggested_os_name,
suggested_main_opts=suggested_main_opts,
parent=GlobalInfo.main_window,
**kwargs,
)
dialog.setModal(True)
dialog.exec_()
return dialog.load_options, dialog.simos
except LoadBinaryError:
pass
return None, None, None
return None, None

@staticmethod
def binary_arch_detect_failed(filename: str, archinfo_msg: str) -> None:
Expand Down
Loading
Loading