feat: hyper decryption device

with_status
WorldObservationLog 4 months ago
parent 356a5b2478
commit c84c3818e8
  1. 4
      config.example.toml
  2. 37
      src/adb.py
  3. 5
      src/cmd.py
  4. 2
      src/config.py
  5. 17
      src/decrypt.py
  6. 14
      src/rip.py

@ -18,6 +18,10 @@ agentPort = 10020
# For Magisk user, the recommend value is "su -c". For other Root solutions, the recommend value is "su 0"
# If not sure which method to use, execute “su 0 ls /” and “su -c ls /” respectively in adb shell and choose the output method
suMethod = "su -c"
# Inject multiple scripts into devices to simulate multi-device decryption, which can speed up decryption
# Experimental feature
hyperDecrypt = false
hyperDecryptNum = 2
[m3u8Api]
# Use zhaarey's m3u8 api to get higher song m3u8.

@ -14,8 +14,24 @@ from src.exceptions import ADBConnectException, FailedGetAuthParamException, \
from src.types import AuthParams
class HyperDecryptDevice:
host: str
fridaPort: int
decryptLock: asyncio.Lock
serial: str
_father_device = None
def __init__(self, host: str, port: int, father_device):
self.host = host
self.fridaPort = port
self.decryptLock = asyncio.Lock()
self.serial = f"{host}:{port}"
self._father_device = father_device
class Device:
host: str
serial: str
client: AdbClient
device: AdbDevice
fridaPort: int
@ -25,6 +41,7 @@ class Device:
authParams: AuthParams = None
suMethod: str
decryptLock: asyncio.Lock
hyperDecryptDevices: list[HyperDecryptDevice] = []
def __init__(self, host="127.0.0.1", port=5037, su_method: str = "su -c"):
self.client = AdbClient(host, port)
@ -41,6 +58,7 @@ class Device:
if not status:
raise ADBConnectException
self.device = self.client.device(f"{host}:{port}")
self.serial = self.device.serial
def _execute_command(self, cmd: str, su: bool = False, sh: bool = False) -> Optional[str]:
whoami = self.device.shell("whoami")
@ -143,3 +161,22 @@ class Device:
self.authParams = AuthParams(dsid=dsid, accountToken=token,
accountAccessToken=access_token, storefront=storefront)
return self.authParams
def hyper_decrypt(self, ports: list[int]):
if not self._if_frida_running():
raise FridaNotRunningException
logger.debug("injecting agent script with hyper decrypt")
self.fridaPort = ports[0]
if not self.fridaDevice:
frida.get_device_manager().add_remote_device(self.device.serial)
self.fridaDevice = frida.get_device_manager().get_device(self.device.serial)
self.pid = self.fridaDevice.spawn("com.apple.android.music")
self.fridaSession = self.fridaDevice.attach(self.pid)
for port in ports:
self._start_forward(port, port)
with open("agent.js", "r") as f:
agent = f.read().replace("2147483647", str(port))
script: frida.core.Script = self.fridaSession.create_script(agent)
script.load()
self.hyperDecryptDevices.append(HyperDecryptDevice(host=self.host, port=port, father_device=self))
self.fridaDevice.resume(self.pid)

@ -67,7 +67,10 @@ class NewInteractiveShell:
if not self.storefront_device_mapping.get(auth_params.storefront.lower()):
self.storefront_device_mapping.update({auth_params.storefront.lower(): []})
self.storefront_device_mapping[auth_params.storefront.lower()].append(device)
device.start_inject_frida(device_info.agentPort)
if device_info.hyperDecrypt:
device.hyper_decrypt(list(range(device_info.agentPort, device_info.agentPort + device_info.hyperDecryptNum)))
else:
device.start_inject_frida(device_info.agentPort)
async def command_parser(self, cmd: str):
if not cmd.strip():

@ -13,6 +13,8 @@ class Device(BaseModel):
port: int
agentPort: int
suMethod: str
hyperDecrypt: bool
hyperDecryptNum: int
class M3U8Api(BaseModel):

