trueseeing 2.2.4__tar.gz → 2.2.5__tar.gz

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.
Files changed (99) hide show
  1. trueseeing-2.2.5/.github/FUNDING.yml +14 -0
  2. {trueseeing-2.2.4 → trueseeing-2.2.5}/Dockerfile +1 -0
  3. {trueseeing-2.2.4 → trueseeing-2.2.5}/PKG-INFO +7 -5
  4. {trueseeing-2.2.4 → trueseeing-2.2.5}/README.md +4 -2
  5. trueseeing-2.2.5/trueseeing/__init__.py +2 -0
  6. {trueseeing-2.2.4 → trueseeing-2.2.5}/trueseeing/api.py +3 -1
  7. {trueseeing-2.2.4 → trueseeing-2.2.5}/trueseeing/app/cmd/__init__.py +2 -0
  8. {trueseeing-2.2.4 → trueseeing-2.2.5}/trueseeing/app/cmd/android/asm.py +48 -10
  9. {trueseeing-2.2.4 → trueseeing-2.2.5}/trueseeing/app/cmd/android/engage.py +104 -42
  10. {trueseeing-2.2.4 → trueseeing-2.2.5}/trueseeing/app/cmd/android/recon.py +12 -8
  11. {trueseeing-2.2.4 → trueseeing-2.2.5}/trueseeing/app/cmd/android/search.py +8 -8
  12. {trueseeing-2.2.4 → trueseeing-2.2.5}/trueseeing/app/cmd/android/show.py +6 -6
  13. {trueseeing-2.2.4 → trueseeing-2.2.5}/trueseeing/app/inspect.py +117 -37
  14. {trueseeing-2.2.4 → trueseeing-2.2.5}/trueseeing/app/shell.py +10 -8
  15. trueseeing-2.2.5/trueseeing/core/analyze.py +65 -0
  16. {trueseeing-2.2.4/trueseeing/core/android/analysis → trueseeing-2.2.5/trueseeing/core/android/analyze}/flow.py +1 -1
  17. trueseeing-2.2.5/trueseeing/core/android/analyze/nat.py +44 -0
  18. {trueseeing-2.2.4 → trueseeing-2.2.5}/trueseeing/core/android/asm.py +31 -6
  19. {trueseeing-2.2.4 → trueseeing-2.2.5}/trueseeing/core/android/context.py +60 -13
  20. {trueseeing-2.2.4 → trueseeing-2.2.5}/trueseeing/core/android/db.py +26 -2
  21. {trueseeing-2.2.4 → trueseeing-2.2.5}/trueseeing/core/android/device.py +1 -1
  22. {trueseeing-2.2.4 → trueseeing-2.2.5}/trueseeing/core/android/model.py +10 -2
  23. {trueseeing-2.2.4 → trueseeing-2.2.5}/trueseeing/core/context.py +13 -0
  24. {trueseeing-2.2.4 → trueseeing-2.2.5}/trueseeing/core/db.py +6 -6
  25. {trueseeing-2.2.4 → trueseeing-2.2.5}/trueseeing/core/env.py +5 -1
  26. {trueseeing-2.2.4 → trueseeing-2.2.5}/trueseeing/core/ext.py +44 -14
  27. trueseeing-2.2.5/trueseeing/core/ios/analyze.py +74 -0
  28. trueseeing-2.2.5/trueseeing/core/ios/context.py +107 -0
  29. trueseeing-2.2.5/trueseeing/core/ios/db.py +60 -0
  30. trueseeing-2.2.5/trueseeing/core/ios/model.py +15 -0
  31. trueseeing-2.2.5/trueseeing/core/ios/store.py +34 -0
  32. trueseeing-2.2.5/trueseeing/core/ios/swift.py +120 -0
  33. {trueseeing-2.2.4 → trueseeing-2.2.5}/trueseeing/core/scan.py +1 -1
  34. {trueseeing-2.2.4 → trueseeing-2.2.5}/trueseeing/core/tools.py +1 -1
  35. {trueseeing-2.2.4 → trueseeing-2.2.5}/trueseeing/core/ui.py +28 -8
  36. {trueseeing-2.2.4 → trueseeing-2.2.5}/trueseeing/libs/android/store.0.sql +1 -0
  37. trueseeing-2.2.5/trueseeing/libs/android/store.1.sql +2 -0
  38. trueseeing-2.2.5/trueseeing/libs/ios/store.0.sql +1 -0
  39. trueseeing-2.2.5/trueseeing/libs/ios/store.1.sql +2 -0
  40. trueseeing-2.2.5/trueseeing/sig/android/__init__.py +0 -0
  41. {trueseeing-2.2.4 → trueseeing-2.2.5}/trueseeing/sig/android/crypto.py +1 -1
  42. {trueseeing-2.2.4 → trueseeing-2.2.5}/trueseeing/sig/android/fingerprint.py +1 -1
  43. trueseeing-2.2.5/trueseeing/sig/android/nat.py +93 -0
  44. {trueseeing-2.2.4 → trueseeing-2.2.5}/trueseeing/sig/android/privacy.py +1 -1
  45. {trueseeing-2.2.4 → trueseeing-2.2.5}/trueseeing/sig/android/security.py +1 -1
  46. trueseeing-2.2.5/trueseeing/sig/ios/__init__.py +0 -0
  47. trueseeing-2.2.5/trueseeing/sig/ios/base.py +880 -0
  48. trueseeing-2.2.4/trueseeing/__init__.py +0 -2
  49. {trueseeing-2.2.4 → trueseeing-2.2.5}/.dockerignore +0 -0
  50. {trueseeing-2.2.4 → trueseeing-2.2.5}/.github/workflows/deploy.yaml +0 -0
  51. {trueseeing-2.2.4 → trueseeing-2.2.5}/.github/workflows/lint.yaml +0 -0
  52. {trueseeing-2.2.4 → trueseeing-2.2.5}/.github/workflows/publish.yaml +0 -0
  53. {trueseeing-2.2.4 → trueseeing-2.2.5}/.github/workflows/stale.yaml +0 -0
  54. {trueseeing-2.2.4 → trueseeing-2.2.5}/.gitignore +0 -0
  55. {trueseeing-2.2.4 → trueseeing-2.2.5}/COPYING +0 -0
  56. {trueseeing-2.2.4 → trueseeing-2.2.5}/pyproject.toml +0 -0
  57. {trueseeing-2.2.4 → trueseeing-2.2.5}/trueseeing/app/__init__.py +0 -0
  58. {trueseeing-2.2.4 → trueseeing-2.2.5}/trueseeing/app/cmd/alias.py +0 -0
  59. {trueseeing-2.2.4 → trueseeing-2.2.5}/trueseeing/app/cmd/analyze.py +0 -0
  60. {trueseeing-2.2.4 → trueseeing-2.2.5}/trueseeing/app/cmd/android/__init__.py +0 -0
  61. {trueseeing-2.2.4 → trueseeing-2.2.5}/trueseeing/app/cmd/config.py +0 -0
  62. {trueseeing-2.2.4 → trueseeing-2.2.5}/trueseeing/app/cmd/info.py +0 -0
  63. {trueseeing-2.2.4 → trueseeing-2.2.5}/trueseeing/app/cmd/report.py +0 -0
  64. {trueseeing-2.2.4 → trueseeing-2.2.5}/trueseeing/app/cmd/scan.py +0 -0
  65. {trueseeing-2.2.4 → trueseeing-2.2.5}/trueseeing/app/cmd/search.py +0 -0
  66. {trueseeing-2.2.4 → trueseeing-2.2.5}/trueseeing/app/cmd/show.py +0 -0
  67. {trueseeing-2.2.4 → trueseeing-2.2.5}/trueseeing/app/scan.py +0 -0
  68. {trueseeing-2.2.4 → trueseeing-2.2.5}/trueseeing/core/__init__.py +0 -0
  69. {trueseeing-2.2.4/trueseeing/core/android/analysis → trueseeing-2.2.5/trueseeing/core/android/analyze}/__init__.py +0 -0
  70. {trueseeing-2.2.4/trueseeing/core/android/analysis → trueseeing-2.2.5/trueseeing/core/android/analyze}/op.py +0 -0
  71. {trueseeing-2.2.4 → trueseeing-2.2.5}/trueseeing/core/android/store.py +0 -0
  72. {trueseeing-2.2.4 → trueseeing-2.2.5}/trueseeing/core/android/tools.py +0 -0
  73. {trueseeing-2.2.4 → trueseeing-2.2.5}/trueseeing/core/config.py +0 -0
  74. {trueseeing-2.2.4 → trueseeing-2.2.5}/trueseeing/core/cvss.py +0 -0
  75. {trueseeing-2.2.4 → trueseeing-2.2.5}/trueseeing/core/exc.py +0 -0
  76. {trueseeing-2.2.4/trueseeing/core/model → trueseeing-2.2.5/trueseeing/core/ios}/__init__.py +0 -0
  77. {trueseeing-2.2.4/trueseeing/sig/android → trueseeing-2.2.5/trueseeing/core/model}/__init__.py +0 -0
  78. {trueseeing-2.2.4 → trueseeing-2.2.5}/trueseeing/core/model/cmd.py +0 -0
  79. {trueseeing-2.2.4 → trueseeing-2.2.5}/trueseeing/core/model/issue.py +0 -0
  80. {trueseeing-2.2.4 → trueseeing-2.2.5}/trueseeing/core/model/sig.py +0 -0
  81. {trueseeing-2.2.4 → trueseeing-2.2.5}/trueseeing/core/report.py +0 -0
  82. {trueseeing-2.2.4 → trueseeing-2.2.5}/trueseeing/core/store.py +0 -0
  83. {trueseeing-2.2.4 → trueseeing-2.2.5}/trueseeing/core/z.py +0 -0
  84. {trueseeing-2.2.4 → trueseeing-2.2.5}/trueseeing/libs/LICENSE.md +0 -0
  85. {trueseeing-2.2.4 → trueseeing-2.2.5}/trueseeing/libs/android/abe.jar +0 -0
  86. {trueseeing-2.2.4 → trueseeing-2.2.5}/trueseeing/libs/android/apkeditor.jar +0 -0
  87. {trueseeing-2.2.4 → trueseeing-2.2.5}/trueseeing/libs/android/apksigner.jar +0 -0
  88. {trueseeing-2.2.4 → trueseeing-2.2.5}/trueseeing/libs/android/frida-app.smali +0 -0
  89. {trueseeing-2.2.4 → trueseeing-2.2.5}/trueseeing/libs/android/frida-scriptdir.config +0 -0
  90. {trueseeing-2.2.4 → trueseeing-2.2.5}/trueseeing/libs/android/nsc.xml +0 -0
  91. {trueseeing-2.2.4 → trueseeing-2.2.5}/trueseeing/libs/files.0.sql +0 -0
  92. {trueseeing-2.2.4 → trueseeing-2.2.5}/trueseeing/libs/public_suffix_list.dat +0 -0
  93. {trueseeing-2.2.4 → trueseeing-2.2.5}/trueseeing/libs/store.0.sql +0 -0
  94. {trueseeing-2.2.4 → trueseeing-2.2.5}/trueseeing/libs/store.s.sql +0 -0
  95. {trueseeing-2.2.4 → trueseeing-2.2.5}/trueseeing/libs/template/report.html +0 -0
  96. {trueseeing-2.2.4 → trueseeing-2.2.5}/trueseeing/libs/tlds.txt +0 -0
  97. {trueseeing-2.2.4 → trueseeing-2.2.5}/trueseeing/py.typed +0 -0
  98. {trueseeing-2.2.4 → trueseeing-2.2.5}/trueseeing/sig/__init__.py +0 -0
  99. {trueseeing-2.2.4 → trueseeing-2.2.5}/trueseeing/sig/android/manifest.py +0 -0
