首页
论坛
课程
招聘
[原创]android 11 手动root笔记
2021-7-4 21:14 7913

[原创]android 11 手动root笔记

2021-7-4 21:14
7913

android 11 手动root笔记

0. 前言

目标是在已解锁的android11手机上,通过修改原有系统的方式,以尽量少的痕迹获取(adb)root权限并全局装入frida-gadget。
关于为什么不用magisk或其他框架,因为感觉magisk的解决方案对我来说功能过于冗余,并且重视安全的应用都会试图通过各种姿势检测magisk或其他框架以及root (在某些群天天看到群友问某某应用检测root 怎么办) 一想到有可能会遇到有关magisk的兼容性问题,还要去定位解决,就头大 (也是还没用过magisk)

1. root思路

之前了解过adb root的原理:adbd服务最初以root用户的权限运行,读取判断一些系统配置后会自行降权,切换至shell用户。直接修改系统配置的值虽然操作不难,但是可以被检测到,在某些系统上还会导致log增多影响性能,所以想法是patch系统上的adbd文件使其不会执行降权。

2. 获取原系统镜像文件

手机的系统发布商向公众提供了OTA包下载途径,特点是zip文件内含payload.bin。
可以使用 https://github.com/vm03/payload_dumper 解包其中的镜像文件。
使用前需要运行如下命令以安装其依赖:
pip install protobuf bsdiff4
git clone https://github.com/vm03/payload_dumper之后解压OTA包将payload.bin移至payload_dumper文件夹下,运行python payload_dumper.py payload.bin,输出在output文件夹里。

3. 获取recovery模式下的root权限

recovery模式的系统文件在boot.img中,可以使用 https://github.com/cfig/Android_boot_image_editor 解包。
使用前需要安装较高版本的java,我安装的是jdk16,安装后需要手动将jdk中的bin路径添加到系统变量中。
此工具使用gradle进行项目部署及运行,需要联网从maven仓库下载一些包,如感觉速度慢则需要高速国际网络或者换源。这里讲一下换源的方法:在git clone https://github.com/cfig/Android_boot_image_editor之后编辑其中的build.gradle.kts找到

1
2
3
4
5
6
buildscript {
    repositories {
        mavenCentral()
    }
    ...
}

在repositories中添加三行

1
2
3
4
5
6
7
8
9
buildscript {
    repositories {
        maven {
          setUrl("http://maven.aliyun.com/nexus/content/groups/public/")
        }
        mavenCentral()
    }
    ...
}

将解包出的boot.img与vbmeta.img复制进Android_boot_image_editor文件夹里,运行gradlew unpack输出的文件系统在build/unzip_boot/root下。
编辑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模式下的系统属性。

 

运行gradlew pack,usb连接手机依次运行

1
2
3
4
adb reboot bootloader
fastboot flash --disable-verity --disable-verification vbmeta vbmeta.img.signed
fastboot flash boot boot.img.signed
fastboot reboot recovery

之后在recovery模式下运行adb shell,提示符已经变为#表示为root权限。

4. 在recovery模式下修改system文件的方式

该系统使用虚拟A/B的方式升级,system文件系统在/dev/block/byname/super中偏移为1024*1024的位置处。为了有效率地在windows下直接编辑android系统中未挂载的ext4文件系统而不用重新刷写,找到了 https://github.com/cubinator/ext4 并添加了覆写文件的方法,fork并修改后的项目见 https://github.com/tacesrever/ext4 ,并编写了一个简陋的socket程序用于转发文件读写操作。
服务器端:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
#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;
            }
        }
    }
}

部署方式是使用ndk或IDE编译后,push进recovery模式的系统中运行,同时运行adb forward tcp:4096 tcp:4096来转发端口。
windows客户端可以使用python脚本就不再写socket方面的代码了,这里用一下pwntools,需要运行pip install pwntools安装
windows客户端代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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)

运行后输出

如果需要修改/path/to/file,使用如下代码

1
2
3
4
5
6
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)

该方式主要用于修改文件,浏览以及读取文件可以使用7-Zip打开system.img。

5. 获取root权限

5.1 修改系统apex包

