This repository has been archived on 2024-09-30. You can view files and clone it, but cannot push or open issues/pull-requests.
hook-frida/venv/Lib/site-packages/frida_tools/application.py

952 lines
34 KiB
Python

import argparse
import codecs
import errno
import numbers
import os
import platform
import re
import select
import shlex
import signal
import sys
import threading
import time
from types import FrameType
from typing import Any, Callable, List, Optional, Tuple, TypeVar, Union
import _frida
if platform.system() == "Windows":
import msvcrt
import colorama
import frida
from frida_tools.reactor import Reactor
AUX_OPTION_PATTERN = re.compile(r"(.+)=\((string|bool|int)\)(.+)")
T = TypeVar("T")
TargetType = Union[List[str], re.Pattern, int, str]
TargetTypeTuple = Tuple[str, TargetType]
def input_with_cancellable(cancellable: frida.Cancellable) -> str:
if platform.system() == "Windows":
result = ""
done = False
while not done:
while msvcrt.kbhit():
c = msvcrt.getwche()
if c in ("\x00", "\xe0"):
msvcrt.getwche()
continue
result += c
if c == "\n":
done = True
break
cancellable.raise_if_cancelled()
time.sleep(0.05)
return result
elif platform.system() in ["Darwin", "FreeBSD"]:
while True:
try:
rlist, _, _ = select.select([sys.stdin], [], [], 0.05)
except OSError as e:
if e.args[0] != errno.EINTR:
raise e
cancellable.raise_if_cancelled()
if sys.stdin in rlist:
return sys.stdin.readline()
else:
with cancellable.get_pollfd() as cancellable_fd:
try:
rlist, _, _ = select.select([sys.stdin, cancellable_fd], [], [])
except OSError as e:
if e.args[0] != errno.EINTR:
raise e
cancellable.raise_if_cancelled()
return sys.stdin.readline()
def await_enter(reactor: Reactor) -> None:
try:
input_with_cancellable(reactor.ui_cancellable)
except frida.OperationCancelledError:
pass
except KeyboardInterrupt:
print("")
def await_ctrl_c(reactor: Reactor) -> None:
while True:
try:
input_with_cancellable(reactor.ui_cancellable)
except frida.OperationCancelledError:
break
except KeyboardInterrupt:
break
def deserialize_relay(value: str) -> frida.Relay:
address, username, password, kind = value.split(",")
return frida.Relay(address, username, password, kind)
def create_target_parser(target_type: str) -> Callable[[str], TargetTypeTuple]:
def parse_target(value: str) -> TargetTypeTuple:
if target_type == "file":
return (target_type, [value])
if target_type == "gated":
return (target_type, re.compile(value))
if target_type == "pid":
return (target_type, int(value))
return (target_type, value)
return parse_target
class ConsoleState:
EMPTY = 1
STATUS = 2
TEXT = 3
class ConsoleApplication:
"""
ConsoleApplication is the base class for all of Frida tools, which contains
the common arguments of the tools. Each application can implement one or
more of several methods that can be inserted inside the flow of the
application.
The subclass should not expose any additional methods aside from __init__
and run methods that are defined by this class. These methods should not be
overridden without calling the super method.
"""
_target: Optional[TargetTypeTuple] = None
def __init__(
self,
run_until_return: Callable[["Reactor"], None] = await_enter,
on_stop: Optional[Callable[[], None]] = None,
args: Optional[List[str]] = None,
):
plain_terminal = os.environ.get("TERM", "").lower() == "none"
# Windows doesn't have SIGPIPE
if hasattr(signal, "SIGPIPE"):
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
colorama.init(strip=True if plain_terminal else None)
parser = self._initialize_arguments_parser()
real_args = compute_real_args(parser, args=args)
options = parser.parse_args(real_args)
# handle scripts that don't need a target
if not hasattr(options, "args"):
options.args = []
self._initialize_device_arguments(parser, options)
self._initialize_target_arguments(parser, options)
self._reactor = Reactor(run_until_return, on_stop)
self._device: Optional[frida.core.Device] = None
self._schedule_on_output = lambda pid, fd, data: self._reactor.schedule(lambda: self._on_output(pid, fd, data))
self._schedule_on_device_lost = lambda: self._reactor.schedule(self._on_device_lost)
self._spawned_pid: Optional[int] = None
self._spawned_argv = None
self._selected_spawn: Optional[_frida.Spawn] = None
self._target_pid: Optional[int] = None
self._session: Optional[frida.core.Session] = None
self._schedule_on_session_detached = lambda reason, crash: self._reactor.schedule(
lambda: self._on_session_detached(reason, crash)
)
self._started = False
self._resumed = False
self._exit_status: Optional[int] = None
self._console_state = ConsoleState.EMPTY
self._have_terminal = sys.stdin.isatty() and sys.stdout.isatty() and not os.environ.get("TERM", "") == "dumb"
self._plain_terminal = plain_terminal
self._quiet = False
if sum(map(lambda v: int(v is not None), (self._device_id, self._device_type, self._host))) > 1:
parser.error("Only one of -D, -U, -R, and -H may be specified")
self._initialize_target(parser, options)
try:
self._initialize(parser, options, options.args)
except Exception as e:
parser.error(str(e))
def _initialize_device_arguments(self, parser: argparse.ArgumentParser, options: argparse.Namespace) -> None:
if self._needs_device():
self._device_id = options.device_id
self._device_type = options.device_type
self._host = options.host
if all([x is None for x in [self._device_id, self._device_type, self._host]]):
self._device_id = os.environ.get("FRIDA_DEVICE")
if self._device_id is None:
self._host = os.environ.get("FRIDA_HOST")
self._certificate = options.certificate or os.environ.get("FRIDA_CERTIFICATE")
self._origin = options.origin or os.environ.get("FRIDA_ORIGIN")
self._token = options.token or os.environ.get("FRIDA_TOKEN")
self._keepalive_interval = options.keepalive_interval
self._session_transport = options.session_transport
self._stun_server = options.stun_server
self._relays = options.relays
else:
self._device_id = None
self._device_type = None
self._host = None
self._certificate = None
self._origin = None
self._token = None
self._keepalive_interval = None
self._session_transport = "multiplexed"
self._stun_server = None
self._relays = None
def _initialize_target_arguments(self, parser: argparse.ArgumentParser, options: argparse.Namespace) -> None:
if self._needs_target():
self._stdio = options.stdio
self._aux = options.aux
self._realm = options.realm
self._runtime = options.runtime
self._enable_debugger = options.enable_debugger
self._squelch_crash = options.squelch_crash
else:
self._stdio = "inherit"
self._aux = []
self._realm = "native"
self._runtime = "qjs"
self._enable_debugger = False
self._squelch_crash = False
def _initialize_target(self, parser: argparse.ArgumentParser, options: argparse.Namespace) -> None:
if self._needs_target():
target = getattr(options, "target", None)
if target is None:
if len(options.args) < 1:
parser.error("target must be specified")
target = infer_target(options.args[0])
options.args.pop(0)
target = expand_target(target)
if target[0] == "file":
if not isinstance(target[1], list):
raise ValueError("file target must be a list of strings")
argv = target[1]
argv.extend(options.args)
options.args = []
self._target = target
else:
self._target = None
def _initialize_arguments_parser(self) -> argparse.ArgumentParser:
parser = self._initialize_base_arguments_parser()
self._add_options(parser)
return parser
def _initialize_base_arguments_parser(self) -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(usage=self._usage())
if self._needs_device():
self._add_device_arguments(parser)
if self._needs_target():
self._add_target_arguments(parser)
parser.add_argument(
"-O", "--options-file", help="text file containing additional command line options", metavar="FILE"
)
parser.add_argument("--version", action="version", version=frida.__version__)
return parser
def _add_device_arguments(self, parser: argparse.ArgumentParser) -> None:
parser.add_argument(
"-D", "--device", help="connect to device with the given ID", metavar="ID", dest="device_id"
)
parser.add_argument(
"-U", "--usb", help="connect to USB device", action="store_const", const="usb", dest="device_type"
)
parser.add_argument(
"-R",
"--remote",
help="connect to remote frida-server",
action="store_const",
const="remote",
dest="device_type",
)
parser.add_argument("-H", "--host", help="connect to remote frida-server on HOST")
parser.add_argument("--certificate", help="speak TLS with HOST, expecting CERTIFICATE")
parser.add_argument("--origin", help="connect to remote server with “Origin” header set to ORIGIN")
parser.add_argument("--token", help="authenticate with HOST using TOKEN")
parser.add_argument(
"--keepalive-interval",
help="set keepalive interval in seconds, or 0 to disable (defaults to -1 to auto-select based on transport)",
metavar="INTERVAL",
type=int,
)
parser.add_argument(
"--p2p",
help="establish a peer-to-peer connection with target",
action="store_const",
const="p2p",
dest="session_transport",
default="multiplexed",
)
parser.add_argument("--stun-server", help="set STUN server ADDRESS to use with --p2p", metavar="ADDRESS")
parser.add_argument(
"--relay",
help="add relay to use with --p2p",
metavar="address,username,password,turn-{udp,tcp,tls}",
dest="relays",
action="append",
type=deserialize_relay,
)
def _add_target_arguments(self, parser: argparse.ArgumentParser) -> None:
parser.add_argument("-f", "--file", help="spawn FILE", dest="target", type=create_target_parser("file"))
parser.add_argument(
"-F",
"--attach-frontmost",
help="attach to frontmost application",
dest="target",
action="store_const",
const=("frontmost", None),
)
parser.add_argument(
"-n",
"--attach-name",
help="attach to NAME",
metavar="NAME",
dest="target",
type=create_target_parser("name"),
)
parser.add_argument(
"-N",
"--attach-identifier",
help="attach to IDENTIFIER",
metavar="IDENTIFIER",
dest="target",
type=create_target_parser("identifier"),
)
parser.add_argument(
"-p", "--attach-pid", help="attach to PID", metavar="PID", dest="target", type=create_target_parser("pid")
)
parser.add_argument(
"-W",
"--await",
help="await spawn matching PATTERN",
metavar="PATTERN",
dest="target",
type=create_target_parser("gated"),
)
parser.add_argument(
"--stdio",
help="stdio behavior when spawning (defaults to “inherit”)",
choices=["inherit", "pipe"],
default="inherit",
)
parser.add_argument(
"--aux",
help="set aux option when spawning, such as “uid=(int)42” (supported types are: string, bool, int)",
metavar="option",
action="append",
dest="aux",
default=[],
)
parser.add_argument("--realm", help="realm to attach in", choices=["native", "emulated"], default="native")
parser.add_argument("--runtime", help="script runtime to use", choices=["qjs", "v8"])
parser.add_argument(
"--debug",
help="enable the Node.js compatible script debugger",
action="store_true",
dest="enable_debugger",
default=False,
)
parser.add_argument(
"--squelch-crash",
help="if enabled, will not dump crash report to console",
action="store_true",
default=False,
)
parser.add_argument("args", help="extra arguments and/or target", nargs="*")
def run(self) -> None:
mgr = frida.get_device_manager()
on_devices_changed = lambda: self._reactor.schedule(self._try_start)
mgr.on("changed", on_devices_changed)
self._reactor.schedule(self._try_start)
self._reactor.schedule(self._show_message_if_no_device, delay=1)
signal.signal(signal.SIGTERM, self._on_sigterm)
self._reactor.run()
if self._started:
try:
self._perform_on_background_thread(self._stop)
except frida.OperationCancelledError:
pass
if self._session is not None:
self._session.off("detached", self._schedule_on_session_detached)
try:
self._perform_on_background_thread(self._session.detach)
except frida.OperationCancelledError:
pass
self._session = None
if self._device is not None:
self._device.off("output", self._schedule_on_output)
self._device.off("lost", self._schedule_on_device_lost)
mgr.off("changed", on_devices_changed)
frida.shutdown()
sys.exit(self._exit_status)
def _add_options(self, parser: argparse.ArgumentParser) -> None:
"""
override this method if you want to add custom arguments to your
command. The parser command is an argparse object, you should add the
options to him.
"""
def _initialize(self, parser: argparse.ArgumentParser, options: argparse.Namespace, args: List[str]) -> None:
"""
override this method if you need to have additional initialization code
before running, maybe to use your custom options from the `_add_options`
method.
"""
def _usage(self) -> str:
"""
override this method if to add a custom usage message
"""
return "%(prog)s [options]"
def _needs_device(self) -> bool:
"""
override this method if your command need to get a device from the user.
"""
return True
def _needs_target(self) -> bool:
"""
override this method if your command does not need to get a target
process from the user.
"""
return False
def _start(self) -> None:
"""
override this method with the logic of your command, it will run after
the class is fully initialized with a connected device/target if you
required one.
"""
def _stop(self) -> None:
"""
override this method if you have something you need to do at the end of
your command, maybe cleaning up some objects.
"""
def _resume(self) -> None:
if self._resumed:
return
if self._spawned_pid is not None:
assert self._device is not None
self._device.resume(self._spawned_pid)
assert self._target is not None
if self._target[0] == "gated":
self._device.disable_spawn_gating()
self._device.off("spawn-added", self._on_spawn_added)
self._resumed = True
def _exit(self, exit_status: int) -> None:
self._exit_status = exit_status
self._reactor.stop()
def _try_start(self) -> None:
if self._device is not None:
return
if self._device_id is not None:
try:
self._device = frida.get_device(self._device_id)
except:
self._update_status(f"Device '{self._device_id}' not found")
self._exit(1)
return
elif (self._host is not None) or (self._device_type == "remote"):
host = self._host
options = {}
if self._certificate is not None:
options["certificate"] = self._certificate
if self._origin is not None:
options["origin"] = self._origin
if self._token is not None:
options["token"] = self._token
if self._keepalive_interval is not None:
options["keepalive_interval"] = self._keepalive_interval
if host is None and len(options) == 0:
self._device = frida.get_remote_device()
else:
self._device = frida.get_device_manager().add_remote_device(
host if host is not None else "127.0.0.1", **options
)
elif self._device_type is not None:
self._device = find_device(self._device_type)
if self._device is None:
return
else:
self._device = frida.get_local_device()
self._on_device_found()
self._device.on("output", self._schedule_on_output)
self._device.on("lost", self._schedule_on_device_lost)
if self._target is not None:
target_type, target_value = self._target
if target_type == "gated":
self._device.on("spawn-added", self._on_spawn_added)
try:
self._device.enable_spawn_gating()
except Exception as e:
self._update_status(f"Failed to enable spawn gating: {e}")
self._exit(1)
return
self._update_status("Waiting for spawn to appear...")
return
spawning = True
try:
if target_type == "frontmost":
try:
app = self._device.get_frontmost_application()
except Exception as e:
self._update_status(f"Unable to get frontmost application on {self._device.name}: {e}")
self._exit(1)
return
if app is None:
self._update_status(f"No frontmost application on {self._device.name}")
self._exit(1)
return
self._target = ("name", app.name)
attach_target = app.pid
elif target_type == "identifier":
spawning = False
app_list = self._device.enumerate_applications()
app_identifier_lc = target_value.lower()
matching = [app for app in app_list if app.identifier.lower() == app_identifier_lc]
if len(matching) == 1 and matching[0].pid != 0:
attach_target = matching[0].pid
elif len(matching) > 1:
raise frida.ProcessNotFoundError(
"ambiguous identifier; it matches: %s"
% ", ".join([f"{process.identifier} (pid: {process.pid})" for process in matching])
)
else:
raise frida.ProcessNotFoundError("unable to find process with identifier '%s'" % target_value)
elif target_type == "file":
argv = target_value
if not self._quiet:
self._update_status(f"Spawning `{' '.join(argv)}`...")
aux_kwargs = {}
if self._aux is not None:
aux_kwargs = dict([parse_aux_option(o) for o in self._aux])
self._spawned_pid = self._device.spawn(argv, stdio=self._stdio, **aux_kwargs)
self._spawned_argv = argv
attach_target = self._spawned_pid
else:
attach_target = target_value
if not isinstance(attach_target, numbers.Number):
attach_target = self._device.get_process(attach_target).pid
if not self._quiet:
self._update_status("Attaching...")
spawning = False
self._attach(attach_target)
except frida.OperationCancelledError:
self._exit(0)
return
except Exception as e:
if spawning:
self._update_status(f"Failed to spawn: {e}")
else:
self._update_status(f"Failed to attach: {e}")
self._exit(1)
return
self._start()
self._started = True
def _attach(self, pid: int) -> None:
self._target_pid = pid
assert self._device is not None
self._session = self._device.attach(pid, realm=self._realm)
self._session.on("detached", self._schedule_on_session_detached)
if self._session_transport == "p2p":
peer_options = {}
if self._stun_server is not None:
peer_options["stun_server"] = self._stun_server
if self._relays is not None:
peer_options["relays"] = self._relays
self._session.setup_peer_connection(**peer_options)
def _on_script_created(self, script: frida.core.Script) -> None:
if self._enable_debugger:
script.enable_debugger()
self._print("Chrome Inspector server listening on port 9229\n")
def _show_message_if_no_device(self) -> None:
if self._device is None:
self._print("Waiting for USB device to appear...")
def _on_sigterm(self, n: int, f: Optional[FrameType]) -> None:
self._reactor.cancel_io()
self._exit(0)
def _on_spawn_added(self, spawn: _frida.Spawn) -> None:
thread = threading.Thread(target=self._handle_spawn, args=(spawn,))
thread.start()
def _handle_spawn(self, spawn: _frida.Spawn) -> None:
pid = spawn.pid
pattern = self._target[1]
if pattern.match(spawn.identifier) is None or self._selected_spawn is not None:
self._print(
colorama.Fore.YELLOW + colorama.Style.BRIGHT + "Ignoring: " + str(spawn) + colorama.Style.RESET_ALL
)
try:
if self._device is not None:
self._device.resume(pid)
except:
pass
return
self._selected_spawn = spawn
self._print(colorama.Fore.GREEN + colorama.Style.BRIGHT + "Handling: " + str(spawn) + colorama.Style.RESET_ALL)
try:
self._attach(pid)
self._reactor.schedule(lambda: self._on_spawn_handled(spawn))
except Exception as e:
error = e
self._reactor.schedule(lambda: self._on_spawn_unhandled(spawn, error))
def _on_spawn_handled(self, spawn: _frida.Spawn) -> None:
self._spawned_pid = spawn.pid
self._start()
self._started = True
def _on_spawn_unhandled(self, spawn: _frida.Spawn, error: Exception) -> None:
self._update_status(f"Failed to handle spawn: {error}")
self._exit(1)
def _on_output(self, pid: int, fd: int, data: Optional[bytes]) -> None:
if pid != self._target_pid or data is None:
return
if fd == 1:
prefix = "stdout> "
stream = sys.stdout
else:
prefix = "stderr> "
stream = sys.stderr
encoding = stream.encoding or "UTF-8"
text = data.decode(encoding, errors="replace")
if text.endswith("\n"):
text = text[:-1]
lines = text.split("\n")
self._print(prefix + ("\n" + prefix).join(lines))
def _on_device_found(self) -> None:
pass
def _on_device_lost(self) -> None:
if self._exit_status is not None:
return
self._print("Device disconnected.")
self._exit(1)
def _on_session_detached(self, reason: str, crash) -> None:
if crash is None:
message = reason[0].upper() + reason[1:].replace("-", " ")
else:
message = "Process crashed: " + crash.summary
self._print(colorama.Fore.RED + colorama.Style.BRIGHT + message + colorama.Style.RESET_ALL)
if crash is not None:
if self._squelch_crash is True:
self._print("\n*** Crash report was squelched due to user setting. ***")
else:
self._print("\n***\n{}\n***".format(crash.report.rstrip("\n")))
self._exit(1)
def _clear_status(self) -> None:
if self._console_state == ConsoleState.STATUS:
print(colorama.Cursor.UP() + (80 * " "))
def _update_status(self, message: str) -> None:
if self._have_terminal:
if self._console_state == ConsoleState.STATUS:
cursor_position = colorama.Cursor.UP()
else:
cursor_position = ""
print("%-80s" % (cursor_position + colorama.Style.BRIGHT + message + colorama.Style.RESET_ALL,))
self._console_state = ConsoleState.STATUS
else:
print(colorama.Style.BRIGHT + message + colorama.Style.RESET_ALL)
def _print(self, *args: Any, **kwargs: Any) -> None:
encoded_args: List[Any] = []
encoding = sys.stdout.encoding or "UTF-8"
if encoding == "UTF-8":
encoded_args = list(args)
else:
for arg in args:
if isinstance(arg, str):
encoded_args.append(arg.encode(encoding, errors="backslashreplace").decode(encoding))
else:
encoded_args.append(arg)
print(*encoded_args, **kwargs)
self._console_state = ConsoleState.TEXT
def _log(self, level: str, text: str) -> None:
if level == "info":
self._print(text)
else:
color = colorama.Fore.RED if level == "error" else colorama.Fore.YELLOW
text = color + colorama.Style.BRIGHT + text + colorama.Style.RESET_ALL
if level == "error":
self._print(text, file=sys.stderr)
else:
self._print(text)
def _perform_on_reactor_thread(self, f: Callable[[], T]) -> T:
completed = threading.Event()
result = [None, None]
def work() -> None:
try:
result[0] = f()
except Exception as e:
result[1] = e
completed.set()
self._reactor.schedule(work)
while not completed.is_set():
try:
completed.wait()
except KeyboardInterrupt:
self._reactor.cancel_io()
continue
error = result[1]
if error is not None:
raise error
return result[0]
def _perform_on_background_thread(self, f: Callable[[], T], timeout: Optional[float] = None) -> T:
result = [None, None]
def work() -> None:
with self._reactor.io_cancellable:
try:
result[0] = f()
except Exception as e:
result[1] = e
worker = threading.Thread(target=work)
worker.start()
try:
worker.join(timeout)
except KeyboardInterrupt:
self._reactor.cancel_io()
if timeout is not None and worker.is_alive():
self._reactor.cancel_io()
while worker.is_alive():
try:
worker.join()
except KeyboardInterrupt:
pass
error = result[1]
if error is not None:
raise error
return result[0]
def _get_default_frida_dir(self) -> str:
return os.path.join(os.path.expanduser("~"), ".frida")
def _get_windows_frida_dir(self) -> str:
appdata = os.environ["LOCALAPPDATA"]
return os.path.join(appdata, "frida")
def _get_or_create_config_dir(self) -> str:
config_dir = os.path.join(self._get_default_frida_dir(), "config")
if platform.system() == "Linux":
xdg_config_home = os.getenv("XDG_CONFIG_HOME", os.path.expanduser("~/.config"))
config_dir = os.path.join(xdg_config_home, "frida")
elif platform.system() == "Windows":
config_dir = os.path.join(self._get_windows_frida_dir(), "Config")
if not os.path.exists(config_dir):
os.makedirs(config_dir)
return config_dir
def _get_or_create_data_dir(self) -> str:
data_dir = os.path.join(self._get_default_frida_dir(), "data")
if platform.system() == "Linux":
xdg_data_home = os.getenv("XDG_DATA_HOME", os.path.expanduser("~/.local/share"))
data_dir = os.path.join(xdg_data_home, "frida")
elif platform.system() == "Windows":
data_dir = os.path.join(self._get_windows_frida_dir(), "Data")
if not os.path.exists(data_dir):
os.makedirs(data_dir)
return data_dir
def _get_or_create_state_dir(self) -> str:
state_dir = os.path.join(self._get_default_frida_dir(), "state")
if platform.system() == "Linux":
xdg_state_home = os.getenv("XDG_STATE_HOME", os.path.expanduser("~/.local/state"))
state_dir = os.path.join(xdg_state_home, "frida")
elif platform.system() == "Windows":
appdata = os.environ["LOCALAPPDATA"]
state_dir = os.path.join(appdata, "frida", "State")
if not os.path.exists(state_dir):
os.makedirs(state_dir)
return state_dir
def compute_real_args(parser: argparse.ArgumentParser, args: Optional[List[str]] = None) -> List[str]:
if args is None:
args = sys.argv[1:]
real_args = normalize_options_file_args(args)
files_processed = set()
while True:
offset = find_options_file_offset(real_args, parser)
if offset == -1:
break
file_path = os.path.abspath(real_args[offset + 1])
if file_path in files_processed:
parser.error(f"File '{file_path}' given twice as -O argument")
if os.path.isfile(file_path):
with codecs.open(file_path, "r", "utf-8") as f:
new_arg_text = f.read()
else:
parser.error(f"File '{file_path}' following -O option is not a valid file")
real_args = insert_options_file_args_in_list(real_args, offset, new_arg_text)
files_processed.add(file_path)
return real_args
def normalize_options_file_args(raw_args: List[str]) -> List[str]:
result = []
for arg in raw_args:
if arg.startswith("--options-file="):
result.append(arg[0:14])
result.append(arg[15:])
else:
result.append(arg)
return result
def find_options_file_offset(arglist: List[str], parser: argparse.ArgumentParser) -> int:
for i, arg in enumerate(arglist):
if arg in ("-O", "--options-file"):
if i < len(arglist) - 1:
return i
else:
parser.error("No argument given for -O option")
return -1
def insert_options_file_args_in_list(args: List[str], offset: int, new_arg_text: str) -> List[str]:
new_args = shlex.split(new_arg_text)
new_args = normalize_options_file_args(new_args)
new_args_list = args[:offset] + new_args + args[offset + 2 :]
return new_args_list
def find_device(device_type: str) -> Optional[frida.core.Device]:
for device in frida.enumerate_devices():
if device.type == device_type:
return device
return None
def infer_target(target_value: str) -> TargetTypeTuple:
if (
target_value.startswith(".")
or target_value.startswith(os.path.sep)
or (
platform.system() == "Windows"
and target_value[0].isalpha()
and target_value[1] == ":"
and target_value[2] == "\\"
)
):
return ("file", [target_value])
try:
return ("pid", int(target_value))
except:
pass
return ("name", target_value)
def expand_target(target: TargetTypeTuple) -> TargetTypeTuple:
target_type, target_value = target
if target_type == "file" and isinstance(target_value, list):
target_value = [target_value[0]]
return (target_type, target_value)
def parse_aux_option(option: str) -> Tuple[str, Union[str, bool, int]]:
m = AUX_OPTION_PATTERN.match(option)
if m is None:
raise ValueError("expected name=(type)value, e.g. “uid=(int)42”; supported types are: string, bool, int")
name = m.group(1)
type_decl = m.group(2)
raw_value = m.group(3)
if type_decl == "string":
value = raw_value
elif type_decl == "bool":
value = bool(raw_value)
else:
value = int(raw_value)
return (name, value)