-
-
Save hackcatml/2dfd671599d563488f742c96edc71b7b to your computer and use it in GitHub Desktop.
This issue occurred in the recently updated version of "com.android.art". |
The issue was resolved by applying the above patch.
But when spawning the app, the Failed to reach single-threaded state
error frequently occurred, causing the phone to soft reboot.
By referring to this commit, I modified frida-core/lib/payload/cloak.vala
as follows, and the issue was resolved.
diff --git a/lib/payload/cloak.vala b/lib/payload/cloak.vala
index f0ca3f5e..b2e703ee 100644
--- a/lib/payload/cloak.vala
+++ b/lib/payload/cloak.vala
@@ -1,3 +1,4 @@
+// https://github.com/frida/frida-core/blob/b1fab8470aaa2edb4606d740466cd1446ed88918/lib/payload/cloak.vala
namespace Frida {
public class ThreadIgnoreScope {
public enum Kind {
@@ -42,122 +43,101 @@ namespace Frida {
}
}
-#if ANDROID
+#if LINUX
public class ThreadCountCloaker : Object {
- private ReadFunc * read_slot;
- private static ReadFunc old_read_impl;
-
- private static string expected_magic = "%u (".printf (Posix.getpid ());
-
- [CCode (has_target = false)]
- private delegate ssize_t ReadFunc (int fd, void * buf, size_t count);
+ private ReadListener listener;
construct {
- Gum.Module.enumerate_imports ("libart.so", imp => {
- if (imp.name == "read") {
- read_slot = (ReadFunc *) imp.slot;
- return false;
- }
- return true;
- });
- if (read_slot != null)
- old_read_impl = update_read_slot (on_read);
+ listener = new ReadListener ();
+ Gum.Interceptor.obtain ().attach (
+ (void*) Gum.Module.find_export_by_name (Gum.Process.query_libc_name (), "read"),
+ listener);
}
~ThreadCountCloaker () {
- if (read_slot != null)
- update_read_slot (old_read_impl);
+ // Gum.Interceptor.obtain ().detach (listener);
}
- private ReadFunc update_read_slot (ReadFunc new_impl) {
- Gum.PageProtection old_prot = READ;
- Gum.Memory.query_protection (read_slot, out old_prot);
+ private class ReadListener : Object, Gum.InvocationListener {
+ private static string expected_magic = "%u (".printf (Posix.getpid ());
- bool is_writable = (old_prot & Gum.PageProtection.WRITE) != 0;
- if (!is_writable)
- Gum.mprotect (read_slot, sizeof (void *), old_prot | WRITE);
-
- ReadFunc old_impl = *read_slot;
- *read_slot = new_impl;
+ public void on_enter (Gum.InvocationContext context) {
+ Invocation * invocation = context.get_listener_invocation_data (sizeof (Invocation));
+ invocation.fd = (int) context.get_nth_argument (0);
+ invocation.buf = context.get_nth_argument (1);
+ invocation.count = (size_t) context.get_nth_argument (2);
+ }
- if (!is_writable)
- Gum.mprotect (read_slot, sizeof (void *), old_prot);
+ public void on_leave (Gum.InvocationContext context) {
+ var n = (ssize_t) context.get_return_value ();
+ if (n > 0) {
+ Invocation * invocation = context.get_listener_invocation_data (sizeof (Invocation));
+ if (file_content_might_be_from_proc_self_stat (invocation.buf, n)) {
+ try {
+ if (file_descriptor_is_proc_self_stat (invocation.fd)) {
+ unowned string raw_str = (string) invocation.buf;
+ string str = raw_str.substring (0, n);
+
+ MatchInfo info;
+ if (/^(\d+ \(.+\)(?: [^ ]+){17}) \d+ (.+)/s.match (str, 0, out info)) {
+ string fields_before = info.fetch (1);
+ string fields_after = info.fetch (2);
+
+ // We cannot simply use the value we got from the kernel and subtract the number of cloaked threads,
+ // as there's a chance the total may have changed in the last moment.
+ uint num_uncloaked_threads = query_num_uncloaked_threads ();
+
+ string adjusted_str = "%s %u %s".printf (fields_before, num_uncloaked_threads, fields_after);
+
+ var adjusted_length = adjusted_str.length;
+ if (adjusted_length <= invocation.count) {
+ Memory.copy (invocation.buf, adjusted_str, adjusted_length);
+ context.replace_return_value ((void *) adjusted_length);
+ }
+ }
+ }
+ } catch (FileError e) {
+ }
+ }
+ }
+ }
- return old_impl;
- }
+ private static bool file_content_might_be_from_proc_self_stat (void * content, ssize_t size) {
+ if (size < expected_magic.length)
+ return false;
+ if (Memory.cmp (content, expected_magic, expected_magic.length) != 0)
+ return false;
+ unowned string raw_str = (string) content;
+ return raw_str[size - 1] == '\n';
+ }
- private static ssize_t on_read (int fd, void * buf, size_t count) {
- var n = old_read_impl (fd, buf, count);
- if (n <= 0)
- return n;
+ private static bool file_descriptor_is_proc_self_stat (int fd) throws FileError {
+ string path = FileUtils.read_link ("/proc/self/fd/%d".printf (fd));
+ uint pid = Posix.getpid ();
+ return (path == "/proc/%u/stat".printf (pid)) ||
+ (path == "/proc/%u/task/%u/stat".printf (pid, pid));
+ }
- if (!file_content_might_be_from_proc_self_stat (buf, n))
+ private static uint query_num_uncloaked_threads () throws FileError {
+ uint n = 0;
+ var dir = Dir.open ("/proc/self/task");
+ string? name;
+ while ((name = dir.read_name ()) != null) {
+ var tid = uint.parse (name);
+ if (!Gum.Cloak.has_thread (tid))
+ n++;
+ }
return n;
-
- try {
- if (!file_descriptor_is_proc_self_stat (fd))
- return n;
-
- unowned string raw_str = (string) buf;
- string str = raw_str.substring (0, n);
-
- MatchInfo info;
- if (!/^(\d+ \(.+\)(?: [^ ]+){17}) \d+ (.+)/s.match (str, 0, out info))
- return n;
- string fields_before = info.fetch (1);
- string fields_after = info.fetch (2);
-
- // We cannot simply use the value we got from the kernel and subtract the number of cloaked threads,
- // as there's a chance the total may have changed in the last moment.
- uint num_uncloaked_threads = query_num_uncloaked_threads ();
-
- string adjusted_str = "%s %u %s".printf (fields_before, num_uncloaked_threads, fields_after);
-
- var adjusted_length = adjusted_str.length;
- if (adjusted_length > count)
- return n;
- Memory.copy (buf, adjusted_str, adjusted_length);
- n = adjusted_length;
- } catch (FileError e) {
}
- return n;
- }
-
- private static bool file_content_might_be_from_proc_self_stat (void * content, ssize_t size) {
- if (size < expected_magic.length)
- return false;
- if (Memory.cmp (content, expected_magic, expected_magic.length) != 0)
- return false;
- unowned string raw_str = (string) content;
- return raw_str[size - 1] == '\n';
- }
-
- private static bool file_descriptor_is_proc_self_stat (int fd) throws FileError {
- string path = FileUtils.read_link ("/proc/self/fd/%d".printf (fd));
- uint pid = Posix.getpid ();
- return (path == "/proc/%u/stat".printf (pid)) ||
- (path == "/proc/%u/task/%u/stat".printf (pid, pid));
- }
-
- private static uint query_num_uncloaked_threads () throws FileError {
- uint n = 0;
- var dir = Dir.open ("/proc/self/task");
- string? name;
- while ((name = dir.read_name ()) != null) {
- var tid = uint.parse (name);
- if (!Gum.Cloak.has_thread (tid))
- n++;
+ private struct Invocation {
+ public int fd;
+ public void * buf;
+ public size_t count;
}
- return n;
}
}
-#else
- public class ThreadCountCloaker : Object {
- }
-#endif
-#if LINUX
public class ThreadListCloaker : Object, DirListFilter {
private string our_dir_by_pid;
private DirListCloaker cloaker;
@@ -464,6 +444,9 @@ namespace Frida {
public abstract bool matches_file (string name);
}
#else
+ public class ThreadCountCloaker : Object {
+ }
+
public class ThreadListCloaker : Object {
}
@hackcatml Unfortunately, this has the same problem as the original PR. On certain phones (e.g. fully updated S21 Ultra, including the latest Google Play update), the function passed to Java.perform
never gets called. No exceptions are thrown but the frida script is basically useless :(
@hackcatml Unfortunately, this has the same problem as the original PR. On certain phones (e.g. fully updated S21 Ultra, including the latest Google Play update), the function passed to
Java.perform
never gets called. No exceptions are thrown but the frida script is basically useless :(
Try this.
Java.perform(function()
{
Java.deoptimizeEverything();
// Code
});
Java.deoptimizeEverything();
No change :(
It might be better to wait for the official Frida update.
Definitely, but it seems like nobody's working on it :( Ole said he doesn't have the time and everybody else that gave it a go seems a bit stuck...
Java.deoptimizeEverything();
No change :(
It seems fine to me (com.android.art@350820960). But Java hooking feels unstable. It might be better to wait for the official Frida update.
What phone are you using?
Pixel 4a, Android 13 with August 1 Google Play system update
I tried several phones, includ a Pixel 4a. The Pixel was the only one on which it worked.
The
frida-java-bridge/lib/android.js
file was modified by referring to this pull request.This android.js dynamically searches for the functions, allowing it to be used even with older versions of
com.android.art
.