AndroidFridaManager 1.9.3__py3-none-any.whl → 1.9.5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,4 +2,4 @@
2
2
  # -*- coding: utf-8 -*-
3
3
 
4
4
  __author__ = "Daniel Baier"
5
- __version__ = "1.9.3"
5
+ __version__ = "1.9.5"
@@ -1,18 +1,62 @@
1
1
  #!/usr/bin/env python3
2
2
  # -*- coding: utf-8 -*-
3
+ """Frida Job management for AndroidFridaManager.
3
4
 
5
+ This module provides the Job class for managing individual Frida instrumentation
6
+ jobs, including script loading, message handling, and lifecycle management.
7
+ """
4
8
 
5
9
  import threading
6
10
  import frida
7
11
  import uuid
8
12
  import logging
13
+ import datetime
14
+ from typing import Optional, List, Callable, Any
15
+
9
16
 
10
17
  # Define a custom exception for handling frida based exceptions
11
18
  class FridaBasedException(Exception):
12
19
  pass
13
20
 
21
+
14
22
  class Job:
15
- def __init__(self, frida_script_name, custom_hooking_handler, process):
23
+ """Represents a single Frida instrumentation job.
24
+
25
+ A job encapsulates a Frida script, its message handler, and lifecycle
26
+ management. Jobs run in separate threads and can be started/stopped
27
+ independently.
28
+
29
+ Attributes:
30
+ job_id: Unique identifier for this job.
31
+ job_type: Category of job (e.g., "fritap", "dexray", "trigdroid", "custom").
32
+ display_name: Human-readable name for UI display.
33
+ hooks_registry: List of methods/functions this job hooks (for conflict detection).
34
+ priority: Job priority (lower = higher priority, default 50).
35
+ state: Current state ("initialized", "running", "stopping").
36
+ started_at: Timestamp when job was started (None if not started).
37
+ """
38
+
39
+ def __init__(
40
+ self,
41
+ frida_script_name: str,
42
+ custom_hooking_handler: Callable,
43
+ process: Any,
44
+ job_type: str = "custom",
45
+ display_name: Optional[str] = None,
46
+ hooks_registry: Optional[List[str]] = None,
47
+ priority: int = 50,
48
+ ):
49
+ """Initialize a new Job.
50
+
51
+ Args:
52
+ frida_script_name: Path to the Frida script file.
53
+ custom_hooking_handler: Callback function for handling Frida messages.
54
+ process: Frida session/process to attach the script to.
55
+ job_type: Category of job for coordination (default: "custom").
56
+ display_name: Human-readable name (default: script filename).
57
+ hooks_registry: List of hooked methods for conflict detection.
58
+ priority: Job priority, lower = higher (default: 50).
59
+ """
16
60
  self.frida_script_name = frida_script_name
17
61
  self.job_id = str(uuid.uuid4())
18
62
  self.state = "initialized"
@@ -24,6 +68,13 @@ class Job:
24
68
  self.is_script_created = False
25
69
  self.logger = logging.getLogger(__name__)
26
70
 
71
+ # New metadata fields for job coordination
72
+ self.job_type = job_type
73
+ self.display_name = display_name or frida_script_name
74
+ self.hooks_registry = hooks_registry or []
75
+ self.priority = priority
76
+ self.started_at: Optional[datetime.datetime] = None
77
+
27
78
 
28
79
  def create_job_script(self):
29
80
  self.instrument(self.process_session)
@@ -31,7 +82,8 @@ class Job:
31
82
 
32
83
 
33
84
  def run_job(self):
34
- #self.is_running_as_thread = True
85
+ """Start the job execution in a separate thread."""
86
+ self.started_at = datetime.datetime.now()
35
87
  self.run_job_as_thread()
36
88
 
37
89
 
@@ -82,15 +134,38 @@ class Job:
82
134
  raise FridaBasedException("Connection is closed. Probably the target app crashed")
83
135
 
84
136
 
85
- def close_job(self):
137
+ def close_job(self, timeout: float = 5.0) -> bool:
138
+ """Stop the job and cleanup resources.
139
+
140
+ Args:
141
+ timeout: Maximum seconds to wait for thread to stop.
142
+ Default 5.0 seconds. Use 0 for no wait.
143
+
144
+ Returns:
145
+ True if job stopped cleanly, False if timed out.
146
+ """
86
147
  self.state = "stopping"
