2016-05-09

android kernel symbol usage

主要针对arm64,现在ida6.8并不支持arm64的反汇编,所以在定位代码过程中会很麻烦,而32位是直接可以f5对照源码看的。

获取内核符号表

先放一个内核符号表的部分样子:

    ffffffc000081000 do_undefinstr
    ffffffc000081000 _stext
    ffffffc000081000 __exception_text_start
    ffffffc000081348 do_mem_abort
    ffffffc0000813e4 do_sp_pc_abort
    ffffffc000081428 do_debug_exception
    ffffffc0000814d4 gic_handle_irq
    ffffffc000081554 gic_handle_irq
    ffffffc0000815d4 __exception_text_end
    ffffffc0000815d8 match_dev_by_uuid
    ffffffc000081618 name_to_dev_t
    ffffffc000082000 swp_handler.part.1
    ffffffc000082138 swp_handler
    ffffffc0000821fc clear_os_lock
    ffffffc000082208 create_debug_debugfs_entry
    ffffffc000082238 brk_handler.part.2
    ffffffc0000822a0 brk_handler
    ffffffc00008238c single_step_handler.part.3
    ffffffc0000823f8 single_step_handler
    ffffffc0000824f8 debug_monitors_arch
    ffffffc000082504 enable_debug_monitors
    ffffffc000082620 disable_debug_monitors
    ffffffc00008270c register_step_hook
    ffffffc000082764 unregister_step_hook
    ffffffc0000827c4 register_break_hook 
    ...

有root权限

两条命令即可: c adb shell, echo 0 > /proc/sys/kernel/kptr_restrict sysctl adb pull /proc/kallsyms kallsyms.txt

无root权限

1.获取kernel文件,解压rom包找到boot.img文件,然后bootimg提取kernel压缩文件,直接解压获取kernel文件。如果解压失败,用二进制文件打开,查找特征值1F 8B 08,删除之前数据保存并解压。

2.使用kallsymsprint获取,参数为kernel文件路径。

3.懒人的方法,打包一个脚本dump_kallsyms搞定 : )

patch selinux mode

内核提权过程中,完成了addr_limit字段的patch之后,那么用户态就可以任意读写内核态了,但是如果系统的selinux使用的是enforcing模式,后续的提权过程需要pacth selinux mode。

源码中#define selinux_enforcing 1,该模式为enforcing,获得任意读写能力之后将其置0即可改为permissive

查找内核符号表,变量selinux_enforcing并没有导出地址(部分会有),查找源码找到使用该值的地方,例如在enforcing_setup函数中:

static int __init enforcing_setup(char *str)
{
    unsigned long enforcing;
    if (!kstrtoul(str, 0, &enforcing))
        selinux_enforcing = enforcing ? 1 : 0;
    return 1;
}

在内核符号表中找到函数enforcing_setup的地址为0xffffffc000d75878,反汇编该地址处的代码。 ida无法反汇编arm64,可以通过python的capstone模块进行反汇编,用法可参考

