首页
论坛
课程
招聘
[原创] 绕过Android内核模块加载验证
2022-1-16 11:36 19888

[原创] 绕过Android内核模块加载验证

2022-1-16 11:36
19888

绕过Android内核模块加载验证

以小米10为例,系统版本:MIUI 12.5.10.0

一、boot.img的提取

获取boot.img有两个途径:

  1. 官方的镜像包里面提取
  2. 手机里面提取(需要root权限)

方法一、官方镜像包提取

  1. 前往XiaomiRom,寻找对应的线刷包

    本文示例ROM下载地址:https://xiaomirom.com/download/mi-10-umi-stable-V12.5.10.0.RJBCNXM/#china-fastboot

  2. 解压,在images文件夹里面找到boot.img

方法二、手机里面提取

adb shell 执行下面的命令(需要root权限)

1
dd if=$(readlink /dev/block/by-name/boot) of=/sdcard/boot.img

把 boot.img 拉到电脑上来

1
adb pull /sdcard/boot.img boot.img

二、解包 boot.img 获取 kernel

推荐使用 magiskboot

 

自行编译:https://github.com/topjohnwu/Magisk 获取 magiskboot 可执行文件

1
./magiskboot unpack boot.img

截屏2022-01-16 上午10.38.36.png

 

kernel文件就是我们需要的内核二进制文件

三、patch kernel 去除内核模块加载验证

内核模块加载验证分析

insmod 使用 sys_init_module 系统调用,去源码中查看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
SYSCALL_DEFINE3(init_module, void __user *, umod,
        unsigned long, len, const char __user *, uargs)
{
    int err;
    struct load_info info = { };
 
    err = may_init_module();
    if (err)
        return err;
 
    pr_debug("init_module: umod=%p, len=%lu, uargs=%p\n",
           umod, len, uargs);
 
    err = copy_module_from_user(umod, len, &info);
    if (err)
        return err;
 
    return load_module(&info, uargs, 0);
}

查看 load_module 主函数

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
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
/* Allocate and load the module: note that size of section 0 is always
   zero, and we rely on this for optional sections. */
