trueseeing 2.2.2__tar.gz → 2.2.4__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 (90) hide show
  1. {trueseeing-2.2.2 → trueseeing-2.2.4}/PKG-INFO +4 -3
  2. {trueseeing-2.2.2 → trueseeing-2.2.4}/README.md +1 -1
  3. {trueseeing-2.2.2 → trueseeing-2.2.4}/pyproject.toml +3 -1
  4. {trueseeing-2.2.2 → trueseeing-2.2.4}/trueseeing/__init__.py +1 -1
  5. {trueseeing-2.2.2 → trueseeing-2.2.4}/trueseeing/api.py +4 -1
  6. {trueseeing-2.2.2 → trueseeing-2.2.4}/trueseeing/app/cmd/__init__.py +1 -2
  7. {trueseeing-2.2.2 → trueseeing-2.2.4}/trueseeing/app/cmd/android/asm.py +13 -5
  8. trueseeing-2.2.2/trueseeing/app/cmd/android/exploit.py → trueseeing-2.2.4/trueseeing/app/cmd/android/engage.py +355 -49
  9. trueseeing-2.2.2/trueseeing/app/cmd/android/device.py → trueseeing-2.2.4/trueseeing/app/cmd/android/recon.py +46 -125
  10. {trueseeing-2.2.2 → trueseeing-2.2.4}/trueseeing/app/cmd/android/search.py +18 -111
  11. {trueseeing-2.2.2 → trueseeing-2.2.4}/trueseeing/app/cmd/android/show.py +32 -61
  12. {trueseeing-2.2.2 → trueseeing-2.2.4}/trueseeing/app/cmd/config.py +30 -2
  13. {trueseeing-2.2.2 → trueseeing-2.2.4}/trueseeing/app/cmd/info.py +7 -14
  14. {trueseeing-2.2.2 → trueseeing-2.2.4}/trueseeing/app/cmd/scan.py +2 -2
  15. {trueseeing-2.2.2 → trueseeing-2.2.4}/trueseeing/app/cmd/search.py +1 -1
  16. {trueseeing-2.2.2 → trueseeing-2.2.4}/trueseeing/app/inspect.py +101 -38
  17. {trueseeing-2.2.2 → trueseeing-2.2.4}/trueseeing/app/shell.py +5 -0
  18. trueseeing-2.2.4/trueseeing/core/android/analysis/flow.py +357 -0
  19. trueseeing-2.2.4/trueseeing/core/android/analysis/op.py +55 -0
  20. {trueseeing-2.2.2 → trueseeing-2.2.4}/trueseeing/core/android/asm.py +37 -9
  21. trueseeing-2.2.4/trueseeing/core/android/context.py +422 -0
  22. trueseeing-2.2.4/trueseeing/core/android/db.py +136 -0
  23. {trueseeing-2.2.2 → trueseeing-2.2.4}/trueseeing/core/android/device.py +13 -7
  24. trueseeing-2.2.4/trueseeing/core/android/model.py +60 -0
  25. {trueseeing-2.2.2 → trueseeing-2.2.4}/trueseeing/core/context.py +51 -14
  26. {trueseeing-2.2.2 → trueseeing-2.2.4}/trueseeing/core/env.py +1 -1
  27. {trueseeing-2.2.2 → trueseeing-2.2.4}/trueseeing/core/model/issue.py +3 -5
  28. {trueseeing-2.2.2 → trueseeing-2.2.4}/trueseeing/core/store.py +6 -0
  29. {trueseeing-2.2.2 → trueseeing-2.2.4}/trueseeing/core/tools.py +25 -16
  30. {trueseeing-2.2.2 → trueseeing-2.2.4}/trueseeing/core/ui.py +76 -1
  31. trueseeing-2.2.4/trueseeing/libs/android/store.0.sql +10 -0
  32. {trueseeing-2.2.2 → trueseeing-2.2.4}/trueseeing/libs/store.s.sql +1 -0
  33. {trueseeing-2.2.2 → trueseeing-2.2.4}/trueseeing/sig/__init__.py +1 -2
  34. {trueseeing-2.2.2 → trueseeing-2.2.4}/trueseeing/sig/android/crypto.py +60 -59
  35. {trueseeing-2.2.2 → trueseeing-2.2.4}/trueseeing/sig/android/fingerprint.py +14 -14
  36. {trueseeing-2.2.2 → trueseeing-2.2.4}/trueseeing/sig/android/manifest.py +8 -8
  37. {trueseeing-2.2.2 → trueseeing-2.2.4}/trueseeing/sig/android/privacy.py +14 -14
  38. {trueseeing-2.2.2 → trueseeing-2.2.4}/trueseeing/sig/android/security.py +75 -73
  39. trueseeing-2.2.2/trueseeing/core/android/analysis/flow.py +0 -331
  40. trueseeing-2.2.2/trueseeing/core/android/analysis/smali.py +0 -144
  41. trueseeing-2.2.2/trueseeing/core/android/context.py +0 -216
  42. trueseeing-2.2.2/trueseeing/core/android/db.py +0 -187
  43. trueseeing-2.2.2/trueseeing/core/android/model/code.py +0 -55
  44. trueseeing-2.2.2/trueseeing/libs/android/store.0.sql +0 -4
  45. trueseeing-2.2.2/trueseeing/libs/android/store.1.sql +0 -77
  46. trueseeing-2.2.2/trueseeing/sig/android/__init__.py +0 -0
  47. {trueseeing-2.2.2 → trueseeing-2.2.4}/.dockerignore +0 -0
  48. {trueseeing-2.2.2 → trueseeing-2.2.4}/.github/workflows/deploy.yaml +0 -0
  49. {trueseeing-2.2.2 → trueseeing-2.2.4}/.github/workflows/lint.yaml +0 -0
  50. {trueseeing-2.2.2 → trueseeing-2.2.4}/.github/workflows/publish.yaml +0 -0
  51. {trueseeing-2.2.2 → trueseeing-2.2.4}/.github/workflows/stale.yaml +0 -0
  52. {trueseeing-2.2.2 → trueseeing-2.2.4}/.gitignore +0 -0
  53. {trueseeing-2.2.2 → trueseeing-2.2.4}/COPYING +0 -0
  54. {trueseeing-2.2.2 → trueseeing-2.2.4}/Dockerfile +0 -0
  55. {trueseeing-2.2.2 → trueseeing-2.2.4}/trueseeing/app/__init__.py +0 -0
  56. {trueseeing-2.2.2 → trueseeing-2.2.4}/trueseeing/app/cmd/alias.py +0 -0
  57. {trueseeing-2.2.2 → trueseeing-2.2.4}/trueseeing/app/cmd/analyze.py +0 -0
  58. {trueseeing-2.2.2 → trueseeing-2.2.4}/trueseeing/app/cmd/android/__init__.py +0 -0
  59. {trueseeing-2.2.2 → trueseeing-2.2.4}/trueseeing/app/cmd/report.py +0 -0
  60. {trueseeing-2.2.2 → trueseeing-2.2.4}/trueseeing/app/cmd/show.py +0 -0
  61. {trueseeing-2.2.2 → trueseeing-2.2.4}/trueseeing/app/scan.py +0 -0
  62. {trueseeing-2.2.2 → trueseeing-2.2.4}/trueseeing/core/__init__.py +0 -0
  63. {trueseeing-2.2.2 → trueseeing-2.2.4}/trueseeing/core/android/analysis/__init__.py +0 -0
  64. {trueseeing-2.2.2 → trueseeing-2.2.4}/trueseeing/core/android/store.py +0 -0
  65. {trueseeing-2.2.2 → trueseeing-2.2.4}/trueseeing/core/android/tools.py +0 -0
  66. {trueseeing-2.2.2 → trueseeing-2.2.4}/trueseeing/core/config.py +0 -0
  67. {trueseeing-2.2.2 → trueseeing-2.2.4}/trueseeing/core/cvss.py +0 -0
  68. {trueseeing-2.2.2 → trueseeing-2.2.4}/trueseeing/core/db.py +0 -0
  69. {trueseeing-2.2.2 → trueseeing-2.2.4}/trueseeing/core/exc.py +0 -0
  70. {trueseeing-2.2.2 → trueseeing-2.2.4}/trueseeing/core/ext.py +0 -0
  71. {trueseeing-2.2.2/trueseeing/core/android → trueseeing-2.2.4/trueseeing/core}/model/__init__.py +0 -0
  72. {trueseeing-2.2.2 → trueseeing-2.2.4}/trueseeing/core/model/cmd.py +0 -0
  73. {trueseeing-2.2.2 → trueseeing-2.2.4}/trueseeing/core/model/sig.py +0 -0
  74. {trueseeing-2.2.2 → trueseeing-2.2.4}/trueseeing/core/report.py +0 -0
  75. {trueseeing-2.2.2 → trueseeing-2.2.4}/trueseeing/core/scan.py +0 -0
  76. {trueseeing-2.2.2 → trueseeing-2.2.4}/trueseeing/core/z.py +0 -0
  77. {trueseeing-2.2.2 → trueseeing-2.2.4}/trueseeing/libs/LICENSE.md +0 -0
  78. {trueseeing-2.2.2 → trueseeing-2.2.4}/trueseeing/libs/android/abe.jar +0 -0
  79. {trueseeing-2.2.2 → trueseeing-2.2.4}/trueseeing/libs/android/apkeditor.jar +0 -0
  80. {trueseeing-2.2.2 → trueseeing-2.2.4}/trueseeing/libs/android/apksigner.jar +0 -0
  81. {trueseeing-2.2.2 → trueseeing-2.2.4}/trueseeing/libs/android/frida-app.smali +0 -0
  82. {trueseeing-2.2.2 → trueseeing-2.2.4}/trueseeing/libs/android/frida-scriptdir.config +0 -0
  83. {trueseeing-2.2.2 → trueseeing-2.2.4}/trueseeing/libs/android/nsc.xml +0 -0
  84. {trueseeing-2.2.2 → trueseeing-2.2.4}/trueseeing/libs/files.0.sql +0 -0
  85. {trueseeing-2.2.2 → trueseeing-2.2.4}/trueseeing/libs/public_suffix_list.dat +0 -0
  86. {trueseeing-2.2.2 → trueseeing-2.2.4}/trueseeing/libs/store.0.sql +0 -0
  87. {trueseeing-2.2.2 → trueseeing-2.2.4}/trueseeing/libs/template/report.html +0 -0
  88. {trueseeing-2.2.2 → trueseeing-2.2.4}/trueseeing/libs/tlds.txt +0 -0
  89. {trueseeing-2.2.2 → trueseeing-2.2.4}/trueseeing/py.typed +0 -0
  90. {trueseeing-2.2.2/trueseeing/core/model → trueseeing-2.2.4/trueseeing/sig/android}/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: trueseeing