能够修改system文件之后,目标是patch adbd,然而adbd还被封装在/system/apex/com.android.adbd.apex中。使用7-Zip拿出com.android.adbd.apex后发现可以直接解压。解压后发现其中有个文件是apex_payload.img,又是一个小型的ext4文件系统镜像,而adbd就在这个文件系统里。(好家伙,俄罗斯套娃套起来了...)
借助ext4模块可以编辑apex_payload.img中的adbd文件,但是改完了要怎么把apex_payload.img放回去?重新打包的话并不能保证com.android.adbd.apex文件的大小不变,也就无法再将其放回system文件系统里了。
这时发现这个apex文件压缩方式是仅存储,也就是并没有压缩,apex_payload.img的完整数据可以直接在这个文件中找到。因为patch adbd并不会改变该文件的大小,于是可以用python里的replace替换该文件,同时需要替换该文件的crc32。实验发现幸好android系统并不会验证在/system/apex/目录下已经安装了的apex包的签名。

5.2 patch adbd

再使用7-Zip将adbd从apex_payload.img拿出来,之后使用ida打开adbd。

 

adbd判断是否降权的依据一是调用__android_log_is_debuggable最终判断ro.debuggable,这个函数是导入函数,存在对其的包装函数,ida会将其自动识别出其名称为.__android_log_is_debuggable,使用G跳转到它,按ctrl+alt+k使用keypatch将前两行修改为

1
2
MOV             X0, #1
RET

使用keypatch前需要已经安装keystone:pip install keystone-engine不然按此快捷键不会有反应。

因为不想运行adb root留下service.adb.root属性痕迹,所以接下来还要patch掉对它的判断。
搜索对字符串service.adb.root的引用然后F5往下找找到判断逻辑,在if处按tab转回汇编代码会定位到条件跳转,为一行CBZ指令,将此CBZ改为B指令。


 

改完之后点击左上角菜单Edit->Patch program->Apply patches to input file...,如果没有备份可以选中弹窗左下角的Create backup,点击OK。

 

之后复制原apex_payload.img为apex_payload.img.bak后,将修改后的adbd放回apex_payload.img:

1
2
3
4
5
import ext4
img = 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())

将apex_payload.img放回com.android.adbd.apex:

1
2
3
4
5
6
7
8
9
10
import zlib
from 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)

用com.android.adbd.apex.new覆写/system/apex/com.android.adbd.apex:

1
2
3
4
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

之后还需要修改selinux策略使得未降权的adbd能够正常运行。该系统版本是release版本,selinux策略中不包含对u:r:su:s0的一系列允许策略。如果只是单纯关闭selinux,不仅会降低系统安全性,而且很容易被app发现你没有开启selinux,可能会开心地从设备上读取各种信息并送入可疑设备名单,所以这里选择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后就是我们需要的制限解除代码。
然而从te到cil还需要一层编译转换。看了看文档后发现可以选择手动编译;
te文件中类似调用函数的宏定义在system/sepolicy/prebuilts/api/系统api版本/public/global_macros里,类别的属性集定义在system/sepolicy/prebuilts/api/系统api版本/private/access_vectors,有了这两个文件就可以进行手动展开。比如:

1
allow su self:capability_class_set *;

可以在global_macros中找到define('capability_class_set', '{ capability capability2 cap_userns cap2_userns }')
展开为

1
2
3
4
allow su self:capability *;
allow su self:capability2 *;
allow su self:cap_userns *;
allow su self:cap2_userns *;

capability等类的属性集在access_vectors可以找到其定义

1
2
3
4
5
6
7
8
common cap
{
    chown
    dac_override
    ...
}
class capability
inherits cap

就可以继续展开并转化为cil语法

1
2
3
4
5
; 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)))

te文件中还有一种语法是typeattribute su 名称,对应到cil中会变成(typeattributeset 名称 (类型集))在类型集中要将su添加进去。
结合这两种修改方式,可以将su.te中的基本策略展开为su.cil,并将typeattribute定义写进typeattribute.txt(见附件)
最终写出修改plat_sepolicy.cil的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
import ext4
from 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()

运行之后重启并进入android系统,运行adb shell:

6. 将system挂载为可写

获取到root权限后发现仍然无法将system挂载为可写,一番查阅发现该系统镜像使用了EXT4_FEATURE_RO_COMPAT_SHARED_BLOCKS特性。虽然可以通过使用RemoteStream大法强行更改ext4 superblock结构的s_feature_ro_compat去掉EXT4_FEATURE_RO_COMPAT_SHARED_BLOCKS(0x4000)从而可以挂载为可写,但是要删除原有文件释放空间的话仍然会出现问题。因为该特性正如其名SHARED_BLOCKS,各个文件之间相同内容的块(ext4下块大小为4096)被合并了,在文件驱动无视该特性删除文件时,其他含有相同内容的文件也会被破坏。

