Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Switch to JSON config and add support for changing update ownership #5

Merged
merged 1 commit into from
Aug 4, 2024
Merged
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
21 changes: 14 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
[![latest release badge](https://img.shields.io/github/v/release/chenxiaolong/AlterInstaller?sort=semver)](https://github.com/chenxiaolong/AlterInstaller/releases/latest)
[![license badge](https://img.shields.io/github/license/chenxiaolong/AlterInstaller)](./LICENSE)

AlterInstaller is a simple Magisk/KernelSU module that changes apps' installer and initiating installer fields in the Android package manager database. This makes it possible to spoof where an app is installed from.
AlterInstaller is a simple Magisk/KernelSU module that changes apps' installer, initiating installer, and update owner fields in the Android package manager database. This makes it possible to spoof where an app is installed from and control which app store is allowed to update it.

The module directly modifies `/data/system/packages.xml` before the package manager service starts and thus, does not require runtime code injection (eg. Zygisk). Because this state file is modified, the changes persist even if the module is uninstalled.

Expand All @@ -15,12 +15,19 @@ The module directly modifies `/data/system/packages.xml` before the package mana

2. Install the module from the Magisk/KernelSU app.

3. Create `/data/local/tmp/AlterInstaller.properties` listing the package IDs to modify:

```properties
# Syntax: <Package> = <Installer>
# For example, to mark VLC as being installed by the Play Store:
org.videolan.vlc = com.android.vending
3. Create `/data/local/tmp/AlterInstaller.json` listing the package IDs to modify:

```jsonc
{
// The top level key is the package to modify. The values below can be
// omitted or set to null to leave the fields unchanged.
"org.videolan.vlc": {
// Mark VLC as being installed by the Play Store.
"installer": "com.android.vending",
// Mark VLC as being only updatable by Droid-ify.
"updateOwner": "com.looker.droidify"
}
}
```

4. Reboot. The log file is written to `/data/local/tmp/AlterInstaller.log`.
Expand Down
33 changes: 24 additions & 9 deletions app/module/post-fs-data.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,16 @@ header() {
echo "----- ${*} -----"
}

run_cli_apk() {
CLASSPATH=$(echo "${mod_dir}"/app-*.apk) \
app_process / @[email protected] "${@}" &
pid=${!}
wait "${pid}"
echo "Exit status: ${?}"
echo "Logcat:"
logcat -d --pid "${pid}"
}

header Environment
echo "Timestamp: $(date)"
echo "Script: ${0}"
Expand All @@ -16,14 +26,19 @@ cp -v /data/system/packages.xml \
/data/local/tmp/AlterInstaller.backup.xml \
|| exit 1

if [ -f /data/local/tmp/AlterInstaller.properties ] \
&& [ ! -f /data/local/tmp/AlterInstaller.json ]; then
header Migrating legacy properties config to JSON
run_cli_apk \
migrate-config \
/data/local/tmp/AlterInstaller.properties \
/data/local/tmp/AlterInstaller.json \
|| exit 1
fi

header Altering PackageManager installer fields
CLASSPATH=$(echo "${mod_dir}/"app-*.apk) app_process / @[email protected] \
/data/local/tmp/AlterInstaller.properties \
/data/system/packages.xml \
run_cli_apk \
apply \
/data/local/tmp/AlterInstaller.json \
/data/system/packages.xml \
&
pid=${!}
wait "${pid}"
echo "Exit status: ${?}"
echo "Logcat:"
logcat -d --pid "${pid}"
/data/system/packages.xml
Original file line number Diff line number Diff line change
Expand Up @@ -8,31 +8,64 @@
import java.io.OutputStream;
import java.io.Writer;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

public class AlterInstallerSerializer implements XmlSerializer {
public interface OnChangeListener {
void onFieldChanged(String packageName, String field, String oldValue, String newValue);
}

private static final Set<String> UPDATABLE_ATTRIBUTES;

static {
HashSet<String> attrs = new HashSet<>();
attrs.add("installer");
attrs.add("installInitiator");
attrs.add("updateOwner");
UPDATABLE_ATTRIBUTES = attrs;
}

private final XmlSerializer inner;
private final Map<String, String> packageToInstaller;
private final Map<String, PackageConfig> packageToConfig;
private final OnChangeListener listener;

private final ArrayList<String> tags = new ArrayList<>();
private String packageName = null;
private PackageConfig packageConfig = null;
private final HashSet<String> seenAttributes = new HashSet<>();

public AlterInstallerSerializer(XmlSerializer inner, Map<String, String> packageToInstaller,
public AlterInstallerSerializer(XmlSerializer inner, Map<String, PackageConfig> packageToConfig,
OnChangeListener listener) {
this.inner = inner;
this.packageToInstaller = packageToInstaller;
this.packageToConfig = packageToConfig;
this.listener = listener;
}

private boolean isPackageElement() {
return tags.size() == 2 && "packages".equals(tags.get(0)) && "package".equals(tags.get(1));
}

private String getPackageAttribute(String attribute) {
return switch (attribute) {
case "installer", "installInitiator" -> packageConfig.installer();
case "updateOwner" -> packageConfig.updateOwner();
default -> null;
};
}

private void writeMissingAttributes(String namespace) throws IOException {
if (packageConfig != null && isPackageElement()) {
for (String attribute : UPDATABLE_ATTRIBUTES) {
if (!seenAttributes.contains(attribute)) {
XmlSerializer result = attribute(namespace, attribute, null);
assert result == this;
}
}
}
}

@Override
public void setFeature(String name, boolean state) throws IllegalArgumentException, IllegalStateException {
inner.setFeature(name, state);
Expand Down Expand Up @@ -100,6 +133,8 @@ public String getName() {

@Override
public XmlSerializer startTag(String namespace, String name) throws IOException, IllegalArgumentException, IllegalStateException {
writeMissingAttributes(namespace);

XmlSerializer result = inner.startTag(namespace, name);
assert result == inner;

Expand All @@ -112,12 +147,15 @@ public XmlSerializer startTag(String namespace, String name) throws IOException,
public XmlSerializer attribute(String namespace, String name, String value) throws IOException, IllegalArgumentException, IllegalStateException {
if (isPackageElement()) {
if ("name".equals(name)) {
// PackageManager always serializes the name first, so we just rely on the ordering
// when updating the other fields.
packageName = value;
} else if ("installer".equals(name) || "installInitiator".equals(name)) {
// PackageManager always serializes the name first, so we just rely on the ordering.
if (packageToInstaller.containsKey(packageName)) {
String newValue = packageToInstaller.get(packageName);
packageConfig = packageToConfig.get(value);
} else if (packageConfig != null && UPDATABLE_ATTRIBUTES.contains(name)) {
seenAttributes.add(name);

String newValue = getPackageAttribute(name);
if (newValue != null) {
if (listener != null) {
listener.onFieldChanged(packageName, name, value, newValue);
}
Expand All @@ -127,14 +165,24 @@ public XmlSerializer attribute(String namespace, String name, String value) thro
}
}

XmlSerializer result = inner.attribute(namespace, name, value);
assert result == inner;
if (value != null) {
XmlSerializer result = inner.attribute(namespace, name, value);
assert result == inner;
}

return this;
}

@Override
public XmlSerializer endTag(String namespace, String name) throws IOException, IllegalArgumentException, IllegalStateException {
writeMissingAttributes(namespace);

if (isPackageElement()) {
packageName = null;
packageConfig = null;
seenAttributes.clear();
}

XmlSerializer result = inner.endTag(namespace, name);
assert result == inner;

Expand All @@ -147,10 +195,6 @@ public XmlSerializer endTag(String namespace, String name) throws IOException, I
throw new IllegalStateException("Expected to pop " + name + ", but have " + prev);
}

if (isPackageElement()) {
packageName = null;
}

return this;
}

Expand Down
Loading