@@ -0,0 +1,14 @@
1
+ # These are supported funding model platforms
2
+
3
+ github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
4
+ patreon: # Replace with a single Patreon username
5
+ open_collective: # Replace with a single Open Collective username
6
+ ko_fi: # Replace with a single Ko-fi username
7
+ tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8
+ community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9
+ liberapay: # Replace with a single Liberapay username
10
+ issuehunt: # Replace with a single IssueHunt username
11
+ lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
12
+ polar: # Replace with a single Polar username
13
+ buy_me_a_coffee: alterakey
14
+ custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
@@ -22,5 +22,6 @@ env TS2_IN_DOCKER=1
22
22
  env TS2_CACHEDIR=/cache
23
23
  env TS2_HOME=/data
24
24
  env TS2_EXTDIR=/ext
25
+ env TS2_SWIFT_DEMANGLER_URL=http://ts2-swift-demangle
25
26
  workdir /out
26
27
  entrypoint ["trueseeing"]
@@ -1,7 +1,7 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.3
2
2
  Name: trueseeing
3
- Version: 2.2.4
4
- Summary: Trueseeing is a non-decompiling Android application vulnerability scanner.
3
+ Version: 2.2.5
4
+ Summary: Trueseeing is a non-decompiling iOS/Android application vulnerability scanner.
5
5
  Keywords: android,security,pentest,hacking
