trueseeing 2.2.2__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 (106) hide show
  1. trueseeing-2.2.5/.github/FUNDING.yml +14 -0
  2. {trueseeing-2.2.2 → trueseeing-2.2.5}/Dockerfile +1 -0
  3. {trueseeing-2.2.2 → trueseeing-2.2.5}/PKG-INFO +9 -6
  4. {trueseeing-2.2.2 → trueseeing-2.2.5}/README.md +4 -2
  5. {trueseeing-2.2.2 → trueseeing-2.2.5}/pyproject.toml +3 -1
  6. trueseeing-2.2.5/trueseeing/__init__.py +2 -0
  7. {trueseeing-2.2.2 → trueseeing-2.2.5}/trueseeing/api.py +7 -2
  8. {trueseeing-2.2.2 → trueseeing-2.2.5}/trueseeing/app/cmd/__init__.py +3 -2
  9. {trueseeing-2.2.2 → trueseeing-2.2.5}/trueseeing/app/cmd/android/asm.py +61 -15
  10. trueseeing-2.2.2/trueseeing/app/cmd/android/exploit.py → trueseeing-2.2.5/trueseeing/app/cmd/android/engage.py +430 -62
  11. trueseeing-2.2.2/trueseeing/app/cmd/android/device.py → trueseeing-2.2.5/trueseeing/app/cmd/android/recon.py +57 -132
  12. {trueseeing-2.2.2 → trueseeing-2.2.5}/trueseeing/app/cmd/android/search.py +25 -118
  13. {trueseeing-2.2.2 → trueseeing-2.2.5}/trueseeing/app/cmd/android/show.py +34 -63
  14. {trueseeing-2.2.2 → trueseeing-2.2.5}/trueseeing/app/cmd/config.py +30 -2
  15. {trueseeing-2.2.2 → trueseeing-2.2.5}/trueseeing/app/cmd/info.py +7 -14
  16. {trueseeing-2.2.2 → trueseeing-2.2.5}/trueseeing/app/cmd/scan.py +2 -2
  17. {trueseeing-2.2.2 → trueseeing-2.2.5}/trueseeing/app/cmd/search.py +1 -1
  18. {trueseeing-2.2.2 → trueseeing-2.2.5}/trueseeing/app/inspect.py +212 -69
  19. {trueseeing-2.2.2 → trueseeing-2.2.5}/trueseeing/app/shell.py +15 -8
  20. trueseeing-2.2.5/trueseeing/core/analyze.py +65 -0
  21. trueseeing-2.2.5/trueseeing/core/android/analyze/flow.py +357 -0
  22. trueseeing-2.2.5/trueseeing/core/android/analyze/nat.py +44 -0
  23. trueseeing-2.2.5/trueseeing/core/android/analyze/op.py +55 -0
  24. {trueseeing-2.2.2 → trueseeing-2.2.5}/trueseeing/core/android/asm.py +64 -11
  25. trueseeing-2.2.5/trueseeing/core/android/context.py +469 -0
  26. trueseeing-2.2.5/trueseeing/core/android/db.py +160 -0
  27. {trueseeing-2.2.2 → trueseeing-2.2.5}/trueseeing/core/android/device.py +14 -8
  28. trueseeing-2.2.5/trueseeing/core/android/model.py +68 -0
  29. {trueseeing-2.2.2 → trueseeing-2.2.5}/trueseeing/core/context.py +64 -14
  30. {trueseeing-2.2.2 → trueseeing-2.2.5}/trueseeing/core/db.py +6 -6
  31. {trueseeing-2.2.2 → trueseeing-2.2.5}/trueseeing/core/env.py +5 -1
  32. {trueseeing-2.2.2 → trueseeing-2.2.5}/trueseeing/core/ext.py +44 -14
  33. trueseeing-2.2.5/trueseeing/core/ios/analyze.py +74 -0
  34. trueseeing-2.2.5/trueseeing/core/ios/context.py +107 -0
  35. trueseeing-2.2.5/trueseeing/core/ios/db.py +60 -0
  36. trueseeing-2.2.5/trueseeing/core/ios/model.py +15 -0
  37. trueseeing-2.2.5/trueseeing/core/ios/store.py +34 -0
  38. trueseeing-2.2.5/trueseeing/core/ios/swift.py +120 -0
  39. {trueseeing-2.2.2 → trueseeing-2.2.5}/trueseeing/core/model/issue.py +3 -5
  40. {trueseeing-2.2.2 → trueseeing-2.2.5}/trueseeing/core/scan.py +1 -1
  41. {trueseeing-2.2.2 → trueseeing-2.2.5}/trueseeing/core/store.py +6 -0
  42. {trueseeing-2.2.2 → trueseeing-2.2.5}/trueseeing/core/tools.py +26 -17
  43. {trueseeing-2.2.2 → trueseeing-2.2.5}/trueseeing/core/ui.py +103 -8
  44. trueseeing-2.2.5/trueseeing/libs/android/store.0.sql +11 -0
  45. trueseeing-2.2.5/trueseeing/libs/android/store.1.sql +2 -0
  46. trueseeing-2.2.5/trueseeing/libs/ios/store.0.sql +1 -0
  47. trueseeing-2.2.5/trueseeing/libs/ios/store.1.sql +2 -0
  48. {trueseeing-2.2.2 → trueseeing-2.2.5}/trueseeing/libs/store.s.sql +1 -0
  49. {trueseeing-2.2.2 → trueseeing-2.2.5}/trueseeing/sig/__init__.py +1 -2
  50. {trueseeing-2.2.2 → trueseeing-2.2.5}/trueseeing/sig/android/crypto.py +61 -60
  51. {trueseeing-2.2.2 → trueseeing-2.2.5}/trueseeing/sig/android/fingerprint.py +15 -15
  52. {trueseeing-2.2.2 → trueseeing-2.2.5}/trueseeing/sig/android/manifest.py +8 -8
  53. trueseeing-2.2.5/trueseeing/sig/android/nat.py +93 -0
  54. {trueseeing-2.2.2 → trueseeing-2.2.5}/trueseeing/sig/android/privacy.py +15 -15
  55. {trueseeing-2.2.2 → trueseeing-2.2.5}/trueseeing/sig/android/security.py +76 -74
  56. trueseeing-2.2.5/trueseeing/sig/ios/__init__.py +0 -0
  57. trueseeing-2.2.5/trueseeing/sig/ios/base.py +880 -0
  58. trueseeing-2.2.2/trueseeing/__init__.py +0 -2
  59. trueseeing-2.2.2/trueseeing/core/android/analysis/flow.py +0 -331
  60. trueseeing-2.2.2/trueseeing/core/android/analysis/smali.py +0 -144
  61. trueseeing-2.2.2/trueseeing/core/android/context.py +0 -216
  62. trueseeing-2.2.2/trueseeing/core/android/db.py +0 -187
  63. trueseeing-2.2.2/trueseeing/core/android/model/code.py +0 -55
  64. trueseeing-2.2.2/trueseeing/libs/android/store.0.sql +0 -4
  65. trueseeing-2.2.2/trueseeing/libs/android/store.1.sql +0 -77
  66. {trueseeing-2.2.2 → trueseeing-2.2.5}/.dockerignore +0 -0
  67. {trueseeing-2.2.2 → trueseeing-2.2.5}/.github/workflows/deploy.yaml +0 -0
  68. {trueseeing-2.2.2 → trueseeing-2.2.5}/.github/workflows/lint.yaml +0 -0
  69. {trueseeing-2.2.2 → trueseeing-2.2.5}/.github/workflows/publish.yaml +0 -0
  70. {trueseeing-2.2.2 → trueseeing-2.2.5}/.github/workflows/stale.yaml +0 -0
  71. {trueseeing-2.2.2 → trueseeing-2.2.5}/.gitignore +0 -0
  72. {trueseeing-2.2.2 → trueseeing-2.2.5}/COPYING +0 -0
  73. {trueseeing-2.2.2 → trueseeing-2.2.5}/trueseeing/app/__init__.py +0 -0
  74. {trueseeing-2.2.2 → trueseeing-2.2.5}/trueseeing/app/cmd/alias.py +0 -0
  75. {trueseeing-2.2.2 → trueseeing-2.2.5}/trueseeing/app/cmd/analyze.py +0 -0
  76. {trueseeing-2.2.2 → trueseeing-2.2.5}/trueseeing/app/cmd/android/__init__.py +0 -0
  77. {trueseeing-2.2.2 → trueseeing-2.2.5}/trueseeing/app/cmd/report.py +0 -0
  78. {trueseeing-2.2.2 → trueseeing-2.2.5}/trueseeing/app/cmd/show.py +0 -0
  79. {trueseeing-2.2.2 → trueseeing-2.2.5}/trueseeing/app/scan.py +0 -0
  80. {trueseeing-2.2.2 → trueseeing-2.2.5}/trueseeing/core/__init__.py +0 -0
  81. {trueseeing-2.2.2/trueseeing/core/android/analysis → trueseeing-2.2.5/trueseeing/core/android/analyze}/__init__.py +0 -0
  82. {trueseeing-2.2.2 → trueseeing-2.2.5}/trueseeing/core/android/store.py +0 -0
  83. {trueseeing-2.2.2 → trueseeing-2.2.5}/trueseeing/core/android/tools.py +0 -0
  84. {trueseeing-2.2.2 → trueseeing-2.2.5}/trueseeing/core/config.py +0 -0
  85. {trueseeing-2.2.2 → trueseeing-2.2.5}/trueseeing/core/cvss.py +0 -0
  86. {trueseeing-2.2.2 → trueseeing-2.2.5}/trueseeing/core/exc.py +0 -0
  87. {trueseeing-2.2.2/trueseeing/core/android/model → trueseeing-2.2.5/trueseeing/core/ios}/__init__.py +0 -0
  88. {trueseeing-2.2.2 → trueseeing-2.2.5}/trueseeing/core/model/__init__.py +0 -0
  89. {trueseeing-2.2.2 → trueseeing-2.2.5}/trueseeing/core/model/cmd.py +0 -0
  90. {trueseeing-2.2.2 → trueseeing-2.2.5}/trueseeing/core/model/sig.py +0 -0
  91. {trueseeing-2.2.2 → trueseeing-2.2.5}/trueseeing/core/report.py +0 -0
  92. {trueseeing-2.2.2 → trueseeing-2.2.5}/trueseeing/core/z.py +0 -0
  93. {trueseeing-2.2.2 → trueseeing-2.2.5}/trueseeing/libs/LICENSE.md +0 -0
  94. {trueseeing-2.2.2 → trueseeing-2.2.5}/trueseeing/libs/android/abe.jar +0 -0
  95. {trueseeing-2.2.2 → trueseeing-2.2.5}/trueseeing/libs/android/apkeditor.jar +0 -0
  96. {trueseeing-2.2.2 → trueseeing-2.2.5}/trueseeing/libs/android/apksigner.jar +0 -0
  97. {trueseeing-2.2.2 → trueseeing-2.2.5}/trueseeing/libs/android/frida-app.smali +0 -0
  98. {trueseeing-2.2.2 → trueseeing-2.2.5}/trueseeing/libs/android/frida-scriptdir.config +0 -0
  99. {trueseeing-2.2.2 → trueseeing-2.2.5}/trueseeing/libs/android/nsc.xml +0 -0
  100. {trueseeing-2.2.2 → trueseeing-2.2.5}/trueseeing/libs/files.0.sql +0 -0
  101. {trueseeing-2.2.2 → trueseeing-2.2.5}/trueseeing/libs/public_suffix_list.dat +0 -0
  102. {trueseeing-2.2.2 → trueseeing-2.2.5}/trueseeing/libs/store.0.sql +0 -0
  103. {trueseeing-2.2.2 → trueseeing-2.2.5}/trueseeing/libs/template/report.html +0 -0
  104. {trueseeing-2.2.2 → trueseeing-2.2.5}/trueseeing/libs/tlds.txt +0 -0
  105. {trueseeing-2.2.2 → trueseeing-2.2.5}/trueseeing/py.typed +0 -0
  106. {trueseeing-2.2.2 → trueseeing-2.2.5}/trueseeing/sig/android/__init__.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.2
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
@@ -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"
@@ -38,7 +39,7 @@ Provides-Extra: dev
38
39
  ![Main branch deploy status](https://github.com/alterakey/trueseeing/workflows/deploy/badge.svg)
39
40
  ![Main branch last commit](https://img.shields.io/github/last-commit/alterakey/trueseeing/main)
40
41
 
41
- 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.
42
43
 
43
44
  ## Capability
44
45
 
@@ -50,6 +51,8 @@ Currently we can:
50
51
  * Copy in/out app data through debug interface
51
52
  * Search for certain calls/consts/sput/iput
52
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))
53
56
  * etc.
