其他
Android 11 手动root笔记
关于为什么不用magisk或其他框架,因为感觉magisk的解决方案对我来说功能过于冗余,并且重视安全的应用都会试图通过各种姿势检测magisk或其他框架以及root (在某些群看到群友问某某应用检测root 怎么办) 一想到有可能会遇到有关magisk的兼容性问题,还要去定位解决,很头大 (也是还没用过magisk)。
1
root思路
2
获取原系统镜像文件
使用前需要运行如下命令以安装其依赖:pip install protobuf bsdiff4。
3
获取recovery模式下的root权限
使用前需要安装较高版本的java,我安装的是jdk16,安装后需要手动将jdk中的bin路径添加到系统变量中。此工具使用gradle进行项目部署及运行,需要联网从maven仓库下载一些包,如感觉速度慢则需要高速国际网络或者换源。
buildscript { repositories { mavenCentral() } ...}
buildscript { repositories { maven { setUrl("http://maven.aliyun.com/nexus/content/groups/public/") } mavenCentral() } ...}
编辑build/unzip_boot/vbmeta.avb.json将header.flags改为2来禁用全局verity,否则手机不会接受我们修改后的boot.img。
编辑build/unzip_boot/root/prop.default,将ro.secure改为0,ro.adb.secure改为0,ro.debuggable改为1,该修改仅影响recovery模式下的系统属性。
adb reboot bootloaderfastboot flash --disable-verity --disable-verification vbmeta vbmeta.img.signedfastboot flash boot boot.img.signedfastboot reboot recovery
4
在recovery模式下修改system文件的方式
服务器端:
#include <stdlib.h>#include <fcntl.h>#include <unistd.h>#include <string.h>#include <sys/types.h>#include <sys/socket.h>#include <arpa/inet.h>#include <errno.h> #define BLOCK_SIZE 4096#define LISTEN_PORT 4096 typedef enum { READ, SEEK, WRITE, TELL, OPEN, CLOSE} foperate; typedef struct { foperate op; u_int64_t arg;} fcommand; int recv_all(int sock_fd, u_char* buffer, uint64_t size) { uint64_t recved_size = 0, recv_size; while (recved_size < size) { recv_size = recv(sock_fd, buffer + recved_size, size, MSG_WAITALL); if(recv_size <= 0) { break; } else { recved_size += recv_size; } } return recved_size;} int send_all(int sock_fd, u_char* buffer, uint64_t size) { uint64_t sended_size = 0, send_size; while (sended_size < size) { send_size = send(sock_fd, buffer + sended_size, size, MSG_WAITALL); if(send_size <= 0) { break; } else { sended_size += send_size; } } return sended_size;} int main(int argc, const char**argv) { int super_fd, sock_fd, client_addr_size, conn_fd, shutdown; uint64_t transfered_size, transfer_size; struct sockaddr_in listen_addr, client_addr; u_char* buffer; fcommand command; buffer = (u_char*)malloc(BLOCK_SIZE); super_fd = open("/dev/block/by-name/super", O_RDWR | O_SYNC); sock_fd = socket(PF_INET, SOCK_STREAM, 0); memset(&listen_addr, 0, sizeof(listen_addr)); listen_addr.sin_family = AF_INET; listen_addr.sin_addr.s_addr = htonl(INADDR_ANY); listen_addr.sin_port = htons(LISTEN_PORT); bind(sock_fd, (struct sockaddr*)&listen_addr, sizeof(listen_addr)); listen(sock_fd, 2); while (1) { client_addr_size = sizeof(client_addr); conn_fd = accept(sock_fd, (struct sockaddr *)&client_addr, &client_addr_size); shutdown = 0; while (1) { if(recv_all(conn_fd, &command.op, 4) != 4) { close(conn_fd); break; }; if(recv_all(conn_fd, &command.arg, 8) != 8) { close(conn_fd); break; }; switch (command.op) { case READ: transfered_size = 0; while (transfered_size < command.arg) { transfer_size = command.arg - transfered_size > BLOCK_SIZE ? BLOCK_SIZE : command.arg - transfered_size; read(super_fd, buffer, transfer_size); if(send_all(conn_fd, buffer, transfer_size) != transfer_size) { shutdown = 1; break; } transfered_size += transfer_size; } break; case SEEK: lseek64(super_fd, command.arg, SEEK_SET); break; case WRITE: transfered_size = 0; while (transfered_size < command.arg) { transfer_size = command.arg - transfered_size > BLOCK_SIZE ? BLOCK_SIZE : command.arg - transfered_size; if(recv_all(conn_fd, buffer, transfer_size) != transfer_size) { shutdown = 1; break; } write(super_fd, buffer, transfer_size); transfered_size += transfer_size; } break; case TELL: command.arg = lseek64(super_fd, 0, SEEK_CUR); if(send_all(conn_fd, &command.arg, 8) != 8) { shutdown = 1; }; break; case OPEN: if(recv_all(conn_fd, buffer, command.arg) != command.arg) { shutdown = 1; break; }; buffer[command.arg] = 0; close(super_fd); super_fd = open(buffer, O_RDWR | O_SYNC); break; case CLOSE: shutdown = 1; break; } if(shutdown) { close(conn_fd); break; } } }}
from pwn import *import ext4 class RemoteStream: def __init__(self) -> None: self.remote = remote('127.0.0.1', 4096) def read(self, size) -> bytes: self.remote.send(p32(0) + p64(size)) return self.remote.recvn(size) def seek(self, offset, whence): self.remote.send(p32(1) + p64(offset)) def write(self, data): self.remote.send(p32(2) + p64(len(data))) self.remote.send(data) def tell(self) -> int: self.remote.send(p32(3) + p64(0)) return u64(self.remote.recvn(8)) def open(self, path): self.remote.send(p32(4) + p64(len(path))) self.remote.send(path) def flush(self): pass rfs = RemoteStream()system = ext4.Volume(rfs, offset=1024*1024)ext4.Tools.list_dir(system, system.root)
rfs = RemoteStream()system = ext4.Volume(rfs, offset=1024*1024)targetfile = system.root.get_inode("path", "to", "file").open_read()targetdata = open('editedfile', 'rb').read() # 本地读取修改后的数据# 因为是在文件系统上直接覆写文件,修改后的文件大小一定要和原文件相同targetfile.rewrite(targetdata)
5
获取root权限
5.1 修改系统apex包
借助ext4模块可以编辑apex_payload.img中的adbd文件,但是改完了要怎么把apex_payload.img放回去?重新打包的话并不能保证com.android.adbd.apex文件的大小不变,也就无法再将其放回system文件系统里了。
这时发现这个apex文件压缩方式是仅存储,也就是并没有压缩,apex_payload.img的完整数据可以直接在这个文件中找到。
5.2 patch adbd
MOV X0, #1RET
搜索对字符串service.adb.root的引用然后F5往下找找到判断逻辑,在if处按tab转回汇编代码会定位到条件跳转,为一行CBZ指令,将此CBZ改为B指令。
import ext4img = open("apex_payload.img", "rb+")imgfs = ext4.Volume(img, offset=0)target_adbd = imgfs.root.get_inode("bin", "adbd").open_read()target_adbd.rewrite(open("adbd", "rb").read())
import zlibfrom pwn import *apex = bytearray(open("com.android.adbd.apex", "rb").read())org_img = open("apex_payload.img.bak", "rb").read()org_crc = p32(zlib.crc32(org_img))new_img = open("apex_payload.img", "rb").read()new_crc = p32(zlib.crc32(new_img))apex = apex.replace(org_img, new_img)apex = apex.replace(org_crc, new_crc)open("com.android.adbd.apex.new", "wb").write(apex)
rfs = RemoteStream()system = ext4.Volume(rfs, offset=1024*1024)apex = system.root.get_inode("system", "apex", "com.android.adbd.apex").open_read()apex.rewrite(open("com.android.adbd.apex.new", "rb").read())
5.3 patch selinux
system的主要selinux策略文件在/system/etc/selinux/plat_sepolicy.cil,使用编辑器打开发现是文本文件,并且里面有大量自动生成的注释,这就在不能改变文件大小的条件下给我提供了可操作的空间,可以删掉注释并把自定义的selinux策略添加进去。
cil文件中基本的selinux策略语法为(行为 主体 客体 (类别 (属性集)))说明当主体对客体的某些类别的某些属性产生访问事件时所采取的行为。
之后问题是对u:r:su:s0制限解除的策略代码要怎么写,首先要找一些参考资料。aosp中完整的sepolicy策略项目在https://android.googlesource.com/platform/system/sepolicy ,在该项目目录下可以找到prebuilts/api/系统api版本/public/su.te,此文件中将dontaudit改为allow后就是我们需要的制限解除代码。
类别的属性集定义在system/sepolicy/prebuilts/api/系统api版本/private/access_vectors,有了这两个文件就可以进行手动展开。比如:
allow su self:capability_class_set *;
allow su self:capability *;allow su self:capability2 *;allow su self:cap_userns *;allow su self:cap2_userns *;
common cap{ chown dac_override ...}class capabilityinherits cap
; allow su self:capability_class_set *;(allow su self (capability (chown dac_override dac_read_search fowner fsetid kill setgid setuid setpcap linux_immutable net_bind_service net_broadcast net_admin net_raw ipc_lock ipc_owner sys_module sys_rawio sys_chroot sys_ptrace sys_pacct sys_admin sys_boot sys_nice sys_resource sys_time sys_tty_config mknod lease audit_write audit_control setfcap)))(allow su self (capability2 (mac_override mac_admin syslog wake_alarm block_suspend audit_read)))(allow su self (cap_userns (chown dac_override dac_read_search fowner fsetid kill setgid setuid setpcap linux_immutable net_bind_service net_broadcast net_admin net_raw ipc_lock ipc_owner sys_module sys_rawio sys_chroot sys_ptrace sys_pacct sys_admin sys_boot sys_nice sys_resource sys_time sys_tty_config mknod lease audit_write audit_control setfcap)))(allow su self (cap2_userns (mac_override mac_admin syslog wake_alarm block_suspend audit_read)))
import ext4from pwn import *class RemoteStream: def __init__(self) -> None: self.remote = remote('127.0.0.1', 4096) def read(self, size) -> bytes: self.remote.send(p32(0) + p64(size)) return self.remote.recvn(size) def seek(self, offset, whence): self.remote.send(p32(1) + p64(offset)) def write(self, data): self.remote.send(p32(2) + p64(len(data))) self.remote.send(data) def tell(self) -> int: self.remote.send(p32(3) + p64(0)) return u64(self.remote.recvn(8)) def open(self, path): self.remote.send(p32(4) + p64(len(path))) self.remote.send(path) def flush(self): pass def zipcil(): orgcil = open('plat_sepolicy.cil').readlines() newcil = [] for line in orgcil: line = line.strip() if line.startswith(';'): pass elif len(line): newcil.append(line.encode('latin-1')) open('plat_sepolicy_ziped.cil', 'wb').write(b'\n'.join(newcil)) def build_newcil(): oldlen = len(open('plat_sepolicy.cil').read()) sepolicy = open('plat_sepolicy_ziped.cil').read() typeattributes = open('typeattribute.txt').readlines() for line in typeattributes: attr_name = line.strip().split(' ')[-1] pre = "typeattributeset " + attr_name + " (" end = ")" start = sepolicy.find(pre) + len(pre) end = sepolicy.find(end, start) if start == len(pre)-1: sepolicy = sepolicy + '\n(typeattributeset ' + attr_name + " (su))" else: groups = sepolicy[start:end].split(' ') if 'su' not in groups: sepolicy = sepolicy[:start] + 'su ' + sepolicy[start:] payload = open('su.cil').read() sepolicy = sepolicy + '\n' + payload + '\n;' sepolicy += ' '*(oldlen - len(sepolicy)) open('plat_sepolicy_patched.cil', 'wb').write(sepolicy.encode('latin-1')) def rwrite(): s = RemoteStream() system = ext4.Volume(s, offset=1024*1024) sepolicy = system.root.get_inode("system", "etc", "selinux", "plat_sepolicy.cil").open_read() sepolicy.rewrite(open('plat_sepolicy_patched.cil', 'rb').read())zipcil()build_newcil()rwrite()
6
将system挂载为可写
from pwn import *#省略 class RemoteStreamrfs = RemoteStream()rfs.seek(1024*1024 + 0x400 + 0x64)s_feature_ro_compat = u32(rfs.read(4))if s_feature_ro_compat & 0x4000: s_feature_ro_compat = s_feature_ro_compat ^ 0x4000rfs.seek(1024*1024 + 0x400 + 0x64)rfs.write(p32(s_feature_ro_compat))
6.1 删除system内文件释放空间的方式
在这里重点感谢一下ext4模块作者,通过print open_read打开的文件就可以看到mappedEntrys - 文件块到物理块的映射列表。
要清楚system文件系统是被一次性制作出来,并未经过删改的。可以相信如果文件没有共享块则它的映射一定是连续的一整块。
7
安装frida-gadget
见: https://bbs.pediy.com/thread-266785.htm
END
看雪ID:tacesrever
https://bbs.pediy.com/user-home-888604.htm
# 往期推荐
球分享
球点赞
球在看
点击“阅读原文”,了解更多!