3
- Version: 2.2.2
3
+ Version: 2.2.4
4
4
  Summary: Trueseeing is a non-decompiling Android application vulnerability scanner.
5
5
  Keywords: android,security,pentest,hacking
6
6
  Author-email: Takahiro Yoshimura <alterakey@protonmail.com>
@@ -16,7 +16,6 @@ Classifier: License :: OSI Approved :: GNU General Public License v3 or later (G
16
16
  Requires-Dist: lxml~=5.0
17
17
  Requires-Dist: pyyaml~=6.0
18
18
  Requires-Dist: jinja2~=3.1
19
- Requires-Dist: attrs~=23.2
20
19
  Requires-Dist: pypubsub~=4.0
21
20
  Requires-Dist: termcolor~=2.4
22
21
  Requires-Dist: progressbar2~=4.3
@@ -25,6 +24,8 @@ Requires-Dist: asn1crypto~=1.5
25
24
  Requires-Dist: zstandard~=0.22
26
25
  Requires-Dist: aiohttp~=3.9
27
26
  Requires-Dist: lief~=0.14
27
+ Requires-Dist: pyaxmlparser~=0.3
28
+ Requires-Dist: prompt-toolkit~=3.0
28
29
  Requires-Dist: mypy~=1.7 ; extra == "dev"
29
30
  Requires-Dist: pyproject-flake8~=6.1 ; extra == "dev"
30
31
  Requires-Dist: typing_extensions~=4.1 ; extra == "dev"
@@ -83,7 +84,7 @@ Alternatively, you can install our package with pip as follows. This form of ins
83
84
  You can interactively scan/analyze/patch/etc. apps -- making it the ideal choice for manual analysis:
84
85
 
85
86
  $ trueseeing target.apk
86
- [+] trueseeing 2.2.2
87
+ [+] trueseeing 2.2.4
87
88
  ts[target.apk]> ?
88
89
  ...
89
90
  ts[target.apk]> i # show generic information
@@ -50,7 +50,7 @@ Alternatively, you can install our package with pip as follows. This form of ins
50
50
  You can interactively scan/analyze/patch/etc. apps -- making it the ideal choice for manual analysis:
51
51
 
52
52
  $ trueseeing target.apk
53
- [+] trueseeing 2.2.2
53
+ [+] trueseeing 2.2.4
54
54
  ts[target.apk]> ?
55
55
  ...
56
56
  ts[target.apk]> i # show generic information
@@ -22,7 +22,6 @@ dependencies = [
22
22
  "lxml~=5.0",
23
23
  "pyyaml~=6.0",
24
24
  "jinja2~=3.1",
25
- "attrs~=23.2",
26
25
  "pypubsub~=4.0",
27
26
  "termcolor~=2.4",
28
27
  "progressbar2~=4.3",
@@ -31,6 +30,8 @@ dependencies = [
31
30
  "zstandard~=0.22",
32
31
  "aiohttp~=3.9",
33
32
  "lief~=0.14",
33
+ "pyaxmlparser~=0.3",
34
+ "prompt-toolkit~=3.0",
34
35
  ]
35
36
  requires-python = ">=3.9"
36
37
  dynamic = ['version', 'description']
@@ -59,6 +60,7 @@ module = [
59
60
  "jinja2",
60
61
  "pubsub",
61
62
  "asn1crypto.*",
63
+ "pyaxmlparser.*",
62
64
  ]
63
65
  ignore_missing_imports = true
64
66
 
@@ -1,2 +1,2 @@
1
1
  """Trueseeing is a non-decompiling Android application vulnerability scanner."""
2
- __version__ = '2.2.2'
2
+ __version__ = '2.2.4'
@@ -10,9 +10,12 @@ if TYPE_CHECKING:
10
10
  from trueseeing.core.android.context import APKContext
11
11
  from trueseeing.core.model.issue import Issue, IssueConfidence
12
12
 
13
+ ModifierEvent = Literal['begin', 'end']
14
+
13
15
  CommandEntrypoint = Callable[[deque[str]], Coroutine[Any, Any, None]]
14
16
  CommandlineEntrypoint = Callable[[str], Coroutine[Any, Any, None]]
15
17
  CommandPatternEntrypoints = Union[CommandEntrypoint, CommandlineEntrypoint]
18
+ ModifierListenerEntrypoint = Callable[[ModifierEvent, str], Coroutine[Any, Any, None]]
16
19
  SignatureEntrypoint = Callable[[], Coroutine[Any, Any, None]]
17
20
  FormatHandlerEntrypoint = Callable[[str], Optional[Context]]
18
21
  ConfigGetterEntrypoint = Callable[[], Any]
@@ -34,7 +37,7 @@ if TYPE_CHECKING:
34
37
  pass
35
38
 
36
39
  class ModifierEntry(Entry):
37
- pass
40
+ e: Optional[ModifierListenerEntrypoint] # type: ignore[misc]
38
41
 
39
42
  class ConfigEntry(TypedDict):
40
43
  g: ConfigGetterEntrypoint
@@ -8,11 +8,10 @@ if TYPE_CHECKING:
8
8
  def discover() -> Iterator[Type[Command]]:
9
9
  from trueseeing.api import Command
10
10
  from importlib import import_module
11
- from trueseeing.core.model.cmd import CommandMixin
12
11
  from trueseeing.core.tools import get_public_subclasses, get_missing_methods, discover_modules_under
13
12
 
14
13
  for mod in discover_modules_under('trueseeing.app.cmd'):
15
14
  m = import_module(mod)
16
- for c in get_public_subclasses(m, Command, [CommandMixin]): # type:ignore[type-abstract]
15
+ for c in get_public_subclasses(m, Command, 'CommandMixin'): # type:ignore[type-abstract]
17
16
  assert not get_missing_methods(c)
18
17
  yield c
@@ -47,16 +47,24 @@ class AssembleCommand(CommandMixin):
47
47
  ui.fatal('need root path')
48
48
 
49
49
  import os
50
+ import re
50
51
  import time
51
52
  from tempfile import TemporaryDirectory
52
53
  from trueseeing.core.android.asm import APKAssembler
53
54
  from trueseeing.core.android.tools import move_apk
54
55
 
55
56
  root = args.popleft()
56
- origapk = apk.replace('.apk', '.apk.orig')
57
+ origapk = re.sub(r'(\.x?apk)$', r'\1.orig', apk)
57
58
 
58
- if os.path.exists(origapk) and not cmd.endswith('!'):
59
- ui.fatal('backup file exists; force (!) to overwrite')
59
+ stem = re.sub(r'\.x?apk$', '', apk)
60
+ for typ in ['apk', 'xapk']:
61
+ print(origapk, f'{stem}.{typ}.orig')
62
+ if os.path.exists(f'{stem}.{typ}.orig') and not cmd.endswith('!'):
63
+ ui.fatal('backup file exists; force (!) to overwrite')
64
+
65
+ if apk.endswith('.xapk'):
66
+ ui.warn('assembling xapk is not supported; assembling as merged apk')
67
+ apk = apk.replace('.xapk', '.apk')
60
68
 
61
69
  opts = self._helper.get_effective_options(self._helper.get_modifiers(args))
62
70
 
@@ -140,7 +148,7 @@ class AssembleCommand(CommandMixin):
140
148
  at = time.time()
141
149
 
142
150
  with TemporaryDirectory() as td:
143
- await APKDisassembler.disassemble_to_path(apk, td, nodex=nodex)
151
+ await APKDisassembler.disassemble_to_path(apk, td, nodex=nodex, merge=apk.endswith('.xapk'))
144
152
 
145
153
  if not archive:
146
154
  with FileTransferProgressReporter('disassemble: writing').scoped() as progress:
@@ -185,7 +193,7 @@ class AssembleCommand(CommandMixin):
185
193
 
186
194
  at = time.time()
187
195
  extracted = 0
188
- context = self._helper.get_context().require_type('apk')
196
+ context = self._helper.get_context()
189
197
  q = context.store().query()
190
198
 
191
199
  if not archive:
@@ -7,45 +7,55 @@ from trueseeing.core.model.cmd import CommandMixin
7
7
  from trueseeing.core.ui import ui
8
8
 
9
9
  if TYPE_CHECKING:
10
- from typing import Any, Optional, Dict, Mapping
10
+ from typing import Any, Optional, Dict, Mapping, List, Iterator, Tuple
11
11
  from trueseeing.api import CommandHelper, Command, CommandMap, OptionMap
12
12
  from trueseeing.core.android.context import APKContext
13
+ from trueseeing.core.android.model import XAPKManifest
13
14
 
14
- class ExploitCommand(CommandMixin):
15
+ class EngageCommand(CommandMixin):
15
16
  def __init__(self, helper: CommandHelper) -> None:
16
17
  self._helper = helper
17
18
 
18
19
  @staticmethod
19
20
  def create(helper: CommandHelper) -> Command:
20
- return ExploitCommand(helper)
21
+ return EngageCommand(helper)
21
22
 
22
23
  def get_commands(self) -> CommandMap:
23
24
  return {
24
- 'xq':dict(e=self._exploit_discard, n='xq', d='exploit: discard changes'),
25
- 'xx':dict(e=self._exploit_apply, n='xx[!]', d='exploit: apply and rebuild apk'),
26
- 'xx!':dict(e=self._exploit_apply),
27
- 'xf':dict(e=self._exploit_inject_frida, n='xf[!] [config]', d='exploit; inject frida gadget'),
28
- 'xf!':dict(e=self._exploit_inject_frida),
29
- 'xfs':dict(e=self._exploit_inject_frida_scriptdir, n='xfs[!] [path]', d='exploit; inject frida gadget in script dir mode'),
30
- 'xfs!':dict(e=self._exploit_inject_frida_scriptdir),
31
- 'xu':dict(e=self._exploit_disable_pinning, n='xu', d='exploit: disable SSL/TLS pinning'),
32
- 'xd':dict(e=self._exploit_enable_debug, n='xd', d='exploit: make debuggable'),
33
- 'xb':dict(e=self._exploit_enable_backup, n='xb', d='exploit: make backupable'),
34
- 'xt':dict(e=self._exploit_patch_target_api_level, n='xt[!] <api level>', d='exploit: patch target api level'),
35
- 'xt!':dict(e=self._exploit_patch_target_api_level),
36
- 'xp':dict(e=self._exploit_device_list_packages, n='xp', d='device: list installed packages'),
37
- 'xco':dict(e=self._exploit_device_copyout, n='xco[!] package [data.tar]', d='device: copy-out package data'),
38
- 'xco!':dict(e=self._exploit_device_copyout),
39
- 'xci':dict(e=self._exploit_device_copyin, n='xci[!] package [data.tar]', d='device: copy-in package data'),
40
- 'xci!':dict(e=self._exploit_device_copyin),
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),
41
50
  }
42
51
 
43
52
  def get_options(self) -> OptionMap:
44
53
  return {
45
- 'vers':dict(n='vers=X.Y.Z', d='specify frida-gadget version to use [xf,xfs]')
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]'),
46
56
  }
