Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 23 additions & 19 deletions android_unpinner/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ def patch_apk_files(apks: list[Path]) -> list[Path]:

def ensure_device_connected() -> None:
try:
adb("get-state")
adb(["get-state"])
except subprocess.CalledProcessError:
raise RuntimeError("No device connected via ADB.")

Expand All @@ -93,13 +93,13 @@ def install_apk(apk_files: list[Path]) -> None:
)

logging.info("Uninstall existing app...")
adb(f"uninstall {package_name}")
adb(["uninstall", package_name])

logging.info(f"Installing {package_name}...")
if len(apk_files) > 1:
adb(f"install-multiple --no-incremental {' '.join(str(x) for x in apk_files)}")
adb(["install-multiple", "--no-incremental", *[str(x) for x in apk_files]])
else:
adb(f"install --no-incremental {apk_files[0]}")
adb(["install", "--no-incremental", str(apk_files[0])])


def copy_files() -> None:
Expand All @@ -109,17 +109,17 @@ def copy_files() -> None:
# TODO: We could later provide the option to use a custom script dir.
ensure_device_connected()
logging.info("Detect architecture...")
abi = adb("shell getprop ro.product.cpu.abi").stdout.strip()
abi = adb(["shell", "getprop", "ro.product.cpu.abi"]).stdout.strip()
if abi == "armeabi-v7a":
abi = "arm"
gadget_file = gadget_files.get(abi, gadget_files["arm64"])
logging.info(f"Copying matching gadget: {gadget_file.name}...")
adb(f"push {gadget_file} /data/local/tmp/{LIBGADGET}")
adb(f"push {gadget_config_file} /data/local/tmp/{LIBGADGET_CONF}")
adb(["push", str(gadget_file), f"/data/local/tmp/{LIBGADGET}"])
adb(["push", str(gadget_config_file), f"/data/local/tmp/{LIBGADGET_CONF}"])

logging.info("Copying builtin Frida scripts to /data/local/tmp/android-unpinner...")
adb(f"push {here / 'scripts'}/. /data/local/tmp/android-unpinner/")
active_scripts = adb("shell ls /data/local/tmp/android-unpinner").stdout.splitlines(
adb(["push", f"{here / 'scripts'}/.", "/data/local/tmp/android-unpinner/"])
active_scripts = adb(["shell", "ls", "/data/local/tmp/android-unpinner"]).stdout.splitlines(
keepends=False
)
logging.info(f"Active frida scripts: {active_scripts}")
Expand All @@ -128,17 +128,21 @@ def copy_files() -> None:
def start_app_on_device(package_name: str) -> None:
ensure_device_connected()
logging.info("Start app (suspended)...")
adb(f"shell am set-debug-app -w {package_name}")
activity = adb(
f'shell cmd "package resolve-activity --brief {package_name} | tail -n 1"'
).stdout.strip()
adb(f"shell am start -n {activity}")
adb(["shell", "am", "set-debug-app", "-w", package_name])
activity_lines = adb([
"shell", "cmd", "package", "resolve-activity",
"--brief", package_name
]).stdout.strip().splitlines()
activity = activity_lines[-1] if activity_lines else ""
if not activity or activity.lower().startswith("no activity found"):
raise RuntimeError("Activity not found")
adb(["shell", "am", "start", "-n", activity])

logging.info("Obtain process id...")
pid = None
for i in range(5):
try:
pid = adb(f"shell pidof {package_name}").stdout.strip()
pid = adb(["shell", "pidof", package_name]).stdout.strip()
break
except subprocess.CalledProcessError:
if i:
Expand All @@ -147,7 +151,7 @@ def start_app_on_device(package_name: str) -> None:
raise
sleep(1)
logging.debug(f"{pid=}")
local_port = int(adb(f"forward tcp:0 jdwp:{pid}").stdout)
local_port = int(adb(["forward", "tcp:0", f"jdwp:{pid}"]).stdout)
logging.debug(f"{local_port=}")

async def inject_frida():
Expand Down Expand Up @@ -175,7 +179,7 @@ async def inject_frida():


def get_packages() -> list[str]:
packages = adb("shell pm list packages").stdout.strip().splitlines()
packages = adb(["shell", "pm", "list", "packages"]).stdout.strip().splitlines()
return [p.removeprefix("package:") for p in sorted(packages)]


Expand Down Expand Up @@ -379,7 +383,7 @@ def get_apks(package: str, outdir: Path) -> None:
if package not in get_packages():
raise RuntimeError(f"Could not find package: {package}")

package_info = adb(f"shell pm path {package}").stdout
package_info = adb(["shell", "pm", "path", package]).stdout
if not package_info.startswith("package:"):
raise RuntimeError(f"Unxepected output from pm path: {package_info!r}")
apks = [p.removeprefix("package:") for p in package_info.splitlines()]
Expand All @@ -393,7 +397,7 @@ def get_apks(package: str, outdir: Path) -> None:
f"Overwrite existing file: {outfile.absolute()}?", abort=True
):
outfile.unlink()
adb(f"pull {apk} {outfile.absolute()}")
adb(["pull", apk, str(outfile.absolute())])

logging.info("All done! 🎉")

Expand Down
15 changes: 8 additions & 7 deletions android_unpinner/vendor/platform_tools/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,21 +14,22 @@
adb_binary = here / "linux" / "adb"


def adb(cmd: str) -> subprocess.CompletedProcess[str]:
def adb(cmd: list[str]) -> subprocess.CompletedProcess[str]:
"""Helper function to call adb and capture stdout."""
base = f"{adb_binary}"
base = [str(adb_binary)]
if device:
base += f" -s {device}"
base += ["-s", device]
logging.debug(f"Using device: {device}")
full_cmd = f"{base} {cmd}"
full_cmd = base + cmd
full_cmd_str = " ".join(full_cmd)
try:
proc = subprocess.run(
full_cmd, shell=True, check=True, capture_output=True, text=True
full_cmd, check=True, capture_output=True, text=True
)
except subprocess.CalledProcessError as e:
logging.debug(f"cmd='{full_cmd}'\n" f"{e.stdout=}\n" f"{e.stderr=}")
logging.debug(f"cmd='{full_cmd_str}'\n" f"{e.stdout=}\n" f"{e.stderr=}")
raise
logging.debug(f"cmd='{full_cmd}'\n" f"{proc.stdout=}\n" f"{proc.stderr=}")
logging.debug(f"cmd='{full_cmd_str}'\n" f"{proc.stdout=}\n" f"{proc.stderr=}")
return proc


Expand Down