static int load_module(struct load_info *info, const char __user *uargs,
               int flags)
{
    struct module *mod;
    long err = 0;
    char *after_dashes;
 
    /*
     * Do the signature check (if any) first. All that
     * the signature check needs is info->len, it does
     * not need any of the section info. That can be
     * set up later. This will minimize the chances
     * of a corrupt module causing problems before
     * we even get to the signature check.
     *
     * The check will also adjust info->len by stripping
     * off the sig length at the end of the module, making
     * checks against info->len more correct.
     */
    err = module_sig_check(info, flags); // 第一处签名校验
    if (err)
        goto free_copy;
 
    /*
     * Do basic sanity checks against the ELF header and
     * sections.
     */
    err = elf_validity_check(info);
    if (err) {
        pr_err("Module has invalid ELF structures\n");
        goto free_copy;
    }
 
    /*
     * Everything checks out, so set up the section info
     * in the info structure.
     */
    err = setup_load_info(info, flags);
    if (err)
        goto free_copy;
 
    /*
     * Now that we know we have the correct module name, check
     * if it's blacklisted.
     */
    if (blacklisted(info->name)) {
        err = -EPERM;
        goto free_copy;
    }
 
    err = rewrite_section_headers(info, flags);
    if (err)
        goto free_copy;
 
    // modstruct 版本校验
    /* Check module struct version now, before we try to use module. */
    if (!check_modstruct_version(info, info->mod)) {
        err = -ENOEXEC;
        goto free_copy;
    }
 
    /* Figure out module layout, and allocate all the memory. */
    mod = layout_and_allocate(info, flags);
    if (IS_ERR(mod)) {
        err = PTR_ERR(mod);
        goto free_copy;
    }
 
    audit_log_kern_module(mod->name);
 
    /* Reserve our place in the list. */
    err = add_unformed_module(mod);
    if (err)
        goto free_module;
 
/*第二处签名校验*/
#ifdef CONFIG_MODULE_SIG
    mod->sig_ok = info->sig_ok;
    if (!mod->sig_ok) {
        pr_notice_once("%s: module verification failed: signature "
                   "and/or required key missing - tainting "
                   "kernel\n", mod->name);
        add_taint_module(mod, TAINT_UNSIGNED_MODULE, LOCKDEP_STILL_OK);
    }
#endif
 
    /* To avoid stressing percpu allocator, do this once we're unique. */
    err = percpu_modalloc(mod, info);
    if (err)
        goto unlink_mod;
 
    /* Now module is in final location, initialize linked lists, etc. */
    err = module_unload_init(mod);
    if (err)
        goto unlink_mod;
 
    init_param_lock(mod);
 
    /* Now we've got everything in the final locations, we can
     * find optional sections. */
    err = find_module_sections(mod, info);
    if (err)
        goto free_unload;
 
    err = check_module_license_and_versions(mod);
    if (err)
        goto free_unload;
 
    /* Set up MODINFO_ATTR fields */
    setup_modinfo(mod, info);
 
    /* Fix up syms, so that st_value is a pointer to location. */
    err = simplify_symbols(mod, info);
    if (err < 0)
        goto free_modinfo;
 
    err = apply_relocations(mod, info);
    if (err < 0)
        goto free_modinfo;
 
    err = post_relocation(mod, info);
    if (err < 0)
        goto free_modinfo;
 
    flush_module_icache(mod);
 
    /* Now copy in args */
    mod->args = strndup_user(uargs, ~0UL >> 1);
    if (IS_ERR(mod->args)) {
        err = PTR_ERR(mod->args);
        goto free_arch_cleanup;
    }
 
    dynamic_debug_setup(mod, info->debug, info->num_debug);
 
    /* Ftrace init must be called in the MODULE_STATE_UNFORMED state */
    ftrace_module_init(mod);
 
    /* Finally it's fully formed, ready to start executing. */
    err = complete_formation(mod, info);
    if (err)
        goto ddebug_cleanup;
 
    err = prepare_coming_module(mod);
    if (err)
        goto bug_cleanup;
 
    /* Module is ready to execute: parsing args may do that. */
    after_dashes = parse_args(mod->name, mod->args, mod->kp, mod->num_kp,
                  -32768, 32767, mod,
                  unknown_module_param_cb);
    if (IS_ERR(after_dashes)) {
        err = PTR_ERR(after_dashes);
        goto coming_cleanup;
    } else if (after_dashes) {
        pr_warn("%s: parameters '%s' after `--' ignored\n",
               mod->name, after_dashes);
    }
 
    /* Link in to sysfs. */
    err = mod_sysfs_setup(mod, info, mod->kp, mod->num_kp);
    if (err < 0)
        goto coming_cleanup;
 
    if (is_livepatch_module(mod)) {
        err = copy_module_elf(mod, info);
        if (err < 0)
            goto sysfs_cleanup;
    }
 
    /* Get rid of temporary copy. */
    free_copy(info);
 
    /* Done! */
    trace_module_load(mod);
 
    return do_init_module(mod);
 
 sysfs_cleanup:
    mod_sysfs_teardown(mod);
 coming_cleanup:
    mod->state = MODULE_STATE_GOING;
    destroy_params(mod->kp, mod->num_kp);
    blocking_notifier_call_chain(&module_notify_list,
                     MODULE_STATE_GOING, mod);
    klp_module_going(mod);
 bug_cleanup:
    mod->state = MODULE_STATE_GOING;
    /* module_bug_cleanup needs module_mutex protection */
    mutex_lock(&module_mutex);
    module_bug_cleanup(mod);
    mutex_unlock(&module_mutex);
 
 ddebug_cleanup:
    ftrace_release_mod(mod);
    dynamic_debug_remove(mod, info->debug);
    synchronize_rcu();
    kfree(mod->args);
 free_arch_cleanup:
    module_arch_cleanup(mod);
 free_modinfo:
    free_modinfo(mod);
 free_unload:
    module_unload_free(mod);
 unlink_mod:
    mutex_lock(&module_mutex);
    /* Unlink carefully: kallsyms could be walking list. */
    list_del_rcu(&mod->list);
    mod_tree_remove(mod);
    wake_up_all(&module_wq);
    /* Wait for RCU-sched synchronizing before releasing mod->list. */
    synchronize_rcu();
    mutex_unlock(&module_mutex);
 free_module:
    /* Free lock-classes; relies on the preceding sync_rcu() */
    lockdep_free_key_range(mod->core_layout.base, mod->core_layout.size);
 
    module_deallocate(mod, info);
 free_copy:
    free_copy(info);
    return err;
}

影响加载验证的主要有两个:

  1. module_sig_check 模块签名校验
  2. check_modstruct_version 版本校验。

check_modstruct_version 函数源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
static inline int check_modstruct_version(const struct load_info *info,
                      struct module *mod)
{
    const s32 *crc;
 
    /*
     * Since this should be found in kernel (which can't be removed), no
     * locking is necessary -- use preempt_disable() to placate lockdep.
     */
    preempt_disable();
    if (!find_symbol("module_layout", NULL, &crc, NULL, true, false)) {
        preempt_enable();
        BUG();
    }
    preempt_enable();
    return check_version(info, "module_layout", mod, crc);
}