47
57
 
48
- async def _exploit_discard(self, args: deque[str]) -> None:
58
+ async def _engage_tamper_discard(self, args: deque[str]) -> None:
49
59
  apk = self._helper.require_target()
50
60
 
51
61
  _ = args.popleft()
@@ -63,7 +73,7 @@ class ExploitCommand(CommandMixin):
63
73
 
64
74
  ui.success('done ({t:.02f} sec.)'.format(t=(time.time() - at)))
65
75
 
66
- async def _exploit_apply(self, args: deque[str]) -> None:
76
+ async def _engage_tamper_apply(self, args: deque[str]) -> None:
67
77
  apk = self._helper.require_target()
68
78
 
69
79
  cmd = args.popleft()
@@ -107,7 +117,7 @@ class ExploitCommand(CommandMixin):
107
117
 
108
118
  ui.success('done ({t:.02f} sec.)'.format(t=(time.time() - at)))
109
119
 
110
- async def _exploit_disable_pinning(self, args: deque[str]) -> None:
120
+ async def _engage_tamper_disable_pinning(self, args: deque[str]) -> None:
111
121
  apk = self._helper.require_target()
112
122
 
113
123
  _ = args.popleft()
@@ -157,7 +167,7 @@ class ExploitCommand(CommandMixin):
157
167
 