87
148
  self.stop_event.set()
88
- if self.thread:
89
- self.thread.join()
149
+
150
+ timed_out = False
151
+ if self.thread and self.thread.is_alive():
152
+ self.thread.join(timeout=timeout if timeout > 0 else None)
153
+ if self.thread.is_alive():
154
+ self.logger.warning(
155
+ f"Job {self.job_id} thread did not stop within {timeout}s"
156
+ )
157
+ timed_out = True
158
+
159
+ # Try to unload script even if thread timed out
90
160
  if self.script:
91
- self.script.unload()
92
-
93
- self.logger.info(f"Job {self.job_id} stopped")
161
+ try:
162
+ self.script.unload()
163
+ except Exception as e:
164
+ self.logger.warning(f"Error unloading script: {e}")
165
+
166
+ status = "timed out" if timed_out else "stopped"
167
+ self.logger.info(f"Job {self.job_id} {status}")
168
+ return not timed_out
94
169
 
95
170
 
96
171
  def get_id(self):
@@ -98,4 +173,22 @@ class Job:
98
173
 
99
174
 
100
175
  def get_script_of_job(self):
101
- return self.script
176
+ return self.script
177
+
178
+ def get_info(self) -> dict:
179
+ """Get job information as a dictionary for UI display.
180
+
181
+ Returns:
182
+ Dictionary containing job metadata and state information.
183
+ """
184
+ return {
185
+ "job_id": self.job_id,
186
+ "job_type": self.job_type,
187
+ "display_name": self.display_name,
188
+ "state": self.state,
189
+ "priority": self.priority,
190
+ "hooks_count": len(self.hooks_registry),
191
+ "hooks_registry": self.hooks_registry.copy(),
192
+ "started_at": self.started_at.isoformat() if self.started_at else None,
193
+ "script_name": self.frida_script_name,
194
+ }
@@ -1,30 +1,45 @@
1
1
  #!/usr/bin/env python3
2
2
  # -*- coding: utf-8 -*-
3
+ """Frida JobManager for coordinating multiple instrumentation jobs.
4
+
5
+ This module provides the JobManager class for managing Frida sessions and
6
+ coordinating multiple instrumentation jobs on Android devices.
7
+ """
3
8
 
4
9
  import atexit
5
10
  import subprocess
6
11
  import frida
7
- from typing import Optional, Dict, Union, List
12
+ from typing import Optional, Dict, Union, List, Callable
8
13
  from .job import Job, FridaBasedException
9
14
  import time
10
15
  import re
11
16
  import logging
12
17
 
18
+
13
19
  class JobManager(object):
14
- """ A class representing the current Job manager with multi-device support. """
20
+ """Job manager with multi-device support and hook coordination.
15
21
 
22
+ Manages Frida sessions and coordinates multiple instrumentation jobs,
23
+ providing hook conflict detection and session status information.
24
+
25
+ Attributes:
26
+ jobs: Dictionary of active jobs by job_id.
27
+ process_session: Current Frida session.
28
+ device: Current Frida device.
29
+ package_name: Name of the attached/spawned package.
30
+ pid: Process ID (-1 if attach mode).
31
+ """
16
32
 
17
33
  def __init__(self, host="", enable_spawn_gating=False, device_serial: Optional[str] = None) -> None:
18
- """
19
- Init a new job manager with optional device targeting.
34
+ """Init a new job manager with optional device targeting.
20
35
 