check_version 函数源码:

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
static int check_version(const struct load_info *info,
             const char *symname,
             struct module *mod,
             const s32 *crc)
{
    Elf_Shdr *sechdrs = info->sechdrs;
    unsigned int versindex = info->index.vers;
    unsigned int i, num_versions;
    struct modversion_info *versions;
 
    /* Exporting module didn't supply crcs?  OK, we're already tainted. */
    if (!crc)
        return 1;
 
    /* No versions at all?  modprobe --force does this. */
    if (versindex == 0)
        return try_to_force_load(mod, symname) == 0;
 
    versions = (void *) sechdrs[versindex].sh_addr;
    num_versions = sechdrs[versindex].sh_size
        / sizeof(struct modversion_info);
 
    for (i = 0; i < num_versions; i++) {
        u32 crcval;
 
        if (strcmp(versions[i].name, symname) != 0)
            continue;
 
        if (IS_ENABLED(CONFIG_MODULE_REL_CRCS))
            crcval = resolve_rel_crc(crc);
        else
            crcval = *crc;
        if (versions[i].crc == crcval)
            return 1;
        pr_debug("Found checksum %X vs module %lX\n",
             crcval, versions[i].crc);
        goto bad_version;
    }
 
    /* Broken toolchain. Warn once, then let it go.. */
    pr_warn_once("%s: no symbol version for %s\n", info->name, symname);
    return 1;
 
bad_version:
    pr_warn("%s: disagrees about version of symbol %s\n",
           info->name, symname);
    return 0;
}

module_sig_check 函数源码:

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
static int module_sig_check(struct load_info *info, int flags)
{
    int err = -ENODATA;
    const unsigned long markerlen = sizeof(MODULE_SIG_STRING) - 1;
    const char *reason;
    const void *mod = info->hdr;
 
    /*
     * Require flags == 0, as a module with version information
     * removed is no longer the module that was signed
     */
    if (flags == 0 &&
        info->len > markerlen &&
        memcmp(mod + info->len - markerlen, MODULE_SIG_STRING, markerlen) == 0) {
        /* We truncate the module to discard the signature */
        info->len -= markerlen;
        err = mod_verify_sig(mod, info);
    }
 
    switch (err) {
    case 0:
        info->sig_ok = true; // 关键点!
        return 0;
 
        /* We don't permit modules to be loaded into trusted kernels
         * without a valid signature on them, but if we're not
         * enforcing, certain errors are non-fatal.
         */
    case -ENODATA:
        reason = "unsigned module";
        break;
    case -ENOPKG:
        reason = "module with unsupported crypto";
        break;
    case -ENOKEY:
        reason = "module with unavailable key";
        break;
 
        /* All other errors are fatal, including nomem, unparseable
         * signatures and signature check failures - even if signatures
         * aren't required.
         */
    default:
        return err;
    }
 
    if (is_module_sig_enforced()) {
        pr_notice("Loading of %s is rejected\n", reason);
        return -EKEYREJECTED;
    }
 
    return security_locked_down(LOCKDOWN_MODULE_SIGNATURE);
}

内核模块加载验证去除

patch思路

  1. 强制 check_version 返回 1
  2. module_sig_check 返回 0 并且让 info->sig_ok = true;

小米10的内核并没有开启签名校验,所以只需要让 check_version 返回 1 即可,这个是很好修改的,在实践中,module_sig_check比较难改,通常它会被内联进load_module函数,此时需要分析load_module函数来进行定位修改。

操作流程

check_version 是非常好定位的,可以用字符串“%s: disagrees about version of symbol %s\n”来很方便地定位它。

 

使用ida打开后,它没有自动开始分析,手动

 

截屏2022-01-16 上午11.02.46.png

 

使用 ida python,手动 MakeCode 让它开始分析

1
2
3
import idc
for i in range(0, 0x100000):
    idc.MakeCode(i)

分析完毕后,在strings中搜索上述字符串,查找交叉引用即可找到 check_version 函数

 

当然也有取巧的办法:

  1. 找到字符串的位置
  2. 用keystone获取 “add x0, x0, #(位置%0x1000)” 的汇编代码
  3. 搜索上述汇编代码
  4. check_version 函数就在附近
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
from capstone import *
from keystone import *
 
def bin_finder(kernel_image: bytes, bin_data: bytes):
    return kernel_image.find(bin_data)
 
def string_finder(kernel_image: bytes, string: str):
    return bin_finder(kernel_image, string.encode("utf-8"))
 