54
57
 
55
58
  ## Installation
@@ -83,7 +86,7 @@ Alternatively, you can install our package with pip as follows. This form of ins
83
86
  You can interactively scan/analyze/patch/etc. apps -- making it the ideal choice for manual analysis:
84
87
 
85
88
  $ trueseeing target.apk
86
- [+] trueseeing 2.2.2
89
+ [+] trueseeing 2.2.5
87
90
  ts[target.apk]> ?
88
91
  ...
89
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.2
55
+ [+] trueseeing 2.2.5
54
56
  ts[target.apk]> ?
55
57
  ...
56
58
  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
 
@@ -0,0 +1,2 @@
1
+ """Trueseeing is a non-decompiling iOS/Android application vulnerability scanner."""
2
+ __version__ = '2.2.5'
@@ -4,15 +4,18 @@ 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
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]
@@ -22,6 +25,7 @@ if TYPE_CHECKING:
22
25
  e: CommandEntrypoint
23
26
  n: str
24
27
  d: str
28
+ t: Set[str]
25
29
 
26
30
  class CommandEntry(Entry):
27
31
  pass
@@ -34,7 +38,7 @@ if TYPE_CHECKING:
34
38
  pass
35
39
 
36
40
  class ModifierEntry(Entry):
37
- pass
41
+ e: Optional[ModifierListenerEntrypoint] # type: ignore[misc]
38
42
 