21
- :param host: Remote host for Frida connection (ip:port format)
22
- :param enable_spawn_gating: Enable spawn gating for child process tracking
23
- :param device_serial: Specific device serial to target (e.g., 'emulator-5554').
24
- If None, uses default device selection.
36
+ Args:
37
+ host: Remote host for Frida connection (ip:port format).
38
+ enable_spawn_gating: Enable spawn gating for child process tracking.
39
+ device_serial: Specific device serial to target (e.g., 'emulator-5554').
40
+ If None, uses default device selection.
25
41
  """
26
-
27
- self.jobs = {}
42
+ self.jobs: Dict[str, Job] = {}
28
43
  self.is_first_job = True
29
44
  self.process_session = None
30
45
  self.host = host
@@ -42,6 +57,11 @@ class JobManager(object):
42
57
  self._device_serial = device_serial
43
58
  self._multiple_devices = self._check_multiple_devices()
44
59
 
60
+ # Hook coordination (NEW)
61
+ self._hook_registry: Dict[str, str] = {} # hook_target -> job_id
62
+ self._mode: Optional[str] = None # "spawn" or "attach"
63
+ self._paused: bool = False # Track if spawned process is paused
64
+
45
65
  atexit.register(self.cleanup)
46
66
 
47
67
  def _ensure_logging_setup(self):
@@ -134,13 +154,80 @@ class JobManager(object):
134
154
  '''
135
155
 
136
156
  def spawn(self, target_process):
157
+ """Spawn a new process and attach to it.
158
+
159
+ Args:
160
+ target_process: Package name to spawn.
161
+
162
+ Returns:
163
+ Process ID of the spawned process.
164
+ """
165
+ self._mode = "spawn"
137
166
  self.package_name = target_process
138
167
  self.logger.info("[*] spawning app: "+ target_process)
139
168
  pid = self.device.spawn(target_process)
140
169
  self.process_session = self.device.attach(pid)
170
+ self.pid = pid
171
+ self._paused = True # Spawned processes start paused
141
172
  self.logger.info(f"Spawned {target_process} with PID {pid}")
142
173
  return pid
143
174
 
175
+ def spawn_paused(self, target_process: str) -> int:
176
+ """Spawn a process but keep it paused for multi-tool loading.
177
+
178
+ Unlike the regular spawn flow where resume happens on first job,
179
+ this method keeps the process paused until explicitly resumed
180
+ via resume_app(). This allows loading multiple Jobs before
181
+ the app starts executing.
182
+
183
+ Args:
184
+ target_process: Package name to spawn.
185
+
186
+ Returns:
187
+ Process ID of the spawned (paused) process.
188
+ """
189
+ self._mode = "spawn"
190
+ self.package_name = target_process
191
+ self.logger.info(f"[*] spawning app (paused): {target_process}")
192
+ pid = self.device.spawn(target_process)
193
+ self.process_session = self.device.attach(pid)
194
+ self.pid = pid
195
+ self._paused = True
196
+ # Mark that auto-resume should NOT happen
197
+ self.is_first_job = False # Prevent auto-resume in start_job()
198
+ self.logger.info(f"Spawned {target_process} with PID {pid} (PAUSED - awaiting manual resume)")
199
+ return pid
200
+
201
+ def resume_app(self) -> bool:
202
+ """Resume a paused spawned process.
203
+
204
+ Call this after loading all Jobs to start the app with
205
+ all hooks already installed.
206
+
207
+ Returns:
208
+ True if resumed successfully, False if not paused or failed.
209
+ """
210
+ if not self._paused or self.pid == -1:
211
+ self.logger.warning("No paused process to resume")
212
+ return False
213
+
214
+ try:
215
+ self.device.resume(self.pid)
216
+ self._paused = False
217
+ time.sleep(1) # Required for Java.perform stability
218
+ self.logger.info(f"Resumed process {self.pid}")
219
+ return True
220
+ except Exception as e:
221
+ self.logger.error(f"Failed to resume process: {e}")
222
+ return False
223
+
224
+ def is_paused(self) -> bool:
225
+ """Check if the spawned process is currently paused.
226
+
227
+ Returns:
228
+ True if process is spawned and waiting for resume.
229
+ """
230
+ return self._paused
144
231
 
145
232
  def start_android_app(self, package_name: str, main_activity: Optional[str] = None, extras: Optional[Dict[str, Union[str, bool]]] = None):
146
233
  """
@@ -190,6 +277,13 @@ class JobManager(object):
190
277
 
191
278
 
192
279
  def attach_app(self, target_process, foreground=False):
280
+ """Attach to a running process.
281
+
282
+ Args:
283
+ target_process: Package name or PID to attach to.
284
+ foreground: If True, attach to the frontmost application.
285
+ """
286
+ self._mode = "attach"
193
287
  self.package_name = target_process
194
288
 
195
289
  if foreground:
@@ -261,11 +355,44 @@ class JobManager(object):
261
355
  raise FridaBasedException(f"Frida-Error: {fe}")
