This commit is contained in:
Jean-Marie 'Histausse' Mineau 2025-03-14 17:47:02 +01:00
parent bce66067b0
commit 8a192b0e1a
Signed by: histausse
GPG key ID: B66AEEDA9B645AD2
5 changed files with 273 additions and 7 deletions

View file

@ -83,9 +83,9 @@ pub fn transform_method(
while move_ret.as_ref() != iter.next() {}
}
let end_label = if method == &*MTH_INVOKE {
format!("end_reflection_call_at_{}", "TODO_ADDR")
format!("end_reflection_call_at_{}", addr_label.clone())
} else if method == &*CLASS_NEW_INST || method == &*CNSTR_NEW_INST {
format!("end_reflection_instanciation_at_{}", "TODO_ADDR")
format!("end_reflection_instanciation_at_{}", addr_label.clone())
} else {
panic!("Should not happen!")
};
@ -740,9 +740,9 @@ fn get_cnstr_new_inst_block(
}
let abort_label = format!(
"end_static_instance_with_{}_at_{}",
"end_static_instance_with_{}_at_{:08X}",
ref_data.constructor.try_to_smali()?,
"TODO_ADDR"
ref_data.addr
);
let mut insns = test_cnstr(
@ -854,9 +854,9 @@ fn get_class_new_inst_block(
let class_reg = class_reg as u8;
let abort_label = format!(
"end_static_instance_with_{}_at_{}",
"end_static_instance_with_{}_at_{:08X}",
ref_data.constructor.try_to_smali()?,
"TODO_ADDR"
ref_data.addr
);
let obj_reg = match move_result {

3
theseus_autopatcher/.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
dist
__pycache__
src/theseus_autopatcher/patcher_86_64_musl

View file

@ -0,0 +1,36 @@
# Android Theseus Patcher
This is mostly glueware between the [theseus frida](../frida) python package (used to get runtime information) and the [theseus patcher](../patcher) (rust binary used tp patch the apk).
This package embed the patcher binary for ease of use. The embedded version is build for linux x86_64, statically linked to musl. For other target platform (windows, arm, ect), a different patcher binary can provided at runtime.
## Build
TODO: use nix build the project
Before building this package, the patcher binary must be built with the musl target. This require the `x86_64-unknown-linux-musl` to be installed, as well as `musl-gcc`:
```
rustup target add x86_64-unknown-linux-musl
doas pacman -S musl
```
Build the patcher:
```
cd ../patcher
cargo build --release --target=x86_64-unknown-linux-musl
cd -
```
Copy to patcher to the python directory:
```
cp ../patcher/target/x86_64-unknown-linux-musl/release/patcher src/theseus_autopatcher/patcher_86_64_musl
```
Build the package:
```
poetry build
```

View file

@ -13,6 +13,9 @@ dependencies = [
[tool.poetry]
packages = [{include = "theseus_autopatcher", from = "src"}]
include = [
{ path = "src/theseus_autopatcher/patcher_86_64_musl", format = ["sdist", "wheel"] },
]
[build-system]

View file

@ -1,2 +1,226 @@
import os
import argparse
import subprocess
import tempfile
from pathlib import Path
from shutil import which
from theseus_frida import collect_runtime
def get_android_sdk_path() -> Path | None:
if "ANDROID_HOME" in os.environ:
return os.environ["ANDROID_HOME"]
default = Path.home() / "Android" / "Sdk"
if default.exists():
return default
return None
def get_build_tools_path(toolname: str) -> Path | None:
def score_version(name: str):
score = []
for n in name.split("."):
if n.isdecimal():
score.append(int(n))
else:
score.append(-1)
return score
path = which(toolname)
if path is not None:
return path
path = which(toolname + ".exe")
if path is not None:
return path
sdk = get_android_sdk_path()
if sdk is None:
return None
tools = sdk / "build-tools"
if not tools.exists():
return None
options = []
for d in tools.iterdir():
if (d / toolname).exists():
options.append(d / toolname)
if (d / (toolname + ".exe")).exists():
options.append(d / (toolname + ".exe"))
if not options:
return None
return max(options, key=lambda d: score_version(d.parent.name))
def get_keytool_path() -> Path | None:
path = which("keytool")
if path is not None:
return path
return which("keytool.exe")
def gen_keystore(keytool: Path, storepath: Path):
print(f"{str(storepath)} does not exist, creating it.")
subprocess.run(
[
str(keytool),
"-genkeypair",
"-validity",
"1000",
"-dname",
"CN=SomeKey,O=SomeOne,C=FR",
"-keystore",
str(storepath),
"-alias",
"SignKey",
"-keyalg",
"RSA",
"-v",
]
)
PATCHER_BIN_PATH = Path(__file__).parent / "patcher_86_64_musl"
def patch_apk(
runtime_data: Path,
apk: Path,
apkout: Path,
zipalign: Path,
apksigner: Path,
keystore: Path,
):
def dbg(l):
print(" ".join(l))
return l
subprocess.run(
dbg(
[
str(PATCHER_BIN_PATH.absolute()),
"--runtime-data",
str(runtime_data.absolute()),
"--path",
str(apk.absolute()),
"--out",
str(apkout.absolute()),
"-k",
str(keystore.absolute()),
"-z",
str(zipalign.absolute()),
"-a",
str(apksigner.absolute()),
]
)
)
def main():
print("hello void")
parser = argparse.ArgumentParser(prog="Android Theseus project")
parser.add_argument(
"-a", "--apk", required=True, help="Target application", type=Path
)
parser.add_argument(
"-s",
"--device",
default="",
help="The android device to connect to, eg: 'emulator-5554'",
type=str,
)
parser.add_argument(
"-o",
"--output-apk",
required=True,
help="Where to write the repackaged apk",
type=Path,
)
parser.add_argument(
"--zipalign",
required=False,
help="Path to the zipalign executable to use",
type=Path,
)
parser.add_argument(
"--apksigner",
required=False,
help="Path to the apksigner executable to use",
type=Path,
)
parser.add_argument(
"-k",
"--keystore",
required=False,
help="Path to the apksigner executable to use",
type=Path,
default=Path(".") / "TheseusKey.keystore",
)
parser.add_argument(
"--keytool",
required=False,
help="Path to the keytool executable to use",
type=Path,
)
parser.add_argument(
"--patch",
required=False,
help="Path to the patcher executable to use. By default, use the one embeded with \
the package. (static x86_64 linux build with musl)",
type=Path,
)
args = parser.parse_args()
if args.zipalign is None:
zipalign = get_build_tools_path("zipalign")
else:
zipalign = args.zipalign
if args.apksigner is None:
apksigner = get_build_tools_path("apksigner")
else:
apksigner = args.apksigner
if args.keytool is None:
keytool = get_keytool_path()
else:
keytool = args.keytool
if zipalign is None:
print(
"Could not find zipalign, please install an android build-tools package. "
"If one is already installed, please use `--zipalign` to provide the path "
"to the zipalign executable."
)
exit(1)
if apksigner is None:
print(
"Could not find apksigner, please install an android build-tools package. "
"If one is already installed, please use `--apksigner` to provide the path "
"to the apksigner executable."
)
exit(1)
if keytool is None and not args.keystore.exists():
print(
f"Could not find keytool and {str(args.keystore)} does not exist. Either "
"provide an existing keystore with -k or install a JDK. If one is already installed, "
"please use --keytool to provide the path to the keytool executable."
)
exit(1)
if not args.keystore.exists():
gen_keystore(keytool, args.keystore)
tmpdname = "/tmp/tmp.xzq9jLxUbQ/tmp"
if True:
# with tempfile.TemporaryDirectory() as tmpdname:
tmpd = Path(tmpdname)
(tmpd / "dex").mkdir()
with (tmpd / "runtime.json").open("w") as fp:
collect_runtime(
apk=args.apk, device=args.device, file_storage=tmpd / "dex", output=fp
)
patch_apk(
runtime_data=tmpd / "runtime.json",
apk=args.apk,
apkout=args.output_apk,
zipalign=zipalign,
apksigner=apksigner,
keystore=args.keystore,
)