開啟adb root
直接看adb源碼:
- __android_log_is_debuggable就是判斷ro.debuggable屬性值,感興趣可以在 源碼下grep下實現看看。
- auth_required :在adb源碼下定義的全局變量,默認等于true,。看名字就是是否需要用戶授權的flag, 這里不再繼續跟代碼,只要知道這個值在ro.debuggable=1時,會等于ro.adb.secure這個屬性即可。我們不想開啟adb時彈出用戶授權,因此要將這個屬性改為0.
// /packages/modules/adb/daemon/main.cpp
int adbd_main(int server_port) {...#if defined(__ANDROID__)// If we're on userdebug/eng or the device is unlocked, permit no-authentication.bool device_unlocked = "orange" == android::base::GetProperty("ro.boot.verifiedbootstate", "");if (__android_log_is_debuggable() || device_unlocked) {auth_required = android::base::GetBoolProperty("ro.adb.secure", false);}
#endif...
}
當在PC端執行adb root時,android系統會走到下面代碼,可以看到如果__android_log_is_debuggable不為true的話,就return了。
函數最后設置了service.adb.root=1,實際測試了下,adb root后再adb shell,
這個屬性=1,直接adb shell進去,這個屬性=0
// /packages/modules/adb/daemon/restart_service.cppvoid restart_root_service(unique_fd fd) {if (getuid() == 0) {WriteFdExactly(fd.get(), "adbd is already running as root\n");return;}if (!__android_log_is_debuggable()) {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");WriteFdExactly(fd.get(), "restarting adbd as root\n");
}
service.adb.root使用的地方:
PC端執行adb shell后,會走到下面代碼,在main函數里,會判斷should_drop_privileges,然后通過linux capability機制
給進程降低權限,感興趣可以搜一下linux capability機制。這里推測就是是否從root降低權限到shell用戶。
// packages/modules/adb/daemon/main.cpp
int adbd_main(int server_port) {
...
#if defined(__ANDROID__)drop_privileges(server_port);
#endif
...
}
static void drop_privileges(int server_port) {
...if (should_drop_privileges()) {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);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);...
}
關鍵在should_drop_privileges:
ro.secure在user,userdebug版本=1,也就是說adb shell進來 默認是降權的,非root的。
如果**__android_log_is_debuggable && service.adb.root 為true**,就不會降權為非root. 前面說了PC端輸入adb root后, service.adb.root 這個值就會等于1,
綜合上面分析:只要把ro.debuggable設置為1,就可以在user版本下實現adb root
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;}
具體的修改策略:
在編譯版本時,定義一個全局變量,可以把它定義在編譯腳本中,
export USE_ROOT_ADB =1
USE_ROOT_ADB 代表在user版本中啟用adb root
# /build/make/core/main.mk
## user/userdebug ##
# +++ 這里加一個編譯過程中對USE_ROOT_ADB的值的打印。
$(warning USE_ROOT_ADB $(USE_ROOT_ADB))
# user_variant:不是user,就是userdebug
user_variant := $(filter user userdebug,$(TARGET_BUILD_VARIANT))
enable_target_debugging := true
tags_to_install :=
ifneq (,$(user_variant))# Target is secure in user builds.ADDITIONAL_SYSTEM_PROPERTIES += ro.secure=1ADDITIONAL_SYSTEM_PROPERTIES += security.perf_harden=1ifeq ($(user_variant),user)# +++ 如果是user版本,并且USE_ROOT_ADB=1,將ro.adb.secure設置為0ifeq ($(USE_ROOT_ADB),true)ADDITIONAL_SYSTEM_PROPERTIES += ro.adb.secure=0elseADDITIONAL_SYSTEM_PROPERTIES += ro.adb.secure=1endifendififeq ($(user_variant),userdebug)# Pick up some extra useful toolstags_to_install += debugelse# +++ 如果是user版本,并且USE_ROOT_ADB=1,將tags_to_install 追加debug字段,# +++ 還有要注意,源碼未修改前,如果是user版本,enable_target_debugging會被置為空# +++ 這里改為:如果是user版本,enable_target_debugging依然是true.# @@@ 后面繼續分析tags_to_install 和 enable_target_debuggingifeq ($(USE_ROOT_ADB),true)# Pick up some extra useful toolstags_to_install += debugelse# Disable debugging in plain user builds.enable_target_debugging :=endifendif # Disallow mock locations by default for user buildsADDITIONAL_SYSTEM_PROPERTIES += ro.allow.mock.location=0else # !user_variant# Turn on checkjni for non-user builds.ADDITIONAL_SYSTEM_PROPERTIES += ro.kernel.android.checkjni=1# Set device insecure for non-user builds.ADDITIONAL_SYSTEM_PROPERTIES += ro.secure=0# Allow mock locations by default for non user buildsADDITIONAL_SYSTEM_PROPERTIES += ro.allow.mock.location=1
endif # !user_variant
# @@@ 如果enable_target_debugging為true,ro.debuggable=1
# @@@ 達到了我們想要的效果
ifeq (true,$(strip $(enable_target_debugging)))# Target is more debuggable and adbd is on by defaultADDITIONAL_SYSTEM_PROPERTIES += ro.debuggable=1# Enable Dalvik lock contention logging.ADDITIONAL_SYSTEM_PROPERTIES += dalvik.vm.lockprof.threshold=500
else # !enable_target_debugging# Target is less debuggable and adbd is off by defaultADDITIONAL_SYSTEM_PROPERTIES += ro.debuggable=0
endif # !enable_target_debugging
adb remount 和 su
這兩個放到一起說,上一節說到將tags_to_install 追加debug字段。
還是在/build/make/core/main.mk中,tags_to_install=true,編譯時會帶上PRODUCT_PACKAGES_DEBUG
# $(1): product makefile
define product-installed-files$(eval _pif_modules := \$(call get-product-var,$(1),PRODUCT_PACKAGES) \$(if $(filter eng,$(tags_to_install)),$(call get-product-var,$(1),PRODUCT_PACKAGES_ENG)) \# @@@ 當tags_to_install有debug字段,編譯時會帶上PRODUCT_PACKAGES_DEBUG$(if $(filter debug,$(tags_to_install)),$(call get-product-var,$(1),PRODUCT_PACKAGES_DEBUG)) \$(if $(filter tests,$(tags_to_install)),$(call get-product-var,$(1),PRODUCT_PACKAGES_TESTS)) \$(if $(filter asan,$(tags_to_install)),$(call get-product-var,$(1),PRODUCT_PACKAGES_DEBUG_ASAN)) \$(if $(filter java_coverage,$(tags_to_install)),$(call get-product-var,$(1),PRODUCT_PACKAGES_DEBUG_JAVA_COVERAGE)) \$(call auto-included-modules) \) \
PRODUCT_PACKAGES_DEBUG定義在/build/make/target/product/base_system.mk, 當然也可以在自己的mk中對PRODUCT_PACKAGES_DEBUG進行追加。su 就在PRODUCT_PACKAGES_DEBUG中,如果再加上remount, 就實現了user版本下使用su和adb remount
383 # Packages included only for eng or userdebug builds, previously debug tagged
384 PRODUCT_PACKAGES_DEBUG := \
385 adb_keys \
386 arping \
387 dmuserd \
388 idlcli \
389 init-debug.rc \
390 iotop \
391 iperf3 \
392 iw \
393 layertracegenerator \
394 libclang_rt.ubsan_standalone \
395 logpersist.start \
396 logtagd.rc \
397 procrank \
398 profcollectd \
399 profcollectctl \
400 record_binder \
401 servicedispatcher \
402 showmap \
403 sqlite3 \
404 ss \
405 start_with_lockagent \
406 strace \# @@@ 已經有su
407 su \
408 sanitizer-status \
409 tracepath \
410 tracepath6 \
411 traceroute6 \
412 unwind_info \
413 unwind_reg_info \
414 unwind_symbols \# +++ 加上remountremount \
另外這里還涉及到selinux,
在system/sepolicy/private下,可以看到su.te中對su的定義:
permissive su;
su被定義為permissive domain,這是什么?
參考redhat上的定義:
Permissive Domains
When SELinux is running in permissive mode, SELinux does not deny access, but denials are logged for actions that would have been denied if running in enforcing mode. Previously, it was not possible to make a single domain permissive (remember: processes run in domains). In certain situations, this led to making the whole system permissive to troubleshoot issues.
Permissive domains allow an administrator to configure a single process (domain) to run permissive, rather than making the whole system permissive. SELinux checks are still performed for permissive domains; however, the kernel allows access and reports an AVC denial for situations where SELinux would have denied access.
Permissive domains have the following uses:
They can be used for making a single process (domain) run permissive to troubleshoot an issue without putting the entire system at risk by making it permissive.
They allow an administrator to create policies for new applications. Previously, it was recommended that a minimal policy be created, and then the entire machine put into permissive mode, so that the application could run, but SELinux denials still logged. audit2allow could then be used to help write the policy. This put the whole system at risk. With permissive domains, only the domain in the new policy can be marked permissive, without putting the whole system at risk.
理解就是可以在強制模式(enforcing mode)下,定義一個寬容模式的domain,跑在Permissive domain下的進程,只會打印avc log,而不會限制它的行為。su這么牛逼的進程,當然是Permissive domain。
但是在user版本下,是不允許定義Permissive domain的。 為此需要修改如下代碼:
# /system/sepolicy/Android.mk$(LOCAL_BUILT_MODULE): $(sepolicy.recovery.conf) $(HOST_OUT_EXECUTABLES)/checkpolicy \$(HOST_OUT_EXECUTABLES)/sepolicy-analyze@mkdir -p $(dir $@)$(hide) $(CHECKPOLICY_ASAN_OPTIONS) $(HOST_OUT_EXECUTABLES)/checkpolicy -M -c \$(POLICYVERS) -o $@.tmp $<$(hide) $(HOST_OUT_EXECUTABLES)/sepolicy-analyze $@.tmp permissive > $@.permissivedomains# +++ 下面的if表示如果是user版本,并且permissivedomains存在的話,就會輸出如下錯誤,并退出。因此需要用USE_ROOT_ADB宏來排除這部分處理。注意:該文件下有2處檢查permissivedomains 的地方,需要用同樣的方式注釋掉。
ifndef USE_ROOT_ADB$(hide) if [ "$(TARGET_BUILD_VARIANT)" = "user" -a -s $@.permissivedomains ]; then \echo "==========" 1>&2; \echo "ERROR: permissive domains not allowed in user builds" 1>&2; \echo "List of invalid domains:" 1>&2; \cat $@.permissivedomains 1>&2; \exit 1; \fi
endif$(hide) mv $@.tmp $@
關閉selinux
臨時關閉selinux,只要設置setenforce 0即可,但重啟后就會恢復為強制模式。
這里討論永久關閉。
如果不想深究,直接修改IsEnforcing(),讓它永久返回false, 這樣走到security_setenforce,永久關閉selinux。
// /system/core/init/selinux.cppvoid SelinuxSetEnforcement() {bool kernel_enforcing = (security_getenforce() == 1);bool is_enforcing = IsEnforcing();if (kernel_enforcing != is_enforcing) {if (security_setenforce(is_enforcing)) {PLOG(FATAL) << "security_setenforce(" << (is_enforcing ? "true" : "false")<< ") failed";}}if (auto result = WriteFile("/sys/fs/selinux/checkreqprot", "0"); !result.ok()) {LOG(FATAL) << "Unable to write to /sys/fs/selinux/checkreqprot: " << result.error();}
}
bool IsEnforcing() {// MODIFY START 永久返回falsereturn false;// MODIFY ENDif (ALLOW_PERMISSIVE_SELINUX) {return StatusFromProperty() == SELINUX_ENFORCING;}return true;
}
如果想繼續看下是怎么實現的,我們繼續分析,先看security_getenforce函數,這里不貼代碼實現了,源碼在external/selinux下,主要就是讀取/sys/fs/selinux/enforce 的值。可以是1 or 0
/sys/fs/selinux/enforce中的值,又是根據內核的cmdline中的androidboot.selinux來的。可以在自己的mk中修改這個值:
BOARD_KERNEL_CMDLINE += androidboot.selinux=enforcing or permissive
再分析IsEnforcing函數,如果ALLOW_PERMISSIVE_SELINUX為false時,直接返回true。ALLOW_PERMISSIVE_SELINUX在mk中可以看到,如果是user版本,該值就是false, 這里可以看出,如果編譯user版本,一定會開啟selinux,即使上面配置內核啟動參數cmdline中的androidboot.selinux=permissive,也無濟于事。
#/system/core/init/Android.mk# userdebug,eng版本走if,user走else
ifneq (,$(filter userdebug eng,$(TARGET_BUILD_VARIANT)))init_options += \-DALLOW_FIRST_STAGE_CONSOLE=1 \-DALLOW_LOCAL_PROP_OVERRIDE=1 \-DALLOW_PERMISSIVE_SELINUX=1 \-DREBOOT_BOOTLOADER_ON_PANIC=1 \-DWORLD_WRITABLE_KMSG=1 \-DDUMP_ON_UMOUNT_FAILURE=1elseinit_options += \-DALLOW_FIRST_STAGE_CONSOLE=0 \-DALLOW_LOCAL_PROP_OVERRIDE=0 \-DALLOW_PERMISSIVE_SELINUX=0 \-DREBOOT_BOOTLOADER_ON_PANIC=0 \-DWORLD_WRITABLE_KMSG=0 \-DDUMP_ON_UMOUNT_FAILURE=0endif
試想,如果我們修改上面的mk,讓ALLOW_PERMISSIVE_SELINUX在user版本下也等于1,會不會也可以實現關閉selinux。繼續分析:
如果ALLOW_PERMISSIVE_SELINUX=1,IsEnforcing函數返回值就依賴
StatusFromProperty() == SELINUX_ENFORCING;
在android12之前,StatusFromProperty函數只讀取cmdline的androidboot.selinux,
那么在android12之前,可以通過修改:
ALLOW_PERMISSIVE_SELINUX=1 并且 BOARD_KERNEL_CMDLINE += androidboot.selinux=permissive
實現user版本永久關閉selinux。
從android12開始,StatusFromProperty函數還會判斷Bootconfig的androidboot.selinux值。內核cmdline和Bootconfig的androidboot.selinux值都等于enforcing時,StatusFromProperty會返回enforcing。
那么從android12開始,可以通過修改:
ALLOW_PERMISSIVE_SELINUX=1 并且 ( BOARD_KERNEL_CMDLINE += androidboot.selinux=permissive 或者 BOARD_BOOTCONFIG += androidboot..selinux=permissive)
實現user版本永久關閉selinux。
EnforcingStatus StatusFromProperty() {EnforcingStatus status = SELINUX_ENFORCING;ImportKernelCmdline([&](const std::string& key, const std::string& value) {if (key == "androidboot.selinux" && value == "permissive") {status = SELINUX_PERMISSIVE;}});if (status == SELINUX_ENFORCING) {ImportBootconfig([&](const std::string& key, const std::string& value) {if (key == "androidboot.selinux" && value == "permissive") {status = SELINUX_PERMISSIVE;}});}return status;}