可以通过如下代码去掉EXT4_FEATURE_RO_COMPAT_SHARED_BLOCKS并将系统挂载为可写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from pwn import *
# 省略 RemoteStream 和 adb_shell
from remotestream import RemoteStream
 
rfs = 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 ^ 0x4000
rfs.seek(1024*1024 + 0x400 + 0x64)
rfs.write(s_feature_ro_compat)
 
adb_shell("setenforce 0")
adb_shell("blockdev --setrw /dev/block/dm-0")
adb_shell("mount -o rw,remount /")
adb_shell("setenforce 1")

一种思路是去掉EXT4_FEATURE_RO_COMPAT_SHARED_BLOCKS,挂载为可写并删除文件后,利用删除前该文件的ext4信息修复该文件被释放掉的共享物理块,并重新将这些物理块其在ext4文件系统中重新标记为使用中。
在这里重点感谢一下ext4模块作者,通过print open_read打开的文件就可以看到mappedEntrys - 文件块到物理块的映射列表。

 

通过计算每个物理块的引用计数,可以知道哪些是共享物理块。在给ext4模块添加了操作bitmap的代码后,写出完整的删除并修复共享块的代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
import ext4
from pwn import *
# 省略 RemoteStream 和 adb_shell
from remotestream import RemoteStream
from android_tools import adb_shell
 
lfs = open('system.img', 'rb')
rfs = RemoteStream()
 
remote_system = ext4.Volume(rfs, offset=1024*1024)
local_system = ext4.Volume(lfs, offset=0)
block_size = remote_system.block_size
block_refs = None
 
# 将系统挂载为可写
def remount():
    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 ^ 0x4000
    rfs.seek(1024*1024 + 0x400 + 0x64)
    rfs.write(p32(s_feature_ro_compat))
 
    adb_shell("setenforce 0")
    adb_shell("blockdev --setrw /dev/block/dm-0")
    adb_shell("mount -o rw,remount /")
    adb_shell("setenforce 1")
 
# 计算引用计数
def calc_block_refs(system, removed = []):
    global block_refs
    sb = system.superblock
    block_refs = [0] * sb.s_blocks_count
    # 遍历ext4中的inode
    def count_group_block_ref(group_descriptor, inode_count):
        inode_table_offset = group_descriptor.bg_inode_table * system.block_size
        for i in range(inode_count):
            inode_offset = inode_table_offset + i * sb.s_inode_size
            node = ext4.Inode(system, inode_offset, None)
            if node.is_file and (node.inode.i_flags & ext4.ext4_inode.EXT4_EXTENTS_FL) != 0:
                block_map = node.open_read().block_map
                for mapping_entry in block_map:
                    for j in range(mapping_entry.block_count):
                        block_refs[mapping_entry.disk_block_idx + j] += 1
 
    for group_descriptor in system.group_descriptors[:-1]:
        count_group_block_ref(group_descriptor, sb.s_inodes_per_group)
    count_group_block_ref(system.group_descriptors[-1], sb.s_inodes_count % sb.s_inodes_per_group)
 
    # 如果已经删除过一些文件,减去它们的计数
    for removed_file_path in removed:
        splited_path = removed_file_path.split('/')
        if not len(splited_path[0]):
            splited_path = splited_path[1:]
        removed_file = local_system.root.get_inode(*splited_path).open_read()
        for mapping_entry in removed_file.block_map:
            for i in range(mapping_entry.disk_block_idx, mapping_entry.disk_block_idx + mapping_entry.block_count):
                block_refs[i] -= 1
 
# 删除文件
def safe_remove(filepath, do_rm = 1):
    splited_path = filepath.split('/')
    if do_rm:
        adb_shell("rm " + filepath)
    if not len(splited_path[0]):
        splited_path = splited_path[1:]
    to_remove = local_system.root.get_inode(*splited_path).open_read()
    to_fix = []
 
    for mapping_entry in to_remove.block_map:
        for i in range(mapping_entry.disk_block_idx, mapping_entry.disk_block_idx + mapping_entry.block_count):
            block_refs[i] -= 1
            if(block_refs[i] != 0 and i not in to_fix):
                to_fix.append(i)
            if(block_refs[i] == 0 and i in to_fix):
                to_fix.remove(i)
 
    # 修复这些块在bitmap中的inuse位
    if len(to_fix):
        to_fix.sort()
        from_idx = to_fix[0]
        to_idx = to_fix[0] + 1
        for block_idx in to_fix[1:]:
            if block_idx == to_idx:
                to_idx += 1
            else:
                remote_system.set_blocks_inuse_bit(from_idx, to_idx, 1)
                from_idx = block_idx
                to_idx = block_idx + 1
        remote_system.set_blocks_inuse_bit(from_idx, to_idx, 1)
 
        # 修复块内容
        for block_idx in to_fix:
            remote_system.write(block_idx * block_size, local_system.read(block_idx * block_size, block_size))
 