0xffffffc000d75878:     stp     x29, x30, [sp, #-0x20]!
0xffffffc000d7587c:     movz    w1, #0
0xffffffc000d75880:     mov     x29, sp
0xffffffc000d75884:     add     x2, x29, #0x18
0xffffffc000d75888:     bl      #0xffffffc000324c88
0xffffffc000d7588c:     cbnz    w0, #0xffffffc000d758a4
0xffffffc000d75890:     ldr     x0, [x29, #0x18]
0xffffffc000d75894:     cmp     x0, xzr
0xffffffc000d75898:     adrp    x0, #0xffffffc000f2f000
0xffffffc000d7589c:     cset    w1, ne
0xffffffc000d758a0:     str     w1, [x0, #0xc0c]
0xffffffc000d758a4:     movz    w0, #0x1
0xffffffc000d758a8:     ldp     x29, x30, [sp], #0x20
0xffffffc000d758ac:     ret

enforcing_setup = 0xffffffc000f2f000 + 0xc0c

patch ioctl

在linux内核PXN开启且代码段只读开启的前提下,如果获得了任意地址写的能力,怎么提权呢,ret2user是不行了,安装一个syscall调用commitcreds也不行了,syscall_table也是不能改的(引自网络)。一种思路是通过改写ptmx_fops->unlocked_ioctl指向rop,然后调用/dev/ptmx的ioctl即可跳入rop中执行。

同样ptmxfops在内核符号表中是没有导出的,如何确定该地址并修改unlockedioctl的指针。

查看源码ptmx_fops使用的地方。

static int __init pty_init(void)
{
    legacy_pty_init();
    unix98_pty_init();
    return 0;
}

static void __init unix98_pty_init(void)
{
    ...
    /* Now create the /dev/ptmx special device */
    tty_default_fops(&ptmx_fops);
    ptmx_fops.open = ptmx_open;

    cdev_init(&ptmx_cdev, &ptmx_fops);
    if (cdev_add(&ptmx_cdev, MKDEV(TTYAUX_MAJOR, 2), 1) ||
        register_chrdev_region(MKDEV(TTYAUX_MAJOR, 2), 1, "/dev/ptmx") < 0)
        panic("Couldn't register /dev/ptmx driver");
    device_create(tty_class, NULL, MKDEV(TTYAUX_MAJOR, 2), NULL, "ptmx");
}

void tty_default_fops(struct file_operations *fops)
{
    *fops = tty_fops;
}

在ptmx设备初始化时tty_default_fops会将指针ttyfops赋值给ptmxfops,调用ptmxfops的时候会通过ttyfops指针调相应的函数,其tty_fops定义为:

static const struct file_operations tty_fops = {
    .llseek     = no_llseek,
    .read       = tty_read,
    .write      = tty_write,
    .poll       = tty_poll,
    .unlocked_ioctl = tty_ioctl,
    .compat_ioctl   = tty_compat_ioctl,
    .open       = tty_open,
    .release    = tty_release,
    .fasync     = tty_fasync,
};

最后会通过fops->unlockedioctl调用ttyioctl.

unix98ptyinit函数为inline类型,编译器编译时候会直接内联到调用函数中,所以从内核符号表中找到pty_init函数地址为0xffffffc000d78e80,然后进行反汇编:

0xffffffc000d78e80:     stp     x29, x30, [sp, #-0x70]!
0xffffffc000d78e84:     mov     x29, sp
0xffffffc000d78e88:     stp     x19, x20, [sp, #0x10]
0xffffffc000d78e8c:     adrp    x20, #0xffffffc000df5000
0xffffffc000d78e90:     stp     x23, x24, [sp, #0x30]
0xffffffc000d78e94:     stp     x21, x22, [sp, #0x20]
0xffffffc000d78e98:     ldr     w0, [x20, #0xef0]
0xffffffc000d78e9c:     adrp    x23, #0xffffffc000df5000
0xffffffc000d78ea0:     stp     x25, x26, [sp, #0x40]
0xffffffc000d78ea4:     stp     x27, x28, [sp, #0x50]
0xffffffc000d78ea8:     cmp     w0, wzr
0xffffffc000d78eac:     adrp    x25, #0xffffffc000c1e000
0xffffffc000d78eb0:     adrp    x22, #0xffffffc000a35000
0xffffffc000d78eb4:     adrp    x24, #0xffffffc000c1e000
0xffffffc000d78eb8:     b.le    #0xffffffc000d79014
0xffffffc000d78ebc:     movz    x1, #0
0xffffffc000d78ec0:     movz    x2, #0x46
0xffffffc000d78ec4:     bl      #0xffffffc0003413d0
0xffffffc000d78ec8:     cmn     x0, #1, lsl #12
0xffffffc000d78ecc:     mov     x19, x0
0xffffffc000d78ed0:     b.ls    #0xffffffc000d78ee0
0xffffffc000d78ed4:     adrp    x0, #0xffffffc000c1e000
0xffffffc000d78ed8:     add     x0, x0, #0x7c0
0xffffffc000d78edc:     bl      #0xffffffc0009a799c
0xffffffc000d78ee0:     ldr     w0, [x20, #0xef0]
0xffffffc000d78ee4:     movz    x1, #0
0xffffffc000d78ee8:     movz    x2, #0x46
...

pty_init函数比较长,纯看汇编很难定位到tty_default_fops(&ptmx_fops)这个地方,一种方法是找到32位的kernel反汇编对照着看,因为32位可以用ida的F5,但是依然麻烦。

这里注意到ttydefaultfops这个函数在内核符号表中是有导出的,地址为0xffffffc0003431b4,现在重新看反汇编,直接查找这个值,定位附近的代码:

0xffffffc000d79174:     add     x0, x0, #0x8f8
0xffffffc000d79178:     bl      #0xffffffc0009a799c
0xffffffc000d7917c:     add     x19, x20, #0x10
0xffffffc000d79180:     add     x20, x20, #0xe8
0xffffffc000d79184:     mov     x0, x19
0xffffffc000d79188:     bl      #0xffffffc0003431b4
0xffffffc000d7918c:     adrp    x2, #0xffffffc00034a000

这里第一个参数x0即为ptmx_fops值。 x0 = x19 = x20 + 0x10 往上看x20赋值的地方。

0xffffffc000d79010:     bl      #0xffffffc0009a799c
0xffffffc000d79014:     adrp    x21, #0xffffffc000f36000
0xffffffc000d79018:     movz    w0, #0x10, lsl #16
0xffffffc000d7901c:     movz    x1, #0
0xffffffc000d79020:     movz    x2, #0x5e
0xffffffc000d79024:     add     x20, x21, #0xd80
0xffffffc000d79028:     bl      #0xffffffc0003413d0

x20 = x21 + 0xd80 = 0xffffffc000f36000 + 0xd80 得出 ptmx_fops = 0xffffffc000f36000 + 0xd80 + 0x10

计算函数偏移

接上边,在path ioctl过程中,如何找到ptmx对应的tty_ioctl,这个syscall调用流程为: sysioctl -> dovfsioctl -> ttyioctl 部分源码:

int do_vfs_ioctl(struct file *filp, unsigned int fd, unsigned int cmd,
         unsigned long arg)
{
    ...
    default:
        if (S_ISREG(inode->i_mode))
            error = file_ioctl(filp, cmd, arg);
        else
            error = vfs_ioctl(filp, cmd, arg);
        break;
    }
    return error;
}

static long vfs_ioctl(struct file *filp, unsigned int cmd,
              unsigned long arg)
{
    int error = -ENOTTY;

    if (!filp->f_op || !filp->f_op->unlocked_ioctl)
        goto out;

    error = filp->f_op->unlocked_ioctl(filp, cmd, arg);
    if (error == -ENOIOCTLCMD)
        error = -ENOTTY;
 out:
    return error;
}

最后调用filp->f_op->unlocked_ioctl(filp, cmd, arg)

所以确定ttyioctl的偏移即可以从相应的反汇编的寄存器传值看出,当然也可以直接从定义的ttyfops结构中算出,但是如果是复杂的结构体呢。

通过内核符号表找到dovfsioctl的地址为0xffffffc0001a4ca4,反汇编代码:

0xffffffc0001a4ca4: stp x29, x30, [sp, #-0x90]!
0xffffffc0001a4ca8: movz    w4, #0x5452
0xffffffc0001a4cac: mov x29, sp
0xffffffc0001a4cb0: stp x19, x20, [sp, #0x10]
0xffffffc0001a4cb4: stp x21, x22, [sp, #0x20]
0xffffffc0001a4cb8: str x23, [sp, #0x30]
0xffffffc0001a4cbc: cmp w2, w4
0xffffffc0001a4cc0: mov x19, x0
0xffffffc0001a4cc4: mov x20, x3
0xffffffc0001a4cc8: ldr x21, [x0, #0x20]
0xffffffc0001a4ccc: b.eq    #0xffffffc0001a5068
0xffffffc0001a4cd0: b.ls    #0xffffffc0001a4e4c
0xffffffc0001a4cd4: movz    w0, #0x5877
0xffffffc0001a4cd8: movk    w0, #0xc004, lsl #16
0xffffffc0001a4cdc: cmp w2, w0
0xffffffc0001a4ce0: b.eq    #0xffffffc0001a4fa0
...
0xffffffc0001a4fa0: movz    w0, #0x15
0xffffffc0001a4fa4: ldr x19, [x21, #0x28]
0xffffffc0001a4fa8: bl  #0xffffffc0000a7294
0xffffffc0001a4fac: uxtb    w0, w0
0xffffffc0001a4fb0: cbz w0, #0xffffffc0001a51a0
0xffffffc0001a4fb4: ldr x0, [x19, #0x30]
0xffffffc0001a4fb8: ldr x0, [x0, #0x40]
0xffffffc0001a4fbc: cbz x0, #0xffffffc0001a5218
0xffffffc0001a4fc0: mov x0, x19
0xffffffc0001a4fc4: bl  #0xffffffc000197d18
0xffffffc0001a4fc8: b   #0xffffffc0001a4e84
0xffffffc0001a4fcc: ldrh    w0, [x21]
0xffffffc0001a4fd0: and w0, w0, #0xf000
0xffffffc0001a4fd4: cmp w0, #8, lsl #12
0xffffffc0001a4fd8: b.eq    #0xffffffc0001a5100
0xffffffc0001a4fdc: ldr x0, [x19, #0x28]
0xffffffc0001a4fe0: cbz x0, #0xffffffc0001a5004
0xffffffc0001a4fe4: ldr x3, [x0, #0x40]
0xffffffc0001a4fe8: cbz x3, #0xffffffc0001a5004
0xffffffc0001a4fec: mov w1, w2
0xffffffc0001a4ff0: mov x0, x19
0xffffffc0001a4ff4: mov x2, x20
0xffffffc0001a4ff8: blr x3
0xffffffc0001a4ffc: cmn w0, #0x203
0xffffffc0001a5000: b.ne    #0xffffffc0001a4e84
0xffffffc0001a5004: movn    w0, #0x18
0xffffffc0001a5008: b   #0xffffffc0001a4e84
0xffffffc0001a500c: mov x0, sp
0xffffffc0001a5010: and x1, x0, #0xffffffffffffc000
...

代码比较长,直接看是看不出来的,查看源码中函数调用:

error = filp->f_op->unlocked_ioctl(filp, cmd, arg);
if (error == -ENOIOCTLCMD)
    error = -ENOTTY;

返回值会与ENOIOCTLCMD做比较,查找源码:

#define ENOIOCTLCMD    515    /* No ioctl command */

返回值在w0或x0中,直接在反汇编中搜特征值515对应的16进制0x203,如果结果过多,可以通过查看前后调用相关寄存器操作等其他特征进一步确定,这里就找到一处,定位到调用代码片段:

0xffffffc0001a4fdc: ldr x0, [x19, #0x28]
0xffffffc0001a4fe0: cbz x0, #0xffffffc0001a5004
0xffffffc0001a4fe4: ldr x3, [x0, #0x40]
0xffffffc0001a4fe8: cbz x3, #0xffffffc0001a5004
0xffffffc0001a4fec: mov w1, w2
0xffffffc0001a4ff0: mov x0, x19
0xffffffc0001a4ff4: mov x2, x20
0xffffffc0001a4ff8: blr x3
0xffffffc0001a4ffc: cmn w0, #0x203

所以x0为fop指针,其偏移0x40即为ttyioctl地址。

总结

  1. 内核漏洞利用过程中,不管是path哪些地址,最好的方法就是直接找到该地方调用处,然后根据编译好的kernel文件去定位。
  2. 同样在找结构体偏移值时,通过调用地方查看寄存器传参来确定偏移值,如果通过源码计算不仅麻烦还容易算错。
  3. 多读源码涨姿势。

没有评论:

发表评论

Android Root Zap Framework

‎ 1. Warning 请遵守GPL开源协议, 请遵守法律法规, 本项目仅供学习和交流, 请勿用于非法用途! 道路千万条, 安全第一条, 行车不规范, 亲人两行泪. 2. Android Root Zap Frame...