-
Notifications
You must be signed in to change notification settings - Fork 8
/
get_bundle_id.py
96 lines (73 loc) · 3.33 KB
/
get_bundle_id.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
import os
import zipfile
import plistlib
from tempfile import NamedTemporaryFile as NTF
import requests
from PIL import Image
# returns genre id
def save_appstore_icon(bundle: str) -> dict:
x = requests.get(f"https://itunes.apple.com/lookup?bundleId={bundle}&limit=1&country=US").json()
try:
icon_url = x["results"][0]["artworkUrl512"]
genres = x["results"][0]["genreIds"]
except (KeyError, IndexError):
# type 1 = app
return {"genre": 1, "err": True} # invalid appstore app, will have to extract from ipa
with NTF() as tmp:
tmp.write(requests.get(icon_url).content)
with Image.open(tmp.name) as img:
img.save(f"icons/{bundle}.png", "PNG") # usually jpg, so we save as png instead
if "6014" in genres or any(genre.startswith("70") for genre in genres):
return {"genre": 2, "err": False} # type 2 = game
return {"genre": 1, "err": False}
# this is shit so gotta seperate into its own func lol
# TIL: the namelist doesnt always have the .app name??
def get_app_name(nl: list[str]) -> str:
for name in nl:
if ".app/" in name and len(name.split("/")) >= 2:
return "/".join(name.split("/")[:2])
return ""
# uses same method as seashell cli:
# https://github.com/EntySec/SeaShell/blob/8ae1ecba722ba303c961c537633b663717fcfbe7/seashell/core/ipa.py#L189
def no_seashell(path: str) -> dict:
with zipfile.ZipFile(path) as zf:
app: str = get_app_name((nl := zf.namelist()))
if f"{app}/mussel" in nl:
return {"unsafe": 1}
# note: `CFBundleSignature` is now appearing in the real world?
# why is this even becoming an official key? whatever
with zf.open((pl_name := f"{app}/Info.plist")) as pl:
plist = plistlib.load(pl)
# if "CFBundleSignature" in plist:
# return {"unsafe": 1}
return {"pl": plist, "nl": nl, "pl_name": pl_name}
# if called, guaranteed that icon is not yet saved
def get_single_bundle_id(url, name = "temp.ipa") -> dict:
with open(name, "wb") as f:
f.write(requests.get(url).content)
os.makedirs("icons", exist_ok=True)
try:
assert(zipfile.is_zipfile(name))
except AssertionError:
print(f"[!] bad zipfile: {os.path.basename(url)} ({url})")
return {"error": 1}
try:
assert("unsafe" not in (sscheck := no_seashell(name)))
except AssertionError:
print(f"[!] seashell detected in: {os.path.basename(url)} ({url})")
return {"error": 1}
with zipfile.ZipFile(name) as archive:
bundleId = sscheck["pl"]["CFBundleIdentifier"]
if (res := save_appstore_icon(bundleId))["err"]:
try:
icon_path = sscheck["pl"]["CFBundleIcons"]["CFBundlePrimaryIcon"]["CFBundleIconFiles"][0]
for name in sscheck["nl"]:
if icon_path in name:
icon_path = name # im so tired
break
except (KeyError, IndexError):
# is this doing what i think it's doing..?
icon_path = f"{os.path.dirname(sscheck["pl_name"])}/{sscheck["pl"]["CFBundleIconFiles"][0]}"
with archive.open(icon_path) as orig, open(f"icons/{bundleId}.png", "wb") as new:
new.write(orig.read())
return {"bundle": bundleId, "genre": res["genre"]}