158
168
  ui.success('done ({t:.02f} sec.)'.format(t=(time.time() - at)))
159
169
 
160
- async def _exploit_enable_debug(self, args: deque[str]) -> None:
170
+ async def _engage_tamper_enable_debug(self, args: deque[str]) -> None:
161
171
  apk = self._helper.require_target()
162
172
 
163
173
  _ = args.popleft()
@@ -179,7 +189,7 @@ class ExploitCommand(CommandMixin):
179
189
 
180
190
  ui.success('done ({t:.02f} sec.)'.format(t=(time.time() - at)))
181
191
 
182
- async def _exploit_enable_backup(self, args: deque[str]) -> None:
192
+ async def _engage_tamper_enable_backup(self, args: deque[str]) -> None:
183
193
  apk = self._helper.require_target()
184
194
 
185
195
  _ = args.popleft()
@@ -203,7 +213,7 @@ class ExploitCommand(CommandMixin):
203
213
 
204
214
  ui.success('done ({t:.02f} sec.)'.format(t=(time.time() - at)))
205
215
 
206
- async def _exploit_patch_target_api_level(self, args: deque[str]) -> None:
216
+ async def _engage_tamper_patch_target_api_level(self, args: deque[str]) -> None:
207
217
  apk = self._helper.require_target()