calc_block_refs(local_system, [])
remount()
safe_remove("/system/data-app/一些没啥用的apk")

7 安装frida-gadget

见 https://bbs.pediy.com/thread-266785.htm (已更新)


第五届安全开发者峰会(SDC 2021)议题征集正式开启!

最后于 2021-7-13 07:12 被tacesrever编辑 ,原因: 更新一下下
上传的附件:
收藏
点赞12
打赏
分享
最新回复 (24)
雪    币: 3329
活跃值: 活跃值 (537)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
bluefish蓝鱼 活跃值 2021-7-4 22:26
2
0
好东西,谢谢分享
雪    币: 283
活跃值: 活跃值 (352)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
hackbs 活跃值 2021-7-4 23:17
3
0
收藏了,谢谢
雪    币: 784
活跃值: 活跃值 (2100)
能力值: ( LV7,RANK:115 )
在线值:
发帖
回帖
粉丝
ChicWalk 活跃值 2 2021-7-5 02:31
4
0
build/unzip_boot/vbmeta.avb.json将header.flags改为2来禁用全局verity 这个相关资料有吗,或者能不能详细解释一下,我改过低版本的,但是发现会有验证问题,不是很了解andorid boot验证这一块
雪    币: 1264
活跃值: 活跃值 (2202)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
lhxdiao 活跃值 2021-7-5 06:41
5
0
挺方便
雪    币: 7
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
聆风人 活跃值 2021-7-5 10:07
6
0
很溜,改天试试
雪    币: 94
活跃值: 活跃值 (279)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
wangzehua 活跃值 2021-7-5 10:30
7
0
好东西
雪    币: 11
活跃值: 活跃值 (242)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
RiDiN 活跃值 2021-7-5 16:42
8
0
ChicWalk build/unzip_boot/vbmeta.avb.json将header.flags改为2来禁用全局verity 这个相关资料有吗,或者能不能详细解释一下,我改过低版本的,但是发现会有验证问题, ...
那可能是你改错地方了,有些并不是改 boot.img 里的 avb,而是专门的 vbmeta 分区。
关于为什么改成2,请看这里 https://android.googlesource.com/platform/external/avb/+/master/libavb/avb_vbmeta_image.h#61
雪    币: 11
活跃值: 活跃值 (242)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
RiDiN 活跃值 2021-7-5 16:44
9
0
写的挺棒
雪    币: 478
活跃值: 活跃值 (581)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
abcz316 活跃值 2021-7-6 09:44
10
3

这样做太麻烦啦,拿root权限有一步到位的方法,直接反汇编修改kernel里面的cred,手补五十来行arm64汇编嵌进去,直接B跳转,判断是某个进程直接cred清0,完事,通杀所有安卓,不区分版本。拿到root权限后,过掉selinux也有更暴力的方法,还是改kernel,将secure_file_open、secure_capable、secure_ptrace前面潜入几十行内嵌汇编,直接B跳转判断一下,放行,开机后就能注入init进程了,注入后context变成最高级了,selinux就废了

