apkdev 2.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- apkdev/__init__.py +13 -0
- apkdev/__main__.py +5 -0
- apkdev/apkfile.py +168 -0
- apkdev/builder.py +671 -0
- apkdev/cli.py +925 -0
- apkdev/completion.py +14 -0
- apkdev/device.py +161 -0
- apkdev/inspector.py +291 -0
- apkdev/optimizer.py +58 -0
- apkdev/reverser.py +62 -0
- apkdev/sdk.py +526 -0
- apkdev/utils.py +74 -0
- apkdev-2.0.0.dist-info/METADATA +159 -0
- apkdev-2.0.0.dist-info/RECORD +17 -0
- apkdev-2.0.0.dist-info/WHEEL +5 -0
- apkdev-2.0.0.dist-info/entry_points.txt +2 -0
- apkdev-2.0.0.dist-info/top_level.txt +1 -0
apkdev/__init__.py
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""
|
|
2
|
+
apkdev — Android APK Development & Reverse Engineering Toolkit.
|
|
3
|
+
|
|
4
|
+
One command for everything APK:
|
|
5
|
+
- Create Kotlin projects with Gradle
|
|
6
|
+
- Build, sign, and install APKs
|
|
7
|
+
- Decode APKs with apktool
|
|
8
|
+
- Decompile to Java with jadx
|
|
9
|
+
- Disassemble/assemble smali
|
|
10
|
+
- Convert DEX <-> JAR
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
__version__ = "2.0.0"
|
apkdev/__main__.py
ADDED
apkdev/apkfile.py
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
"""APK file operations — pure Python, no external dependencies."""
|
|
2
|
+
|
|
3
|
+
import zipfile
|
|
4
|
+
import os
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from rich.console import Console
|
|
8
|
+
from rich.table import Table
|
|
9
|
+
from rich.panel import Panel
|
|
10
|
+
from rich.text import Text
|
|
11
|
+
|
|
12
|
+
console = Console()
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def list_contents(apk: str) -> None:
|
|
16
|
+
"""List files inside an APK using pure Python zipfile module."""
|
|
17
|
+
apk_path = Path(apk)
|
|
18
|
+
if not apk_path.exists():
|
|
19
|
+
console.print(f"[red]✘ File not found: {apk}[/]")
|
|
20
|
+
return
|
|
21
|
+
|
|
22
|
+
try:
|
|
23
|
+
with zipfile.ZipFile(str(apk_path), "r") as zf:
|
|
24
|
+
entries = zf.infolist()
|
|
25
|
+
except zipfile.BadZipFile:
|
|
26
|
+
console.print(f"[red]✘ Invalid APK file: {apk}[/]")
|
|
27
|
+
return
|
|
28
|
+
|
|
29
|
+
if not entries:
|
|
30
|
+
console.print("[dim]Empty APK[/]")
|
|
31
|
+
return
|
|
32
|
+
|
|
33
|
+
# Gather file info
|
|
34
|
+
files = []
|
|
35
|
+
dirs = {}
|
|
36
|
+
total_size = 0
|
|
37
|
+
|
|
38
|
+
for entry in entries:
|
|
39
|
+
name = entry.filename
|
|
40
|
+
size = entry.file_size
|
|
41
|
+
total_size += size
|
|
42
|
+
files.append((size, name, name.endswith("/")))
|
|
43
|
+
|
|
44
|
+
parts = name.rstrip("/").split("/")
|
|
45
|
+
if len(parts) > 1 and not name.endswith("/"):
|
|
46
|
+
dir_name = parts[0]
|
|
47
|
+
dirs.setdefault(dir_name, {"count": 0, "size": 0})
|
|
48
|
+
dirs[dir_name]["count"] += 1
|
|
49
|
+
dirs[dir_name]["size"] += size
|
|
50
|
+
elif name.endswith("/"):
|
|
51
|
+
# Directory entry
|
|
52
|
+
dir_name = name.rstrip("/")
|
|
53
|
+
dirs.setdefault(dir_name, {"count": 0, "size": 0})
|
|
54
|
+
|
|
55
|
+
# Add root files
|
|
56
|
+
root_count = sum(1 for s, n, d in files if "/" not in n and not d)
|
|
57
|
+
root_size = sum(s for s, n, d in files if "/" not in n and not d)
|
|
58
|
+
if root_count > 0:
|
|
59
|
+
dirs["(root)"] = {"count": root_count, "size": root_size}
|
|
60
|
+
|
|
61
|
+
# Summary
|
|
62
|
+
console.print(f"[bold cyan]📦 {apk_path.name}[/] — {len([f for f in files if not f[2]])} files, {total_size/1024/1024:.1f} MB")
|
|
63
|
+
console.print()
|
|
64
|
+
|
|
65
|
+
# Directory table
|
|
66
|
+
t = Table(border_style="blue")
|
|
67
|
+
t.add_column("Directory", style="bold")
|
|
68
|
+
t.add_column("Files", justify="right")
|
|
69
|
+
t.add_column("Size", justify="right")
|
|
70
|
+
|
|
71
|
+
for dir_name, info in sorted(dirs.items()):
|
|
72
|
+
size_str = f"{info['size']/1024:.0f} KB" if info['size'] < 1024*1024 else f"{info['size']/1024/1024:.1f} MB"
|
|
73
|
+
t.add_row(f"📁 {dir_name}", str(info["count"]), size_str)
|
|
74
|
+
console.print(t)
|
|
75
|
+
|
|
76
|
+
# Notable files
|
|
77
|
+
console.print()
|
|
78
|
+
notable_extensions = {".dex", ".so", ".apk", ".xml", ".arsc", ".png", ".jar", ".ttf", ".ogg", ".mp4"}
|
|
79
|
+
notable_names = {"AndroidManifest.xml", "resources.arsc", "classes.dex", "classes2.dex", "classes3.dex", "classes4.dex"}
|
|
80
|
+
|
|
81
|
+
notable = [(s, n) for s, n, d in files if not d and
|
|
82
|
+
(Path(n).suffix in notable_extensions or Path(n).name in notable_names)]
|
|
83
|
+
notable.sort(key=lambda x: -x[0])
|
|
84
|
+
|
|
85
|
+
if notable:
|
|
86
|
+
t2 = Table(border_style="cyan")
|
|
87
|
+
t2.add_column("File", style="bold")
|
|
88
|
+
t2.add_column("Size", justify="right")
|
|
89
|
+
|
|
90
|
+
icons = {".dex": "⚙️", ".so": "🦀", ".arsc": "🎨", ".xml": "📋", ".png": "🖼️",
|
|
91
|
+
".jar": "📦", ".ttf": "🔤", ".ogg": "🎵", ".mp4": "🎬"}
|
|
92
|
+
name_icons = {"AndroidManifest.xml": "📋", "resources.arsc": "🎨"}
|
|
93
|
+
|
|
94
|
+
for size, name in notable[:40]:
|
|
95
|
+
ext = Path(name).suffix
|
|
96
|
+
icon = name_icons.get(Path(name).name) or icons.get(ext, "📄")
|
|
97
|
+
size_str = f"{size/1024:.0f} KB" if size < 1024*1024 else f"{size/1024/1024:.1f} MB"
|
|
98
|
+
t2.add_row(f"{icon} {name}", size_str)
|
|
99
|
+
console.print(t2)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def extract_apk(apk: str, output: str | None = None) -> None:
|
|
103
|
+
"""Extract APK contents using pure Python zipfile module."""
|
|
104
|
+
apk_path = Path(apk)
|
|
105
|
+
if not apk_path.exists():
|
|
106
|
+
console.print(f"[red]✘ File not found: {apk}[/]")
|
|
107
|
+
return
|
|
108
|
+
|
|
109
|
+
out_dir = Path(output or apk_path.stem)
|
|
110
|
+
out_dir.mkdir(parents=True, exist_ok=True)
|
|
111
|
+
|
|
112
|
+
try:
|
|
113
|
+
with zipfile.ZipFile(str(apk_path), "r") as zf:
|
|
114
|
+
console.print(f"[yellow]📦 Extracting {apk_path.name} → {out_dir}/...[/]")
|
|
115
|
+
zf.extractall(path=str(out_dir))
|
|
116
|
+
|
|
117
|
+
count = sum(1 for _ in out_dir.rglob("*") if _.is_file())
|
|
118
|
+
console.print(f"[green]✔ Extracted {count} files → {out_dir}/[/]")
|
|
119
|
+
except Exception as e:
|
|
120
|
+
console.print(f"[red]✘ Extraction failed: {e}[/]")
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def certificate(apk: str) -> None:
|
|
124
|
+
"""Show APK signing certificate info using pure Python + apksigner if available."""
|
|
125
|
+
import shutil
|
|
126
|
+
import subprocess
|
|
127
|
+
|
|
128
|
+
apk_path = Path(apk)
|
|
129
|
+
if not apk_path.exists():
|
|
130
|
+
console.print(f"[red]✘ File not found: {apk}[/]")
|
|
131
|
+
return
|
|
132
|
+
|
|
133
|
+
# Try apksigner first (requires Java)
|
|
134
|
+
apksigner = shutil.which("apksigner") or shutil.which("apksigner.jar")
|
|
135
|
+
if apksigner:
|
|
136
|
+
r = subprocess.run(
|
|
137
|
+
[apksigner, "verify", "--print-certs", str(apk_path)],
|
|
138
|
+
capture_output=True, text=True, timeout=15
|
|
139
|
+
)
|
|
140
|
+
if r.returncode == 0 and r.stdout.strip():
|
|
141
|
+
lines = [l for l in r.stdout.strip().split("\n") if l.strip()]
|
|
142
|
+
for line in lines:
|
|
143
|
+
if ":" in line:
|
|
144
|
+
key, val = line.split(":", 1)
|
|
145
|
+
console.print(f" [bold]{key.strip()}:[/] {val.strip()}")
|
|
146
|
+
else:
|
|
147
|
+
console.print(f" {line}")
|
|
148
|
+
return
|
|
149
|
+
|
|
150
|
+
# Fallback: extract RSA from META-INF using pure Python
|
|
151
|
+
try:
|
|
152
|
+
with zipfile.ZipFile(str(apk_path), "r") as zf:
|
|
153
|
+
# Find signature files
|
|
154
|
+
sig_files = [n for n in zf.namelist() if n.startswith("META-INF/") and
|
|
155
|
+
(n.endswith(".RSA") or n.endswith(".DSA") or n.endswith(".EC") or
|
|
156
|
+
n.endswith(".SF") or "SIG" in n)]
|
|
157
|
+
|
|
158
|
+
if sig_files:
|
|
159
|
+
console.print(f"[yellow]Certificate info (extracted from APK metadata):[/]")
|
|
160
|
+
for sf in sig_files:
|
|
161
|
+
info = zf.getinfo(sf)
|
|
162
|
+
size_kb = info.file_size / 1024
|
|
163
|
+
console.print(f" 📜 {sf} ({size_kb:.1f} KB)")
|
|
164
|
+
console.print("\n[dim]For detailed cert info, install Java + apksigner[/]")
|
|
165
|
+
else:
|
|
166
|
+
console.print("[yellow]No signature files found — APK may be unsigned[/]")
|
|
167
|
+
except zipfile.BadZipFile:
|
|
168
|
+
console.print(f"[red]✘ Invalid APK file[/]")
|