From 85229982a603d89fff48bcd780d183d4f548247a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole=20Andr=C3=A9=20Vadla=20Ravn=C3=A5s?= Date: Wed, 3 Aug 2022 05:43:00 -0400 Subject: [PATCH] session: Add support for the new snapshot APIs (#212) --- examples/snapshot.py | 36 +++++++++++++ frida/core.py | 4 ++ src/_frida.c | 119 ++++++++++++++++++++++++++++++++++++++----- 3 files changed, 146 insertions(+), 13 deletions(-) create mode 100644 examples/snapshot.py diff --git a/examples/snapshot.py b/examples/snapshot.py new file mode 100644 index 0000000..f64d912 --- /dev/null +++ b/examples/snapshot.py @@ -0,0 +1,36 @@ +import frida + + +embed_script = """ +const button = { + color: 'blue', +}; + +function mutateButton() { + button.color = 'red'; +} +""" + +warmup_script = """ +mutateButton(); +""" + +test_script = """ +console.log('Button before:', JSON.stringify(button)); +mutateButton(); +console.log('Button after:', JSON.stringify(button)); +""" + +runtime = "v8" + + +session = frida.attach(0) + +snapshot = session.snapshot_script(embed_script, warmup_script=warmup_script, runtime=runtime) + +def on_message(message, data): + print("on_message:", message) + +script = session.create_script(test_script, snapshot=snapshot, runtime=runtime) +script.on("message", on_message) +script.load() diff --git a/frida/core.py b/frida/core.py index ac4164d..dd95d2a 100644 --- a/frida/core.py +++ b/frida/core.py @@ -277,6 +277,10 @@ class Session(object): def compile_script(self, *args, **kwargs): return self._impl.compile_script(*args, **kwargs) + @cancellable + def snapshot_script(self, *args, **kwargs): + return self._impl.snapshot_script(*args, **kwargs) + @cancellable def setup_peer_connection(self, *args, **kwargs): self._impl.setup_peer_connection(*args, **kwargs) diff --git a/src/_frida.c b/src/_frida.c index d109c13..b2d8cfe 100644 --- a/src/_frida.c +++ b/src/_frida.c @@ -413,7 +413,11 @@ static PyObject * PySession_disable_child_gating (PySession * self); static PyObject * PySession_create_script (PySession * self, PyObject * args, PyObject * kw); static PyObject * PySession_create_script_from_bytes (PySession * self, PyObject * args, PyObject * kw); static PyObject * PySession_compile_script (PySession * self, PyObject * args, PyObject * kw); -static FridaScriptOptions * PySession_parse_script_options (const gchar * name, const gchar * runtime_value); +static PyObject * PySession_snapshot_script (PySession * self, PyObject * args, PyObject * kw); +static FridaScriptOptions * PySession_parse_script_options (const gchar * name, gconstpointer snapshot_data, gsize snapshot_size, + const gchar * runtime_value); +static PyObject * PySession_snapshot_script (PySession * self, PyObject * args, PyObject * kw); +static FridaSnapshotOptions * PySession_parse_snapshot_options (const gchar * warmup_script, const gchar * runtime_value); static PyObject * PySession_setup_peer_connection (PySession * self, PyObject * args, PyObject * kw); static FridaPeerOptions * PySession_parse_peer_options (const gchar * stun_server, PyObject * relays); static PyObject * PySession_join_portal (PySession * self, PyObject * args, PyObject * kw); @@ -613,6 +617,7 @@ static PyMethodDef PySession_methods[] = { "create_script", (PyCFunction) PySession_create_script, METH_VARARGS | METH_KEYWORDS, "Create a new script." }, { "create_script_from_bytes", (PyCFunction) PySession_create_script_from_bytes, METH_VARARGS | METH_KEYWORDS, "Create a new script from bytecode." }, { "compile_script", (PyCFunction) PySession_compile_script, METH_VARARGS | METH_KEYWORDS, "Compile script source code to bytecode." }, + { "snapshot_script", (PyCFunction) PySession_snapshot_script, METH_VARARGS | METH_KEYWORDS, "Evaluate script and snapshot the resulting VM state." }, { "setup_peer_connection", (PyCFunction) PySession_setup_peer_connection, METH_VARARGS | METH_KEYWORDS, "Set up a peer connection with the target process." }, { "join_portal", (PyCFunction) PySession_join_portal, METH_VARARGS | METH_KEYWORDS, "Join a portal." }, { NULL } @@ -4050,18 +4055,24 @@ static PyObject * PySession_create_script (PySession * self, PyObject * args, PyObject * kw) { PyObject * result = NULL; - static char * keywords[] = { "source", "name", "runtime", NULL }; + static char * keywords[] = { "source", "name", "snapshot", "runtime", NULL }; char * source; char * name = NULL; + gconstpointer snapshot_data = NULL; + Py_ssize_t snapshot_size = 0; const char * runtime_value = NULL; FridaScriptOptions * options; GError * error = NULL; FridaScript * handle; - if (!PyArg_ParseTupleAndKeywords (args, kw, "es|esz", keywords, "utf-8", &source, "utf-8", &name, &runtime_value)) +#if PY_MAJOR_VERSION >= 3 + if (!PyArg_ParseTupleAndKeywords (args, kw, "es|esy#z", keywords, "utf-8", &source, "utf-8", &name, &snapshot_data, &snapshot_size, &runtime_value)) +#else + if (!PyArg_ParseTupleAndKeywords (args, kw, "es|ess#z", keywords, "utf-8", &source, "utf-8", &name, &snapshot_data, &snapshot_size, &runtime_value)) +#endif return NULL; - options = PySession_parse_script_options (name, runtime_value); + options = PySession_parse_script_options (name, snapshot_data, snapshot_size, runtime_value); if (options == NULL) goto beach; @@ -4086,26 +4097,28 @@ static PyObject * PySession_create_script_from_bytes (PySession * self, PyObject * args, PyObject * kw) { PyObject * result = NULL; - static char * keywords[] = { "data", "name", "runtime", NULL }; + static char * keywords[] = { "data", "name", "snapshot", "runtime", NULL }; guint8 * data; Py_ssize_t size; char * name = NULL; + gconstpointer snapshot_data = NULL; + Py_ssize_t snapshot_size = 0; const char * runtime_value = NULL; GBytes * bytes; FridaScriptOptions * options; GError * error = NULL; FridaScript * handle; - #if PY_MAJOR_VERSION >= 3 - if (!PyArg_ParseTupleAndKeywords (args, kw, "y#|esz", keywords, &data, &size, "utf-8", &name, &runtime_value)) - #else - if (!PyArg_ParseTupleAndKeywords (args, kw, "s#|esz", keywords, &data, &size, "utf-8", &name, &runtime_value)) - #endif +#if PY_MAJOR_VERSION >= 3 + if (!PyArg_ParseTupleAndKeywords (args, kw, "y#|esy#z", keywords, &data, &size, "utf-8", &name, &snapshot_data, &snapshot_size, &runtime_value)) +#else + if (!PyArg_ParseTupleAndKeywords (args, kw, "s#|ess#z", keywords, &data, &size, "utf-8", &name, &snapshot_data, &snapshot_size, &runtime_value)) +#endif return NULL; bytes = g_bytes_new (data, size); - options = PySession_parse_script_options (name, runtime_value); + options = PySession_parse_script_options (name, snapshot_data, snapshot_size, runtime_value); if (options == NULL) goto beach; @@ -4141,7 +4154,7 @@ PySession_compile_script (PySession * self, PyObject * args, PyObject * kw) if (!PyArg_ParseTupleAndKeywords (args, kw, "es|esz", keywords, "utf-8", &source, "utf-8", &name, &runtime_value)) return NULL; - options = PySession_parse_script_options (name, runtime_value); + options = PySession_parse_script_options (name, NULL, 0, runtime_value); if (options == NULL) goto beach; @@ -4170,7 +4183,7 @@ beach: } static FridaScriptOptions * -PySession_parse_script_options (const gchar * name, const gchar * runtime_value) +PySession_parse_script_options (const gchar * name, gconstpointer snapshot_data, gsize snapshot_size, const gchar * runtime_value) { FridaScriptOptions * options; @@ -4179,6 +4192,13 @@ PySession_parse_script_options (const gchar * name, const gchar * runtime_value) if (name != NULL) frida_script_options_set_name (options, name); + if (snapshot_data != NULL) + { + GBytes * snapshot = g_bytes_new (snapshot_data, snapshot_size); + frida_script_options_set_snapshot (options, snapshot); + g_bytes_unref (snapshot); + } + if (runtime_value != NULL) { FridaScriptRuntime runtime; @@ -4199,6 +4219,79 @@ invalid_argument: } } +static PyObject * +PySession_snapshot_script (PySession * self, PyObject * args, PyObject * kw) +{ + PyObject * result = NULL; + static char * keywords[] = { "embed_script", "warmup_script", "runtime", NULL }; + char * embed_script; + char * warmup_script = NULL; + const char * runtime_value = NULL; + FridaSnapshotOptions * options; + GError * error = NULL; + GBytes * bytes; + + if (!PyArg_ParseTupleAndKeywords (args, kw, "es|esz", keywords, "utf-8", &embed_script, "utf-8", &warmup_script, &runtime_value)) + return NULL; + + options = PySession_parse_snapshot_options (warmup_script, runtime_value); + if (options == NULL) + goto beach; + + Py_BEGIN_ALLOW_THREADS + bytes = frida_session_snapshot_script_sync (PY_GOBJECT_HANDLE (self), embed_script, options, g_cancellable_get_current (), &error); + Py_END_ALLOW_THREADS + + if (error == NULL) + { + result = PyGObject_marshal_bytes_non_nullable (bytes); + + g_bytes_unref (bytes); + } + else + { + result = PyFrida_raise (error); + } + +beach: + g_clear_object (&options); + + PyMem_Free (warmup_script); + PyMem_Free (embed_script); + + return result; +} + +static FridaSnapshotOptions * +PySession_parse_snapshot_options (const gchar * warmup_script, const gchar * runtime_value) +{ + FridaSnapshotOptions * options; + + options = frida_snapshot_options_new (); + + if (warmup_script != NULL) + frida_snapshot_options_set_warmup_script (options, warmup_script); + + if (runtime_value != NULL) + { + FridaScriptRuntime runtime; + + if (!PyGObject_unmarshal_enum (runtime_value, FRIDA_TYPE_SCRIPT_RUNTIME, &runtime)) + goto invalid_argument; + + frida_snapshot_options_set_runtime (options, runtime); + } + + return options; + +invalid_argument: + { + g_object_unref (options); + + return NULL; + } +} + static PyObject * PySession_setup_peer_connection (PySession * self, PyObject * args, PyObject * kw) {