最后于 2021-7-12 13:20 被abcz316编辑 ,原因:
雪    币: 245
活跃值: 活跃值 (62)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
airbus 活跃值 2021-7-8 14:28
11
0
谢谢分享那么好的思路。
雪    币: 20
活跃值: 活跃值 (20)
能力值: ( LV2,RANK:93 )
在线值:
发帖
回帖
粉丝
apengxxx 活跃值 2021-7-8 21:17
12
0
我在复现的时候发现,我这里的adbd运行的路径位于/apex/com.android.adbd/bin/。我patch了/system/apex/com.google.android.adbd.apex这个包后并不能生效,对实际运行的adbd没有影响,动态修改运行时的adbd进程也不能生效,不知道lz有没有遇到过
雪    币: 81
活跃值: 活跃值 (81)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
异灵摆渡 活跃值 2021-7-9 11:33
13
0
文章看完了,确实是干货,不过我要指出一点,vbmeta用原生的就行,刷入boot前后,一定要执行fastboot --disable-verity --disable-verification flash vbmeta vbmeta.img   ,这条指令就是关闭AVB验证的,通过修改vbmeta的话,只要执行fastboot  flash vbmeta vbmeta.img  。
雪    币: 81
活跃值: 活跃值 (81)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
异灵摆渡 活跃值 2021-7-9 11:39
14
0
还有一点,你是在recovery中修改了adb属性,以使官方recovery可以使用adb,并且使用了root权限,这一点正好我近期也在研究。我可以告诉你一个捷径,你将修改recovery的相关修改做到boot里面,开机应该可以做到adb root。这个root还不是完全的root,可能还不能够完全控制system。具体看大神你的了。
雪    币: 81
活跃值: 活跃值 (81)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
异灵摆渡 活跃值 2021-7-9 11:41
15
0
abcz316 这样做太麻烦啦,有一步到位的方法,直接反汇编修改kernel里面的cred,手补五十来行arm64汇编嵌进去,直接B跳转,判断是某个进程直接cred清0,完事,通杀所有安卓,不区分版本
你这个想法太完美了,有时间的话做一个出来,也写个帖子让大家看看。
雪    币: 81
活跃值: 活跃值 (81)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
异灵摆渡 活跃值 2021-7-9 11:42
16
0
ChicWalk build/unzip_boot/vbmeta.avb.json将header.flags改为2来禁用全局verity 这个相关资料有吗,或者能不能详细解释一下,我改过低版本的,但是发现会有验证问题, ...
本论坛有个网友讲述了一些关于vbmeta的东西,我觉得写的不错,你搜一下。关键字vbmeta
雪    币: 81
活跃值: 活跃值 (81)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
异灵摆渡 活跃值 2021-7-9 11:50
17
0
大神在看雪啊,我好几年前就注册了看雪,但是我几乎没过来看过,每个大神都是贴了各种代码,我只能弄点最简单的
雪    币: 478
活跃值: 活跃值 (581)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
abcz316 活跃值 2021-7-9 12:34
18
0
异灵摆渡 你这个想法太完美了,有时间的话做一个出来,也写个帖子让大家看看。
已实现,后续有时间再整理开源。Linux3.10,Linux4.14.117,Linux5.14测试通过。
雪    币: 0
活跃值: 活跃值 (34)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
hequanlun 活跃值 2021-7-9 14:32
19
0
我有一点很不明白,绕过了avb校验,直接刷twrp就可以啦,为什么一定要用官方的recovery。
我的手机是redmi note8,用的系统是MIUI,成功的去除了avb校验,成功的在app_process注入了我自己的so,照样hook,一样很好用。
我在miui系统上,将ro.secure改为0,重启系统,adbd不工作。我试了很多遍都失败,谁可以帮助我?
雪    币: 333
活跃值: 活跃值 (168)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
Rprop 活跃值 2021-7-9 23:26
20
0
hequanlun 我有一点很不明白,绕过了avb校验,直接刷twrp就可以啦,为什么一定要用官方的recovery。 我的手机是redmi note8,用的系统是MIUI,成功的去除了avb校验,成功的在app_pr ...
需要添加selinux规则或禁掉
雪    币: 0
活跃值: 活跃值 (34)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
hequanlun 活跃值 2021-7-10 02:03
21
0
Rprop 需要添加selinux规则或禁掉
添加了selinux规则还是一样,我再试一试别的方法
雪    币: 81
活跃值: 活跃值 (81)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
异灵摆渡 活跃值 2021-7-10 10:17
22
0
abcz316 已实现,后续有时间再整理开源。Linux3.10,Linux4.14.117,Linux5.14测试通过。
等你的帖子
雪    币: 47
活跃值: 活跃值 (246)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
saloyun 活跃值 2021-7-12 08:07
23
0
优秀
雪    币: 228
活跃值: 活跃值 (23)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
yjcpu 活跃值 1 2021-7-12 11:06
24
0
修改selinux部分太秀了。
雪    币: 233
活跃值: 活跃值 (463)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
kakasasa 活跃值 2021-7-12 16:01
25
0
mark
游客
登录 | 注册 方可回帖
返回