Add adb root function in root (Magisk) environment

I have root permissions, but when executing adb root, an error message "adbd cannot run as root in production builds" is reported. The solution is as follows:

---------------------------------------------------------

For Android phones currently on the market, there are two main ways to obtain root permissions:

  1. Through official channels, such as Xiaomi's development version, official root capabilities will be provided, but it is relatively troublesome. First you need to unlock it, then you need to apply for developer permissions, and finally perform root upgrade on your phone;
  2. Obtain it by unlocking the BL (bootloader) lock of the phone and then using the magisk function.

Of course, in addition to the above two methods, root can also be obtained through other methods such as vulnerabilities.

Magisk obtains root permissions by placing su, and the adb root function is not available. This article mainly talks about adding the adb root function in the magisk environment.

adb root process

The architecture of adb is as shown below (extracted from adb source code). We mainly focus on the processing logic of the device side (i.e. adbd). adb root means restarting adbd and letting adbd run as the root user, so that the commands passed in by our adb have root permissions. .

+----------+              +------------------------+
|   ADB    +----------+   |      ADB SERVER        |                   +----------+
|  CLIENT  |          |   |                        |              (USB)|   ADBD   |
+----------+          |   |                     Transport+-------------+ (DEVICE) |
                      |   |                        |                   +----------+
+-----------          |   |                        |
|   ADB    |          v   +                        |                   +----------+
|  CLIENT  +--------->SmartSocket                  |              (USB)|   ADBD   |
+----------+          ^   | (TCP/IP)            Transport+-------------+ (DEVICE) |
                      |   |                        |                   +----------+
+----------+          |   |                        |
|  DDMLIB  |          |   |                     Transport+--+          +----------+
|  CLIENT  +----------+   |                        |        |  (TCP/IP)|   ADBD   |
+----------+              +------------------------+        +----------|(EMULATOR)|
                                                                       +----------+

adb root processing logic