39
43
  class ConfigEntry(TypedDict):
40
44
  g: ConfigGetterEntrypoint
@@ -62,6 +66,7 @@ if TYPE_CHECKING:
62
66
  class CommandHelper(Protocol):
63
67
  def get_target(self) -> Optional[str]: ...
64
68
  def require_target(self, msg: Optional[str] = None) -> str: ...
69
+ def get_context_type(self) -> Optional[Set[ContextType]]: ...
65
70
  @overload
66
71
  def get_context(self) -> Context: ...
67
72
  @overload
@@ -1,18 +1,19 @@
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
11
- from trueseeing.core.model.cmd import CommandMixin
12
13
  from trueseeing.core.tools import get_public_subclasses, get_missing_methods, discover_modules_under
13
14
 
14
15
  for mod in discover_modules_under('trueseeing.app.cmd'):
15
16
  m = import_module(mod)
16
- for c in get_public_subclasses(m, Command, [CommandMixin]): # type:ignore[type-abstract]
17
+ for c in get_public_subclasses(m, Command, 'CommandMixin'): # type:ignore[type-abstract]
17
18
  assert not get_missing_methods(c)
18
19
  yield c
@@ -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:
@@ -47,16 +49,24 @@ class AssembleCommand(CommandMixin):
47
49
  ui.fatal('need root path')