if __name__ == '__main__':
    fp = open("kernel/kernel_10_12_5_10", "rb")
    kernel_image_data = fp.read()
    fp.close()
    offset = bin_finder(kernel_image_data, b"\x014%s: no symbol version for %s")
    print("string at:" + str(hex(offset)))
    ks = Ks(KS_ARCH_ARM64, KS_MODE_LITTLE_ENDIAN)
    code, stat_count = ks.asm("add    x0, x0, #" + str(hex(offset % 0x1000)), as_bytes=True)
    print(code)
    offset = bin_finder(kernel_image_data, code)
    print("xref to:" + str(hex(offset)))
    begin = offset - 0x100
    end = offset + 0x100
    function_bin = kernel_image_data[begin:end]
    cs = Cs(CS_ARCH_ARM64, CS_MODE_ARM)
    for c in cs.disasm(function_bin, begin):
        print("0x%x: %s %s" % (c.address, c.mnemonic, c.op_str))

运行脚本可以获得输出 “xref to:0x121520”

 

可得函数sub_121498即为check_version

 

截屏2022-01-16 上午11.15.15.png

 

把 CBZ X2, loc_121544 改为 B loc_121544 即可

 

截屏2022-01-16 上午11.16.42.png

 

截屏2022-01-16 上午11.16.56.png

 

应用修改到文件即可。

四、重新打包 boot.img

还是使用magiskboot,替换掉kernel后,执行:

1
./magiskboot repack boot.img

可以获得 new-boot.img

五、刷入新的 boot.img

1
2
adb reboot fastboot
fastboot flash boot boot.img

六、最终

现在的内核已经可以加载模块了,但是编译出来的模块还要经过“后处理”

  1. 自行编译模块
  2. 修改模块的vermagic
  3. 修改模块的module_layout

使用工具vermagic:https://github.com/yaxinsn/vermagic

vermagic 和 module_layout 可以在手机里面随便找个系统ko获得

 

执行:

1
2
./vermagic -c "{module_layout, 0X783EB3B8}" hello.ko
./vermagic -v "4.19.113-perf-g545a49d08f11 SMP preempt mod_unload modversions aarch64" hello.ko

测试效果:

 

截屏2022-01-16 上午11.27.53.png


【看雪培训】目录重大更新!《安卓高级研修班》2022年春季班开始招生!

最后于 2022-1-16 11:41 被Ylarod编辑 ,原因:
收藏
点赞2
打赏
分享
最新回复 (9)
雪    币: 2
活跃值: 活跃值 (410)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
发烧小孩 活跃值 2022-1-17 11:10
2
0
雪    币: 376
活跃值: 活跃值 (1047)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
abcz316 活跃值 2022-1-17 11:20
3
0

楼主的方法不适用于高通888以及以上手机的内核,比如小米11,必死机

最后于 2022-1-17 12:06 被abcz316编辑 ,原因:
雪    币: 1042
活跃值: 活跃值 (548)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
Ylarod 活跃值 2022-1-17 20:48
4
0
abcz316 楼主的方法不适用于高通888以及以上手机的内核,比如小米11,必死机
是什么原因呢?
雪    币: 376
活跃值: 活跃值 (1047)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
abcz316 活跃值 2022-1-18 01:06
5
0
Ylarod 是什么原因呢?
你把小米11的内核DEBUG模式调试跑一下,打个断点就知道了
雪    币: 199
活跃值: 活跃值 (194)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
mumuya 活跃值 2022-1-18 11:04
6
0
厉害了,我是全程nop掉!!!
雪    币: 199
活跃值: 活跃值 (194)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
mumuya 活跃值 2022-1-18 11:05
7
0
abcz316 你把小米11的内核DEBUG模式调试跑一下,打个断点就知道了
@abcz316我关注你一年了, 带带我,这帖子好像你也说要发的!
雪    币: 376
活跃值: 活跃值 (1047)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
abcz316 活跃值 2022-1-18 15:20
8
0
mumuya @abcz316我关注你一年了, 带带我,这帖子好像你也说要发的!
之前忙着研发内核免root系统,没有管这回事了
雪    币: 267
活跃值: 活跃值 (284)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
U大师 活跃值 2022-1-18 19:43
9
0
如果内核没有开启编译lkm的选项 ,是否就无法载入自己的ko文件了呢
雪    币: 204
活跃值: 活跃值 (225)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
Ssage泓清 活跃值 2022-1-18 20:06
10
0
abcz316 之前忙着研发内核免root系统,没有管这回事了
大佬 同关注你一段时间了 对你的新一代root方式很期待 加油
游客
登录 | 注册 方可回帖
返回