mirror of
https://github.com/topjohnwu/Magisk.git
synced 2025-02-28 19:27:47 +00:00

Introduce a new communication method between Magisk and Magisk Manager. Magisk used to hardcode classnames and send broadcast/start activities to specific components. This new method makes no assumption of any class names, so Magisk Manager can easily be fully obfuscated. In addition, the new method connects Magisk and Magisk Manager with random abstract Linux sockets instead of socket files in filesystems, bypassing file system complexities (selinux, permissions and such)
259 lines
8.1 KiB
Java
259 lines
8.1 KiB
Java
package com.topjohnwu.magisk;
|
|
|
|
import android.content.Intent;
|
|
import android.content.pm.PackageManager;
|
|
import android.hardware.fingerprint.FingerprintManager;
|
|
import android.net.LocalSocketAddress;
|
|
import android.os.Bundle;
|
|
import android.os.CountDownTimer;
|
|
import android.os.FileObserver;
|
|
import android.text.TextUtils;
|
|
import android.view.View;
|
|
import android.view.Window;
|
|
import android.widget.ArrayAdapter;
|
|
import android.widget.Button;
|
|
import android.widget.ImageView;
|
|
import android.widget.LinearLayout;
|
|
import android.widget.Spinner;
|
|
import android.widget.TextView;
|
|
|
|
import com.topjohnwu.magisk.components.BaseActivity;
|
|
import com.topjohnwu.magisk.container.Policy;
|
|
import com.topjohnwu.magisk.utils.FingerprintHelper;
|
|
import com.topjohnwu.magisk.utils.SuConnector;
|
|
|
|
import java.io.DataOutputStream;
|
|
import java.io.IOException;
|
|
import java.io.OutputStream;
|
|
|
|
import androidx.annotation.Nullable;
|
|
|
|
public class SuRequestActivity extends BaseActivity {
|
|
LinearLayout suPopup;
|
|
Spinner timeout;
|
|
ImageView appIcon;
|
|
TextView appNameView;
|
|
TextView packageNameView;
|
|
Button grant_btn;
|
|
Button deny_btn;
|
|
ImageView fingerprintImg;
|
|
TextView warning;
|
|
|
|
private SuConnector connector;
|
|
private Policy policy;
|
|
private CountDownTimer timer;
|
|
private FingerprintHelper fingerprintHelper;
|
|
|
|
class SuConnectorV1 extends SuConnector {
|
|
|
|
SuConnectorV1(String name) throws IOException {
|
|
socket.connect(new LocalSocketAddress(name, LocalSocketAddress.Namespace.FILESYSTEM));
|
|
new FileObserver(name) {
|
|
@Override
|
|
public void onEvent(int fileEvent, String path) {
|
|
if (fileEvent == FileObserver.DELETE_SELF) {
|
|
finish();
|
|
}
|
|
}
|
|
}.startWatching();
|
|
}
|
|
|
|
@Override
|
|
public void response() {
|
|
try (OutputStream out = getOutputStream()) {
|
|
out.write((policy.policy == Policy.ALLOW ? "socket:ALLOW" : "socket:DENY").getBytes());
|
|
} catch (IOException e) {
|
|
e.printStackTrace();
|
|
}
|
|
}
|
|
}
|
|
|
|
class SuConnectorV2 extends SuConnector {
|
|
|
|
SuConnectorV2(String name) throws IOException {
|
|
socket.connect(new LocalSocketAddress(name, LocalSocketAddress.Namespace.ABSTRACT));
|
|
}
|
|
|
|
@Override
|
|
public void response() {
|
|
try (DataOutputStream out = getOutputStream()) {
|
|
out.writeInt(policy.policy);
|
|
} catch (IOException e) {
|
|
e.printStackTrace();
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public int getDarkTheme() {
|
|
return R.style.SuRequest_Dark;
|
|
}
|
|
|
|
@Override
|
|
public void finish() {
|
|
if (timer != null)
|
|
timer.cancel();
|
|
if (fingerprintHelper != null)
|
|
fingerprintHelper.cancel();
|
|
super.finish();
|
|
}
|
|
|
|
@Override
|
|
public void onBackPressed() {
|
|
if (policy != null) {
|
|
handleAction(Policy.DENY);
|
|
} else {
|
|
finish();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
|
super.onCreate(savedInstanceState);
|
|
supportRequestWindowFeature(Window.FEATURE_NO_TITLE);
|
|
|
|
PackageManager pm = getPackageManager();
|
|
mm.mDB.clearOutdated();
|
|
|
|
// Get policy
|
|
Intent intent = getIntent();
|
|
try {
|
|
connector = intent.getIntExtra("version", 1) == 1 ?
|
|
new SuConnectorV1(intent.getStringExtra("socket")) :
|
|
new SuConnectorV2(intent.getStringExtra("socket"));
|
|
Bundle bundle = connector.readSocketInput();
|
|
int uid = Integer.parseInt(bundle.getString("uid"));
|
|
policy = mm.mDB.getPolicy(uid);
|
|
if (policy == null) {
|
|
policy = new Policy(uid, pm);
|
|
}
|
|
} catch (IOException | PackageManager.NameNotFoundException e) {
|
|
e.printStackTrace();
|
|
finish();
|
|
return;
|
|
}
|
|
|
|
// Never allow com.topjohnwu.magisk (could be malware)
|
|
if (TextUtils.equals(policy.packageName, Const.ORIG_PKG_NAME)) {
|
|
finish();
|
|
return;
|
|
}
|
|
|
|
switch (Data.suResponseType) {
|
|
case Const.Value.SU_AUTO_DENY:
|
|
handleAction(Policy.DENY, 0);
|
|
return;
|
|
case Const.Value.SU_AUTO_ALLOW:
|
|
handleAction(Policy.ALLOW, 0);
|
|
return;
|
|
case Const.Value.SU_PROMPT:
|
|
default:
|
|
}
|
|
|
|
// If not interactive, response directly
|
|
if (policy.policy != Policy.INTERACTIVE) {
|
|
handleAction();
|
|
return;
|
|
}
|
|
|
|
setContentView(R.layout.activity_request);
|
|
ViewBinder.bind(this);
|
|
|
|
appIcon.setImageDrawable(policy.info.loadIcon(pm));
|
|
appNameView.setText(policy.appName);
|
|
packageNameView.setText(policy.packageName);
|
|
|
|
ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(this,
|
|
R.array.allow_timeout, android.R.layout.simple_spinner_item);
|
|
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
|
timeout.setAdapter(adapter);
|
|
|
|
timer = new CountDownTimer(Data.suRequestTimeout * 1000, 1000) {
|
|
@Override
|
|
public void onTick(long millisUntilFinished) {
|
|
deny_btn.setText(getString(R.string.deny_with_str, "(" + millisUntilFinished / 1000 + ")"));
|
|
}
|
|
@Override
|
|
public void onFinish() {
|
|
deny_btn.setText(getString(R.string.deny_with_str, "(0)"));
|
|
handleAction(Policy.DENY);
|
|
}
|
|
};
|
|
|
|
boolean useFingerprint = Data.suFingerprint && FingerprintHelper.canUseFingerprint();
|
|
|
|
if (useFingerprint) {
|
|
try {
|
|
fingerprintHelper = new FingerprintHelper() {
|
|
@Override
|
|
public void onAuthenticationError(int errorCode, CharSequence errString) {
|
|
warning.setText(errString);
|
|
}
|
|
|
|
@Override
|
|
public void onAuthenticationHelp(int helpCode, CharSequence helpString) {
|
|
warning.setText(helpString);
|
|
}
|
|
|
|
@Override
|
|
public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) {
|
|
handleAction(Policy.ALLOW);
|
|
}
|
|
|
|
@Override
|
|
public void onAuthenticationFailed() {
|
|
warning.setText(R.string.auth_fail);
|
|
}
|
|
};
|
|
fingerprintHelper.authenticate();
|
|
} catch (Exception e) {
|
|
e.printStackTrace();
|
|
useFingerprint = false;
|
|
}
|
|
}
|
|
|
|
if (!useFingerprint) {
|
|
grant_btn.setOnClickListener(v -> {
|
|
handleAction(Policy.ALLOW);
|
|
timer.cancel();
|
|
});
|
|
grant_btn.requestFocus();
|
|
}
|
|
|
|
grant_btn.setVisibility(useFingerprint ? View.GONE : View.VISIBLE);
|
|
fingerprintImg.setVisibility(useFingerprint ? View.VISIBLE : View.GONE);
|
|
|
|
deny_btn.setOnClickListener(v -> {
|
|
handleAction(Policy.DENY);
|
|
timer.cancel();
|
|
});
|
|
suPopup.setOnClickListener(v -> cancelTimeout());
|
|
timeout.setOnTouchListener((v, event) -> cancelTimeout());
|
|
timer.start();
|
|
}
|
|
|
|
private boolean cancelTimeout() {
|
|
timer.cancel();
|
|
deny_btn.setText(getString(R.string.deny));
|
|
return false;
|
|
}
|
|
|
|
private void handleAction() {
|
|
connector.response();
|
|
finish();
|
|
}
|
|
|
|
private void handleAction(int action) {
|
|
handleAction(action, Const.Value.timeoutList[timeout.getSelectedItemPosition()]);
|
|
}
|
|
|
|
private void handleAction(int action, int time) {
|
|
policy.policy = action;
|
|
if (time >= 0) {
|
|
policy.until = (time == 0) ? 0 : (System.currentTimeMillis() / 1000 + time * 60);
|
|
mm.mDB.addPolicy(policy);
|
|
}
|
|
handleAction();
|
|
}
|
|
}
|