adbd command processing code
unique_fd daemon_service_to_fd(std::string_view name, atransport* transport) {
    ADB_LOG(Service) << "transport " << transport->serial_name() << " opening service " << name;
... ...

    } else if (android::base::ConsumePrefix(&name, "reboot:")) {
        return reboot_device(std::string(name));
    } else if (name.starts_with("root:")) { // 重点
        return create_service_thread("root", restart_root_service);  // 重点
    } else if (name.starts_with("unroot:")) {
        return create_service_thread("unroot", restart_unroot_service);
... ...

You can see that after adbd receives the root command, it uses create_service_thread to start the thread to execute the restart_root_service logic.

restart_root_service
void restart_root_service(unique_fd fd) {
    if (getuid() == 0) { // 判断 1
        WriteFdExactly(fd.get(), "adbd is already running as root\n");
        return;
    }
    if (!__android_log_is_debuggable()) { // 判断 2
        WriteFdExactly(fd.get(), "adbd cannot run as root in production builds\n");
        return;
    }

    LOG(INFO) << "adbd restarting as root";
    android::base::SetProperty("service.adb.root", "1"); // 3
    WriteFdExactly(fd.get(), "restarting adbd as root\n");
}
  1. First, it is judged that uid == 0, that is, it is already the root user, and returns;
  2. Use __android_log_is_debuggable to determine permissions and whether to run and enable root;
  3. If permissions are allowed, set the system property service.adb.root to 1.
__android_log_is_debuggable
int __android_log_is_debuggable() {
  static int is_debuggable = [] { // 静态变量
    char value[PROP_VALUE_MAX] = {};
    return __system_property_get("ro.debuggable", value) > 0 && !strcmp(value, "1");
  }();

  return is_debuggable;
}

It can be seen that this function returns 1 if ro.debuggable = 1, and is_debuggable is a static variable, that is, it will only be initialized and assigned once. If ro.debuggable is modified, the return value of the function will not be changed, and adbd needs to be restarted.

How does adb root take effect?

The logic of adb root is just to set the attribute service.adb.root. How to restart adbd as the root user?

adbd restart

Assumption: Register the monitoring logic of the service.adb.root attribute in the init.rc script, such as configuring the following command:


on property: service.adb.adb=1
    重启 adbd 为 root 用户

But after searching the code, I couldn't find it, so there are other ways.

Searching the code found:


asocket* create_local_service_socket(std::string_view name, atransport* transport) {
#if !ADB_HOST
    if (asocket* s = daemon_service_to_socket(name); s) {
        return s;
    }
#endif
    unique_fd fd = service_to_fd(name, transport);
    if (fd < 0) {
        return nullptr;
    }

    int fd_value = fd.get();
    asocket* s = create_local_socket(std::move(fd));
    LOG(VERBOSE) << "LS(" << s->id << "): bound to '" << name << "' via " << fd_value;

#if !ADB_HOST
    if ((name.starts_with("root:") && getuid() != 0 && __android_log_is_debuggable()) ||
        (name.starts_with("unroot:") && getuid() == 0) || name.starts_with("usb:") ||
        name.starts_with("tcpip:")) {
        D("LS(%d): enabling exit_on_close", s->id);
        s->exit_on_close = 1; // 设置 exit_on_close 值
    }
#endif

    return s;
}

__android_log_is_debuggable is also called here for judgment. If the conditions are met, exit_on_close is set to 1.

// be sure to hold the socket list lock when calling this
static void local_socket_destroy(asocket* s) {
    int exit_on_close = s->exit_on_close;

    D("LS(%d): destroying fde.fd=%d", s->id, s->fd);

    deferred_close(fdevent_release(s->fde));

    remove_socket(s);
    delete s;

    if (exit_on_close) {
        D("local_socket_destroy: exiting");
        exit(1); // 退出
    }
}

In local_socket_destroy, if exit_on_close is 1, call exit to exit, and adbd will restart.

adbd start

The startup configuration of adbd is in the adbd.rc file, as follows:

service adbd /apex/com.android.adbd/bin/adbd --root_seclabel=u:r:su:s0
    class core
    socket adbd seqpacket 660 system system
    disabled
    override
    seclabel u:r:adbd:s0

As you can see, if user and group are not specified, adbd starts as the root user by default.

int adbd_main(int server_port) {
    ... ...

#if defined(__ANDROID__)
    drop_privileges(server_port);
#endif

    ... ...

After startup, drop_privileges will be called to perform the privilege reduction operation.

static void drop_privileges(int server_port) {
    ScopedMinijail jail(minijail_new());

    gid_t groups[] = {AID_ADB,          AID_LOG,          AID_INPUT,    AID_INET,
                      AID_NET_BT,       AID_NET_BT_ADMIN, AID_SDCARD_R, AID_SDCARD_RW,
                      AID_NET_BW_STATS, AID_READPROC,     AID_UHID,     AID_EXT_DATA_RW,
                      AID_EXT_OBB_RW};
    minijail_set_supplementary_gids(jail.get(), arraysize(groups), groups);

    // Don't listen on a port (default 5037) if running in secure mode.
    // Don't run as root if running in secure mode.
    if (should_drop_privileges()) { // 判断是否需要降权 1
        const bool should_drop_caps = !__android_log_is_debuggable();

        if (should_drop_caps) {
            minijail_use_caps(jail.get(), CAP_TO_MASK(CAP_SETUID) | CAP_TO_MASK(CAP_SETGID));
        }

        minijail_change_gid(jail.get(), AID_SHELL); // 降权 2
        minijail_change_uid(jail.get(), AID_SHELL);
        // minijail_enter() will abort if any priv-dropping step fails.
        minijail_enter(jail.get());

        // Whenever ambient capabilities are being used, minijail cannot
        // simultaneously drop the bounding capability set to just
        // CAP_SETUID|CAP_SETGID while clearing the inheritable, effective,
        // and permitted sets. So we need to do that in two steps.
        using ScopedCaps =
            std::unique_ptr<std::remove_pointer<cap_t>::type, std::function<void(cap_t)>>;
        ScopedCaps caps(cap_get_proc(), &cap_free);
        if (cap_clear_flag(caps.get(), CAP_INHERITABLE) == -1) {  // 降权 2
            PLOG(FATAL) << "cap_clear_flag(INHERITABLE) failed";
        }
        if (cap_clear_flag(caps.get(), CAP_EFFECTIVE) == -1) {
            PLOG(FATAL) << "cap_clear_flag(PEMITTED) failed";
        }
        if (cap_clear_flag(caps.get(), CAP_PERMITTED) == -1) {
            PLOG(FATAL) << "cap_clear_flag(PEMITTED) failed";
        }
        if (cap_set_proc(caps.get()) != 0) {
            PLOG(FATAL) << "cap_set_proc() failed";
        }

        D("Local port disabled");
    } else {
        // minijail_enter() will abort if any priv-dropping step fails.
        minijail_enter(jail.get());

        if (root_seclabel != nullptr) {
            if (selinux_android_setcon(root_seclabel) < 0) { // 不降权 3
                LOG(FATAL) << "Could not set SELinux context";
            }
        }
    }
}
  1. Call should_drop_privileges to determine whether privileges need to be dropped;
  2. If the rights are reduced, modify the user and user group to shell, and clear the capabilities permissions;
  3. If the rights are not reduced, set the security contexts to u:r:su:s0, and root_seclabel is specified in adbd.rc.
static bool should_drop_privileges() {
    // The properties that affect `adb root` and `adb unroot` are ro.secure and
    // ro.debuggable. In this context the names don't make the expected behavior
    // particularly obvious.
    //
    // ro.debuggable:
    //   Allowed to become root, but not necessarily the default. Set to 1 on
    //   eng and userdebug builds.
    //
    // ro.secure:
    //   Drop privileges by default. Set to 1 on userdebug and user builds.
    bool ro_secure = android::base::GetBoolProperty("ro.secure", true);
    bool ro_debuggable = __android_log_is_debuggable();

    // Drop privileges if ro.secure is set...
    bool drop = ro_secure; // 初始值

    // ... except "adb root" lets you keep privileges in a debuggable build.
    std::string prop = android::base::GetProperty("service.adb.root", "");
    bool adb_root = (prop == "1");
    bool adb_unroot = (prop == "0");
    if (ro_debuggable && adb_root) {
        drop = false; // 修改逻辑
    }
    // ... and "adb unroot" lets you explicitly drop privileges.
    if (adb_unroot) {
        drop = true;
    }

    return drop;
}

From the above modified logic, we can see that if ro.debuggable == 1 && service.adb.root == 1, drop is false, that is, the authority will not be dropped.

Summarize

The operations required for adb root to take effect are:

  • Modify ro.debuggable to 1
  • Kill adbd and let __android_log_is_debuggable return to 1
  • Call adb root to configure service.adb.root = 1

Magisk tool usage

Property settings

Use the property modification tool resetprop provided by magisk to set ro.debuggable to 1.

Permission handling

After setting the properties, the execution failed. Check that the SELinux permissions are missing:

[ 5252.630174] type=1400 audit(1626010790.323:6145): avc: denied { setcurrent } for comm="adbd" scontext=u:r:adbd:s0 tcontext=u:r:adbd:s0 tclass=process permissive=1
[ 5252.630353] type=1400 audit(1626010790.323:6146): avc: denied { dyntransition } for comm="adbd" scontext=u:r:adbd:s0 tcontext=u:r:su:s0 tclass=process permissive=1

The following permissions are missing:

allow adbd self:process setcurrent;
allow adbd su:process dyntransition;

Looking at the AOSP code, we found the following:

userdebug_or_eng(`
  allow adbd self:process setcurrent;
  allow adbd su:process dyntransition;
')

As you can see, it is only available in the userdebug and eng versions, so normal users cannot use adb root. In order to avoid restrictions after converting adbd to su domain, we configure su to be in permissive state.

Use the magiskpolicy tool in magisk to configure su as permissive, that is:

magiskpolicy --live 'permissive { su }'

accomplish

#!/system/bin/sh
su -c "resetprop ro.debuggable 1"
su -c "resetprop service.adb.root 1" # 减少调用 adb root
su -c "magiskpolicy --live 'allow adbd adbd process setcurrent'" # 配置缺少的权限
su -c "magiskpolicy --live 'allow adbd su process dyntransition'" # 配置缺少的权限
su -c "magiskpolicy --live 'permissive { su }'" # 将 su 配置为 permissive,防止后续命令执行缺少权限
su -c "kill -9 `ps -A | grep adbd | awk '{print $2}' `" # 杀掉 adbd

Execute the above script, and then execute adb shell. At this time, you already have root permissions.

Summarize

  1. Normally, adbd is started by the root user, and then the rights are downgraded to the shell user. We modify the attributes to prevent the rights from being downgraded;
  2. Also, the adb root operation is normally used in the userdebug and eng versions, and these versions are not available externally. There is only the user version, and the user version has more restrictions on SELinux permissions, so permission issues need to be dealt with.

おすすめ

転載: blog.csdn.net/ab6326795/article/details/135091164