208
218
 
209
219
  cmd = args.popleft()
@@ -239,7 +249,7 @@ class ExploitCommand(CommandMixin):
239
249
  ui.success('done ({t:.02f} sec.)'.format(t=(time.time() - at)))
240
250
 
241
251
  # XXX: long and ugly
242
- async def _exploit_inject_frida(self, args: deque[str], script_dir_mode: bool = False) -> None:
252
+ async def _engage_tamper_inject_frida(self, args: deque[str], script_dir_mode: bool = False) -> None:
243
253
  configfn: Optional[str] = None
244
254
  config_override: Optional[str] = None
245
255
  dev_frida_dir: Optional[str] = None
@@ -419,8 +429,8 @@ class ExploitCommand(CommandMixin):
419
429
 
420
430
  ui.success('done ({t:.02f} sec.)'.format(t=(time.time() - at)))
421
431
 
422
- async def _exploit_inject_frida_scriptdir(self, args: deque[str]) -> None:
423
- await self._exploit_inject_frida(args, script_dir_mode=True)
432
+ async def _engage_tamper_inject_frida_scriptdir(self, args: deque[str]) -> None:
433
+ await self._engage_tamper_inject_frida(args, script_dir_mode=True)
424
434
 
425
435
  def _as_dalvik_classname(self, jn: str) -> str:
426
436
  return 'L{};'.format(jn.replace('.', '/'))
@@ -475,24 +485,7 @@ class ExploitCommand(CommandMixin):
475
485
  except ClientConnectionError:
476
486
  raise InvalidResponseError()
477
487
 
478
- async def _exploit_device_list_packages(self, args: deque[str]) -> None:
479
- _ = args.popleft()
480
-
481
- ui.info('listing packages')
482
-
483
- import time
484
- import re
485
- from trueseeing.core.android.device import AndroidDevice
486
-
487
- at = time.time()
488
- nr = 0
489
- for m in re.finditer(r'^package:(.*)', await AndroidDevice().invoke_adb('shell pm list package'), re.MULTILINE):
490
- p = m.group(1)
491
- ui.info(p)
492
- nr += 1
493
- ui.success('done, {nr} packages found ({t:.02f} sec.)'.format(nr=nr, t=(time.time() - at)))
494
-
495
- async def _exploit_device_copyout(self, args: deque[str]) -> None:
488
+ async def _engage_device_copyout(self, args: deque[str]) -> None:
496
489
  success: bool = False
497
490
 
498
491
  cmd = args.popleft()
@@ -585,7 +578,7 @@ class ExploitCommand(CommandMixin):
585
578
  else:
586
579
  ui.failure('copyout failed')
587
580
 
588
- async def _exploit_device_copyin(self, args: deque[str]) -> None:
581
+ async def _engage_device_copyin(self, args: deque[str]) -> None:
589
582
  success: bool = False
590
583
 
591
584
  _ = args.popleft()
@@ -677,6 +670,272 @@ class ExploitCommand(CommandMixin):
677
670
  else:
678
671
  ui.failure('copyin failed')
679
672
 