262
356
 
263
357
 
264
- def start_job(self,frida_script_name, custom_hooking_handler_name):
358
+ def start_job(
359
+ self,
360
+ frida_script_name: str,
361
+ custom_hooking_handler_name: Callable,
362
+ job_type: str = "custom",
363
+ display_name: Optional[str] = None,
364
+ hooks_registry: Optional[List[str]] = None,
365
+ priority: int = 50,
366
+ ) -> Optional[Job]:
367
+ """Start a new instrumentation job.
368
+
369
+ Args:
370
+ frida_script_name: Path to the Frida script file.
371
+ custom_hooking_handler_name: Callback for handling Frida messages.
372
+ job_type: Category of job (e.g., "fritap", "dexray", "trigdroid").
373
+ display_name: Human-readable name for UI display.
374
+ hooks_registry: List of hooked methods for conflict detection.
375
+ priority: Job priority (lower = higher priority).
376
+
377
+ Returns:
378
+ The created Job instance, or None if no session exists.
379
+
380
+ Raises:
381
+ FridaBasedException: If Frida encounters an error.
382
+ """
383
+ job = None # Initialize before try block for safe exception handling
265
384
  try:
266
385
  if self.process_session:
267
- job = Job(frida_script_name, custom_hooking_handler_name, self.process_session)
268
- self.logger.info(f"[*] created job: {job.job_id}")
386
+ job = Job(
387
+ frida_script_name,
388
+ custom_hooking_handler_name,
389
+ self.process_session,
390
+ job_type=job_type,
391
+ display_name=display_name,
392
+ hooks_registry=hooks_registry,
393
+ priority=priority,
394
+ )
395
+ self.logger.info(f"[*] created job: {job.job_id} ({job.display_name})")
269
396
  self.jobs[job.job_id] = job
270
397
  self.last_created_job = job
271
398
  job.run_job()
@@ -274,13 +401,14 @@ class JobManager(object):
274
401
  self.first_instrumenation_script = custom_hooking_handler_name
275
402
  if self.pid != -1:
276
403
  self.device.resume(self.pid)
277
- time.sleep(1) # without it Java.perform silently fails
404
+ self._paused = False # Mark as resumed
405
+ time.sleep(1) # without it Java.perform silently fails
278
406
 
279
407
  return job
280
408
 
281
409
  else:
282
410
  self.logger.error("[-] no frida session. Aborting...")
283
-
411
+ return None
284
412
 
285
413
  except frida.TransportError as fe:
286
414
  raise FridaBasedException(f"Problems while attaching to frida-server: {fe}")
@@ -291,26 +419,53 @@ class JobManager(object):
291
419
  except frida.ProcessNotFoundError as pe:
292
420
  raise FridaBasedException(f"ProcessNotFoundError: {pe}")
293
421
  except KeyboardInterrupt:
294
- self.stop_app_with_last_job(job,self.package_name)
295
- pass
422
+ if job:
423
+ self.stop_app_with_last_job(job, self.package_name)
424
+ return None
425
+
296
426
 
427
+ def stop_jobs(self, timeout_per_job: float = 3.0) -> dict:
428
+ """Stop all running jobs.
297
429
 
298
- def stop_jobs(self):
430
+ Args:
431
+ timeout_per_job: Maximum seconds to wait per job.
432
+
433
+ Returns:
434
+ Dictionary mapping job_id to success status.
435
+ """
436
+ results = {}
299
437
  jobs_to_stop = [job_id for job_id, job in self.jobs.items() if job.state == "running"]
438
+
300
439
  for job_id in jobs_to_stop:
301
440
  try:
302
- self.logger.info('[job manager] Job: {0} - Stopping'.format(job_id))
303
- self.stop_job_with_id(job_id)
441
+ self.logger.info(f'[job manager] Job: {job_id} - Stopping')
442
+ results[job_id] = self.stop_job_with_id(job_id, timeout=timeout_per_job)
304
443
  except frida.InvalidOperationError:
305
- self.logger.error('[job manager] Job: {0} - An error occurred stopping job. Device may '
306
- 'no longer be available.'.format(job_id))
444
+ self.logger.error(f'[job manager] Job: {job_id} - Error stopping')
445
+ results[job_id] = False
307
446
 
