From c3707294c06c6541bc11105fae6c87a9a9ec997f Mon Sep 17 00:00:00 2001 From: Your Name Date: Fri, 15 Mar 2024 17:53:56 +0000 Subject: [PATCH] Added cshell --- .gitmodules | 3 ++ inject/inject.vala | 52 ++++++++++++++++------ meson.build | 4 +- meson.options | 6 +++ src/cshell/cshell.resources | 2 + src/cshell/cshell.vala | 8 ++++ src/cshell/frida-cshell | 1 + src/cshell/generate-frida-cshell.py | 54 +++++++++++++++++++++++ src/cshell/meson.build | 67 +++++++++++++++++++++++++++++ src/meson.build | 4 ++ 10 files changed, 186 insertions(+), 15 deletions(-) create mode 100644 src/cshell/cshell.resources create mode 100644 src/cshell/cshell.vala create mode 160000 src/cshell/frida-cshell create mode 100755 src/cshell/generate-frida-cshell.py create mode 100644 src/cshell/meson.build diff --git a/.gitmodules b/.gitmodules index e2e8558ab..ecbc1f998 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "releng"] path = releng url = https://github.com/frida/releng.git +[submodule "src/cshell/frida-cshell"] + path = src/cshell/frida-cshell + url = https://github.com/WorksButNotTested/frida-cshell.git diff --git a/inject/inject.vala b/inject/inject.vala index 84042dda7..721780b29 100644 --- a/inject/inject.vala +++ b/inject/inject.vala @@ -7,6 +7,7 @@ namespace Frida.Inject { private static string? target_name; private static string? realm_str; private static string? script_path; + private static bool cshell; private static string? script_runtime_str; private static string? parameters_str; private static bool eternalize; @@ -21,6 +22,7 @@ namespace Frida.Inject { { "name", 'n', 0, OptionArg.STRING, ref target_name, "attach to NAME", "NAME" }, { "realm", 'r', 0, OptionArg.STRING, ref realm_str, "attach in REALM", "REALM" }, { "script", 's', 0, OptionArg.FILENAME, ref script_path, null, "JAVASCRIPT_FILENAME" }, + { "cshell", 'C', 0, OptionArg.NONE, ref cshell, "Load the C-Shell", null }, { "runtime", 'R', 0, OptionArg.STRING, ref script_runtime_str, "Script runtime to use", "qjs|v8" }, { "parameters", 'P', 0, OptionArg.STRING, ref parameters_str, "Parameters as JSON, same as Gadget", "PARAMETERS_JSON" }, { "eternalize", 'e', 0, OptionArg.NONE, ref eternalize, "Eternalize script and exit", null }, @@ -69,15 +71,24 @@ namespace Frida.Inject { } } - if (script_path == null || script_path == "") { - printerr ("Path to JavaScript file must be specified\n"); - return 4; - } - string? script_source = null; - if (script_path == "-") { + if (cshell) { + if (script_path != null && script_path != "") { + printerr ("Cannot specify both -s and -C options\n"); + return 4; + } script_path = null; - script_source = read_stdin (); + script_source = CShellScript.get_source (); + } else { + if (script_path == null || script_path == "") { + printerr ("Path to JavaScript file must be specified\n"); + return 5; + } + + if (script_path == "-") { + script_path = null; + script_source = read_stdin (); + } } ScriptRuntime script_runtime = DEFAULT; @@ -86,7 +97,7 @@ namespace Frida.Inject { script_runtime = ScriptRuntime.from_nick (script_runtime_str); } catch (Error e) { printerr ("%s\n", e.message); - return 5; + return 6; } } @@ -94,26 +105,31 @@ namespace Frida.Inject { if (parameters_str != null) { if (parameters_str == "") { printerr ("Parameters argument must be specified as JSON if present\n"); - return 6; + return 7; } try { var root = Json.from_string (parameters_str); if (root.get_node_type () != OBJECT) { printerr ("Failed to parse parameters argument as JSON: not an object\n"); - return 7; + return 8; } parameters.take_object (root.get_object ()); } catch (GLib.Error e) { printerr ("Failed to parse parameters argument as JSON: %s\n", e.message); - return 8; + return 9; } } if (interactive && eternalize) { printerr ("Cannot specify both -e and -i options\n"); - return 9; + return 10; + } + + if (cshell && eternalize) { + printerr ("Cannot specify both -e and -C options\n"); + return 11; } application = new Application (device_id, spawn_file, target_pid, target_name, options, script_path, script_source, @@ -249,7 +265,15 @@ namespace Frida.Inject { uint pid; if (spawn_file != null) { - pid = yield device.spawn (spawn_file, null, io_cancellable); + /* + * If frida-inject is supposed to be interactive, then don't connect any spawned + * child process to the TTY. + */ + var options = new SpawnOptions (); + if (interactive || cshell) { + options.stdio = PIPE; + } + pid = yield device.spawn (spawn_file, options, io_cancellable); } else if (target_name != null) { var proc = yield device.get_process_by_name (target_name, null, io_cancellable); pid = proc.pid; @@ -265,7 +289,7 @@ namespace Frida.Inject { yield r.start (); script_runner = r; - if (interactive) + if (interactive || cshell) watch_stdin (); if (spawn_file != null) { diff --git a/meson.build b/meson.build index 9f35d9392..95e024377 100644 --- a/meson.build +++ b/meson.build @@ -335,6 +335,7 @@ have_fruity_backend = get_option('fruity_backend').allowed() have_droidy_backend = get_option('droidy_backend').allowed() have_socket_backend = get_option('socket_backend').allowed() have_barebone_backend = quickjs_dep.found() +have_cshell_backend = get_option('cshell_backend').allowed() have_compiler_backend = get_option('compiler_backend') \ .disable_if(not have_local_backend, error_message: 'compiler backend requires the local backend for script execution') \ .disable_auto_if(host_os == 'watchos') \ @@ -371,7 +372,8 @@ foreach b : [['local', have_local_backend], ['droidy', have_droidy_backend], ['socket', have_socket_backend], ['barebone', have_barebone_backend], - ['compiler', have_compiler_backend]] + ['compiler', have_compiler_backend], + ['cshell', have_cshell_backend]] if b[1] vala_flags += '--define=HAVE_@0@_BACKEND'.format(b[0].to_upper()) endif diff --git a/meson.options b/meson.options index 16429c322..925a08826 100644 --- a/meson.options +++ b/meson.options @@ -28,6 +28,12 @@ option('barebone_backend', description: 'Include the Barebone backend', ) +option('cshell_backend', + type: 'feature', + value: 'auto', + description: 'Include the CShell backend', +) + option('compiler_backend', type: 'feature', value: 'auto', diff --git a/src/cshell/cshell.resources b/src/cshell/cshell.resources new file mode 100644 index 000000000..f1ab37687 --- /dev/null +++ b/src/cshell/cshell.resources @@ -0,0 +1,2 @@ +[resource-compiler] +namespace = Frida.Data.Cshell diff --git a/src/cshell/cshell.vala b/src/cshell/cshell.vala new file mode 100644 index 000000000..f67720c7a --- /dev/null +++ b/src/cshell/cshell.vala @@ -0,0 +1,8 @@ +namespace Frida { + public class CShellScript : Object { + public static string get_source () { + string runtime_js = (string) Frida.Data.Cshell.get_frida_cshell_js_blob ().data; + return runtime_js; + } + } +} diff --git a/src/cshell/frida-cshell b/src/cshell/frida-cshell new file mode 160000 index 000000000..58dd048d5 --- /dev/null +++ b/src/cshell/frida-cshell @@ -0,0 +1 @@ +Subproject commit 58dd048d5fbc5ec9db76e3d6b9742f7a12ee6978 diff --git a/src/cshell/generate-frida-cshell.py b/src/cshell/generate-frida-cshell.py new file mode 100755 index 000000000..d129d3af5 --- /dev/null +++ b/src/cshell/generate-frida-cshell.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python3 + +import os +from pathlib import Path +import platform +import shutil +from subprocess import PIPE, STDOUT, CalledProcessError +import subprocess +import sys + + +def generate_runtime(input_dir, output_dir): + output_dir.mkdir(parents=True, exist_ok=True) + + runtime_reldir = Path("frida-cshell") + runtime_srcdir = input_dir / runtime_reldir + runtime_intdir = output_dir / runtime_reldir + if runtime_intdir.exists(): + shutil.rmtree(runtime_intdir) + shutil.copytree(runtime_srcdir, runtime_intdir) + + npm = os.environ.get("NPM", make_script_filename("npm")) + try: + subprocess.run([npm, "install"], stdout=PIPE, stderr=STDOUT, cwd=runtime_intdir, check=True) + shutil.copy(runtime_intdir / "frida-cshell.js", output_dir) + except CalledProcessError as e: + message = "\n".join([ + "", + "***", + "Failed to bootstrap the CShell backend script runtime:", + "\t" + str(e), + "It appears Node.js is not installed.", + "We need it for processing JavaScript code at build-time.", + "Check PATH or set NPM to the absolute path of your npm binary.", + "***\n", + e.stdout.decode("utf-8") + ]) + raise EnvironmentError(message) + + +def make_script_filename(name): + build_os = platform.system().lower() + extension = ".cmd" if build_os == "windows" else "" + return name + extension + + +if __name__ == "__main__": + input_dir, output_dir = [Path(d).resolve() for d in sys.argv[1:3]] + + try: + generate_runtime(input_dir, output_dir) + except Exception as e: + print(e, file=sys.stderr) + sys.exit(1) diff --git a/src/cshell/meson.build b/src/cshell/meson.build new file mode 100644 index 000000000..606bcf6a8 --- /dev/null +++ b/src/cshell/meson.build @@ -0,0 +1,67 @@ +backend_sources += files( + 'cshell.vala', +) +cshell_script_runtime = custom_target('frida-cshell', + input: [ + 'frida-cshell/package.json', + 'frida-cshell/package-lock.json', + 'frida-cshell/tsconfig.json', + 'frida-cshell/src/char.ts', + 'frida-cshell/src/cmdlets/assembly.ts', + 'frida-cshell/src/cmdlets/bt.ts', + 'frida-cshell/src/cmdlets/copy.ts', + 'frida-cshell/src/cmdlets/dump.ts', + 'frida-cshell/src/cmdlets/exit.ts', + 'frida-cshell/src/cmdlets/help.ts', + 'frida-cshell/src/cmdlets/history.ts', + 'frida-cshell/src/cmdlets/math.ts', + 'frida-cshell/src/cmdlets/mod.ts', + 'frida-cshell/src/cmdlets/read.ts', + 'frida-cshell/src/cmdlets/sym.ts', + 'frida-cshell/src/cmdlets/thread.ts', + 'frida-cshell/src/cmdlets.ts', + 'frida-cshell/src/cmdlets/var.ts', + 'frida-cshell/src/cmdlets/vm.ts', + 'frida-cshell/src/cmdlets/write.ts', + 'frida-cshell/src/cmdlet.ts', + 'frida-cshell/src/command.ts', + 'frida-cshell/src/entrypoint.ts', + 'frida-cshell/src/history.ts', + 'frida-cshell/src/input.ts', + 'frida-cshell/src/line.ts', + 'frida-cshell/src/numeric.ts', + 'frida-cshell/src/output.ts', + 'frida-cshell/src/parser.ts', + 'frida-cshell/src/token.ts', + 'frida-cshell/src/util.ts', + 'frida-cshell/src/var.ts', + 'frida-cshell/src/vars.ts', + ], + output: [ + 'frida-cshell.js', + ], + command: [ + find_program('generate-frida-cshell.py'), + meson.current_source_dir(), + meson.current_build_dir(), + ], +) +backend_sources += custom_target('frida-data-cshell', + input: [ + 'cshell.resources', + cshell_script_runtime, + ], + output: [ + 'frida-data-cshell.vapi', + 'frida-data-cshell.h', + 'frida-data-cshell.c', + 'frida-data-cshell-blob.S', + ], + command: [ + resource_compiler, + '--toolchain=' + host_toolchain, + '-c', '@INPUT0@', + '-o', join_paths(meson.current_build_dir(), 'frida-data-cshell'), + '@INPUT1@', + ], +) \ No newline at end of file diff --git a/src/meson.build b/src/meson.build index dcf1c6d60..b4fe80f59 100644 --- a/src/meson.build +++ b/src/meson.build @@ -484,6 +484,10 @@ if have_barebone_backend subdir('barebone') endif +if have_cshell_backend + subdir('cshell') +endif + if host_os_family != 'windows' backend_vala_args_private += '--pkg=posix' endif