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/ps.py

287 lines
11 KiB
Python

def main() -> None:
import argparse
import functools
import json
import math
import platform
import sys
from base64 import b64encode
from typing import List, Tuple, Union
try:
import termios
import tty
except:
pass
import _frida
from frida_tools.application import ConsoleApplication
class PSApplication(ConsoleApplication):
def _add_options(self, parser: argparse.ArgumentParser) -> None:
parser.add_argument(
"-a",
"--applications",
help="list only applications",
action="store_true",
dest="list_only_applications",
default=False,
)
parser.add_argument(
"-i",
"--installed",
help="include all installed applications",
action="store_true",
dest="include_all_applications",
default=False,
)
parser.add_argument(
"-j",
"--json",
help="output results as JSON",
action="store_const",
dest="output_format",
const="json",
default="text",
)
def _initialize(self, parser: argparse.ArgumentParser, options: argparse.Namespace, args: List[str]) -> None:
if options.include_all_applications and not options.list_only_applications:
parser.error("-i cannot be used without -a")
self._list_only_applications = options.list_only_applications
self._include_all_applications = options.include_all_applications
self._output_format = options.output_format
self._terminal_type, self._icon_size = self._detect_terminal()
def _usage(self) -> str:
return "%(prog)s [options]"
def _start(self) -> None:
if self._list_only_applications:
self._list_applications()
else:
self._list_processes()
def _list_processes(self) -> None:
if self._output_format == "text" and self._terminal_type == "iterm2":
scope = "full"
else:
scope = "minimal"
try:
assert self._device is not None
processes = self._device.enumerate_processes(scope=scope)
except Exception as e:
self._update_status(f"Failed to enumerate processes: {e}")
self._exit(1)
return
if self._output_format == "text":
if len(processes) > 0:
pid_column_width = max(map(lambda p: len(str(p.pid)), processes))
icon_width = max(map(compute_icon_width, processes))
name_column_width = icon_width + max(map(lambda p: len(p.name), processes))
header_format = "%" + str(pid_column_width) + "s %s"
self._print(header_format % ("PID", "Name"))
self._print(f"{pid_column_width * '-'} {name_column_width * '-'}")
line_format = "%" + str(pid_column_width) + "d %s"
name_format = "%-" + str(name_column_width - icon_width) + "s"
for process in sorted(processes, key=functools.cmp_to_key(compare_processes)):
if icon_width != 0:
icons = process.parameters.get("icons", None)
if icons is not None:
icon = self._render_icon(icons[0])
else:
icon = " "
name = icon + " " + name_format % process.name
else:
name = name_format % process.name
self._print(line_format % (process.pid, name))
else:
self._log("error", "No running processes.")
elif self._output_format == "json":
result = []
for process in sorted(processes, key=functools.cmp_to_key(compare_processes)):
result.append({"pid": process.pid, "name": process.name})
self._print(json.dumps(result, sort_keys=False, indent=2))
self._exit(0)
def _list_applications(self) -> None:
if self._output_format == "text" and self._terminal_type == "iterm2":
scope = "full"
else:
scope = "minimal"
try:
assert self._device is not None
applications = self._device.enumerate_applications(scope=scope)
except Exception as e:
self._update_status(f"Failed to enumerate applications: {e}")
self._exit(1)
return
if not self._include_all_applications:
applications = list(filter(lambda app: app.pid != 0, applications))
if self._output_format == "text":
if len(applications) > 0:
pid_column_width = max(map(lambda app: len(str(app.pid)), applications))
icon_width = max(map(compute_icon_width, applications))
name_column_width = icon_width + max(map(lambda app: len(app.name), applications))
identifier_column_width = max(map(lambda app: len(app.identifier), applications))
header_format = (
"%"
+ str(pid_column_width)
+ "s "
+ "%-"
+ str(name_column_width)
+ "s "
+ "%-"
+ str(identifier_column_width)
+ "s"
)
self._print(header_format % ("PID", "Name", "Identifier"))
self._print(f"{pid_column_width * '-'} {name_column_width * '-'} {identifier_column_width * '-'}")
line_format = "%" + str(pid_column_width) + "s %s %-" + str(identifier_column_width) + "s"
name_format = "%-" + str(name_column_width - icon_width) + "s"
for app in sorted(applications, key=functools.cmp_to_key(compare_applications)):
if icon_width != 0:
icons = app.parameters.get("icons", None)
if icons is not None:
icon = self._render_icon(icons[0])
else:
icon = " "
name = icon + " " + name_format % app.name
else:
name = name_format % app.name
if app.pid == 0:
self._print(line_format % ("-", name, app.identifier))
else:
self._print(line_format % (app.pid, name, app.identifier))
elif self._include_all_applications:
self._log("error", "No installed applications.")
else:
self._log("error", "No running applications.")
elif self._output_format == "json":
result = []
if len(applications) > 0:
for app in sorted(applications, key=functools.cmp_to_key(compare_applications)):
result.append({"pid": (app.pid or None), "name": app.name, "identifier": app.identifier})
self._print(json.dumps(result, sort_keys=False, indent=2))
self._exit(0)
def _render_icon(self, icon) -> str:
return "\033]1337;File=inline=1;width={}px;height={}px;:{}\007".format(
self._icon_size, self._icon_size, b64encode(icon["image"]).decode("ascii")
)
def _detect_terminal(self) -> Tuple[str, int]:
icon_size = 0
if not self._have_terminal or self._plain_terminal or platform.system() != "Darwin":
return ("simple", icon_size)
fd = sys.stdin.fileno()
old_attributes = termios.tcgetattr(fd)
try:
tty.setraw(fd)
new_attributes = termios.tcgetattr(fd)
new_attributes[3] = new_attributes[3] & ~termios.ICANON & ~termios.ECHO
termios.tcsetattr(fd, termios.TCSANOW, new_attributes)
sys.stdout.write("\033[1337n")
sys.stdout.write("\033[5n")
sys.stdout.flush()
response = self._read_terminal_response("n")
if response not in ("0", "3"):
self._read_terminal_response("n")
if response.startswith("ITERM2 "):
version_tokens = response.split(" ", 1)[1].split(".", 2)
if len(version_tokens) >= 2 and int(version_tokens[0]) >= 3:
sys.stdout.write("\033[14t")
sys.stdout.flush()
height_in_pixels = int(self._read_terminal_response("t").split(";")[1])
sys.stdout.write("\033[18t")
sys.stdout.flush()
height_in_cells = int(self._read_terminal_response("t").split(";")[1])
icon_size = math.ceil((height_in_pixels / height_in_cells) * 1.77)
return ("iterm2", icon_size)
return ("simple", icon_size)
finally:
termios.tcsetattr(fd, termios.TCSANOW, old_attributes)
def _read_terminal_response(self, terminator: str) -> str:
sys.stdin.read(1)
sys.stdin.read(1)
result = ""
while True:
ch = sys.stdin.read(1)
if ch == terminator:
break
result += ch
return result
def compare_applications(a: _frida.Application, b: _frida.Application) -> int:
a_is_running = a.pid != 0
b_is_running = b.pid != 0
if a_is_running == b_is_running:
if a.name > b.name:
return 1
elif a.name < b.name:
return -1
else:
return 0
elif a_is_running:
return -1
else:
return 1
def compare_processes(a: _frida.Process, b: _frida.Process) -> int:
a_has_icon = "icons" in a.parameters
b_has_icon = "icons" in b.parameters
if a_has_icon == b_has_icon:
if a.name > b.name:
return 1
elif a.name < b.name:
return -1
else:
return 0
elif a_has_icon:
return -1
else:
return 1
def compute_icon_width(item: Union[_frida.Application, _frida.Process]) -> int:
for icon in item.parameters.get("icons", []):
if icon["format"] == "png":
return 4
return 0
app = PSApplication()
app.run()
if __name__ == "__main__":
try:
main()
except KeyboardInterrupt:
pass