447
+ return results
308
448
 
309
- def stop_job_with_id(self,job_id):
310
- if job_id in self.jobs:
311
- job = self.jobs[job_id]
312
- job.close_job()
313
- del self.jobs[job_id]
449
+
450
+ def stop_job_with_id(self, job_id: str, timeout: float = 5.0) -> bool:
451
+ """Stop a specific job by ID.
452
+
453
+ Args:
454
+ job_id: UUID of the job to stop.
455
+ timeout: Maximum seconds to wait for job to stop.
456
+
457
+ Returns:
458
+ True if job stopped cleanly, False if timed out or not found.
459
+ """
460
+ if job_id not in self.jobs:
461
+ return False
462
+
463
+ job = self.jobs[job_id]
464
+ # Unregister hooks before closing
465
+ self.unregister_hooks(job_id)
466
+ success = job.close_job(timeout=timeout)
467
+ del self.jobs[job_id]
468
+ return success
314
469
 
315
470
 
316
471
  def get_last_created_job(self):
@@ -325,10 +480,38 @@ class JobManager(object):
325
480
  raise ValueError(f"Job with ID {job_id} not found.")
326
481
 
327
482
 
328
- def detach_from_app(self):
329
- if self.process_session:
483
+ def detach_from_app(self, timeout: float = 3.0) -> bool:
484
+ """Detach from the current app session.
485
+
486
+ Args:
487
+ timeout: Maximum seconds to wait for detach.
488
+
489
+ Returns:
490
+ True if detached successfully, False if timed out or failed.
491
+ """
492
+ if not self.process_session:
493
+ return True
494
+
495
+ import concurrent.futures
496
+
497
+ def _detach():
330
498
  self.process_session.detach()
331
499
 
500
+ try:
501
+ with concurrent.futures.ThreadPoolExecutor(max_workers=1) as executor:
502
+ future = executor.submit(_detach)
503
+ future.result(timeout=timeout)
504
+ self.process_session = None
505
+ return True
506
+ except concurrent.futures.TimeoutError:
507
+ self.logger.warning(f"Detach timed out after {timeout}s")
508
+ self.process_session = None # Clear anyway to avoid reuse
509
+ return False
510
+ except Exception as e:
511
+ self.logger.warning(f"Detach failed: {e}")
512
+ self.process_session = None
513
+ return False
514
+
332
515
 
333
516
  def stop_app(self, app_package):
334
517
  cmd = self._build_adb_command(["shell", "am", "force-stop", app_package])
@@ -411,3 +594,152 @@ class JobManager(object):
411
594
  raise FridaBasedException("Unable to find device")
412
595
  except frida.ServerNotRunningError:
413
596
  raise FridaBasedException("Frida server not running. Start frida-server and try it again.")