@ -2,26 +2,31 @@ import asyncio
import logging
from loguru import logger
from tenacity import retry, retry_if_exception_type, stop_after_attempt, before_sleep_log, RetryCallState
from tenacity import retry, retry_if_exception_type, stop_after_attempt, before_sleep_log
from src.adb import Device
from src.adb import Device, HyperDecryptDevice
from src.exceptions import DecryptException, RetryableDecryptException
from src.models.song_data import Datum
from src.mp4 import SongInfo, SampleInfo
from src.types import defaultId, prefetchKey
from src.utils import timeit
retry_count = {}
@retry(retry=retry_if_exception_type(RetryableDecryptException), stop=stop_after_attempt(3),
before_sleep=before_sleep_log(logger, logging.WARN))
async def decrypt(info: SongInfo, keys: list[str], manifest: Datum, device: Device) -> bytes:
@timeit
async def decrypt(info: SongInfo, keys: list[str], manifest: Datum, device: Device | HyperDecryptDevice) -> bytes:
async with device.decryptLock:
logger.info(f"Decrypting song: {manifest.attributes.artistName} - {manifest.attributes.name}")
if isinstance(device, HyperDecryptDevice):
logger.info(f"Using hyperDecryptDevice {device.serial} to decrypt song: {manifest.attributes.artistName} - {manifest.attributes.name}")
else:
logger.info(f"Using device {device.serial} to decrypt song: {manifest.attributes.artistName} - {manifest.attributes.name}")
try:
reader, writer = await asyncio.open_connection(device.host, device.fridaPort)
except ConnectionRefusedError:
logger.warning(f"Failed to connect to device {device.device.serial}, re-injecting")
logger.warning(f"Failed to connect to device {device.serial}, re-injecting")
device.restart_inject_frida()
raise RetryableDecryptException
decrypted = bytes()
@ -42,7 +47,7 @@ async def decrypt(info: SongInfo, keys: list[str], manifest: Datum, device: Devi
try:
result = await decrypt_sample(writer, reader, sample)
except RetryableDecryptException as e:
if 0 <= retry_count.get(device.device.serial, 0) < 3 or 4 <= retry_count.get(device.device.serial, 0) < 6:
if 0 <= retry_count.get(device.serial, 0) < 3 or 4 <= retry_count.get(device.serial, 0) < 6:
logger.warning(f"Failed to decrypt song: {manifest.attributes.artistName} - {manifest.attributes.name}, retrying")
writer.write(bytes([0, 0, 0, 0]))
writer.close()

@ -1,4 +1,5 @@
import asyncio
import random
import subprocess
from loguru import logger
@ -15,7 +16,7 @@ from src.mp4 import extract_media, extract_song, encapsulate, write_metadata, fi
from src.save import save
from src.types import GlobalAuthParams, Codec
from src.url import Song, Album, URLType, Artist, Playlist
from src.utils import check_song_exists, if_raw_atmos, playlist_write_song_index, get_codec_from_codec_id
from src.utils import check_song_exists, if_raw_atmos, playlist_write_song_index, get_codec_from_codec_id, timeit
task_lock = asyncio.Semaphore(16)
@ -80,7 +81,16 @@ async def rip_song(song: Song, auth_params: GlobalAuthParams, codec: str, config
codec = get_codec_from_codec_id(codec_id)
raw_song = await download_song(song_uri)
song_info = await extract_song(raw_song, codec)
decrypted_song = await decrypt(song_info, keys, song_data, device)
if device.hyperDecryptDevices:
if all([hyper_device.decryptLock.locked() for hyper_device in device.hyperDecryptDevices]):
decrypted_song = await decrypt(song_info, keys, song_data, random.choice(device.hyperDecryptDevices))
else:
for hyperDecryptDevice in device.hyperDecryptDevices:
if not hyperDecryptDevice.decryptLock.locked():
decrypted_song = await decrypt(song_info, keys, song_data, hyperDecryptDevice)
break
else:
decrypted_song = await decrypt(song_info, keys, song_data, device)
song = await encapsulate(song_info, decrypted_song, config.download.atmosConventToM4a)
if not if_raw_atmos(codec, config.download.atmosConventToM4a):
metadata_song = await write_metadata(song, song_metadata, config.metadata.embedMetadata, config.download.coverFormat)

Loading…
Cancel
Save