├── .DS_Store ├── .gitignore ├── README.md ├── fix_failed_to_reach_single_threaded_state.txt ├── main_ubuntu_android.py ├── main_macos_android.py └── main_macos_ios.py /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackcatml/ajeossida/HEAD/.DS_Store -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | assets 3 | ajeossida 4 | venv 5 | .DS_Store 6 | ios-assets 7 | frida-java-bridge 8 | frida-orig 9 | /test/ 10 | 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ajeossida (아저씨다) 2 | Frida with patches that definitively fix basic detection points on Android and iOS.
3 | Unfortunately, I discovered that the patches in several custom Frida builds for bypassing detections are incomplete and still detectable.
4 | For example, `frida_agent_main` in memory and the `gum-js-loop` thread name.
5 | Therefore, I created a Python build script to address these issues. 6 | 7 | Since this is a manual patch that doesn't automatically follow the Frida upstream,
8 | I will occasionally build it, verify that the patch works properly, and then release it. 9 | 10 | # Patches 11 | - Android 12 | - [x] No `frida_agent_main` in memory
13 | - [x] No `gum-js-loop, gmain, gdbus, frida-gadget` thread name in `/proc//task//status`
14 | - [x] No `libfrida-agent-raw.so` in linker's so list 15 | - [x] No libc hooking
16 | 17 | - iOS 18 | - [x] No `frida_agent_main` in memory
19 | - [x] No `gum-js-loop, gmain, gdbus, pool-frida, pool-spawner` thread name
20 | - [x] No `/usr/lib/frida/` 21 | - [x] No `exit, abort, task_threads` hooking
22 | 23 | # Run 24 | - MacOS
25 | Output: server, gadget (Android, iOS)
26 | `python3 main_macos.py` 27 | 28 | - Ubuntu 22.04
29 | Output: server, gagdet (Android)
30 | `python3 main_ubuntu.py` 31 | 32 | # MagiskAjeossida 33 | * A magisk module that automatically runs ajeossida-server on boot. 34 | * To run it in remote mode, use the following command. It will listen on `0.0.0.0:45678`. 35 | `adb shell "su -c sed -i 's/REMOTE=0/REMOTE=1/' /data/adb/modules/magisk_ajeossida/service.sh"` 36 | * You can attach Frida to a pairipcore protected app using this module. 37 | However, the app will crash after a few seconds. Bypassing the crash is up to you. (Spawning the app also causes it to crash) 38 | 39 | # Contact 40 | - Channel: https://t.me/hackcatml1 41 | - Chat: https://t.me/hackcatmlchat 42 | 43 | # References 44 | - [strongR-frida-android](https://github.com/hzzheyang/strongR-frida-android)
45 | - [Florida](https://github.com/Ylarod/Florida) 46 | - [magisk-frida](https://github.com/ViRb3/magisk-frida) 47 | 48 | -------------------------------------------------------------------------------- /fix_failed_to_reach_single_threaded_state.txt: -------------------------------------------------------------------------------- 1 | // https://github.com/frida/frida-core/blob/b1fab8470aaa2edb4606d740466cd1446ed88918/lib/payload/cloak.vala 2 | namespace Frida { 3 | public class ThreadIgnoreScope { 4 | public enum Kind { 5 | APPLICATION_THREAD, 6 | FRIDA_THREAD 7 | } 8 | 9 | private Kind kind; 10 | 11 | private Gum.Interceptor interceptor; 12 | 13 | private Gum.ThreadId thread_id; 14 | 15 | private uint num_ranges; 16 | private Gum.MemoryRange ranges[2]; 17 | 18 | public ThreadIgnoreScope (Kind kind) { 19 | this.kind = kind; 20 | 21 | interceptor = Gum.Interceptor.obtain (); 22 | interceptor.ignore_current_thread (); 23 | 24 | if (kind == FRIDA_THREAD) { 25 | thread_id = Gum.Process.get_current_thread_id (); 26 | Gum.Cloak.add_thread (thread_id); 27 | 28 | num_ranges = Gum.Thread.try_get_ranges (ranges); 29 | for (var i = 0; i != num_ranges; i++) 30 | Gum.Cloak.add_range (ranges[i]); 31 | } 32 | } 33 | 34 | ~ThreadIgnoreScope () { 35 | if (kind == FRIDA_THREAD) { 36 | for (var i = 0; i != num_ranges; i++) 37 | Gum.Cloak.remove_range (ranges[i]); 38 | 39 | Gum.Cloak.remove_thread (thread_id); 40 | } 41 | 42 | interceptor.unignore_current_thread (); 43 | } 44 | } 45 | 46 | #if LINUX 47 | public class ThreadCountCloaker : Object { 48 | private ReadListener listener; 49 | 50 | construct { 51 | listener = new ReadListener (); 52 | Gum.Interceptor.obtain ().attach ( 53 | (void*) Gum.Module.find_export_by_name (Gum.Process.query_libc_name (), "read"), 54 | listener); 55 | } 56 | 57 | ~ThreadCountCloaker () { 58 | // Gum.Interceptor.obtain ().detach (listener); 59 | } 60 | 61 | private class ReadListener : Object, Gum.InvocationListener { 62 | private static string expected_magic = "%u (".printf (Posix.getpid ()); 63 | 64 | public void on_enter (Gum.InvocationContext context) { 65 | Invocation * invocation = context.get_listener_invocation_data (sizeof (Invocation)); 66 | invocation.fd = (int) context.get_nth_argument (0); 67 | invocation.buf = context.get_nth_argument (1); 68 | invocation.count = (size_t) context.get_nth_argument (2); 69 | } 70 | 71 | public void on_leave (Gum.InvocationContext context) { 72 | var n = (ssize_t) context.get_return_value (); 73 | if (n > 0) { 74 | Invocation * invocation = context.get_listener_invocation_data (sizeof (Invocation)); 75 | if (file_content_might_be_from_proc_self_stat (invocation.buf, n)) { 76 | try { 77 | if (file_descriptor_is_proc_self_stat (invocation.fd)) { 78 | unowned string raw_str = (string) invocation.buf; 79 | string str = raw_str.substring (0, n); 80 | 81 | MatchInfo info; 82 | if (/^(\d+ \(.+\)(?: [^ ]+){17}) \d+ (.+)/s.match (str, 0, out info)) { 83 | string fields_before = info.fetch (1); 84 | string fields_after = info.fetch (2); 85 | 86 | // We cannot simply use the value we got from the kernel and subtract the number of cloaked threads, 87 | // as there's a chance the total may have changed in the last moment. 88 | uint num_uncloaked_threads = query_num_uncloaked_threads (); 89 | 90 | string adjusted_str = "%s %u %s".printf (fields_before, num_uncloaked_threads, fields_after); 91 | 92 | var adjusted_length = adjusted_str.length; 93 | if (adjusted_length <= invocation.count) { 94 | Memory.copy (invocation.buf, adjusted_str, adjusted_length); 95 | context.replace_return_value ((void *) adjusted_length); 96 | } 97 | } 98 | } 99 | } catch (FileError e) { 100 | } 101 | } 102 | } 103 | } 104 | 105 | private static bool file_content_might_be_from_proc_self_stat (void * content, ssize_t size) { 106 | if (size < expected_magic.length) 107 | return false; 108 | if (Memory.cmp (content, expected_magic, expected_magic.length) != 0) 109 | return false; 110 | unowned string raw_str = (string) content; 111 | return raw_str[size - 1] == '\n'; 112 | } 113 | 114 | private static bool file_descriptor_is_proc_self_stat (int fd) throws FileError { 115 | string path = FileUtils.read_link ("/proc/self/fd/%d".printf (fd)); 116 | uint pid = Posix.getpid (); 117 | return (path == "/proc/%u/stat".printf (pid)) || 118 | (path == "/proc/%u/task/%u/stat".printf (pid, pid)); 119 | } 120 | 121 | private static uint query_num_uncloaked_threads () throws FileError { 122 | uint n = 0; 123 | var dir = Dir.open ("/proc/self/task"); 124 | string? name; 125 | while ((name = dir.read_name ()) != null) { 126 | var tid = uint.parse (name); 127 | if (!Gum.Cloak.has_thread (tid)) 128 | n++; 129 | } 130 | return n; 131 | } 132 | 133 | private struct Invocation { 134 | public int fd; 135 | public void * buf; 136 | public size_t count; 137 | } 138 | } 139 | } 140 | 141 | public class ThreadListCloaker : Object, DirListFilter { 142 | private string our_dir_by_pid; 143 | private DirListCloaker cloaker; 144 | 145 | construct { 146 | our_dir_by_pid = "/proc/%u/task".printf (Posix.getpid ()); 147 | cloaker = new DirListCloaker (this); 148 | } 149 | 150 | private bool matches_directory (string path) { 151 | return path == "/proc/self/task" || path == our_dir_by_pid; 152 | } 153 | 154 | private bool matches_file (string name) { 155 | var tid = (Gum.ThreadId) uint64.parse (name); 156 | return Gum.Cloak.has_thread (tid); 157 | } 158 | } 159 | 160 | public class FDListCloaker : Object, DirListFilter { 161 | private string our_dir_by_pid; 162 | private DirListCloaker cloaker; 163 | 164 | construct { 165 | our_dir_by_pid = "/proc/%u/fd".printf (Posix.getpid ()); 166 | cloaker = new DirListCloaker (this); 167 | } 168 | 169 | private bool matches_directory (string path) { 170 | return path == "/proc/self/fd" || path == our_dir_by_pid; 171 | } 172 | 173 | private bool matches_file (string name) { 174 | var fd = int.parse (name); 175 | return Gum.Cloak.has_file_descriptor (fd); 176 | } 177 | } 178 | 179 | private class DirListCloaker : Object { 180 | public weak DirListFilter filter { 181 | get; 182 | construct; 183 | } 184 | 185 | private Gee.HashSet listeners = new Gee.HashSet (); 186 | private Gee.HashSet tracked_handles = new Gee.HashSet (); 187 | 188 | public DirListCloaker (DirListFilter filter) { 189 | Object (filter: filter); 190 | } 191 | 192 | construct { 193 | var interceptor = Gum.Interceptor.obtain (); 194 | 195 | unowned string libc = Gum.Process.query_libc_name (); 196 | 197 | var open_listener = new OpenDirListener (this); 198 | listeners.add (open_listener); 199 | interceptor.attach ((void *) Gum.Module.find_export_by_name (libc, "opendir"), open_listener); 200 | 201 | var close_listener = new CloseDirListener (this); 202 | listeners.add (close_listener); 203 | interceptor.attach ((void *) Gum.Module.find_export_by_name (libc, "closedir"), close_listener); 204 | 205 | var readdir_impl = Gum.Module.find_export_by_name (libc, "readdir"); 206 | var readdir_listener = new ReadDirListener (this, LEGACY); 207 | listeners.add (readdir_listener); 208 | interceptor.attach ((void *) readdir_impl, readdir_listener); 209 | 210 | var readdir64_impl = Gum.Module.find_export_by_name (libc, "readdir64"); 211 | if (readdir64_impl != 0 && readdir64_impl != readdir_impl) { 212 | var listener = new ReadDirListener (this, MODERN); 213 | listeners.add (listener); 214 | interceptor.attach ((void *) readdir64_impl, listener); 215 | } 216 | 217 | var readdir_r_impl = Gum.Module.find_export_by_name (libc, "readdir_r"); 218 | var readdir_r_listener = new ReadDirRListener (this, LEGACY); 219 | listeners.add (readdir_r_listener); 220 | interceptor.attach ((void *) readdir_r_impl, readdir_r_listener); 221 | 222 | var readdir64_r_impl = Gum.Module.find_export_by_name (libc, "readdir64_r"); 223 | if (readdir64_r_impl != 0 && readdir64_r_impl != readdir_r_impl) { 224 | var listener = new ReadDirRListener (this, MODERN); 225 | listeners.add (listener); 226 | interceptor.attach ((void *) readdir64_r_impl, listener); 227 | } 228 | } 229 | 230 | ~DirListCloaker () { 231 | var interceptor = Gum.Interceptor.obtain (); 232 | 233 | foreach (var listener in listeners) 234 | interceptor.detach (listener); 235 | } 236 | 237 | public void start_tracking (Posix.Dir handle) { 238 | lock (tracked_handles) 239 | tracked_handles.add (handle); 240 | } 241 | 242 | public void stop_tracking (Posix.Dir handle) { 243 | lock (tracked_handles) 244 | tracked_handles.remove (handle); 245 | } 246 | 247 | public bool is_tracking (Posix.Dir handle) { 248 | lock (tracked_handles) 249 | return tracked_handles.contains (handle); 250 | } 251 | 252 | private class OpenDirListener : Object, Gum.InvocationListener { 253 | public weak DirListCloaker parent { 254 | get; 255 | construct; 256 | } 257 | 258 | public OpenDirListener (DirListCloaker parent) { 259 | Object (parent: parent); 260 | } 261 | 262 | public void on_enter (Gum.InvocationContext context) { 263 | Invocation * invocation = context.get_listener_invocation_data (sizeof (Invocation)); 264 | 265 | invocation.path = (string *) context.get_nth_argument (0); 266 | } 267 | 268 | public void on_leave (Gum.InvocationContext context) { 269 | Invocation * invocation = context.get_listener_invocation_data (sizeof (Invocation)); 270 | if (!parent.filter.matches_directory (invocation.path)) 271 | return; 272 | 273 | unowned Posix.Dir? handle = (Posix.Dir?) context.get_return_value (); 274 | if (handle != null) 275 | parent.start_tracking (handle); 276 | } 277 | 278 | private struct Invocation { 279 | public string * path; 280 | } 281 | } 282 | 283 | private class CloseDirListener : Object, Gum.InvocationListener { 284 | public weak DirListCloaker parent { 285 | get; 286 | construct; 287 | } 288 | 289 | public CloseDirListener (DirListCloaker parent) { 290 | Object (parent: parent); 291 | } 292 | 293 | public void on_enter (Gum.InvocationContext context) { 294 | unowned Posix.Dir? handle = (Posix.Dir?) context.get_nth_argument (0); 295 | if (handle != null) 296 | parent.stop_tracking (handle); 297 | } 298 | } 299 | 300 | private class ReadDirListener : Object, Gum.InvocationListener { 301 | public weak DirListCloaker parent { 302 | get; 303 | construct; 304 | } 305 | 306 | public DirEntKind kind { 307 | get; 308 | construct; 309 | } 310 | 311 | public ReadDirListener (DirListCloaker parent, DirEntKind kind) { 312 | Object (parent: parent, kind: kind); 313 | } 314 | 315 | public void on_enter (Gum.InvocationContext context) { 316 | Invocation * invocation = context.get_listener_invocation_data (sizeof (Invocation)); 317 | invocation.handle = (Posix.Dir?) context.get_nth_argument (0); 318 | } 319 | 320 | public void on_leave (Gum.InvocationContext context) { 321 | Invocation * invocation = context.get_listener_invocation_data (sizeof (Invocation)); 322 | if (!parent.is_tracking (invocation.handle)) 323 | return; 324 | 325 | var entry = context.get_return_value (); 326 | do { 327 | if (entry == null) 328 | return; 329 | 330 | var name = parse_dirent_name (entry, kind); 331 | 332 | if (name == "." || name == "..") 333 | return; 334 | 335 | if (!parent.filter.matches_file (name)) 336 | return; 337 | 338 | var impl = (ReadDirFunc) context.function; 339 | entry = impl (invocation.handle); 340 | 341 | context.replace_return_value (entry); 342 | } while (true); 343 | } 344 | 345 | private struct Invocation { 346 | public unowned Posix.Dir? handle; 347 | } 348 | 349 | [CCode (has_target = false)] 350 | private delegate void * ReadDirFunc (Posix.Dir dir); 351 | } 352 | 353 | private class ReadDirRListener : Object, Gum.InvocationListener { 354 | public weak DirListCloaker parent { 355 | get; 356 | construct; 357 | } 358 | 359 | public DirEntKind kind { 360 | get; 361 | construct; 362 | } 363 | 364 | public ReadDirRListener (DirListCloaker parent, DirEntKind kind) { 365 | Object (parent: parent, kind: kind); 366 | } 367 | 368 | public void on_enter (Gum.InvocationContext context) { 369 | Invocation * invocation = context.get_listener_invocation_data (sizeof (Invocation)); 370 | invocation.handle = (Posix.Dir?) context.get_nth_argument (0); 371 | invocation.entry = context.get_nth_argument (1); 372 | invocation.result = context.get_nth_argument (2); 373 | } 374 | 375 | public void on_leave (Gum.InvocationContext context) { 376 | Invocation * invocation = context.get_listener_invocation_data (sizeof (Invocation)); 377 | if (!parent.is_tracking (invocation.handle)) 378 | return; 379 | 380 | var result = (int) context.get_return_value (); 381 | do { 382 | if (result != 0) 383 | return; 384 | 385 | if (*invocation.result == null) 386 | return; 387 | 388 | var name = parse_dirent_name (*invocation.result, kind); 389 | 390 | if (name == "." || name == "..") 391 | return; 392 | 393 | if (!parent.filter.matches_file (name)) 394 | return; 395 | 396 | var impl = (ReadDirRFunc) context.function; 397 | result = impl (invocation.handle, invocation.entry, invocation.result); 398 | 399 | context.replace_return_value ((void *) result); 400 | } while (true); 401 | } 402 | 403 | private struct Invocation { 404 | public unowned Posix.Dir? handle; 405 | public void * entry; 406 | public void ** result; 407 | } 408 | 409 | [CCode (has_target = false)] 410 | private delegate int ReadDirRFunc (Posix.Dir dir, void * entry, void ** result); 411 | } 412 | 413 | private static unowned string parse_dirent_name (void * entry, DirEntKind kind) { 414 | unowned string? name = null; 415 | 416 | if (kind == LEGACY) { 417 | unowned Posix.DirEnt ent = (Posix.DirEnt) entry; 418 | name = (string) ent.d_name; 419 | } else if (kind == MODERN) { 420 | unowned DirEnt64 ent = (DirEnt64) entry; 421 | name = (string) ent.d_name; 422 | } 423 | 424 | return name; 425 | } 426 | 427 | private enum DirEntKind { 428 | LEGACY, 429 | MODERN 430 | } 431 | } 432 | 433 | [Compact] 434 | public class DirEnt64 { 435 | public uint64 d_ino; 436 | public int64 d_off; 437 | public uint16 d_reclen; 438 | public uint8 d_type; 439 | public char d_name[256]; 440 | } 441 | 442 | public interface DirListFilter : Object { 443 | public abstract bool matches_directory (string path); 444 | public abstract bool matches_file (string name); 445 | } 446 | #else 447 | public class ThreadCountCloaker : Object { 448 | } 449 | 450 | public class ThreadListCloaker : Object { 451 | } 452 | 453 | public class FDListCloaker : Object { 454 | } 455 | #endif 456 | } -------------------------------------------------------------------------------- /main_ubuntu_android.py: -------------------------------------------------------------------------------- 1 | import gzip 2 | import os 3 | import shutil 4 | import subprocess 5 | import sys 6 | import requests 7 | 8 | CUSTOM_NAME = "ajeossida" 9 | 10 | # Temporarily fixing the issue 'Failed to reach single-threaded state', Process.enumerateThreads() crash 11 | TEMP = 0 12 | 13 | 14 | def run_command(command, cwd=None): 15 | try: 16 | result = subprocess.run(command, shell=True, cwd=cwd, check=True, text=True) 17 | return result.returncode 18 | except subprocess.CalledProcessError as e: 19 | print(f"Error while running command: {command}\nError: {e}") 20 | sys.exit(1) 21 | 22 | 23 | def git_clone_repo(): 24 | repo_url = "https://github.com/frida/frida.git" 25 | destination_dir = os.path.join(os.getcwd(), CUSTOM_NAME) 26 | 27 | print(f"\n[*] Cloning repository {repo_url} to {destination_dir}...") 28 | run_command(f"git clone --recurse-submodules {repo_url} {destination_dir}") 29 | 30 | 31 | def download_ndk(): 32 | url = "https://dl.google.com/android/repository/android-ndk-r25c-linux.zip" 33 | file_name = "android-ndk-r25c-linux.zip" 34 | unzip_dir = "android-ndk-r25c" 35 | 36 | print(f"\n[*] Downloading {file_name}...") 37 | response = requests.get(url, stream=True) 38 | with open(file_name, 'wb') as file: 39 | for chunk in response.iter_content(chunk_size=128): 40 | file.write(chunk) 41 | print(f"\n[*] Downloaded {file_name}") 42 | 43 | run_command(f"unzip {file_name} >/dev/null") 44 | 45 | os.remove(file_name) 46 | 47 | return unzip_dir 48 | 49 | 50 | def configure_build(ndk_path, arch): 51 | build_dir = os.path.join(os.getcwd(), CUSTOM_NAME, arch) 52 | os.makedirs(build_dir, exist_ok=True) 53 | 54 | os.environ['ANDROID_NDK_ROOT'] = ndk_path 55 | 56 | result = run_command(f"{os.path.join('..', 'configure')} --host={arch}", cwd=build_dir) 57 | 58 | if result == 0: 59 | return build_dir 60 | else: 61 | print("\n[!] Failed to configure") 62 | sys.exit(1) 63 | 64 | 65 | def build(build_dir): 66 | run_command("make", cwd=build_dir) 67 | 68 | 69 | def replace_strings_in_files(directory, search_string, replace_string): 70 | if os.path.isfile(directory): 71 | file_path = directory 72 | with open(file_path, 'r+', encoding='utf-8') as file: 73 | content = file.read() 74 | if search_string in content: 75 | print(f"Patch {file.name}") 76 | patched_content = content.replace(search_string, replace_string) 77 | file.seek(0) 78 | file.write(patched_content) 79 | file.truncate() 80 | else: 81 | for root, dirs, files in os.walk(directory): 82 | for file_name in files: 83 | file_path = os.path.join(root, file_name) 84 | try: 85 | with open(file_path, 'r+', encoding='utf-8') as file: 86 | content = file.read() 87 | if search_string in content: 88 | print(f"Patch {file.name}") 89 | patched_content = content.replace(search_string, replace_string) 90 | file.seek(0) 91 | file.write(patched_content) 92 | file.truncate() 93 | except Exception as e: 94 | pass 95 | 96 | 97 | def compress_file(file_path): 98 | try: 99 | # Create a .gz file from the original file 100 | with open(file_path, 'rb') as f_in: 101 | with gzip.open(file_path + '.gz', 'wb') as f_out: 102 | shutil.copyfileobj(f_in, f_out) 103 | print(f"[*] Compressed {file_path} to {file_path}.gz") 104 | except Exception as e: 105 | print(f"[!] Error compressing {file_path}: {e}") 106 | 107 | 108 | def capitalize_first_lower_char(word): 109 | for index, char in enumerate(word): 110 | if char.islower(): 111 | # Replace the first lowercase character with its uppercase equivalent 112 | return word[:index] + char.upper() + word[index + 1:] 113 | return word 114 | 115 | 116 | def fix_failed_to_reach_single_threaded_state(custom_dir): 117 | old_cloak_vala_path = os.path.join(custom_dir, "subprojects/frida-core/lib/payload/cloak.vala") 118 | new_cloak_vala_path = os.path.join(os.getcwd(), "fix_failed_to_reach_single_threaded_state.txt") 119 | os.remove(old_cloak_vala_path) 120 | shutil.copy(new_cloak_vala_path, old_cloak_vala_path) 121 | 122 | 123 | def fix_process_enumerate_threads_crash(custom_dir): 124 | # On the Pixel 4a, if there is a thread named perfetto_hprof_, Process.enumerateThreads() crashes with SEGV_ACCERR 125 | gumprocess_linux_path = os.path.join(custom_dir, "subprojects/frida-gum/gum/backend-linux/gumprocess-linux.c") 126 | replace_strings_in_files(gumprocess_linux_path, 127 | ' details.name = thread_name;', 128 | ' details.name = thread_name;\n' 129 | ' if (strcmp(details.name, "perfetto_hprof_") == 0)\n' 130 | ' continue;') 131 | 132 | 133 | def main(): 134 | custom_dir = os.path.join(os.getcwd(), CUSTOM_NAME) 135 | if os.path.exists(custom_dir): 136 | print(f"\n[*] Cleaning {custom_dir}...") 137 | shutil.rmtree(custom_dir) 138 | 139 | assets_dir = os.path.join(os.getcwd(), "assets") 140 | if os.path.exists(assets_dir): 141 | print(f"\n[*] Cleaning {assets_dir}...") 142 | shutil.rmtree(assets_dir) 143 | os.mkdir(assets_dir) 144 | 145 | git_clone_repo() 146 | 147 | ndk_path = os.path.join(os.getcwd(), download_ndk()) 148 | 149 | architectures = ["android-arm64", "android-arm", "android-x86_64", "android-x86"] 150 | if TEMP == 1: 151 | architectures = ["android-arm64"] 152 | build_dirs = [configure_build(ndk_path, arch) for arch in architectures] 153 | 154 | # libfrida-agent-raw.so patch 155 | print(f"\n[*] Patch 'libfrida-agent-raw.so' with 'lib{CUSTOM_NAME}-agent-raw.so' recursively...") 156 | patch_string = "libfrida-agent-raw.so" 157 | replace_strings_in_files(custom_dir, 158 | patch_string, 159 | patch_string.replace("frida", CUSTOM_NAME)) 160 | 161 | # re.frida.server patch 162 | print(f"\n[*] Patch 're.frida.server' with 're.{CUSTOM_NAME}.server' recursively...") 163 | patch_string = "re.frida.server" 164 | replace_strings_in_files(custom_dir, 165 | patch_string, 166 | patch_string.replace("frida", CUSTOM_NAME)) 167 | 168 | # frida-helper patch 169 | print(f"\n[*] Patch 'frida-helper' with '{CUSTOM_NAME}-helper' recursively...") 170 | patch_strings = ["frida-helper-32", "frida-helper-64", "get_frida_helper_", "\"/frida-\""] 171 | for patch_string in patch_strings: 172 | replace_strings_in_files(custom_dir, 173 | patch_string, 174 | patch_string.replace("frida", CUSTOM_NAME)) 175 | 176 | # frida-agent patch 177 | print(f"\n[*] Patch 'frida-agent' with '{CUSTOM_NAME}-agent' recursively...") 178 | patch_strings = ["frida-agent-", "\"agent\" / \"frida-agent.", "\'frida-agent\'", "\"frida-agent\"", 179 | "get_frida_agent_", "\'FridaAgent\'"] 180 | for patch_string in patch_strings: 181 | replace_strings_in_files(custom_dir, 182 | patch_string, 183 | patch_string.replace("Frida", capitalize_first_lower_char( 184 | CUSTOM_NAME)) if "Frida" in patch_string else 185 | patch_string.replace("frida", CUSTOM_NAME)) 186 | 187 | # Patch the original file back, which has incorrectly patched strings. 188 | wrong_patch_strings = [f'{CUSTOM_NAME}-agent-x86.symbols', f'{CUSTOM_NAME}-agent-android.version'] 189 | for wrong_patch_string in wrong_patch_strings: 190 | replace_strings_in_files(custom_dir, 191 | wrong_patch_string, 192 | wrong_patch_string.replace(CUSTOM_NAME, 'frida')) 193 | 194 | # memfd_create patch, memfd:ajeossida-agent-64.so --> memfd:jit-cache 195 | print(f"\n[*] Patch MemoryFileDescriptor.memfd_create") 196 | frida_helper_backend_path = os.path.join(custom_dir, 197 | "subprojects/frida-core/src/linux/frida-helper-backend.vala") 198 | replace_strings_in_files(frida_helper_backend_path, 199 | 'return Linux.syscall (SysCall.memfd_create, name, flags);', 200 | 'return Linux.syscall (SysCall.memfd_create, \"jit-cache\", flags);') 201 | 202 | # frida-server patch 203 | print(f"\n[*] Patch 'frida-server' with '{CUSTOM_NAME}-server' recursively...") 204 | frida_server_meson_path = os.path.join(custom_dir, "subprojects/frida-core/server/meson.build") 205 | patch_strings = ["frida-server-raw", "\'frida-server\'", "\"frida-server\"", "frida-server-universal"] 206 | for patch_string in patch_strings: 207 | replace_strings_in_files(frida_server_meson_path, 208 | patch_string, 209 | patch_string.replace("frida", CUSTOM_NAME)) 210 | 211 | frida_core_compat_build_path = os.path.join(custom_dir, "subprojects/frida-core/compat/build.py") 212 | patch_string = "frida-server" 213 | replace_strings_in_files(frida_core_compat_build_path, 214 | patch_string, 215 | patch_string.replace("frida", CUSTOM_NAME)) 216 | 217 | # frida-gadget patch 218 | print(f"\n[*] Patch 'frida-gadget' with '{CUSTOM_NAME}-gadget' recursively...") 219 | patch_strings = ["\"frida-gadget\"", "\"frida-gadget-tcp", "\"frida-gadget-unix"] 220 | for patch_string in patch_strings: 221 | replace_strings_in_files(custom_dir, 222 | patch_string, 223 | patch_string.replace("frida", CUSTOM_NAME)) 224 | 225 | frida_core_meson_path = os.path.join(custom_dir, "subprojects/frida-core/meson.build") 226 | patch_string = "gadget_name = 'frida-gadget' + shlib_suffix" 227 | replace_strings_in_files(frida_core_meson_path, 228 | patch_string, 229 | patch_string.replace("frida", CUSTOM_NAME)) 230 | 231 | frida_core_compat_build_py_path = os.path.join(custom_dir, "subprojects/frida-core/compat/build.py") 232 | patch_string = "frida-gadget" 233 | replace_strings_in_files(frida_core_compat_build_py_path, 234 | patch_string, 235 | patch_string.replace("frida", CUSTOM_NAME)) 236 | 237 | frida_gadget_meson_path = os.path.join(custom_dir, "subprojects/frida-core/lib/gadget/meson.build") 238 | patch_strings = ["frida-gadget-modulated", "libfrida-gadget-modulated", "frida-gadget-raw", "\'frida-gadget\'", 239 | "frida-gadget-universal", "FridaGadget.dylib"] 240 | for patch_string in patch_strings: 241 | replace_strings_in_files(frida_gadget_meson_path, 242 | patch_string, 243 | patch_string.replace("Frida", capitalize_first_lower_char( 244 | CUSTOM_NAME)) if "Frida" in patch_string else 245 | patch_string.replace("frida", CUSTOM_NAME)) 246 | 247 | # gum-js-loop patch 248 | print(f"\n[*] Patch 'gum-js-loop' with '{CUSTOM_NAME}-js-loop' recursively...") 249 | patch_string = "\"gum-js-loop\"" 250 | replace_strings_in_files(custom_dir, 251 | patch_string, 252 | patch_string.replace("gum", CUSTOM_NAME)) 253 | 254 | # pool-frida patch 255 | print(f"\n[*] Patch 'pool-frida' with 'pool-{CUSTOM_NAME}' recursively...") 256 | patch_string = "g_set_prgname (\"frida\");" 257 | replace_strings_in_files(custom_dir, 258 | patch_string, 259 | patch_string.replace("frida", CUSTOM_NAME)) 260 | 261 | # No libc.so libsystem_c.dylib hooking 262 | print(f"\n[*] Patch not to hook libc function") 263 | # frida/subprojects/frida-core/lib/payload/exit-monitor.vala 264 | exit_monitor_path = os.path.join(custom_dir, "subprojects/frida-core/lib/payload/exit-monitor.vala") 265 | patch_string = "interceptor.attach" 266 | replace_strings_in_files(exit_monitor_path, 267 | patch_string, 268 | "// " + patch_string) 269 | 270 | # frida/subprojects/frida-gum/gum/backend-posix/gumexceptor-posix.c 271 | gumexceptor_posix_path = os.path.join(custom_dir, "subprojects/frida-gum/gum/backend-posix/gumexceptor-posix.c") 272 | gumexceptor_posix_patch_strings = ["gum_interceptor_replace", 273 | "gum_exceptor_backend_replacement_signal, self, NULL);", 274 | "gum_exceptor_backend_replacement_sigaction, self, NULL);"] 275 | for patch_string in gumexceptor_posix_patch_strings: 276 | replace_strings_in_files(gumexceptor_posix_path, 277 | patch_string, 278 | "// " + patch_string) 279 | 280 | # Selinux patch for Android 281 | print(f"\n[*] Patch 'frida_file', 'frida_memfd with '{CUSTOM_NAME}_file', '{CUSTOM_NAME}_memfd recursively...") 282 | patch_strings = ["\"frida_file\"", "\"frida_memfd\"", ":frida_file", ":frida_memfd"] 283 | for patch_string in patch_strings: 284 | replace_strings_in_files(custom_dir, 285 | patch_string, 286 | patch_string.replace("frida", CUSTOM_NAME)) 287 | 288 | # Perform the first build 289 | for build_dir in build_dirs: 290 | print(f"\n[*] First build for {build_dir.rsplit('/')[-1]}") 291 | build(build_dir) 292 | 293 | # frida_agent_main patch 294 | print(f"\n[*] Patch 'frida_agent_main' with '{CUSTOM_NAME}_agent_main' recursively...") 295 | patch_string = "frida_agent_main" 296 | replace_strings_in_files(custom_dir, 297 | patch_string, 298 | patch_string.replace("frida", CUSTOM_NAME)) 299 | 300 | if TEMP == 1: 301 | fix_failed_to_reach_single_threaded_state(custom_dir) 302 | fix_process_enumerate_threads_crash(custom_dir) 303 | 304 | # Second build after patching 305 | for build_dir in build_dirs: 306 | print(f"\n[*] Second build for {build_dir.rsplit('/')[-1]}") 307 | build(build_dir) 308 | 309 | # Patch gmain, gdbus, pool-spawner 310 | gmain = bytes.fromhex('67 6d 61 69 6e 00') 311 | amain = bytes.fromhex('61 6d 61 69 6e 00') 312 | 313 | gdbus = bytes.fromhex('67 64 62 75 73 00') 314 | gdbug = bytes.fromhex('67 64 62 75 67 00') 315 | 316 | pool_spawner = bytes.fromhex('70 6f 6f 6c 2d 73 70 61 77 6e 65 72 00') 317 | pool_spoiler = bytes.fromhex('70 6f 6f 6c 2d 73 70 6f 69 6c 65 72 00') 318 | 319 | patch_list = [os.path.join(build_dir, f"subprojects/frida-core/server/{CUSTOM_NAME}-server") for build_dir in build_dirs] + \ 320 | [os.path.join(build_dir, f"subprojects/frida-core/lib/agent/{CUSTOM_NAME}-agent.so") for build_dir in build_dirs] + \ 321 | [os.path.join(build_dir, f"subprojects/frida-core/lib/gadget/{CUSTOM_NAME}-gadget.so") for build_dir in build_dirs] 322 | 323 | for file_path in patch_list: 324 | # Open the binary file for reading and writing 325 | with open(file_path, 'rb+') as f: 326 | print(f"\n[*] gmain, gdbus, pool-spawner patch for {file_path}") 327 | # Read the entire file content 328 | content = f.read() 329 | patched_content = content.replace(gmain, amain) 330 | patched_content = patched_content.replace(gdbus, gdbug) 331 | patched_content = patched_content.replace(pool_spawner, pool_spoiler) 332 | 333 | f.seek(0) 334 | f.write(patched_content) 335 | f.truncate() 336 | 337 | # Get frida version 338 | frida_version_py = os.path.join(custom_dir, "releng/frida_version.py") 339 | result = subprocess.run(['python3', frida_version_py], capture_output=True, text=True) 340 | frida_version = result.stdout.strip() 341 | 342 | # Rename 343 | for file_path in patch_list: 344 | if '-agent.so' in file_path: 345 | continue 346 | 347 | arch = [i for i in file_path.split(os.sep) if i.startswith('android-')] 348 | arch = arch[0] if arch else '' 349 | 350 | if file_path.endswith('.so'): 351 | new_file_path = f"{file_path.rsplit('.so', 1)[0]}-{frida_version}-{arch}.so" 352 | if TEMP == 1: 353 | new_file_path = f"{file_path.rsplit('.so', 1)[0]}-{frida_version}-{arch}-temp.so" 354 | else: 355 | new_file_path = f"{file_path}-{frida_version}-{arch}" 356 | if TEMP == 1: 357 | new_file_path = f"{file_path}-{frida_version}-{arch}-temp" 358 | 359 | try: 360 | os.rename(file_path, new_file_path) 361 | print(f"\n[*] Renamed {file_path} to {new_file_path}") 362 | compress_file(new_file_path) 363 | 364 | shutil.move(f"{new_file_path}.gz", f"{assets_dir}") 365 | except Exception as e: 366 | print(f"[!] Error renaming {file_path}: {e}") 367 | 368 | 369 | if __name__ == "__main__": 370 | main() 371 | -------------------------------------------------------------------------------- /main_macos_android.py: -------------------------------------------------------------------------------- 1 | import gzip 2 | import os 3 | import shutil 4 | import subprocess 5 | import sys 6 | 7 | CUSTOM_NAME = "ajeossida" 8 | 9 | # Temporarily fixing the issue 'Failed to reach single-threaded state', Process.enumerateThreads() crash 10 | TEMP = 0 11 | 12 | 13 | def run_command(command, cwd=None): 14 | try: 15 | result = subprocess.run(command, shell=True, cwd=cwd, check=True, text=True) 16 | return result.returncode 17 | except subprocess.CalledProcessError as e: 18 | print(f"Error while running command: {command}\nError: {e}") 19 | sys.exit(1) 20 | 21 | 22 | def git_clone_repo(): 23 | repo_url = "https://github.com/frida/frida.git" 24 | destination_dir = os.path.join(os.getcwd(), CUSTOM_NAME) 25 | 26 | print(f"\n[*] Cloning repository {repo_url} to {destination_dir}...") 27 | run_command(f"git clone --recurse-submodules {repo_url} {destination_dir}") 28 | 29 | 30 | def check_ndk_version(): 31 | home_path = os.path.expanduser("~") 32 | ndk_base = os.path.join(home_path, "Library/Android/sdk/ndk") 33 | 34 | ndk_versions = [] 35 | 36 | for ndk_version in os.listdir(ndk_base): 37 | dir_path = os.path.join(ndk_base, ndk_version) 38 | 39 | if os.path.isdir(dir_path) and ndk_version.startswith("25."): 40 | ndk_versions.append(ndk_version) 41 | 42 | if not ndk_versions: 43 | print("\n[!] Android NDK r25 is needed") 44 | sys.exit(1) 45 | 46 | # Sort versions and pick the largest 47 | biggest_version = max(ndk_versions, key=lambda v: list(map(int, v.split('.')))) 48 | 49 | biggest_version_path = os.path.join(ndk_base, biggest_version) 50 | print(f"[*] NDK version: {biggest_version}") 51 | return biggest_version_path 52 | 53 | 54 | def configure_build(ndk_path, arch): 55 | build_dir = os.path.join(os.getcwd(), CUSTOM_NAME, arch) 56 | os.makedirs(build_dir, exist_ok=True) 57 | 58 | os.environ['ANDROID_NDK_ROOT'] = ndk_path 59 | 60 | print(f"\n[*] Configuring the build for {arch}...") 61 | result = run_command(f"{os.path.join('..', 'configure')} --host={arch}", cwd=build_dir) 62 | 63 | if result == 0: 64 | return build_dir 65 | else: 66 | print("\n[!] Failed to configure") 67 | sys.exit(1) 68 | 69 | 70 | def build(build_dir): 71 | run_command("make", cwd=build_dir) 72 | 73 | 74 | def replace_strings_in_files(directory, search_string, replace_string): 75 | if os.path.isfile(directory): 76 | file_path = directory 77 | with open(file_path, 'r+', encoding='utf-8') as file: 78 | content = file.read() 79 | if search_string in content: 80 | print(f"Patch {file.name}") 81 | patched_content = content.replace(search_string, replace_string) 82 | file.seek(0) 83 | file.write(patched_content) 84 | file.truncate() 85 | else: 86 | for root, dirs, files in os.walk(directory): 87 | for file_name in files: 88 | file_path = os.path.join(root, file_name) 89 | try: 90 | with open(file_path, 'r+', encoding='utf-8') as file: 91 | content = file.read() 92 | if search_string in content: 93 | print(f"Patch {file.name}") 94 | patched_content = content.replace(search_string, replace_string) 95 | file.seek(0) 96 | file.write(patched_content) 97 | file.truncate() 98 | except Exception as e: 99 | pass 100 | 101 | 102 | def compress_file(file_path): 103 | try: 104 | # Create a .gz file from the original file 105 | with open(file_path, 'rb') as f_in: 106 | with gzip.open(file_path + '.gz', 'wb') as f_out: 107 | shutil.copyfileobj(f_in, f_out) 108 | print(f"[*] Compressed {file_path} to {file_path}.gz") 109 | except Exception as e: 110 | print(f"[!] Error compressing {file_path}: {e}") 111 | 112 | 113 | def capitalize_first_lower_char(word): 114 | for index, char in enumerate(word): 115 | if char.islower(): 116 | # Replace the first lowercase character with its uppercase equivalent 117 | return word[:index] + char.upper() + word[index + 1:] 118 | return word 119 | 120 | 121 | def fix_failed_to_reach_single_threaded_state(custom_dir): 122 | old_cloak_vala_path = os.path.join(custom_dir, "subprojects/frida-core/lib/payload/cloak.vala") 123 | new_cloak_vala_path = os.path.join(os.getcwd(), "fix_failed_to_reach_single_threaded_state.txt") 124 | os.remove(old_cloak_vala_path) 125 | shutil.copy(new_cloak_vala_path, old_cloak_vala_path) 126 | 127 | 128 | def fix_process_enumerate_threads_crash(custom_dir): 129 | # On the Pixel 4a, if there's a thread named perfetto_hprof_, then Process.enumerateThreads() crashes with SEGV_ACCERR. 130 | gumprocess_linux_path = os.path.join(custom_dir, "subprojects/frida-gum/gum/backend-linux/gumprocess-linux.c") 131 | replace_strings_in_files(gumprocess_linux_path, 132 | ' details.name = thread_name;', 133 | ' details.name = thread_name;\n' 134 | ' if (strcmp(details.name, "perfetto_hprof_") == 0)\n' 135 | ' continue;') 136 | 137 | 138 | def main(): 139 | custom_dir = os.path.join(os.getcwd(), CUSTOM_NAME) 140 | if os.path.exists(custom_dir): 141 | print(f"\n[*] Cleaning {custom_dir}...") 142 | shutil.rmtree(custom_dir) 143 | 144 | assets_dir = os.path.join(os.getcwd(), "assets") 145 | if os.path.exists(assets_dir): 146 | print(f"\n[*] Cleaning {assets_dir}...") 147 | shutil.rmtree(assets_dir) 148 | os.mkdir(assets_dir) 149 | 150 | git_clone_repo() 151 | 152 | ndk_version_path = check_ndk_version() 153 | 154 | architectures = ["android-arm64", "android-arm", "android-x86_64", "android-x86"] 155 | if TEMP == 1: 156 | architectures = ["android-arm64"] 157 | build_dirs = [configure_build(ndk_version_path, arch) for arch in architectures] 158 | 159 | # libfrida-agent-raw.so patch 160 | print(f"\n[*] Patch 'libfrida-agent-raw.so' with 'lib{CUSTOM_NAME}-agent-raw.so' recursively...") 161 | patch_string = "libfrida-agent-raw.so" 162 | replace_strings_in_files(custom_dir, 163 | patch_string, 164 | patch_string.replace("frida", CUSTOM_NAME)) 165 | 166 | # re.frida.server patch 167 | print(f"\n[*] Patch 're.frida.server' with 're.{CUSTOM_NAME}.server' recursively...") 168 | patch_string = "re.frida.server" 169 | replace_strings_in_files(custom_dir, 170 | patch_string, 171 | patch_string.replace("frida", CUSTOM_NAME)) 172 | 173 | # frida-helper patch 174 | print(f"\n[*] Patch 'frida-helper' with '{CUSTOM_NAME}-helper' recursively...") 175 | patch_strings = ["frida-helper-32", "frida-helper-64", "get_frida_helper_", "\"/frida-\""] 176 | for patch_string in patch_strings: 177 | replace_strings_in_files(custom_dir, 178 | patch_string, 179 | patch_string.replace("frida", CUSTOM_NAME)) 180 | 181 | # frida-agent patch 182 | print(f"\n[*] Patch 'frida-agent' with '{CUSTOM_NAME}-agent' recursively...") 183 | patch_strings = ["frida-agent-", "\"agent\" / \"frida-agent.", "\'frida-agent\'", "\"frida-agent\"", 184 | "get_frida_agent_", "\'FridaAgent\'"] 185 | for patch_string in patch_strings: 186 | replace_strings_in_files(custom_dir, 187 | patch_string, 188 | patch_string.replace("Frida", capitalize_first_lower_char( 189 | CUSTOM_NAME)) if "Frida" in patch_string else 190 | patch_string.replace("frida", CUSTOM_NAME)) 191 | 192 | # Patch the original file back, which has incorrectly patched strings. 193 | wrong_patch_strings = [f'{CUSTOM_NAME}-agent-x86.symbols', f'{CUSTOM_NAME}-agent-android.version'] 194 | for wrong_patch_string in wrong_patch_strings: 195 | replace_strings_in_files(custom_dir, 196 | wrong_patch_string, 197 | wrong_patch_string.replace(CUSTOM_NAME, 'frida')) 198 | 199 | # memfd_create patch, memfd:ajeossida-agent-64.so --> memfd:jit-cache 200 | print(f"\n[*] Patch MemoryFileDescriptor.memfd_create") 201 | frida_helper_backend_path = os.path.join(custom_dir, 202 | "subprojects/frida-core/src/linux/frida-helper-backend.vala") 203 | replace_strings_in_files(frida_helper_backend_path, 204 | 'return Linux.syscall (SysCall.memfd_create, name, flags);', 205 | 'return Linux.syscall (SysCall.memfd_create, \"jit-cache\", flags);') 206 | 207 | # frida-server patch 208 | print(f"\n[*] Patch 'frida-server' with '{CUSTOM_NAME}-server' recursively...") 209 | frida_server_meson_path = os.path.join(custom_dir, "subprojects/frida-core/server/meson.build") 210 | patch_strings = ["frida-server-raw", "\'frida-server\'", "\"frida-server\"", "frida-server-universal"] 211 | for patch_string in patch_strings: 212 | replace_strings_in_files(frida_server_meson_path, 213 | patch_string, 214 | patch_string.replace("frida", CUSTOM_NAME)) 215 | 216 | frida_core_compat_build_path = os.path.join(custom_dir, "subprojects/frida-core/compat/build.py") 217 | patch_string = "frida-server" 218 | replace_strings_in_files(frida_core_compat_build_path, 219 | patch_string, 220 | patch_string.replace("frida", CUSTOM_NAME)) 221 | 222 | # frida-gadget patch 223 | print(f"\n[*] Patch 'frida-gadget' with '{CUSTOM_NAME}-gadget' recursively...") 224 | patch_strings = ["\"frida-gadget\"", "\"frida-gadget-tcp", "\"frida-gadget-unix"] 225 | for patch_string in patch_strings: 226 | replace_strings_in_files(custom_dir, 227 | patch_string, 228 | patch_string.replace("frida", CUSTOM_NAME)) 229 | 230 | frida_core_meson_path = os.path.join(custom_dir, "subprojects/frida-core/meson.build") 231 | patch_string = "gadget_name = 'frida-gadget' + shlib_suffix" 232 | replace_strings_in_files(frida_core_meson_path, 233 | patch_string, 234 | patch_string.replace("frida", CUSTOM_NAME)) 235 | 236 | frida_core_compat_build_py_path = os.path.join(custom_dir, "subprojects/frida-core/compat/build.py") 237 | patch_string = "frida-gadget" 238 | replace_strings_in_files(frida_core_compat_build_py_path, 239 | patch_string, 240 | patch_string.replace("frida", CUSTOM_NAME)) 241 | 242 | frida_gadget_meson_path = os.path.join(custom_dir, "subprojects/frida-core/lib/gadget/meson.build") 243 | patch_strings = ["frida-gadget-modulated", "libfrida-gadget-modulated", "frida-gadget-raw", "\'frida-gadget\'", 244 | "frida-gadget-universal", "FridaGadget.dylib"] 245 | for patch_string in patch_strings: 246 | replace_strings_in_files(frida_gadget_meson_path, 247 | patch_string, 248 | patch_string.replace("Frida", capitalize_first_lower_char( 249 | CUSTOM_NAME)) if "Frida" in patch_string else 250 | patch_string.replace("frida", CUSTOM_NAME)) 251 | 252 | # gum-js-loop patch 253 | print(f"\n[*] Patch 'gum-js-loop' with '{CUSTOM_NAME}-js-loop' recursively...") 254 | patch_string = "\"gum-js-loop\"" 255 | replace_strings_in_files(custom_dir, 256 | patch_string, 257 | patch_string.replace("gum", CUSTOM_NAME)) 258 | 259 | # pool-frida patch 260 | print(f"\n[*] Patch 'pool-frida' with 'pool-{CUSTOM_NAME}' recursively...") 261 | patch_string = "g_set_prgname (\"frida\");" 262 | replace_strings_in_files(custom_dir, 263 | patch_string, 264 | patch_string.replace("frida", CUSTOM_NAME)) 265 | 266 | # No libc.so hooking 267 | print(f"\n[*] Patch not to hook libc function") 268 | # frida/subprojects/frida-core/lib/payload/exit-monitor.vala 269 | exit_monitor_path = os.path.join(custom_dir, "subprojects/frida-core/lib/payload/exit-monitor.vala") 270 | patch_string = "interceptor.attach" 271 | replace_strings_in_files(exit_monitor_path, 272 | patch_string, 273 | "// " + patch_string) 274 | 275 | # frida/subprojects/frida-gum/gum/backend-posix/gumexceptor-posix.c 276 | gumexceptor_posix_path = os.path.join(custom_dir, "subprojects/frida-gum/gum/backend-posix/gumexceptor-posix.c") 277 | gumexceptor_posix_patch_strings = ["gum_interceptor_replace", 278 | "gum_exceptor_backend_replacement_signal, self, NULL);", 279 | "gum_exceptor_backend_replacement_sigaction, self, NULL);"] 280 | for patch_string in gumexceptor_posix_patch_strings: 281 | replace_strings_in_files(gumexceptor_posix_path, 282 | patch_string, 283 | "// " + patch_string) 284 | 285 | # Selinux patch for Android 286 | print(f"\n[*] Patch 'frida_file', 'frida_memfd with '{CUSTOM_NAME}_file', '{CUSTOM_NAME}_memfd' recursively...") 287 | patch_strings = ["\"frida_file\"", "\"frida_memfd\"", ":frida_file", ":frida_memfd"] 288 | for patch_string in patch_strings: 289 | replace_strings_in_files(custom_dir, 290 | patch_string, 291 | patch_string.replace("frida", CUSTOM_NAME)) 292 | 293 | # Perform the first build 294 | for build_dir in build_dirs: 295 | print(f"\n[*] First build for {build_dir.rsplit('/')[-1]}") 296 | build(build_dir) 297 | 298 | # frida_agent_main patch 299 | print(f"\n[*] Patch 'frida_agent_main' with '{CUSTOM_NAME}_agent_main' recursively...") 300 | patch_string = "frida_agent_main" 301 | replace_strings_in_files(custom_dir, 302 | patch_string, 303 | patch_string.replace("frida", CUSTOM_NAME)) 304 | 305 | if TEMP == 1: 306 | fix_failed_to_reach_single_threaded_state(custom_dir) 307 | fix_process_enumerate_threads_crash(custom_dir) 308 | 309 | # Second build after patching 310 | for build_dir in build_dirs: 311 | print(f"\n[*] Second build for {build_dir.rsplit('/')[-1]}") 312 | build(build_dir) 313 | 314 | # Patch gmain, gdbus, pool-spawner 315 | gmain = bytes.fromhex('67 6d 61 69 6e 00') 316 | amain = bytes.fromhex('61 6d 61 69 6e 00') 317 | 318 | gdbus = bytes.fromhex('67 64 62 75 73 00') 319 | gdbug = bytes.fromhex('67 64 62 75 67 00') 320 | 321 | pool_spawner = bytes.fromhex('70 6f 6f 6c 2d 73 70 61 77 6e 65 72 00') 322 | pool_spoiler = bytes.fromhex('70 6f 6f 6c 2d 73 70 6f 69 6c 65 72 00') 323 | 324 | patch_list = [os.path.join(build_dir, f"subprojects/frida-core/server/{CUSTOM_NAME}-server") for build_dir in 325 | build_dirs] + \ 326 | [os.path.join(build_dir, f"subprojects/frida-core/lib/agent/{CUSTOM_NAME}-agent.so") for build_dir in 327 | build_dirs] + \ 328 | [os.path.join(build_dir, f"subprojects/frida-core/lib/gadget/{CUSTOM_NAME}-gadget.so") for build_dir in 329 | build_dirs] 330 | 331 | for file_path in patch_list: 332 | # Open the binary file for reading and writing 333 | with open(file_path, 'rb+') as f: 334 | print(f"\n[*] gmain, gdbus, pool-spawner patch for {file_path}") 335 | # Read the entire file content 336 | content = f.read() 337 | patched_content = content.replace(gmain, amain) 338 | patched_content = patched_content.replace(gdbus, gdbug) 339 | patched_content = patched_content.replace(pool_spawner, pool_spoiler) 340 | 341 | f.seek(0) 342 | f.write(patched_content) 343 | f.truncate() 344 | 345 | # Get frida version 346 | frida_version_py = os.path.join(custom_dir, "releng/frida_version.py") 347 | result = subprocess.run(['python3', frida_version_py], capture_output=True, text=True) 348 | frida_version = result.stdout.strip() 349 | 350 | # Rename 351 | for file_path in patch_list: 352 | if '-agent.so' in file_path: 353 | continue 354 | 355 | arch = [i for i in file_path.split(os.sep) if i.startswith('android-')] 356 | arch = arch[0] if arch else '' 357 | 358 | if file_path.endswith('.so'): 359 | new_file_path = f"{file_path.rsplit('.so', 1)[0]}-{frida_version}-{arch}.so" 360 | if TEMP == 1: 361 | new_file_path = f"{file_path.rsplit('.so', 1)[0]}-{frida_version}-{arch}-temp.so" 362 | else: 363 | new_file_path = f"{file_path}-{frida_version}-{arch}" 364 | if TEMP == 1: 365 | new_file_path = f"{file_path}-{frida_version}-{arch}-temp" 366 | 367 | try: 368 | os.rename(file_path, new_file_path) 369 | print(f"\n[*] Renamed {file_path} to {new_file_path}") 370 | compress_file(new_file_path) 371 | 372 | shutil.move(f"{new_file_path}.gz", f"{assets_dir}") 373 | except Exception as e: 374 | print(f"[!] Error renaming {file_path}: {e}") 375 | 376 | print(f"\n[*] Building of {CUSTOM_NAME} completed. The output is in the assets directory") 377 | 378 | 379 | if __name__ == "__main__": 380 | main() 381 | -------------------------------------------------------------------------------- /main_macos_ios.py: -------------------------------------------------------------------------------- 1 | import gzip 2 | import os 3 | import shutil 4 | import subprocess 5 | import sys 6 | 7 | CUSTOM_NAME = "ajeossida" 8 | 9 | 10 | def run_command(command, cwd=None): 11 | try: 12 | result = subprocess.run(command, shell=True, cwd=cwd, check=True, text=True) 13 | return result.returncode 14 | except subprocess.CalledProcessError as e: 15 | print(f"Error while running command: {command}\nError: {e}") 16 | if "ios" in cwd: 17 | # frida-helper patch 18 | print(f"\n[*] Patch 'get_frida_helper_' with 'get_{CUSTOM_NAME}_helper_' recursively...") 19 | patch_strings = ["get_frida_helper_"] 20 | for patch_string in patch_strings: 21 | replace_strings_in_files(os.path.join(os.getcwd(), CUSTOM_NAME), patch_string, 22 | patch_string.replace("frida", f"{CUSTOM_NAME}")) 23 | # frida-agent patch 24 | print(f"\n[*] Patch 'get_frida_agent_' with 'get_{CUSTOM_NAME}_agent_' recursively...") 25 | patch_strings = ["get_frida_agent_"] 26 | for patch_string in patch_strings: 27 | replace_strings_in_files(os.path.join(os.getcwd(), CUSTOM_NAME), patch_string, 28 | patch_string.replace("frida", f"{CUSTOM_NAME}")) 29 | # build again 30 | build(cwd) 31 | else: 32 | sys.exit(1) 33 | 34 | 35 | def git_clone_repo(): 36 | repo_url = "https://github.com/frida/frida.git" 37 | destination_dir = os.path.join(os.getcwd(), CUSTOM_NAME) 38 | 39 | print(f"\n[*] Cloning repository {repo_url} to {destination_dir}...") 40 | run_command(f"git clone --recurse-submodules {repo_url} {destination_dir}") 41 | 42 | 43 | def configure_build(arch): 44 | build_dir = os.path.join(os.getcwd(), CUSTOM_NAME, arch) 45 | os.makedirs(build_dir, exist_ok=True) 46 | 47 | ios_keychain = "frida-signing" 48 | print(f"\n[*] Check {ios_keychain}...") 49 | result = subprocess.run(f"security find-identity -v -p codesigning | grep {ios_keychain}", shell=True, text=False) 50 | if result.returncode != 0: 51 | print("\n[!] Cannot find \"frida-signing\" keychain") 52 | sys.exit(1) 53 | print(f"\n[*] Set 'MACOS_CERTID' and 'IOS_CERTID' environment variables with {ios_keychain}") 54 | os.environ['MACOS_CERTID'] = ios_keychain 55 | os.environ['IOS_CERTID'] = ios_keychain 56 | 57 | print(f"\n[*] Configuring the build for {arch}...") 58 | result = run_command( 59 | f"{os.path.join('..', 'configure')} --prefix=/usr --host={arch} --enable-portal -- -Dfrida-gum:devkits=gum,gumjs -Dfrida-core:assets=installed -Dfrida-core:devkits=core", 60 | cwd=build_dir) 61 | 62 | if result == 0: 63 | return build_dir 64 | else: 65 | print("\n[!] Failed to configure") 66 | sys.exit(1) 67 | 68 | 69 | def build(build_dir): 70 | run_command("make", cwd=build_dir) 71 | 72 | 73 | def replace_strings_in_files(directory, search_string, replace_string): 74 | if os.path.isfile(directory): 75 | file_path = directory 76 | with open(file_path, 'r+', encoding='utf-8') as file: 77 | content = file.read() 78 | if search_string in content: 79 | print(f"Patch {file.name}") 80 | patched_content = content.replace(search_string, replace_string) 81 | file.seek(0) 82 | file.write(patched_content) 83 | file.truncate() 84 | else: 85 | for root, dirs, files in os.walk(directory): 86 | for file_name in files: 87 | file_path = os.path.join(root, file_name) 88 | try: 89 | with open(file_path, 'r+', encoding='utf-8') as file: 90 | content = file.read() 91 | if search_string in content: 92 | print(f"Patch {file.name}") 93 | patched_content = content.replace(search_string, replace_string) 94 | file.seek(0) 95 | file.write(patched_content) 96 | file.truncate() 97 | except Exception as e: 98 | pass 99 | 100 | 101 | def capitalize_first_lower_char(word): 102 | for index, char in enumerate(word): 103 | if char.islower(): 104 | # Replace the first lowercase character with its uppercase equivalent 105 | return word[:index] + char.upper() + word[index + 1:] 106 | return word 107 | 108 | 109 | def main(): 110 | custom_dir = os.path.join(os.getcwd(), CUSTOM_NAME) 111 | if os.path.exists(custom_dir): 112 | print(f"\n[*] Cleaning {custom_dir}...") 113 | shutil.rmtree(custom_dir) 114 | 115 | assets_dir = os.path.join(os.getcwd(), "assets") 116 | if os.path.exists(assets_dir): 117 | print(f"\n[*] Cleaning {assets_dir}...") 118 | shutil.rmtree(assets_dir) 119 | os.mkdir(assets_dir) 120 | 121 | ios_assets_dir = os.path.join(os.getcwd(), "ios-assets") 122 | if os.path.exists(ios_assets_dir): 123 | print(f"\n[*] Cleaning {ios_assets_dir}...") 124 | shutil.rmtree(ios_assets_dir) 125 | ios_assets_frida_server_dir = os.path.join(ios_assets_dir, 'usr/bin') 126 | ios_assets_frida_agent_dir = os.path.join(ios_assets_dir, f'usr/lib/{CUSTOM_NAME}') 127 | os.makedirs(ios_assets_frida_server_dir, exist_ok=True) 128 | os.makedirs(ios_assets_frida_agent_dir, exist_ok=True) 129 | 130 | git_clone_repo() 131 | 132 | architectures = ["ios-arm64e"] 133 | build_dirs = [configure_build(arch) for arch in architectures] 134 | 135 | # re.frida.server patch 136 | print(f"\n[*] Patch 're.frida.server' with 're.{CUSTOM_NAME}.server' recursively...") 137 | patch_string = "re.frida.server" 138 | replace_strings_in_files(custom_dir, 139 | patch_string, 140 | patch_string.replace("frida", CUSTOM_NAME)) 141 | 142 | # frida-helper patch 143 | print(f"\n[*] Patch 'frida-helper' with '{CUSTOM_NAME}-helper' recursively...") 144 | patch_strings = ["frida-helper-32", "frida-helper-64", "get_frida_helper_", "\"/frida-\""] 145 | for patch_string in patch_strings: 146 | replace_strings_in_files(custom_dir, 147 | patch_string, 148 | patch_string.replace("frida", CUSTOM_NAME)) 149 | 150 | # frida-agent patch 151 | print(f"\n[*] Patch 'frida-agent' with '{CUSTOM_NAME}-agent' recursively...") 152 | patch_strings = ["frida-agent-", "\"agent\" / \"frida-agent.", "\'frida-agent\'", "\"frida-agent\"", 153 | "get_frida_agent_", "\'FridaAgent\'"] 154 | for patch_string in patch_strings: 155 | replace_strings_in_files(custom_dir, 156 | patch_string, 157 | patch_string.replace("Frida", capitalize_first_lower_char( 158 | CUSTOM_NAME)) if "Frida" in patch_string else 159 | patch_string.replace("frida", CUSTOM_NAME)) 160 | 161 | # Patch the original file back, which has incorrectly patched strings. 162 | wrong_patch_strings = [f'{CUSTOM_NAME}-agent-x86.symbols', f'{CUSTOM_NAME}-agent-android.version'] 163 | for wrong_patch_string in wrong_patch_strings: 164 | replace_strings_in_files(custom_dir, 165 | wrong_patch_string, 166 | wrong_patch_string.replace(CUSTOM_NAME, 'frida')) 167 | 168 | # frida-server patch 169 | print(f"\n[*] Patch 'frida-server' with '{CUSTOM_NAME}-server' recursively...") 170 | frida_server_meson_path = os.path.join(custom_dir, "subprojects/frida-core/server/meson.build") 171 | patch_strings = ["frida-server-raw", "\'frida-server\'", "\"frida-server\"", "frida-server-universal"] 172 | for patch_string in patch_strings: 173 | replace_strings_in_files(frida_server_meson_path, 174 | patch_string, 175 | patch_string.replace("frida", CUSTOM_NAME)) 176 | 177 | frida_core_compat_build_path = os.path.join(custom_dir, "subprojects/frida-core/compat/build.py") 178 | patch_string = "frida-server" 179 | replace_strings_in_files(frida_core_compat_build_path, 180 | patch_string, 181 | patch_string.replace("frida", CUSTOM_NAME)) 182 | 183 | # frida-gadget patch 184 | print(f"\n[*] Patch 'frida-gadget' with '{CUSTOM_NAME}-gadget' recursively...") 185 | patch_strings = ["\"frida-gadget\"", "\"frida-gadget-tcp", "\"frida-gadget-unix"] 186 | for patch_string in patch_strings: 187 | replace_strings_in_files(custom_dir, 188 | patch_string, 189 | patch_string.replace("frida", CUSTOM_NAME)) 190 | 191 | frida_core_meson_path = os.path.join(custom_dir, "subprojects/frida-core/meson.build") 192 | patch_string = "gadget_name = 'frida-gadget' + shlib_suffix" 193 | replace_strings_in_files(frida_core_meson_path, 194 | patch_string, 195 | patch_string.replace("frida", CUSTOM_NAME)) 196 | 197 | frida_core_compat_build_py_path = os.path.join(custom_dir, "subprojects/frida-core/compat/build.py") 198 | patch_string = "frida-gadget" 199 | replace_strings_in_files(frida_core_compat_build_py_path, 200 | patch_string, 201 | patch_string.replace("frida", CUSTOM_NAME)) 202 | 203 | frida_gadget_meson_path = os.path.join(custom_dir, "subprojects/frida-core/lib/gadget/meson.build") 204 | patch_strings = ["frida-gadget-modulated", "libfrida-gadget-modulated", "frida-gadget-raw", "\'frida-gadget\'", 205 | "frida-gadget-universal", "FridaGadget.dylib"] 206 | for patch_string in patch_strings: 207 | replace_strings_in_files(frida_gadget_meson_path, 208 | patch_string, 209 | patch_string.replace("Frida", capitalize_first_lower_char( 210 | CUSTOM_NAME)) if "Frida" in patch_string else 211 | patch_string.replace("frida", CUSTOM_NAME)) 212 | 213 | # gum-js-loop patch 214 | print(f"\n[*] Patch 'gum-js-loop' with '{CUSTOM_NAME}-js-loop' recursively...") 215 | patch_string = "\"gum-js-loop\"" 216 | replace_strings_in_files(custom_dir, 217 | patch_string, 218 | patch_string.replace("gum", CUSTOM_NAME)) 219 | 220 | # pool-frida patch 221 | print(f"\n[*] Patch 'pool-frida' with 'pool-{CUSTOM_NAME}' recursively...") 222 | patch_string = "g_set_prgname (\"frida\");" 223 | replace_strings_in_files(custom_dir, 224 | patch_string, 225 | patch_string.replace("frida", CUSTOM_NAME)) 226 | 227 | # No libsystem_c.dylib hooking 228 | print(f"\n[*] Patch not to hook libc function") 229 | # frida/subprojects/frida-core/lib/payload/exit-monitor.vala 230 | exit_monitor_path = os.path.join(custom_dir, "subprojects/frida-core/lib/payload/exit-monitor.vala") 231 | patch_string = "interceptor.attach" 232 | replace_strings_in_files(exit_monitor_path, 233 | patch_string, 234 | "// " + patch_string) 235 | 236 | # frida/subprojects/frida-gum/gum/backend-posix/gumexceptor-posix.c 237 | gumexceptor_posix_path = os.path.join(custom_dir, "subprojects/frida-gum/gum/backend-posix/gumexceptor-posix.c") 238 | gumexceptor_posix_patch_strings = ["gum_interceptor_replace", 239 | "gum_exceptor_backend_replacement_signal, self, NULL);", 240 | "gum_exceptor_backend_replacement_sigaction, self, NULL);"] 241 | for patch_string in gumexceptor_posix_patch_strings: 242 | replace_strings_in_files(gumexceptor_posix_path, 243 | patch_string, 244 | "// " + patch_string) 245 | 246 | # No removing cloaked threads 247 | print(f"\n[*] Patch thread-suspend-monitor for iOS") 248 | # frida/subprojects/frida-core/lib/payload/thread-suspend-monitor.vala 249 | thread_suspend_monitor_path = os.path.join(custom_dir, 250 | "subprojects/frida-core/lib/payload/thread-suspend-monitor.vala") 251 | patch_string = "interceptor.replace ((void *) task_threads" 252 | replace_strings_in_files(thread_suspend_monitor_path, 253 | patch_string, 254 | "// " + patch_string) 255 | 256 | # # Need to patch?? frida/subprojects/frida-core/lib/payload/unwind-sitter.vala 257 | # print(f"\n[*] Patch unwind-sitter.vala for iOS") 258 | # unwind_sitter_path = os.path.join(custom_dir, "subprojects/frida-core/lib/payload/unwind-sitter.vala") 259 | # patch_string = "interceptor.replace" 260 | # replace_strings_in_files(unwind_sitter_path, 261 | # patch_string, 262 | # "// " + patch_string) 263 | 264 | # Perform the first build 265 | for build_dir in build_dirs: 266 | print(f"\n[*] First build for {build_dir.rsplit('/')[-1]}") 267 | build(build_dir) 268 | 269 | # frida_agent_main patch 270 | print(f"\n[*] Patch 'frida_agent_main' with '{CUSTOM_NAME}_agent_main' recursively...") 271 | patch_string = "frida_agent_main" 272 | replace_strings_in_files(custom_dir, 273 | patch_string, 274 | patch_string.replace("frida", CUSTOM_NAME)) 275 | 276 | # Second build after patching 277 | for build_dir in build_dirs: 278 | print(f"\n[*] Second build for {build_dir.rsplit('/')[-1]}") 279 | build(build_dir) 280 | if 'ios-' in build_dir: 281 | # No patch gumexceptor-posix.c to prevent the crash issue when scanning the memory 282 | for patch_string in gumexceptor_posix_patch_strings: 283 | replace_strings_in_files(gumexceptor_posix_path, 284 | "// " + patch_string, 285 | patch_string) 286 | 287 | # /usr/lib/frida/frida-agent.dylib path patch 288 | print(f"\n[*] Patch 'usr/lib/frida' with 'usr/lib/{CUSTOM_NAME}' recursively...") 289 | patch_string = "usr/lib/frida" 290 | replace_strings_in_files(custom_dir, 291 | patch_string, 292 | patch_string.replace("frida", CUSTOM_NAME)) 293 | 294 | # Third build for iOS 295 | print(f"\n[*] Third build for {build_dir.rsplit('/')[-1]}") 296 | build(build_dir) 297 | 298 | # Patch gmain, gdbus, pool-spawner 299 | gmain = bytes.fromhex('67 6d 61 69 6e 00') 300 | amain = bytes.fromhex('61 6d 61 69 6e 00') 301 | 302 | gdbus = bytes.fromhex('67 64 62 75 73 00') 303 | gdbug = bytes.fromhex('67 64 62 75 67 00') 304 | 305 | pool_spawner = bytes.fromhex('70 6f 6f 6c 2d 73 70 61 77 6e 65 72 00') 306 | pool_spoiler = bytes.fromhex('70 6f 6f 6c 2d 73 70 6f 69 6c 65 72 00') 307 | 308 | patch_list = [os.path.join(build_dir, f"subprojects/frida-core/server/{CUSTOM_NAME}-server") for build_dir in 309 | build_dirs] + \ 310 | [os.path.join(build_dir, 311 | f"subprojects/frida-core/lib/agent/{CUSTOM_NAME}-agent.dylib") for build_dir in 312 | build_dirs] + \ 313 | [os.path.join(build_dir, 314 | f"subprojects/frida-core/lib/gadget/{CUSTOM_NAME}-gadget.dylib") for build_dir in 315 | build_dirs] 316 | 317 | for file_path in patch_list: 318 | # Open the binary file for reading and writing 319 | with open(file_path, 'rb+') as f: 320 | print(f"\n[*] gmain, gdbus, pool-spawner patch for {file_path}") 321 | # Read the entire file content 322 | content = f.read() 323 | patched_content = content.replace(gmain, amain) 324 | patched_content = patched_content.replace(gdbus, gdbug) 325 | patched_content = patched_content.replace(pool_spawner, pool_spoiler) 326 | 327 | f.seek(0) 328 | f.write(patched_content) 329 | f.truncate() 330 | 331 | # Patch packcage-server-fruity.sh for iOS 332 | print(f"\n[*] Patch package-server-fruity.sh") 333 | # frida/subprojects/frida-core/tools/package-server-fruity.sh 334 | package_server_fruity_path = os.path.join(custom_dir, "subprojects/frida-core/tools/package-server-fruity.sh") 335 | patch_strings = ["frida-server", "frida-agent.dylib"] 336 | for patch_string in patch_strings: 337 | replace_strings_in_files(package_server_fruity_path, 338 | patch_string, 339 | patch_string.replace("frida", f"{CUSTOM_NAME}")) 340 | 341 | # Get frida version 342 | frida_version_py = os.path.join(custom_dir, "releng/frida_version.py") 343 | result = subprocess.run(['python3', frida_version_py], capture_output=True, text=True) 344 | frida_version = result.stdout.strip() 345 | 346 | # Rename 347 | for file_path in patch_list: 348 | if 'ios-' in file_path: 349 | try: 350 | if '-server' in file_path: 351 | shutil.move(file_path, ios_assets_frida_server_dir) 352 | elif '-agent.dylib' in file_path: 353 | shutil.move(file_path, ios_assets_frida_agent_dir) 354 | else: 355 | shutil.move(file_path, ios_assets_dir) 356 | except Exception as e: 357 | print(f"[!] Error {file_path}: {e}") 358 | 359 | # Create universal frida-server for iOS 360 | # lipo, codesign frida-server, codesign frida-agent.dylib 361 | for arch in ["arm64", "arm64e"]: 362 | run_command(f"lipo {CUSTOM_NAME}-server -thin {arch} -output {CUSTOM_NAME}-server-{arch}", 363 | cwd=f"{ios_assets_dir}/usr/bin") 364 | run_command(f"codesign -f -s \"-\" --preserve-metadata=entitlements {CUSTOM_NAME}-server-{arch}", 365 | cwd=f"{ios_assets_dir}/usr/bin") 366 | run_command(f"codesign -f -s \"-\" {CUSTOM_NAME}-agent.dylib", cwd=f"{ios_assets_dir}/usr/lib/{CUSTOM_NAME}") 367 | # Codesign frida-gadget.dylib 368 | run_command(f"codesign -f -s \"-\" {CUSTOM_NAME}-gadget.dylib", cwd=f"{ios_assets_dir}") 369 | 370 | # Make fat macho 371 | print("\n[*] Making fat macho...") 372 | mkfatmacho_py = os.path.join(custom_dir, "releng/mkfatmacho.py") 373 | result = subprocess.run(["python3", mkfatmacho_py, f"{CUSTOM_NAME}-server", f"{CUSTOM_NAME}-server-arm64", 374 | f"{CUSTOM_NAME}-server-arm64e"], capture_output=True, text=True, 375 | cwd=f"{ios_assets_dir}/usr/bin") 376 | if result.returncode == 0: 377 | for arch in ["arm64", "arm64e"]: 378 | os.remove(f"{ios_assets_dir}/usr/bin/{CUSTOM_NAME}-server-{arch}") 379 | else: 380 | print("Error while making fat macho") 381 | sys.exit(1) 382 | 383 | # deb packaging 384 | print("\n[*] Packaging deb...") 385 | os.environ['FRIDA_VERSION'] = frida_version 386 | deb_packaging_arch = "iphoneos-arm64" 387 | run_command( 388 | f"{custom_dir}/subprojects/frida-core/tools/package-server-fruity.sh {deb_packaging_arch} {ios_assets_dir} {CUSTOM_NAME}_{os.environ['FRIDA_VERSION']}_{deb_packaging_arch}.deb", cwd=assets_dir) 389 | 390 | print(f"\n[*] Building of {CUSTOM_NAME} completed. The output is in the assets directory") 391 | 392 | 393 | if __name__ == "__main__": 394 | main() 395 | --------------------------------------------------------------------------------