199 lines
5.9 KiB
Python
199 lines
5.9 KiB
Python
import abc
|
|
import codecs
|
|
import json
|
|
import os
|
|
from typing import TYPE_CHECKING, Optional, Sequence
|
|
|
|
if TYPE_CHECKING:
|
|
import frida_tools.repl
|
|
|
|
|
|
class Magic(abc.ABC):
|
|
@property
|
|
def description(self) -> str:
|
|
return "no description"
|
|
|
|
@abc.abstractproperty
|
|
def required_args_count(self) -> int:
|
|
pass
|
|
|
|
def execute(self, repl: "frida_tools.repl.REPLApplication", args: Sequence[str]) -> Optional[bool]:
|
|
pass
|
|
|
|
|
|
class Resume(Magic):
|
|
@property
|
|
def description(self) -> str:
|
|
return "resume execution of the spawned process"
|
|
|
|
@property
|
|
def required_args_count(self) -> int:
|
|
return 0
|
|
|
|
def execute(self, repl: "frida_tools.repl.REPLApplication", args: Sequence[str]) -> None:
|
|
repl._reactor.schedule(lambda: repl._resume())
|
|
|
|
|
|
class Load(Magic):
|
|
@property
|
|
def description(self) -> str:
|
|
return "Load an additional script and reload the current REPL state"
|
|
|
|
@property
|
|
def required_args_count(self) -> int:
|
|
return 1
|
|
|
|
def execute(self, repl: "frida_tools.repl.REPLApplication", args: Sequence[str]) -> None:
|
|
try:
|
|
proceed = repl._get_confirmation(
|
|
"Are you sure you want to load a new script and discard all current state?"
|
|
)
|
|
if not proceed:
|
|
repl._print("Discarding load command")
|
|
return
|
|
|
|
repl._user_scripts.append(args[0])
|
|
repl._perform_on_reactor_thread(lambda: repl._load_script())
|
|
except Exception as e:
|
|
repl._print(f"Failed to load script: {e}")
|
|
|
|
|
|
class Reload(Magic):
|
|
@property
|
|
def description(self) -> str:
|
|
return "reload (i.e. rerun) the script that was given as an argument to the REPL"
|
|
|
|
@property
|
|
def required_args_count(self) -> int:
|
|
return 0
|
|
|
|
def execute(self, repl: "frida_tools.repl.REPLApplication", args: Sequence[str]) -> bool:
|
|
try:
|
|
repl._perform_on_reactor_thread(lambda: repl._load_script())
|
|
return True
|
|
except Exception as e:
|
|
repl._print(f"Failed to load script: {e}")
|
|
return False
|
|
|
|
|
|
class Unload(Magic):
|
|
@property
|
|
def required_args_count(self) -> int:
|
|
return 0
|
|
|
|
def execute(self, repl: "frida_tools.repl.REPLApplication", args: Sequence[str]) -> None:
|
|
repl._unload_script()
|
|
|
|
|
|
class Autoperform(Magic):
|
|
@property
|
|
def description(self) -> str:
|
|
return (
|
|
"receive on/off as first and only argument, when switched on will wrap any REPL code with Java.performNow()"
|
|
)
|
|
|
|
@property
|
|
def required_args_count(self) -> int:
|
|
return 1
|
|
|
|
def execute(self, repl: "frida_tools.repl.REPLApplication", args: Sequence[str]) -> None:
|
|
repl._autoperform_command(args[0])
|
|
|
|
|
|
class Autoreload(Magic):
|
|
_VALID_ARGUMENTS = ("on", "off")
|
|
|
|
@property
|
|
def description(self) -> str:
|
|
return "disable or enable auto reloading of script files"
|
|
|
|
@property
|
|
def required_args_count(self) -> int:
|
|
return 1
|
|
|
|
def execute(self, repl: "frida_tools.repl.REPLApplication", args: Sequence[str]) -> None:
|
|
if args[0] not in self._VALID_ARGUMENTS:
|
|
raise ValueError("Autoreload command only receive on or off as an argument")
|
|
|
|
required_state = args[0] == "on"
|
|
if required_state == repl._autoreload:
|
|
repl._print("Autoreloading is already in the desired state")
|
|
return
|
|
|
|
if required_state:
|
|
repl._monitor_all()
|
|
else:
|
|
repl._demonitor_all()
|
|
repl._autoreload = required_state
|
|
|
|
|
|
class Exec(Magic):
|
|
@property
|
|
def description(self) -> str:
|
|
return "execute the given file path in the context of the currently loaded scripts"
|
|
|
|
@property
|
|
def required_args_count(self) -> int:
|
|
return 1
|
|
|
|
def execute(self, repl: "frida_tools.repl.REPLApplication", args: Sequence[str]) -> None:
|
|
if not os.path.exists(args[0]):
|
|
repl._print("Can't read the given file because it does not exist")
|
|
return
|
|
|
|
try:
|
|
with codecs.open(args[0], "rb", "utf-8") as f:
|
|
if not repl._exec_and_print(repl._evaluate_expression, f.read()):
|
|
repl._errors += 1
|
|
except PermissionError:
|
|
repl._print("Can't read the given file because of a permission error")
|
|
|
|
|
|
class Time(Magic):
|
|
@property
|
|
def description(self) -> str:
|
|
return "measure the execution time of the given expression and print it to the screen"
|
|
|
|
@property
|
|
def required_args_count(self) -> int:
|
|
return -2
|
|
|
|
def execute(self, repl: "frida_tools.repl.REPLApplication", args: Sequence[str]) -> None:
|
|
repl._exec_and_print(
|
|
repl._evaluate_expression,
|
|
"""
|
|
(() => {{
|
|
const _startTime = Date.now();
|
|
const _result = eval({expression});
|
|
const _endTime = Date.now();
|
|
console.log('Time: ' + (_endTime - _startTime) + ' ms.');
|
|
return _result;
|
|
}})();""".format(
|
|
expression=json.dumps(" ".join(args))
|
|
),
|
|
)
|
|
|
|
|
|
class Help(Magic):
|
|
@property
|
|
def description(self) -> str:
|
|
return "print a list of available REPL commands"
|
|
|
|
@property
|
|
def required_args_count(self) -> int:
|
|
return 0
|
|
|
|
def execute(self, repl: "frida_tools.repl.REPLApplication", args: Sequence[str]) -> None:
|
|
repl._print("Available commands: ")
|
|
for name, command in repl._magic_command_args.items():
|
|
if command.required_args_count >= 0:
|
|
required_args = f"({command.required_args_count})"
|
|
else:
|
|
required_args = f"({abs(command.required_args_count) - 1}+)"
|
|
|
|
repl._print(f" %{name}{required_args} - {command.description}")
|
|
|
|
repl._print("")
|
|
repl._print("For help with Frida scripting API, check out https://frida.re/docs/")
|
|
repl._print("")
|