597
+
598
+ # ==================== Hook Coordination Methods ====================
599
+
600
+ def register_hooks(self, job_id: str, hooks: List[str]) -> List[str]:
601
+ """Register hooks for a job and detect conflicts.
602
+
603
+ Args:
604
+ job_id: UUID of the job registering hooks.
605
+ hooks: List of hook targets (method names, function signatures).
606
+
607
+ Returns:
608
+ List of conflicting hooks that are already registered by other jobs.
609
+ """
610
+ conflicts = []
611
+ for hook in hooks:
612
+ if hook in self._hook_registry:
613
+ existing_job_id = self._hook_registry[hook]
614
+ if existing_job_id != job_id:
615
+ conflicts.append(hook)
616
+ self.logger.warning(
617
+ f"Hook conflict: '{hook}' already registered by job {existing_job_id[:8]}"
618
+ )
619
+ else:
620
+ self._hook_registry[hook] = job_id
621
+
622
+ if conflicts:
623
+ self.logger.warning(
624
+ f"Job {job_id[:8]} has {len(conflicts)} hook conflict(s)"
625
+ )
626
+
627
+ return conflicts
628
+
629
+ def unregister_hooks(self, job_id: str) -> None:
630
+ """Remove all hooks registered by a job.
631
+
632
+ Args:
633
+ job_id: UUID of the job whose hooks should be removed.
634
+ """
635
+ hooks_to_remove = [
636
+ hook for hook, jid in self._hook_registry.items() if jid == job_id
637
+ ]
638
+ for hook in hooks_to_remove:
639
+ del self._hook_registry[hook]
640
+
641
+ if hooks_to_remove:
642
+ self.logger.debug(
643
+ f"Unregistered {len(hooks_to_remove)} hooks for job {job_id[:8]}"
644
+ )
645
+
646
+ def check_hook_conflicts(self, hooks: List[str]) -> Dict[str, str]:
647
+ """Check for potential conflicts before registering hooks.
648
+
649
+ Args:
650
+ hooks: List of hook targets to check.
651
+
652
+ Returns:
653
+ Dictionary mapping conflicting hooks to their owning job IDs.
654
+ """
655
+ return {
656
+ hook: self._hook_registry[hook]
657
+ for hook in hooks
658
+ if hook in self._hook_registry
659
+ }
660
+
661
+ def get_hook_registry(self) -> Dict[str, str]:
662
+ """Get a copy of the current hook registry.
663
+
664
+ Returns:
665
+ Dictionary mapping hook targets to job IDs.
666
+ """
667
+ return self._hook_registry.copy()
668
+
669
+ # ==================== Session Info Methods ====================
670
+
671
+ def has_active_session(self) -> bool:
672
+ """Check if there's an active Frida session.
673
+
674
+ Returns:
675
+ True if a session is active, False otherwise.
676
+ """
677
+ return self.process_session is not None
678
+
679
+ def get_session_info(self) -> Dict[str, any]:
680
+ """Get current session information for UI display.
681
+
682
+ Returns:
683
+ Dictionary containing session state information.
684
+ """
685
+ return {
686
+ "package": self.package_name,
687
+ "pid": self.pid,
688
+ "mode": self._mode or ("spawn" if self.pid != -1 else "attach"),
689
+ "device_serial": self._device_serial,
690
+ "has_session": self.process_session is not None,
691
+ "job_count": len(self.jobs),
692
+ "running_job_count": len(self.running_jobs()),
693
+ }
694
+
695
+ def get_running_jobs_info(self) -> List[Dict[str, any]]:
696
+ """Get information about running jobs for UI display.
697
+
698
+ Returns:
699
+ List of dictionaries containing job information.
700
+ """
701
+ return [
702
+ job.get_info()
703
+ for job in self.jobs.values()
704
+ if job.state == "running"
705
+ ]
706
+
707
+ def get_all_jobs_info(self) -> List[Dict[str, any]]:
708
+ """Get information about all jobs (running and stopped).
709
+
710
+ Returns:
711
+ List of dictionaries containing job information.
712
+ """
713
+ return [job.get_info() for job in self.jobs.values()]
714
+
715
+ @property
716
+ def mode(self) -> Optional[str]:
717
+ """Get the current session mode ('spawn' or 'attach')."""
718
+ return self._mode
719
+
720
+ def reset_session(self, timeout_per_job: float = 2.0, detach_timeout: float = 2.0) -> None:
721
+ """Reset the session state for a new connection.
722
+
723
+ Args:
724
+ timeout_per_job: Max seconds to wait per job when stopping.
725
+ detach_timeout: Max seconds to wait for session detach.
726
+ """
727
+ # Stop all running jobs with timeout
728
+ self.stop_jobs(timeout_per_job=timeout_per_job)
729
+
730
+ # Clear hook registry
731
+ self._hook_registry.clear()
732
+
733
+ # Detach from app with timeout
734
+ self.detach_from_app(timeout=detach_timeout)
735
+
736
+ # Reset state
737
+ self.process_session = None
738
+ self.pid = -1
739
+ self.package_name = ""
740
+ self._mode = None
741
+ self.is_first_job = True
742
+ self.last_created_job = None
743
+ self.init_last_job = False
744
+
745
+ self.logger.info("[*] Session reset complete")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: AndroidFridaManager
3
- Version: 1.9.3
3
+ Version: 1.9.5
4
4
  Summary: A python API in order to install and run the frida-server on an Android device.
5
5
  Home-page: https://github.com/fkie-cad/AndroidFridaManager
6
6
  Author: Daniel Baier
@@ -33,7 +33,7 @@ Dynamic: requires-dist
33
33
  Dynamic: requires-python
34
34
  Dynamic: summary
35
35
 