6
6
  Author-email: Takahiro Yoshimura <alterakey@protonmail.com>
7
7
  Requires-Python: >=3.9
@@ -39,7 +39,7 @@ Provides-Extra: dev
39
39
  ![Main branch deploy status](https://github.com/alterakey/trueseeing/workflows/deploy/badge.svg)
40
40
  ![Main branch last commit](https://img.shields.io/github/last-commit/alterakey/trueseeing/main)
41
41
 
42
- trueseeing is a fast, accurate and resillient vulnerability scanner for Android apps. We operate on the Dalvik VM level -- i.e. we don't care if the target app is obfuscated or not.
42
+ trueseeing is a fast, accurate and resillient vulnerability scanner for iOS/Android apps. We operate on the Dalvik VM level for Android -- i.e. we don't care if the target app is obfuscated or not.
43
43
 
44
44
  ## Capability
45
45
 
@@ -51,6 +51,8 @@ Currently we can:
51
51
  * Copy in/out app data through debug interface
52
52
  * Search for certain calls/consts/sput/iput
53
53
  * Deduce constants/typesets for args of op
54
+ * Scan API/private calls for native codes (NB: you need [ts2-disasm-ghidra](https://github.com/alterakey/ts2-disasm-ghidra))
55
+ * Scan iOS apps for basic vulnerabilities (NB: you need [ts2-disasm-ghidra](https://github.com/alterakey/ts2-disasm-ghidra))
54
56
  * etc.
55
57
 
56
58
  ## Installation
@@ -84,7 +86,7 @@ Alternatively, you can install our package with pip as follows. This form of ins
84
86
  You can interactively scan/analyze/patch/etc. apps -- making it the ideal choice for manual analysis:
85
87
 
86
88
  $ trueseeing target.apk
87
- [+] trueseeing 2.2.4
89
+ [+] trueseeing 2.2.5
88
90
  ts[target.apk]> ?
89
91
  ...
90
92
  ts[target.apk]> i # show generic information
@@ -5,7 +5,7 @@
5
5
  ![Main branch deploy status](https://github.com/alterakey/trueseeing/workflows/deploy/badge.svg)
6
6
  ![Main branch last commit](https://img.shields.io/github/last-commit/alterakey/trueseeing/main)
7
7
 
8
- trueseeing is a fast, accurate and resillient vulnerability scanner for Android apps. We operate on the Dalvik VM level -- i.e. we don't care if the target app is obfuscated or not.
8
+ trueseeing is a fast, accurate and resillient vulnerability scanner for iOS/Android apps. We operate on the Dalvik VM level for Android -- i.e. we don't care if the target app is obfuscated or not.
9
9
 
10
10
  ## Capability
11
11
 
@@ -17,6 +17,8 @@ Currently we can:
17
17
  * Copy in/out app data through debug interface
18
18
  * Search for certain calls/consts/sput/iput
19
19
  * Deduce constants/typesets for args of op
20
+ * Scan API/private calls for native codes (NB: you need [ts2-disasm-ghidra](https://github.com/alterakey/ts2-disasm-ghidra))
21
+ * Scan iOS apps for basic vulnerabilities (NB: you need [ts2-disasm-ghidra](https://github.com/alterakey/ts2-disasm-ghidra))
20
22
  * etc.
21
23
 
22
24
  ## Installation
@@ -50,7 +52,7 @@ Alternatively, you can install our package with pip as follows. This form of ins
50
52
  You can interactively scan/analyze/patch/etc. apps -- making it the ideal choice for manual analysis:
51
53
 
52
54
  $ trueseeing target.apk
53
- [+] trueseeing 2.2.4
55
+ [+] trueseeing 2.2.5
54
56
  ts[target.apk]> ?
55
57
  ...
56
58
  ts[target.apk]> i # show generic information
@@ -0,0 +1,2 @@
1
+ """Trueseeing is a non-decompiling iOS/Android application vulnerability scanner."""
2
+ __version__ = '2.2.5'
@@ -4,7 +4,7 @@ from abc import ABC, abstractmethod
4
4
 
5
5
  if TYPE_CHECKING:
6
6
  from collections import deque
7
- from typing import Any, TypedDict, Protocol, Optional, Callable, Coroutine, Union, List, Mapping, overload, Literal
7
+ from typing import Any, TypedDict, Protocol, Optional, Callable, Coroutine, Union, List, Mapping, overload, Literal, Set
8
8
  from typing_extensions import deprecated
9
9
  from trueseeing.core.context import Context, ContextType
10
10
  from trueseeing.core.android.context import APKContext
@@ -25,6 +25,7 @@ if TYPE_CHECKING:
25
25
  e: CommandEntrypoint
26
26
  n: str
27
27
  d: str
28
+ t: Set[str]
28
29
 
29
30
  class CommandEntry(Entry):
30
31
  pass
@@ -65,6 +66,7 @@ if TYPE_CHECKING:
65
66
  class CommandHelper(Protocol):
66
67
  def get_target(self) -> Optional[str]: ...
67
68
  def require_target(self, msg: Optional[str] = None) -> str: ...
69
+ def get_context_type(self) -> Optional[Set[ContextType]]: ...
68
70
  @overload
69
71
  def get_context(self) -> Context: ...
70
72
  @overload
@@ -1,10 +1,12 @@
1
1
  from __future__ import annotations
2
2
  from typing import TYPE_CHECKING
3
+ from functools import cache
3
4
 
4
5
  if TYPE_CHECKING:
5
6
  from typing import Type, Iterator
6
7
  from trueseeing.api import Command
7
8
 
9
+ @cache
8
10
  def discover() -> Iterator[Type[Command]]:
9
11
  from trueseeing.api import Command
10
12
  from importlib import import_module
@@ -23,19 +23,21 @@ class AssembleCommand(CommandMixin):
23
23
 
24
24
  def get_commands(self) -> CommandMap:
25
25
  return {
26
- 'ca':dict(e=self._assemble, n='ca[!] /path', d='assemble as target from path'),
27
- 'ca!':dict(e=self._assemble),
28
- 'cd':dict(e=self._disassemble, n='cd[s][!] /path', d='disassemble target into path'),
29
- 'cd!':dict(e=self._disassemble),
30
- 'cds':dict(e=self._disassemble_nodex),
31
- 'cds!':dict(e=self._disassemble_nodex),
32
- 'co':dict(e=self._export_context, n='co[!] /path [pat]', d='export codebase'),
33
- 'co!':dict(e=self._export_context),
26
+ 'ca':dict(e=self._assemble, n='ca[!] /path', d='assemble as target from path', t={'apk'}),
27
+ 'ca!':dict(e=self._assemble, t={'apk'}),
28
+ 'cd':dict(e=self._disassemble, n='cd[s][!] /path', d='disassemble target into path', t={'apk'}),
29
+ 'cd!':dict(e=self._disassemble, t={'apk'}),
30
+ 'cds':dict(e=self._disassemble_nodex, t={'apk'}),
31
+ 'cds!':dict(e=self._disassemble_nodex, t={'apk'}),
32
+ 'cm':dict(e=self._merge, n='cm[!] [output.apk]', d='merge slices into a plain apk', t={'xapk'}),
33
+ 'cm!':dict(e=self._merge, t={'xapk'}),
34
+ 'co':dict(e=self._export_context, n='co[!] /path [pat]', d='export codebase', t={'apk'}),
35
+ 'co!':dict(e=self._export_context, t={'apk'}),
34
36
  }
35
37
 
36
38
  def get_options(self) -> OptionMap:
37
39
  return {
38
- 'nocache':dict(n='nocache', d='do not replicate content before build [ca]')
40
+ 'nocache':dict(n='nocache', d='do not replicate content before build [ca]', t={'apk'}),
39
41
  }
40
42
 
41
43
  async def _assemble(self, args: deque[str]) -> None:
@@ -214,7 +216,7 @@ class AssembleCommand(CommandMixin):
214
216
  if subformat in ['gz']:
215
217
  kwargs.update(dict(compresslevel=3))
216
218
 
217
- with tarfile.open(root, 'w:{}'.format(subformat), **kwargs) as tf: # type: ignore[arg-type]
219
+ with tarfile.open(root, 'w:{}'.format(subformat), **kwargs) as tf: # type: ignore[call-overload]
218
220
  now = int(time.time())
219
221
  for path,blob in q.file_enum(pat=pat, regex=True):
220
222
  target = os.path.join('files', *path.split('/'))
@@ -223,6 +225,7 @@ class AssembleCommand(CommandMixin):
223
225
 
224
226
  bf = BytesIO(blob)
225
227
  ti = tarfile.TarInfo(name=target)
228
+ ti.size = len(blob)
226
229
  ti.uname = 'root'
227
230
  ti.gname = 'root'
228
231
  ti.mode = 0o600
@@ -239,3 +242,38 @@ class AssembleCommand(CommandMixin):
239
242
  return 'tar:gz'
240
243
  else:
241
244
  return None
245
+
246
+ async def _merge(self, args: deque[str]) -> None:
247
+ target = self._helper.require_target('need target')
248
+
249
+ cmd = args.popleft()
250
+
251
+ import os
252
+ import time
253
+ from tempfile import TemporaryDirectory
254
+ from trueseeing.core.android.asm import APKAssembler
255
+ from trueseeing.core.android.tools import move_apk
256
+
257
+ if args:
258
+ outfn = args.popleft()
259
+ else:
260
+ outfn = target.replace('.xapk', '.apk')
261
+
262
+ origfn = outfn.replace('.apk', '.apk.orig')
263
+
264
+ if os.path.exists(origfn) and not cmd.endswith('!'):
265
+ ui.fatal('backup file exists; force (!) to overwrite')
266
+
267
+ ui.info('merging slices {target} -> {outfn}'.format(target=target, outfn=outfn))
268
+
269
+ at = time.time()
270
+
271
+ with TemporaryDirectory() as td:
272
+ apk, sig = await APKAssembler.merge_slices(target, td)
273
+
274
+ if os.path.exists(outfn):
275
+ move_apk(outfn, origfn)
276
+
277
+ move_apk(apk, outfn)
278
+
279
+ ui.success('done ({t:.02f} sec.)'.format(t=(time.time() - at)))
@@ -22,37 +22,40 @@ class EngageCommand(CommandMixin):
22
22
 
23
23
  def get_commands(self) -> CommandMap:
24
24
  return {
25
- 'xtq':dict(e=self._engage_tamper_discard, n='xtq', d='engage: discard changes'),
26
- 'xtx':dict(e=self._engage_tamper_apply, n='xtx[!]', d='engage: apply and rebuild apk'),
27
- 'xtx!':dict(e=self._engage_tamper_apply),
28
- 'xtf':dict(e=self._engage_tamper_inject_frida, n='xtf[!] [config]', d='engage; inject frida gadget'),
29
- 'xtf!':dict(e=self._engage_tamper_inject_frida),
30
- 'xtfs':dict(e=self._engage_tamper_inject_frida_scriptdir, n='xtfs[!] [path]', d='engage; inject frida gadget in script dir mode'),
31
- 'xtfs!':dict(e=self._engage_tamper_inject_frida_scriptdir),
32
- 'xtn':dict(e=self._engage_tamper_disable_pinning, n='xtn', d='engage: patch NSC to disable SSL/TLS pinning'),
33
- 'xtd':dict(e=self._engage_tamper_enable_debug, n='xtd', d='engage: make debuggable'),
34
- 'xtb':dict(e=self._engage_tamper_enable_backup, n='xtb', d='engage: make backupable'),
35
- 'xtt':dict(e=self._engage_tamper_patch_target_api_level, n='xtt[!] <api level>', d='engage: patch target api level'),
36
- 'xtt!':dict(e=self._engage_tamper_patch_target_api_level),
37
- 'xco':dict(e=self._engage_device_copyout, n='xco[!] package [data.tar]', d='engage: copy-out package data'),
38
- 'xco!':dict(e=self._engage_device_copyout),
39
- 'xci':dict(e=self._engage_device_copyin, n='xci[!] package [data.tar]', d='engage: copy-in package data'),
40
- 'xci!':dict(e=self._engage_device_copyin),
41
- 'xpd':dict(e=self._engage_deploy_package, n='xpd[!]', d='engage: deploy target package'),
42
- 'xpd!':dict(e=self._engage_deploy_package),
43
- 'xpu':dict(e=self._engage_undeploy_package, n='xpu', d='engage: remove target package'),
44
- 'xz':dict(e=self._engage_fuzz_intent, n='xz[!] "am-cmdline-template" [output.txt]', d='engage: fuzz intent'),
45
- 'xz!':dict(e=self._engage_fuzz_intent),
46
- 'xzr':dict(e=self._engage_fuzz_command, n='xzr[!] "cmdline-template" [output.txt]', d='engage: fuzz cmdline'),
47
- 'xzr!':dict(e=self._engage_fuzz_command),
48
- 'xg':dict(e=self._engage_grab_package, n='xg[!] package [output.apk]', d='engage: grab package'),
49
- 'xg!':dict(e=self._engage_grab_package),
25
+ 'xtq':dict(e=self._engage_tamper_discard, n='xtq', d='engage: discard changes', t={'apk'}),
26
+ 'xtx':dict(e=self._engage_tamper_apply, n='xtx[!]', d='engage: apply and rebuild apk', t={'apk'}),
27
+ 'xtx!':dict(e=self._engage_tamper_apply, t={'apk'}),
28
+ 'xtf':dict(e=self._engage_tamper_inject_frida, n='xtf[!] [config]', d='engage; inject frida gadget', t={'apk'}),
29
+ 'xtf!':dict(e=self._engage_tamper_inject_frida, t={'apk'}),
30
+ 'xtfs':dict(e=self._engage_tamper_inject_frida_scriptdir, n='xtfs[!] [path]', d='engage; inject frida gadget in script dir mode', t={'apk'}),
31
+ 'xtfs!':dict(e=self._engage_tamper_inject_frida_scriptdir, t={'apk'}),
32
+ 'xtn':dict(e=self._engage_tamper_disable_pinning, n='xtn', d='engage: patch NSC to disable SSL/TLS pinning', t={'apk'}),
33
+ 'xtd':dict(e=self._engage_tamper_enable_debug, n='xtd', d='engage: make debuggable', t={'apk'}),
34
+ 'xtb':dict(e=self._engage_tamper_enable_backup, n='xtb', d='engage: make backupable', t={'apk'}),
35
+ 'xtt':dict(e=self._engage_tamper_patch_target_api_level, n='xtt[!] <api level>', d='engage: patch target api level', t={'apk'}),
36
+ 'xtt!':dict(e=self._engage_tamper_patch_target_api_level, t={'apk'}),
37
+ 'xco':dict(e=self._engage_device_copyout, n='xco[!] package [data.tar]', d='engage: copy-out package data', t={'apk'}),
38
+ 'xco!':dict(e=self._engage_device_copyout, t={'apk'}),
39
+ 'xci':dict(e=self._engage_device_copyin, n='xci[!] package [data.tar]', d='engage: copy-in package data', t={'apk'}),
40
+ 'xci!':dict(e=self._engage_device_copyin, t={'apk'}),
41
+ 'xpd':dict(e=self._engage_deploy_package, n='xpd[!]', d='engage: deploy target package', t={'apk'}),
42
+ 'xpd!':dict(e=self._engage_deploy_package, t={'apk'}),
43
+ 'xpu':dict(e=self._engage_undeploy_package, n='xpu', d='engage: remove target package', t={'apk'}),
44
+ 'xz':dict(e=self._engage_fuzz_intent, n='xz[!] "am-cmdline-template" [output.txt]', d='engage: fuzz intent', t={'apk'}),
45
+ 'xz!':dict(e=self._engage_fuzz_intent, t={'apk'}),
46
+ 'xzr':dict(e=self._engage_fuzz_command, n='xzr[!] "cmdline-template" [output.txt]', d='engage: fuzz cmdline', t={'apk'}),
47
+ 'xzr!':dict(e=self._engage_fuzz_command, t={'apk'}),
48
+ 'xg':dict(e=self._engage_grab_package, n='xg[!] package [output.apk]', d='engage: grab package', t={'apk'}),
49
+ 'xg!':dict(e=self._engage_grab_package, t={'apk'}),
50
+ 'xs':dict(e=self._engage_frida_start_server, n='xs[!] [config]', d='engage: start frida-server (!: force)', t={'apk'}),
51
+ 'xs!':dict(e=self._engage_frida_start_server, t={'apk'}),
52
+ 'xk':dict(e=self._engage_frida_kill_server, n='xk', d='engage: kill frida server', t={'apk'}),
50
53
  }
51
54
 
52
55
  def get_options(self) -> OptionMap:
53
56
  return {
54
- 'vers':dict(n='vers=X.Y.Z', d='specify frida-gadget version to use [xf,xfs]'),
55
- 'w':dict(n='wNAME=FN', d='wordlist, use as {NAME} [xz]'),
57
+ 'vers':dict(n='vers=X.Y.Z', d='specify frida-gadget version to use [xf,xfs]', t={'apk'}),
58
+ 'w':dict(n='wNAME=FN', d='wordlist, use as {NAME} [xz]', t={'apk'}),
56
59
  }
57
60
 
58
61
  async def _engage_tamper_discard(self, args: deque[str]) -> None:
@@ -84,6 +87,10 @@ class EngageCommand(CommandMixin):
84
87
  from trueseeing.core.android.asm import APKAssembler
85
88
  from trueseeing.core.android.tools import move_apk
86
89
 
90
+ if apk.endswith('.xapk'):
91
+ ui.warn('patching xapk as merged apk')
92
+ apk = apk.replace('.xapk', '.apk')
93
+
87
94
  origapk = apk.replace('.apk', '.apk.orig')
88
95
 
89
96
  if os.path.exists(origapk) and not cmd.endswith('!'):
@@ -509,6 +516,7 @@ class EngageCommand(CommandMixin):
509
516
  ui.info(f'copying out: {target} -> {outfn}')
510
517
 
511
518
  import time
519
+ from shlex import quote
512
520
  from subprocess import CalledProcessError
513
521
  from trueseeing.core.tools import invoke_passthru
514
522
  from trueseeing.core.android.device import AndroidDevice
@@ -527,7 +535,7 @@ class EngageCommand(CommandMixin):
527
535
  with toolchains() as tc:
528
536
  await invoke_passthru('java -jar {abe} unpack {outfn}.ab {outfn} 1'.format(
529
537
  abe=tc['abe'],
530
- outfn=outfn,
538
+ outfn=quote(outfn),
531
539
  ))
532
540
  except CalledProcessError:
533
541
  ui.warn('unpack failed (did you give the correct password?); trying the next method')
@@ -556,18 +564,18 @@ class EngageCommand(CommandMixin):
556
564
 
557
565
  tfn0 = self._generate_tempfilename_for_device()
558
566
  ui.info('copying internal storage')
559
- await dev.invoke_adb_passthru(f'shell "run-as {target} tar -cv . > {tfn0}"')
560
- await dev.invoke_adb_passthru(f'pull {tfn0} {outfn0}')
567
+ await dev.invoke_adb_passthru(f"shell 'run-as {target} tar -cv . > {tfn0}'")
568
+ await dev.invoke_adb_passthru(f'pull {tfn0} {quote(outfn0)}')
561
569
  await dev.invoke_adb_passthru(f'shell rm -f {tfn0}')
562
570
  ui.success(f'copied out: {outfn0}')
563
571
 
564
572
  ui.info('copying external storage')
565
573
  tfn1 = self._generate_tempfilename_for_device()
566
574
  try:
567
- await dev.invoke_adb_passthru(f'shell "cd /storage/emulated/0/Android/ && tar -cv data/{target} obb/{target} > {tfn1}"')
575
+ await dev.invoke_adb_passthru(f"shell 'cd /storage/emulated/0/Android/ && tar -cv data/{target} obb/{target} > {tfn1}'")
568
576
  except CalledProcessError:
569
577
  ui.warn('detected errors during extraction from external storage (may indicate partial extraction)')
570
- await dev.invoke_adb_passthru(f'pull {tfn1} {outfn1}')
578
+ await dev.invoke_adb_passthru(f'pull {tfn1} {quote(outfn1)}')
571
579
  await dev.invoke_adb_passthru(f'shell rm -f {tfn1}')
572
580
  ui.success(f'copied out: {outfn1}')
573
581
 
@@ -602,6 +610,7 @@ class EngageCommand(CommandMixin):
602
610
  ui.info(f'copying in: {fn} -> {target}')
603
611
 
604
612
  import time
613
+ from shlex import quote
605
614
  from subprocess import CalledProcessError
606
615
  from trueseeing.core.tools import invoke_passthru
607
616
  from trueseeing.core.android.device import AndroidDevice
@@ -621,14 +630,14 @@ class EngageCommand(CommandMixin):
621
630
  with toolchains() as tc:
622
631
  await invoke_passthru('java -jar {abe} pack-kk {fn} {fn}.ab 1'.format(
623
632
  abe=tc['abe'],
624
- fn=fn,
633
+ fn=quote(fn),
625
634
  ))
626
635
  except CalledProcessError:
627
636
  ui.warn('pack failed; trying the next method')
628
637
  else:
629
638
  ui.success('pack success')
630
639
  ui.info('initiating a restore on device; give "1" as the password if asked')
631
- await dev.invoke_adb_passthru(f'restore {fn}.ab')
640
+ await dev.invoke_adb_passthru(f'restore {quote(fn)}.ab')
632
641
  ui.success(f'copied in: {fn}')
633
642
  success = True
634
643
  finally:
@@ -645,21 +654,21 @@ class EngageCommand(CommandMixin):
645
654
 
646
655
  ui.info('copying internal storage')
647
656
  if not os.path.exists(fn0):
648
- ui.warn('data not found: {fn0}')
657
+ ui.warn(f'data not found: {fn0}')
649
658
  else:
650
659
  tfn0 = self._generate_tempfilename_for_device()
651
- await dev.invoke_adb_passthru(f'push {fn0} {tfn0}')
652
- await dev.invoke_adb_passthru(f'shell "run-as {target} tar -xv < {tfn0}; rm -f {tfn0}"')
660
+ await dev.invoke_adb_passthru(f'push {quote(fn0)} {tfn0}')
661
+ await dev.invoke_adb_passthru(f"shell 'run-as {target} tar -xv < {tfn0}; rm -f {tfn0}'")
653
662
  ui.success(f'copied in: {fn}')
654
663
  success = True
655
664
 
656
665
  ui.info('copying external storage')
657
666
  if not os.path.exists(fn1):
658
- ui.warn('data not found: {fn1}')
667
+ ui.warn(f'data not found: {fn1}')
659
668
  else:
660
669
  tfn1 = self._generate_tempfilename_for_device()
661
- await dev.invoke_adb_passthru(f'push {fn1} {tfn1}')
662
- await dev.invoke_adb_passthru(f'shell "cd /storage/emulated/0/Android/ && tar -xv < {tfn1}; rm -f {tfn1}"')
670
+ await dev.invoke_adb_passthru(f'push {quote(fn1)} {tfn1}')
671
+ await dev.invoke_adb_passthru(f"shell 'cd /storage/emulated/0/Android/ && tar -xv < {tfn1}; rm -f {tfn1}'")
663
672
  ui.success(f'copied in: {fn1}')
664
673
  success = True
665
674
 
@@ -775,6 +784,7 @@ class EngageCommand(CommandMixin):
775
784
  apk = context.target
776
785
 
777
786
  from time import time
787
+ from shlex import quote
778
788
  from pubsub import pub
779
789
  from trueseeing.core.ui import AndroidInstallProgressReporter
780
790
  from trueseeing.core.android.device import AndroidDevice
@@ -798,7 +808,7 @@ class EngageCommand(CommandMixin):
798
808
  with AndroidInstallProgressReporter().scoped():
799
809
  pub.sendMessage('progress.android.adb.begin', what='installing ... ')
800
810
  try:
801
- async for l in dev.invoke_adb_streaming(f'install --no-streaming {apk}', redir_stderr=True):
811
+ async for l in dev.invoke_adb_streaming(f'install --no-streaming {quote(apk)}', redir_stderr=True):
802
812
  pub.sendMessage('progress.android.adb.update')
803
813
  if b'failure' in l.lower():
804
814
  ui.stderr('')
@@ -907,7 +917,7 @@ class EngageCommand(CommandMixin):
907
917
  ui.warn('target has only one slice; using apk format')
908
918
  outfn = outfn.replace('.xapk', '.apk')
909
919
  ui.info('getting {nr} slice'.format(nr=len(splits)))
910
- await dev.invoke_adb('pull {path}/base.apk {outfn}'.format(path=quote(basepath.decode()), outfn=outfn))
920
+ await dev.invoke_adb('pull {path}/base.apk {outfn}'.format(path=quote(basepath.decode()), outfn=quote(outfn)))
911
921
  else:
912
922
  if outfn.endswith('.apk'):
913
923
  ui.warn('target has multiple slices; using xapk format')
@@ -949,6 +959,58 @@ class EngageCommand(CommandMixin):
949
959
  assert manifest is not None
950
960
  return ET.tostring(manifest) # type: ignore[no-any-return]
951
961
 
962
+ async def _engage_frida_start_server(self, args: deque[str]) -> None:
963
+ cmd = args.popleft()
964
+ context: APKContext = self._helper.get_context().require_type('apk')
965
+
966
+ pkg = context.get_package_name()
967
+ scripts = list(args)
968
+
969
+ force = cmd.endswith('!')
970
+
971
+ ui.info("starting frida-server")
972
+
973
+ from time import time
974
+ from shlex import quote
975
+ from trueseeing.core.android.device import AndroidDevice
976
+ from trueseeing.core.tools import invoke_passthru
977
+ from pathlib import Path
978
+
979
+ at = time()
980
+ dev = AndroidDevice()
981
+
982
+ if force:
983
+ ui.warn("killing frida-server if any")
984
+ await dev.invoke_adb("shell 'killall frida-server || exit 0'")
985
+ await dev.invoke_adb("shell 'cd /data/local/tmp && ./frida-server &'")
986
+
987
+ ui.info(f"starting frida on {pkg}")
988
+ scripts_str = []
989
+ for s in scripts:
990
+ p = Path(s)
991
+ if p.is_file():
992
+ scripts_str.append(f"-l {quote(str(p))}")
993
+ elif p.is_dir():
994
+ scripts_str.extend([f"-l {quote(str(m))}" for m in p.rglob('*.js')])
995
+ else:
996
+ ui.warn(f"ignoring unknown path: {p}")
997
+ await invoke_passthru(f"frida -U {pkg} {' '.join(scripts_str)}")
998
+
999
+ ui.success("done ({t:.2f} sec.)".format(t=time() - at))
1000
+
1001
+ async def _engage_frida_kill_server(self, args: deque[str]) -> None:
1002
+ from time import time
1003
+ from trueseeing.core.android.device import AndroidDevice
1004
+
1005
+ ui.info("killing frida-server")
1006
+
1007
+ at = time()
1008
+ dev = AndroidDevice()
1009
+
1010
+ await dev.invoke_adb('shell killall frida-server')
1011
+
1012
+ ui.success("done ({t:.2f} sec.)".format(t=time() - at))
1013
+
952
1014
  class InvalidResponseError(Exception):
953
1015
  pass
954
1016
 
@@ -44,7 +44,9 @@ class ReconCommand(CommandMixin):
44
44
  'rwl!':dict(e=self._recon_watch_logcat),
45
45
  'rwf':dict(e=self._recon_watch_fs, n='rwf', d='recon: watch filesystem'),
46
46
  'rwt':dict(e=self._recon_watch_intent, n='rwt[!] [pat]', d='recon: watch intent'),
47
+ 'rwt!':dict(e=self._recon_watch_intent),
47
48
  'rwu':dict(e=self._recon_watch_ui, n='rwu[!] [pat|xp:xpath] [output.xml]', d='recon: watch device UI'),
49
+ 'rwu!':dict(e=self._recon_watch_ui),
48
50
  'rwx':dict(e=self._recon_watch_start, n='rwx', d='recon: start watching'),
49
51
  'rp':dict(e=self._recon_list_packages, n='rp', d='recon: list installed packages'),
50
52
  'ru':dict(e=self._recon_dump_ui, n='ru [output.xml]', d='recon: dump device UI'),
@@ -140,13 +142,15 @@ class ReconCommand(CommandMixin):
140
142
  ui.fatal('nothing to watch (try d* beforehand)')
141
143
 
142
144
  dev = AndroidDevice()
143
- ctx = self._get_apk_context()
144
- pkg = ctx.get_package_name()
145
+ pkg: Optional[str] = None
146
+ if self._target_only:
147
+ ctx = self._get_apk_context()
148
+ pkg = ctx.get_package_name()
145
149
 
146
150
  async def _log() -> None:
147
151
  pid: Optional[int] = None
148
152
  if self._watch_logcat or self._watch_intent:
149
- if not pid:
153
+ if pkg and not pid:
150
154
  d = await dev.invoke_adb('shell ps')
151
155
  m = re.search(r'^[0-9a-zA-Z_]+ +([0-9]+) .*{}$'.format(pkg).encode(), d.encode(), re.MULTILINE)
152
156
  if m:
@@ -155,7 +159,7 @@ class ReconCommand(CommandMixin):
155
159
 
156
160
  async for l in dev.invoke_adb_streaming('logcat -T1'):
157
161
  l = l.rstrip()
158
- if not pid:
162
+ if pkg and not pid:
159
163
  m = re.search(r' ([0-9]+):{}'.format(pkg).encode(), l)
160
164
  if m:
161
165
  pid = int(m.group(1))
@@ -264,7 +268,7 @@ class ReconCommand(CommandMixin):
264
268
  if not m:
265
269
  raise DumpFailedError(msg)
266
270
  tmpfn = m.group(1)
267
- return await dev.invoke_adb(f'shell "cat {tmpfn}; rm {tmpfn}"')
271
+ return await dev.invoke_adb(f"shell 'cat {tmpfn}; rm {tmpfn}'")
268
272
  except CalledProcessError as e:
269
273
  raise DumpFailedError(e)
270
274
 
@@ -272,11 +276,11 @@ class ReconCommand(CommandMixin):
272
276
  from subprocess import CalledProcessError
273
277
  dev = AndroidDevice()
274
278
  try:
275
- async for msg in dev.invoke_adb_streaming(r'shell "while (true) do uiautomator dump /sdcard/dump-\$(date +%s).xml; sleep {delay}; done"'.format(delay=delay)):
279
+ async for msg in dev.invoke_adb_streaming(r"shell 'while (true) do uiautomator dump /sdcard/dump-$(date +%s).xml; sleep {delay}; done'".format(delay=delay)):
276
280
  m = re.search(r'dumped to: (/.*)', msg.decode())
277
281
  if m:
278
282
  tmpfn = m.group(1)
279
- yield await dev.invoke_adb(f'shell "cat {tmpfn}; rm {tmpfn}"')
283
+ yield await dev.invoke_adb(f"shell 'cat {tmpfn}; rm {tmpfn}'")
280
284
  except CalledProcessError as e:
281
285
  raise DumpFailedError(e)
282
286
 
@@ -288,7 +292,7 @@ class ReconCommand(CommandMixin):
288
292
 
289
293
  dev = AndroidDevice()
290
294
  try:
291
- async for msg in dev.invoke_adb_streaming(r'shell "while (true) do (find -H /storage -mtime -{thres}s -print0 | xargs -0 ls -nlld); echo \"*\"; sleep {delay}; done"'.format(delay=delay, thres=int(delay*3.))):
295
+ async for msg in dev.invoke_adb_streaming(r"shell 'while (true) do (find -H /storage -mtime -{thres}s -print0 | xargs -0 ls -nlld); echo \"*\"; sleep {delay}; done'".format(delay=delay, thres=int(delay*3.))):
292
296
  msg = msg.rstrip()
293
297
  if not msg.startswith(b'*'):
294
298
  m = re.fullmatch(r'(.*00) (.+?)'.encode('latin1'), msg)
@@ -20,14 +20,14 @@ class SearchCommand(CommandMixin):
20
20
 
21
21
  def get_commands(self) -> CommandMap:
22
22
  return {
23
- '/c':dict(e=self._search_call, n='/c [pat]', d='search call for pattern'),
24
- '/k':dict(e=self._search_const, n='/k insn [pat]', d='search consts for pattern'),
25
- '/p':dict(e=self._search_put, n='/p[i] [pat]', d='search s/iputs for pattern'),
26
- '/dp':dict(e=self._search_defined_package, n='/dp [pat]', d='search packages matching pattern'),
27
- '/dc':dict(e=self._search_defined_class, n='/dc [pat]', d='search classes matching pattern'),
28
- '/dcx':dict(e=self._search_derived_class, n='/dcx classpat [methpat]', d='search classes defining methods and extending ones matching patterns'),
29
- '/dci':dict(e=self._search_implementing_class, n='/dci ifacepat [methpat]', d='search classes defining methods and implementing interfaces matching patterns'),
30
- '/dm':dict(e=self._search_defined_method, n='/dm pat', d='search classes defining methods matching pattern'),
23
+ '/c':dict(e=self._search_call, n='/c [pat]', d='search call for pattern', t={'apk'}),
24
+ '/k':dict(e=self._search_const, n='/k insn [pat]', d='search consts for pattern', t={'apk'}),
25
+ '/p':dict(e=self._search_put, n='/p[i] [pat]', d='search s/iputs for pattern', t={'apk'}),
26
+ '/dp':dict(e=self._search_defined_package, n='/dp [pat]', d='search packages matching pattern', t={'apk'}),
27
+ '/dc':dict(e=self._search_defined_class, n='/dc [pat]', d='search classes matching pattern', t={'apk'}),
28
+ '/dcx':dict(e=self._search_derived_class, n='/dcx classpat [methpat]', d='search classes defining methods and extending ones matching patterns', t={'apk'}),
29
+ '/dci':dict(e=self._search_implementing_class, n='/dci ifacepat [methpat]', d='search classes defining methods and implementing interfaces matching patterns', t={'apk'}),
30
+ '/dm':dict(e=self._search_defined_method, n='/dm pat', d='search classes defining methods matching pattern', t={'apk'}),
31
31
  }
32
32
 
33
33
  async def _search_call(self, args: deque[str]) -> None:
@@ -26,10 +26,10 @@ class ShowCommand(CommandMixin):
26
26
 
27
27
  def get_commands(self) -> CommandMap:
28
28
  return {
29
- 'pd':dict(e=self._show_disasm, n='pd qualname', d='show disassembled class/method'),
30
- 'pk':dict(e=self._show_solved_constant, n='pk[!] 0xaddr index', d='guess and show what constant would flow into the index-th arg of op (!: try harder)'),
31
- 'pk!':dict(e=self._show_solved_constant),
32
- 'pt':dict(e=self._show_solved_typeset, n='pt 0xaddr index', d='guess and show what type would flow into the index-th arg of op'),
29
+ 'pd':dict(e=self._show_disasm, n='pd qualname', d='show disassembled class/method', t={'apk'}),
30
+ 'pk':dict(e=self._show_solved_constant, n='pk[!] 0xaddr index', d='guess and show what constant would flow into the index-th arg of op (!: try harder)', t={'apk'}),
31
+ 'pk!':dict(e=self._show_solved_constant, t={'apk'}),
32
+ 'pt':dict(e=self._show_solved_typeset, n='pt 0xaddr index', d='guess and show what type would flow into the index-th arg of op', t={'apk'}),
33
33
  }
34
34
 
35
35
  async def _show_disasm(self, args: deque[str]) -> None:
@@ -75,7 +75,7 @@ class ShowCommand(CommandMixin):
75
75
 
76
76
  limit = self._helper.get_graph_size_limit(self._helper.get_modifiers(args))
77
77
 
78
- from trueseeing.core.android.analysis.flow import DataFlow
78
+ from trueseeing.core.android.analyze.flow import DataFlow
79
79
  with DataFlow.apply_max_graph_size(limit):
80
80
  context = await self._helper.get_context().require_type('apk').analyze()
81
81
  store = context.store()
@@ -112,7 +112,7 @@ class ShowCommand(CommandMixin):
112
112
 
113
113
  limit = self._helper.get_graph_size_limit(self._helper.get_modifiers(args))
114
114
 
115
- from trueseeing.core.android.analysis.flow import DataFlow
115
+ from trueseeing.core.android.analyze.flow import DataFlow
116
116
  with DataFlow.apply_max_graph_size(limit):
117
117
  context = await self._helper.get_context().require_type('apk').analyze()
118
118
  store = context.store()