673
+ async def _engage_fuzz_command(self, args: deque[str], am: bool = False) -> None:
674
+ outfn: Optional[str] = None
675
+
676
+ cmd = args.popleft()
677
+
678
+ if not args:
679
+ if am:
680
+ ui.fatal('an "am" command line pattern required; try giving whatever you would to "adb shell am" (e.g. {} "start-activity .." ..)'.format(cmd))
681
+ else:
682
+ ui.fatal('command line pattern required; try giving you would to "adb shell"')
683
+
684
+ pat = args.popleft()
685
+ if am:
686
+ pat = f'am {pat}'
687
+
688
+ if args and not args[0].startswith('@'):
689
+ import os
690
+ outfn = args.popleft()
691
+ if os.path.exists(outfn) and not cmd.endswith('!'):
692
+ ui.fatal('outfile exists; force (!) to overwrite')
693
+
694
+ wordlist: Dict[str, List[str]] = dict()
695
+ for name, fn in self._helper.get_effective_options(self._helper.get_modifiers(args)).items():
696
+ if name.startswith('w'):
697
+ name = name[1:]
698
+ try:
699
+ with open(fn, 'r') as f:
700
+ wordlist[name] = [x.rstrip() for x in f]
701
+ except OSError as e:
702
+ ui.fatal(f'cannot open wordlist: {e}')
703
+
704
+ if not wordlist:
705
+ ui.fatal('need a wordlist (try @o:wNAME=FN)')
706
+
707
+ ui.info('wordlist built: {} words in {} keys ({})'.format(sum([len(v) for v in wordlist.values()]), len(wordlist), ','.join(wordlist.keys())))
708
+
709
+ def _expand(pat: str, wordlist: Mapping[str, List[str]]) -> Iterator[Tuple[int, int, str]]:
710
+ tries = min(len(v) for v in wordlist.values())
711
+ for nr in range(tries):
712
+ d = {k:v[nr] for k,v in wordlist.items()}
713
+ try:
714
+ yield nr, tries, pat.format(*[], **d)
715
+ except KeyError as e:
716
+ ui.fatal(f'unknown wordlist specified: {e}')
717
+
718
+ ui.info('starting fuzzing, opening log system-wide{}'.format(' [{}]'.format(outfn) if outfn else ''))
719
+
720
+ from trueseeing.core.android.device import AndroidDevice
721
+
722
+ dev = AndroidDevice()
723
+
724
+ async def _log(outfn: Optional[str]) -> None:
725
+ import sys
726
+ nr = 0
727
+
728
+ if not outfn:
729
+ f = sys.stdout.buffer
730
+ else:
731
+ f = open(outfn, 'wb')
732
+
733
+ try:
734
+ async for l in dev.invoke_adb_streaming('logcat -T1'):
735
+ f.write(l)
736
+ nr += 1
737
+ if outfn and nr % 256 == 0:
738
+ ui.info(' ... captured: {}')
739
+ finally:
740
+ if outfn:
741
+ f.close()
742
+
743
+ async def _fuzz(pat: str, wordlist: Mapping[str, List[str]]) -> None:
744
+ from asyncio import sleep
745
+ from subprocess import CalledProcessError
746
+ for nr, tries, t in _expand(pat, wordlist):
747
+ await sleep(.05)
748
+ prog = dict(nr=nr+1, max=tries, cmd=t)
749
+ try:
750
+ await dev.invoke_adb(f'shell {t}')
751
+ ui.info('[{nr}/{max}] {cmd}'.format(**prog))
752
+ except CalledProcessError as e:
753
+ ui.failure('[{nr}/{max}] {cmd}: failed: {code}'.format(code=e.returncode, **prog))
754
+
755
+ from asyncio import create_task, wait, FIRST_COMPLETED, ALL_COMPLETED
756
+ task_log = create_task(_log(outfn))
757
+ task_fuzz = create_task(_fuzz(pat, wordlist))
758
+
759
+ done, pending = await wait([task_log, task_fuzz], return_when=FIRST_COMPLETED)
760
+ for t in pending:
761
+ t.cancel()
762
+ done, _ = await wait([task_log, task_fuzz], return_when=ALL_COMPLETED)
763
+ for t in done:
764
+ exc = t.exception()
765
+ if exc:
766
+ ui.error('unhandled exception', exc=exc)
767
+
768
+ async def _engage_fuzz_intent(self, args: deque[str]) -> None:
769
+ await self._engage_fuzz_command(args, am=True)
770
+
771
+ async def _engage_deploy_package(self, args: deque[str]) -> None:
772
+ cmd = args.popleft()
773
+
774
+ context: APKContext = self._helper.get_context().require_type('apk')
775
+ apk = context.target
776
+
777
+ from time import time
778
+ from pubsub import pub
779
+ from trueseeing.core.ui import AndroidInstallProgressReporter
780
+ from trueseeing.core.android.device import AndroidDevice
781
+ from subprocess import CalledProcessError
782
+
783
+ dev = AndroidDevice()
784
+ at = time()
785
+ pkg = context.get_package_name()
786
+
787
+ ui.info(f'deploying package: {pkg}')
788
+
789
+ if cmd.endswith('!'):
790
+ try:
791
+ async for l in dev.invoke_adb_streaming(f'uninstall {pkg}', redir_stderr=True):
792
+ pub.sendMessage('progress.android.adb.update')
793
+ if b'success' in l.lower():
794
+ ui.warn('removing existing package')
795
+ except CalledProcessError as e:
796
+ ui.fatal('uninstall failed: {}'.format(e.stdout.decode().rstrip()))
797
+
798
+ with AndroidInstallProgressReporter().scoped():
799
+ pub.sendMessage('progress.android.adb.begin', what='installing ... ')
800
+ try:
801
+ async for l in dev.invoke_adb_streaming(f'install --no-streaming {apk}', redir_stderr=True):
802
+ pub.sendMessage('progress.android.adb.update')
803
+ if b'failure' in l.lower():
804
+ ui.stderr('')
805
+ if not cmd.endswith('!'):
806
+ ui.fatal('install failed; force (!) to replace ({})'.format(l.decode('UTF-8')))
807
+ else:
808
+ ui.fatal('install failed ({})'.format(l.decode('UTF-8')))
809
+
810
+ pub.sendMessage('progress.android.adb.done')
811
+ except CalledProcessError as e:
812
+ ui.fatal('install failed: {}'.format(e.stdout.decode().rstrip()))
813
+
814
+ ui.success('done ({t:.02f} sec){trailer}'.format(t=time() - at, trailer=' '*8))
815
+
816
+ async def _engage_undeploy_package(self, args: deque[str]) -> None:
817
+ _ = args.popleft()
818
+
819
+ context: APKContext = self._helper.get_context().require_type('apk')
820
+
821
+ from time import time
822
+ from pubsub import pub
823
+ from trueseeing.core.android.device import AndroidDevice
824
+ from subprocess import CalledProcessError
825
+
826
+ dev = AndroidDevice()
827
+ at = time()
828
+ pkg = context.get_package_name()
829
+
830
+ ui.info(f'removing package: {pkg}')
831
+
832
+ try:
833
+ async for l in dev.invoke_adb_streaming(f'uninstall {pkg}', redir_stderr=True):
834
+ pub.sendMessage('progress.android.adb.update')
835
+ if b'failure' in l.lower():
836
+ import re
837
+ packages = await dev.invoke_adb('shell pm list packages', redir_stderr=True)
838
+ if not re.match(f'{pkg}$', packages, re.MULTILINE):
839
+ ui.fatal('package not found')
840
+ else:
841
+ ui.fatal('uninstall failed ({})'.format(l.decode()))
842
+ except CalledProcessError as e:
843
+ ui.fatal('uninstall failed: {}'.format(e.stdout.decode().rstrip()))
844
+
845
+ ui.success('done ({t:.02f} sec)'.format(t=time() - at))
846
+
847
+ async def _engage_grab_package(self, args: deque[str]) -> None:
848
+ cmd = args.popleft()
849
+
850
+ import os
851
+ if not args:
852
+ ui.fatal('need the package name')
853
+
854
+ pkg = args.popleft()
855
+
856
+ if args:
857
+ outfn = args.popleft()
858
+ else:
859
+ outfn = f'{pkg}.apk'
860
+
861
+ if os.path.exists(outfn):
862
+ if not cmd.endswith('!'):
863
+ ui.fatal('output file exists; force (!) to overwrite')
864
+ else:
865
+ os.remove(outfn)
866
+
867
+ import re
868
+ from time import time
869
+ from tempfile import TemporaryDirectory
870
+ from pubsub import pub
871
+ from trueseeing.core.android.device import AndroidDevice
872
+
873
+ dev = AndroidDevice()
874
+ at = time()
875
+ outfn = os.path.realpath(outfn)
876
+
877
+ ui.info(f'grabbing package: {pkg} -> {outfn}')
878
+
879
+ basepath: Optional[bytes] = None
880
+ splits: List[bytes] = []
881
+
882
+ async for l in dev.invoke_adb_streaming(f'shell pm dump {pkg}', redir_stderr=True):
883
+ pub.sendMessage('progress.android.adb.update')
884
+ if f'unable to find package: {pkg}'.encode() in l.lower():
885
+ ui.fatal(f'package not found: {pkg}')
886
+
887
+ m = re.search(rb'codePath=(/.+)', l)
888
+ if m:
889
+ basepath = m.group(1)
890
+ m = re.search(rb'splits=\[(.+)\]', l)
891
+ if m:
892
+ splits = re.split(rb', *', m.group(1))
893
+
894
+ assert basepath
895
+ assert splits
896
+
897
+ with TemporaryDirectory() as td:
898
+ from os import chdir, getcwd
899
+ from shlex import quote
900
+ from zipfile import ZipFile, ZIP_STORED
901
+ cd = getcwd()
902
+ try:
903
+ chdir(td)
904
+ slicemap = dict()
905
+ if len(splits) == 1:
906
+ if outfn.endswith('.xapk'):
907
+ ui.warn('target has only one slice; using apk format')
908
+ outfn = outfn.replace('.xapk', '.apk')
909
+ 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))
911
+ else:
912
+ if outfn.endswith('.apk'):
913
+ ui.warn('target has multiple slices; using xapk format')
914
+ outfn = outfn.replace('.apk', '.xapk')
915
+ ui.info('getting {nr} slices'.format(nr=len(splits)))
916
+ for s in splits:
917
+ slice = s.decode()
918
+ if slice == 'base':
919
+ fn = f'{pkg}.apk'
920
+ else:
921
+ fn = f'{slice}.apk'
922
+ await dev.invoke_adb('pull {path}/{typ}{slice}.apk {fn}'.format(
923
+ path=quote(basepath.decode()),
924
+ typ='' if slice == 'base' else 'split_',
925
+ slice=slice,
926
+ fn=fn,
927
+ ))
928
+ slicemap[slice] = fn
929
+ XAPKManifestGenerator(slicemap).generate()
930
+ with ZipFile(outfn, 'w', ZIP_STORED) as zf:
931
+ from glob import glob
932
+ for n in glob('*'):
933
+ with open(n, 'rb') as g:
934
+ zf.writestr(n, g.read())
935
+ finally:
936
+ chdir(cd)
937
+ ui.success('done ({t:.02f} sec)'.format(t=time() - at))
938
+
680
939
  def _generate_tempfilename_for_device(self, dir: Optional[str] = None) -> str:
681
940
  import random
682
941
  return (f'{dir}/' if dir is not None else '/data/local/tmp/') + ''.join(random.choices('abcdefghijklmnopqrstuvwxyz0123456789', k=16))
@@ -692,3 +951,50 @@ class ExploitCommand(CommandMixin):
692
951
 
693
952
  class InvalidResponseError(Exception):
694
953
  pass
954
+
955
+ class XAPKManifestGenerator:
956
+ def __init__(self, slicemap: Dict[str, str]) -> None:
957
+ self._slicemap = slicemap
958
+
959
+ def generate(self) -> None:
960
+ from os import stat
961
+ from pyaxmlparser import APK
962
+ manif: XAPKManifest = dict(
963
+ xapk_version='2',
964
+ total_size=0,
965
+ locales_name=dict(),
966
+ split_apks=[],
967
+ )
968
+
969
+ for slice, fn in self._slicemap.items():
970
+ conf = slice.split('.')[-1]
971
+ manif['total_size'] += stat(fn).st_size
972
+ manif['split_apks'].append(dict(id=slice, file=fn))
973
+ if slice == 'base':
974
+ apk = APK(fn)
975
+ manif.update(dict(
976
+ name=apk.get_app_name(),
977
+ icon='icon.png',
978
+ package_name=apk.get_package(),
979
+ version_code=apk.version_code,
980
+ version_name=apk.version_name,
981
+ min_sdk_version=apk.get_min_sdk_version(),
982
+ target_sdk_version=apk.get_target_sdk_version(),
983
+ permissions=apk.get_permissions(),
984
+ ))
985
+ with open('icon.png', 'wb') as f:
986
+ f.write(apk.icon_data)
987
+ elif len(conf) == 2:
988
+ apk = APK(fn)
989
+ country_code = conf
990
+ manif['locales_name'].update({
991
+ country_code:apk.get_app_name(),
992
+ })
993
+ if manif['locales_name']:
994
+ ln: Dict[str, str] = manif['locales_name']
995
+ for k in ln.keys():
996
+ if not ln[k]:
997
+ ln[k] = manif['name']
998
+ with open('manifest.json', 'w') as g:
999
+ from json import dumps
1000
+ g.write(dumps(manif, separators=(',', ':')))