diff --git a/docs/Axinom/patch/SampleChooserActivity.smali b/docs/Axinom/patch/SampleChooserActivity.smali deleted file mode 100644 index 5fac857..0000000 --- a/docs/Axinom/patch/SampleChooserActivity.smali +++ /dev/null @@ -1,47 +0,0 @@ -.method private isNetworkAvailable()Z - .registers 2 - - # const-string v0, "connectivity" - - # .line 139 - # invoke-virtual {p0, v0}, Lcom/axinom/drm/sample/activity/SampleChooserActivity;->getSystemService(Ljava/lang/String;)Ljava/lang/Object; - - # move-result-object v0 - - # check-cast v0, Landroid/net/ConnectivityManager; - - # if-eqz v0, :cond_f - - # .line 142 - # invoke-virtual {v0}, Landroid/net/ConnectivityManager;->getActiveNetworkInfo()Landroid/net/NetworkInfo; - - # move-result-object v0 - - # goto :goto_10 - - # :cond_f - # const/4 v0, 0x0 - - # :goto_10 - # if-eqz v0, :cond_1a - - # .line 144 - # invoke-virtual {v0}, Landroid/net/NetworkInfo;->isConnected()Z - - # move-result v0 - - # if-eqz v0, :cond_1a - - # const/4 v0, 0x1 - - # goto :goto_1b - - # :cond_1a - # const/4 v0, 0x0 - - # :goto_1b - - const/4 v0, 0x1 - - return v0 -.end method diff --git a/docs/Axinom/patch/axinom.apk b/docs/Axinom/patch/axinom.apk deleted file mode 100644 index 023f864..0000000 Binary files a/docs/Axinom/patch/axinom.apk and /dev/null differ diff --git a/docs/Axinom/patch/axinom_signed.apk b/docs/Axinom/patch/axinom_signed.apk deleted file mode 100644 index 9641ec3..0000000 Binary files a/docs/Axinom/patch/axinom_signed.apk and /dev/null differ diff --git a/docs/Axinom/patch/builder_mobile.py b/docs/Axinom/patch/builder_mobile.py deleted file mode 100644 index 2cdde13..0000000 --- a/docs/Axinom/patch/builder_mobile.py +++ /dev/null @@ -1,47 +0,0 @@ -from pathlib import Path - -import yaml - -# @Info: values to be patch -PATCH = { - "assets/samplelist.json": [ - [None, "samplelist.json"] - ], - "com/axinom/drm/sample/activity/SampleChooserActivity.smali": [ - ["SampleChooserActivity.smali", None] - ] -} - -# @Info: Keystore to sign the application -KEYSTORE = { - "algo": "RSA", - "size": 2048, - "sign": "SHA-256", - "validity": 365 * 25, - "password": "Axinom_PASSWORD", - "alias": "Axinom_DRM_DEMO", - "meta": { - "common_name": "Axinom", - "organizational_unit": "Front-End", - "organization": "Axinom", - "locality": "Tartu", - "state": "Tartumaa", - "country": "EE", - } -} - -# @Info: Info about application -METADATA = { - # "name": "Axinom DRM Sample Player", - "version": "202211021", - "source": "https://github.com/Axinom/drm-sample-player-android", - "input": "axinom.apk", - "output": "axinom_signed.apk", -} - -if __name__ == "__main__": - Path("config.yaml").write_text(yaml.dump({ - "metadata": METADATA, - "keystore": KEYSTORE, - "patch": PATCH - })) diff --git a/docs/Axinom/patch/config.yaml b/docs/Axinom/patch/config.yaml deleted file mode 100644 index 4564d30..0000000 --- a/docs/Axinom/patch/config.yaml +++ /dev/null @@ -1,26 +0,0 @@ -keystore: - algo: RSA - alias: Axinom_DRM_DEMO - meta: - common_name: Axinom - country: EE - locality: Tartu - organization: Axinom - organizational_unit: Front-End - state: Tartumaa - password: Axinom_PASSWORD - sign: SHA-256 - size: 2048 - validity: 9125 -metadata: - input: axinom.apk - output: axinom_signed.apk - source: https://github.com/Axinom/drm-sample-player-android - version: '202211021' -patch: - assets/samplelist.json: - - - null - - samplelist.json - com/axinom/drm/sample/activity/SampleChooserActivity.smali: - - - SampleChooserActivity.smali - - null diff --git a/docs/Axinom/patch/patcher.py b/docs/Axinom/patch/patcher.py deleted file mode 100644 index affe01e..0000000 --- a/docs/Axinom/patch/patcher.py +++ /dev/null @@ -1,260 +0,0 @@ -import hashlib -import json -import os -import re -import shutil -from pathlib import Path - -import xmltodict -import yaml - - -def any2str(data: any) -> str: - if isinstance(data, (bytes, bytearray)): - data = data.decode("utf-8") - - if isinstance(data, (dict, list)): - data = json.dumps(data, indent=2, separators=(",", ":")) - - return str(data) - - -class Keystore: - - def __init__( - self, - algo: str = "RSA", - size: int = 2048, - sign: str = "SHA-256", - validity: int = 365, - password: str = None, - alias: str = None, - meta: dict = None, - path: Path = Path("..") - ): - assert path.is_dir(), "Invalid Dir Path" - assert algo in ["RSA", "EC", "DSA"], "Invalid Algorithm" - assert sign in ["MD5", "SHA-1", "SHA-256", "SHA-512"], "Invalid Signature" - - if algo == "RSA": - assert size in [1024, 2048, 3072, 4096], "Invalid RSA Size" - assert sign in ["MD5", "SHA-1", "SHA-256", "SHA-512"], "Invalid RSA Signature" - elif algo == "EC": - assert size in [192, 224, 256, 384, 521], "Invalid EC Size" - assert sign in ["SHA-256", "SHA-512"], "Invalid EC Signature" - elif algo == "DSA": - assert size in [1024], "Invalid DSA Size" - assert sign in ["SHA-1"], "Invalid DSA Signature" - - self.algorithm = algo - self.size = size - self.signature = "{}with{}".format( - sign.replace("-", ""), - "ECDSA" if algo == "EC" else algo - ) - self.digest = sign - self.validity = validity - meta = meta if meta else {} - self.metadata = { - "common_name": meta.get("common_name", "Unknown"), - "organizational_unit": meta.get("organizational_unit", "Unknown"), - "organization": meta.get("organization", "Unknown"), - "locality": meta.get("locality", "Unknown"), - "state": meta.get("state", "Unknown"), - "country": meta.get("country", "Unknown"), - } - - match = re.search(r'[\s:]?([a-zA-Z]+)', self.metadata["common_name"]) - name = re.sub(r'[^A-Za-z0-9]', "", match.group(1)).lower() if match else "keystore" - self.path = path / f"{name}_{algo.lower()}.p12" - self.password = password or f"{name}_password" - self.alias = alias or f"{name}_alias" - - def __repr__(self) -> str: - return json.dumps({ - "path": str(self.path), - "algorithm": self.algorithm, - "size": self.size, - "signature": self.signature, - "digest": self.digest, - "validity": self.validity, - "password": self.password, - "alias": self.alias, - "metadata": self.metadata - }, indent=2) - - def sign(self, path: Path) -> None: - assert path.is_file() and path.suffix == ".apk", "Invalid APK Path" - - if not self.path.is_file(): - tmp = Path("keystore.jks") - os.system( - 'keytool -genkeypair -keystore "{}" -alias "{}" -keyalg "{}" -keysize "{}" -sigalg "{}" -validity "{}" -storepass "{}" -keypass "{}" -dname "CN=\\"{}\\", OU=\\"{}\\", O=\\"{}\\", L=\\"{}\\", ST=\\"{}\\", C=\\"{}\\"" -noprompt'.format( - tmp, self.alias, self.algorithm, self.size, self.signature, - self.validity, self.password, self.password, self.metadata["common_name"], - self.metadata["organizational_unit"], self.metadata["organization"], - self.metadata["locality"], self.metadata["state"], self.metadata["country"] - )) - - os.system( - 'keytool -importkeystore -srckeystore "{}" -srcstorepass "{}" -destkeystore "{}" -deststoretype "PKCS12" -deststorepass "{}" -destkeypass "{}" -srcalias "{}"'.format( - tmp, self.password, self.path, self.password, self.password, self.alias - )) - tmp.unlink(missing_ok=True) - os.system('apksigner sign --ks "{}" --ks-key-alias "{}" --ks-pass "pass:{}" --key-pass "pass:{}" "{}"'.format( - self.path, self.alias, self.password, self.password, path - )) - Path(str(path) + ".idsig").unlink(missing_ok=True) - - def info(self, path: Path) -> None: - assert path.is_file() and path.suffix == ".apk", "Invalid APK Path" - os.system(f'apksigner verify --print-certs "{path}"') - - -class ApkTool: - - def __init__(self, instance: Path = Path(".apktool")): - self.instance = instance - - def decompile(self, path: Path) -> None: - assert path.is_file() and path.suffix == ".apk", "Invalid APK Path" - if not self.instance.is_dir(): - os.system(f'apktool d "{path}" -o "{self.instance}" -f --no-crunch --only-main-classes') - - def compile(self, path: Path) -> None: - assert path.suffix == ".apk", "Invalid APK Path" - if not path.is_file(): - assert self.instance.is_dir(), "Invalid ApkTool Path" - tmp = Path("unaligned.apk") - - os.system(f'apktool b "{self.instance}" -o "{tmp}" -f --no-crunch') - if tmp.is_file(): os.system(f'zipalign -f -p "4" "{tmp}" "{path}"') - if path.is_file(): shutil.rmtree(self.instance, ignore_errors=True) - tmp.unlink(missing_ok=True) - - -def rename_app(parent: Path, name: str) -> None: - manifest_path = parent / "AndroidManifest.xml" - if not manifest_path.is_file(): - raise FileNotFoundError(manifest_path) - - manifest_dict = xmltodict.parse(manifest_path.read_bytes(), encoding="utf-8") - value = str(manifest_dict["manifest"]["application"]["@android:label"]) - if value.startswith("@string/"): - key = value.split("@string/")[1] - - source = None - for path in (parent / "res").iterdir(): - strings_path = path / "strings.xml" - if "values" in str(path) and strings_path.is_file(): - strings_dict = xmltodict.parse(strings_path.read_bytes(), encoding="utf-8") - - for item in strings_dict["resources"]["string"]: - if isinstance(item, dict) and item["@name"] == key: - source = item["#text"] - print(f"I: Patching {strings_path.name} ({strings_path.parent})") - if source != name: - item["#text"] = name - strings_path.write_bytes( - xmltodict.unparse(strings_dict, encoding="utf-8", pretty=True).encode("utf-8")) - break - if not source: - raise ImportError(value) - else: - manifest_dict["manifest"]["application"]["@android:label"] = name - manifest_path.write_bytes(xmltodict.unparse(manifest_dict, encoding="utf-8", pretty=True).encode("utf-8")) - print(f"I: Patching {manifest_path.name} ({manifest_path.parent})") - - -if __name__ == "__main__": - config = Path("config.yaml") - if not config.is_file(): - config = Path(input("Config Path: ")) - if not config.is_file(): - raise FileNotFoundError(config) - - content = yaml.safe_load(config.read_text()) - - apktool = ApkTool() - jks = Keystore(**content["keystore"]) - src = Path(content["metadata"]["input"]) - opt = Path(content["metadata"]["output"]) - - for key, value in content["metadata"].items(): - print(f"I: {key.capitalize()}: {value}") - - if not opt.is_file(): - apktool.decompile(src) - - # @Info: Patch apk - for key, value in content["patch"].items(): # {Path: list[tuple]} - - path = apktool.instance / key - if not path.is_file(): - exist = False - for subp in apktool.instance.iterdir(): - path = subp / key - if path.is_file(): - exist = True - break - - if not exist: - raise FileNotFoundError(key) - - src_data = path.read_text() - for v in value: - if v[0] is None: - if isinstance(v[1], str): - # @Info: Replace complet file using [None, Path] - v[1] = Path(v[1]) - if not v[1].is_file(): - raise FileNotFoundError(v[1]) - src_data = v[1].read_text() - elif v[1] is None: - # @Info: Replace with empty file - src_data = "" - else: - # @Info: Replace with custom char - src_data = any2str(v[1]) - elif v[1] is None: - # @Info: Replace functon using [Path, None] - if not isinstance(v[0], str): - raise ImportError(v[0]) - - v[0] = Path(v[0]) - if not v[0].is_file(): - raise FileNotFoundError(v[0]) - - opt_data = v[0].read_text() - if opt_data not in src_data: - try: - keys = opt_data.split("\n") - start = next(filter(None, keys), None) - stop = next(filter(None, reversed(keys)), None) - start_index = src_data.index(start) - stop_index = src_data.index(stop, start_index) + len(stop) - src_data = src_data.replace(src_data[start_index:stop_index], opt_data) - except Exception as e: - raise ValueError(v[0]) - else: - # @Info: Replace char using [str, str] - if not v[0] in src_data and not v[1] in src_data: - raise ImportError(v[0]) - src_data = src_data.replace(*v) - - path.write_text(src_data) - print(f"I: Patching {path.name} ({path.parent})") - - # @Info: Rename apk - name = content["metadata"].get("name") - if name: - rename_app(apktool.instance, name) - - apktool.compile(opt) - print(f"I: Keystore: {jks.path}") - print(f"I: Validity: {jks.validity}") - jks.sign(opt) - - jks.info(opt) - - print(f'I: MD5: {hashlib.md5(opt.read_bytes()).hexdigest()}') diff --git a/docs/Axinom/patch/samplelist.json b/docs/Axinom/patch/samplelist.json deleted file mode 100644 index 374c712..0000000 --- a/docs/Axinom/patch/samplelist.json +++ /dev/null @@ -1,9 +0,0 @@ -[ - { - "title": "Axinom demo video - single key (DASH; cenc)", - "videoUrl": "https://media.axprod.net/VTB/DrmQuickStart/AxinomDemoVideo-SingleKey/Encrypted_Cenc/Manifest.mpd", - "drmScheme": "widevine", - "licenseServer": "https://drm-widevine-licensing.axtest.net/AcquireLicense", - "licenseToken": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ2ZXJzaW9uIjoxLCJjb21fa2V5X2lkIjoiNjllNTQwODgtZTllMC00NTMwLThjMWEtMWViNmRjZDBkMTRlIiwibWVzc2FnZSI6eyJ2ZXJzaW9uIjoyLCJ0eXBlIjoiZW50aXRsZW1lbnRfbWVzc2FnZSIsImxpY2Vuc2UiOnsiYWxsb3dfcGVyc2lzdGVuY2UiOnRydWV9LCJjb250ZW50X2tleXNfc291cmNlIjp7ImlubGluZSI6W3siaWQiOiIyMTFhYzFkYy1jOGEyLTQ1NzUtYmFmNy1mYTRiYTU2YzM4YWMiLCJ1c2FnZV9wb2xpY3kiOiJUaGVPbmVQb2xpY3kifV19LCJjb250ZW50X2tleV91c2FnZV9wb2xpY2llcyI6W3sibmFtZSI6IlRoZU9uZVBvbGljeSIsInBsYXlyZWFkeSI6eyJwbGF5X2VuYWJsZXJzIjpbIjc4NjYyN0Q4LUMyQTYtNDRCRS04Rjg4LTA4QUUyNTVCMDFBNyJdfX1dfX0.D9FM9sbTFxBmcCOC8yMHrEtTwm0zy6ejZUCrlJbHz_U" - } -] \ No newline at end of file