36
- ![version](https://img.shields.io/badge/version-1.9.3-blue) [![PyPI version](https://badge.fury.io/py/AndroidFridaManager.svg)](https://badge.fury.io/py/AndroidFridaManager) [![Publish status](https://github.com/fkie-cad/friTap/actions/workflows/publish.yml/badge.svg?branch=main)](https://github.com/fkie-cad/AndroidFridaManager/actions/workflows/publish-to-pypi.yml)
36
+ ![version](https://img.shields.io/badge/version-1.9.5-blue) [![PyPI version](https://badge.fury.io/py/AndroidFridaManager.svg)](https://badge.fury.io/py/AndroidFridaManager) [![Publish status](https://github.com/fkie-cad/friTap/actions/workflows/publish.yml/badge.svg?branch=main)](https://github.com/fkie-cad/AndroidFridaManager/actions/workflows/publish-to-pypi.yml)
37
37
 
38
38
  # AndroidFridaManager
39
39
 
@@ -0,0 +1,11 @@
1
+ AndroidFridaManager/FridaManager.py,sha256=pTcis4oLPBN5U7CdBqdUufwuG6QQDWTPBleTC7rtTbI,32145
2
+ AndroidFridaManager/__init__.py,sha256=T6AKtrGSLQ9M5bJoWDQcsRTJbSEbksdgrx3AAAdozRI,171
3
+ AndroidFridaManager/about.py,sha256=wGo_88YOuzXo2vF1EyCEmIO4uxM7wOJLC-FrGLp__Mw,98
4
+ AndroidFridaManager/job.py,sha256=Yzir0XkkpFj5cFIcMRGJlLnIcIuDVZ9jBLFMqwQKTFA,7041
5
+ AndroidFridaManager/job_manager.py,sha256=Ce2lHLezRVKuj7r_RodH6Q8l43zL_cL5ZGhE1zGB-E8,27336
6
+ androidfridamanager-1.9.5.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
7
+ androidfridamanager-1.9.5.dist-info/METADATA,sha256=pjOCtO1KPNhyybRz8O5a56LXfeTqnKRcm0P0Kii5kpA,5141
8
+ androidfridamanager-1.9.5.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
9
+ androidfridamanager-1.9.5.dist-info/entry_points.txt,sha256=GmNngu2fDNCxUcquFRegBa7GWknPKG1jsM4lvWeyKnY,64
10
+ androidfridamanager-1.9.5.dist-info/top_level.txt,sha256=oH2lVMSRlghmt-_tVrOEUqvY462P9hd5Ktgp5-1qF3o,20
11
+ androidfridamanager-1.9.5.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.9.0)
2
+ Generator: setuptools (80.10.2)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,11 +0,0 @@
1
- AndroidFridaManager/FridaManager.py,sha256=pTcis4oLPBN5U7CdBqdUufwuG6QQDWTPBleTC7rtTbI,32145
2
- AndroidFridaManager/__init__.py,sha256=T6AKtrGSLQ9M5bJoWDQcsRTJbSEbksdgrx3AAAdozRI,171
3
- AndroidFridaManager/about.py,sha256=T8Pv1VrgHp0Mn_kJwh5M0yzCUK5pILMP4LdeITJzGtM,98
4
- AndroidFridaManager/job.py,sha256=1NNcfCjkyUtwUkMXSgT4uswA8UStHo3jxbeJwJoWhc8,3352
5
- AndroidFridaManager/job_manager.py,sha256=S3biHhYrk-DUUfrHA-g8vbOqwgl4FnWELrUjMxsFyG8,15983
6
- androidfridamanager-1.9.3.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
7
- androidfridamanager-1.9.3.dist-info/METADATA,sha256=sScuKDxdfBmE7Ag4i-Nd9pu9Ps1Cif29Is4gDOunTvU,5141
8
- androidfridamanager-1.9.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
9
- androidfridamanager-1.9.3.dist-info/entry_points.txt,sha256=GmNngu2fDNCxUcquFRegBa7GWknPKG1jsM4lvWeyKnY,64
10
- androidfridamanager-1.9.3.dist-info/top_level.txt,sha256=oH2lVMSRlghmt-_tVrOEUqvY462P9hd5Ktgp5-1qF3o,20
11
- androidfridamanager-1.9.3.dist-info/RECORD,,