48
50
 
49
51
  import os
52
+ import re
50
53
  import time
51
54
  from tempfile import TemporaryDirectory
52
55
  from trueseeing.core.android.asm import APKAssembler
53
56
  from trueseeing.core.android.tools import move_apk
54
57
 
55
58
  root = args.popleft()
56
- origapk = apk.replace('.apk', '.apk.orig')
59
+ origapk = re.sub(r'(\.x?apk)$', r'\1.orig', apk)
57
60
 
58
- if os.path.exists(origapk) and not cmd.endswith('!'):
59
- ui.fatal('backup file exists; force (!) to overwrite')
61
+ stem = re.sub(r'\.x?apk$', '', apk)
62
+ for typ in ['apk', 'xapk']:
63
+ print(origapk, f'{stem}.{typ}.orig')
64
+ if os.path.exists(f'{stem}.{typ}.orig') and not cmd.endswith('!'):
65
+ ui.fatal('backup file exists; force (!) to overwrite')
66
+
67
+ if apk.endswith('.xapk'):
68
+ ui.warn('assembling xapk is not supported; assembling as merged apk')
69
+ apk = apk.replace('.xapk', '.apk')
60
70
 
61
71
  opts = self._helper.get_effective_options(self._helper.get_modifiers(args))
62
72
 
@@ -140,7 +150,7 @@ class AssembleCommand(CommandMixin):
140
150
  at = time.time()
141
151
 
142
152
  with TemporaryDirectory() as td:
143
- await APKDisassembler.disassemble_to_path(apk, td, nodex=nodex)
153
+ await APKDisassembler.disassemble_to_path(apk, td, nodex=nodex, merge=apk.endswith('.xapk'))
144
154
 
145
155
  if not archive:
146
156
  with FileTransferProgressReporter('disassemble: writing').scoped() as progress:
@@ -185,7 +195,7 @@ class AssembleCommand(CommandMixin):
185
195
 
186
196
  at = time.time()
187
197
  extracted = 0
188
- context = self._helper.get_context().require_type('apk')
198
+ context = self._helper.get_context()
189
199
  q = context.store().query()
190
200
 
191
201
  if not archive:
@@ -206,7 +216,7 @@ class AssembleCommand(CommandMixin):
206
216
  if subformat in ['gz']:
207
217
  kwargs.update(dict(compresslevel=3))
208
218
 
209
- 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]
210
220
  now = int(time.time())
211
221
  for path,blob in q.file_enum(pat=pat, regex=True):
212
222
  target = os.path.join('files', *path.split('/'))
@@ -215,6 +225,7 @@ class AssembleCommand(CommandMixin):
215
225
 
216
226
  bf = BytesIO(blob)
217
227
  ti = tarfile.TarInfo(name=target)
228
+ ti.size = len(blob)
218
229
  ti.uname = 'root'
219
230
  ti.gname = 'root'
220
231
  ti.mode = 0o600
@@ -231,3 +242,38 @@ class AssembleCommand(CommandMixin):
231
242
  return 'tar:gz'
232
243
  else:
233
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)))