fini_array劫持
字数 2033 2025-08-23 18:31:17
ELF程序终止函数劫持技术详解:.fini_array利用
1. 背景知识:ELF程序启动与终止流程
ELF (Executable and Linkable Format) 程序的执行流程比表面看起来要复杂得多:
- 程序起点:
main函数并不是程序的真正起点,.text段的起点是_start函数 - 启动流程:
_start函数调用__libc_start_main完成程序启动和退出main函数的返回地址实际上是__libc_start_main
- 终止流程:
__libc_csu_fini函数是在main函数退出后,通过__libc_start_main调用的- 在
__libc_start_main中,有三个重要参数:rdi→main函数地址rcx→__libc_csu_init函数地址r8→__libc_csu_fini函数地址
2. .fini_array机制详解
.fini_array(或__fini_array)是ELF文件中的一个特殊结构:
- 定义:
.fini_array是一个函数指针数组,用于存储在程序或共享对象终止阶段要执行的清理函数 - 执行顺序:
- 与
.init_array相反,.fini_array中的函数会以逆序执行 - 用于资源清理、关闭文件描述符、释放内存等操作
- 与
- ELF结构:
- 由链接器在链接阶段生成
- 属于ELF的特殊节(section)之一
- 其他重要节包括:
.text:包含程序指令.data:包含已初始化的全局和静态数据.bss:包含未初始化的全局和静态数据
3. 利用技术:fini_array劫持
3.1 基本利用原理
通过修改.fini_array中的函数指针,可以控制程序执行流。关键点:
- 在动态链接的程序中,通常只需要覆盖
.fini_array数组中的原始函数指针 - 在静态链接的程序中,执行顺序更复杂:
- 先执行
.fini_array[1] - 再执行
.fini_array[0]
- 先执行
3.2 利用场景一:动态链接程序
案例特征:
- 无保护机制(NX, PIE, Canary等均未开启)
- 动态链接编译
- 提供任意地址写能力
利用步骤:
-
修改循环控制变量:
- 通常有一个
flag变量控制循环次数 - 通过整数溢出将其改为负数(如将
int类型的flag从0改为-1) - 示例:
write_value(b"0x600bcf", b"0xff")
- 通常有一个
-
覆盖.fini_array指针:
- 将
.fini_array中的函数指针覆盖为可控地址(如.bss段地址) - 示例:
write_value(b"0x600970", b"0x70") write_value(b"0x600971", b"0x0a") write_value(b"0x600972", b"0x20")
- 将
-
布置shellcode:
- 在可控地址(如
.bss段)逐字节写入shellcode - 示例:
shellcode = asm(shellcraft.sh()) for i in range(len(shellcode)): write_value(hex(0x600c60 + i), hex(shellcode[i]))
- 在可控地址(如
-
恢复flag并触发:
- 将
flag改回正常值 - 退出程序触发
.fini_array执行
- 将
3.3 利用场景二:静态链接程序
案例特征:
- 静态链接编译
- 可能去除符号表
- 提供任意地址写能力
利用步骤:
-
构造循环执行:
- 修改
.fini_array使程序循环执行:write(fini_array, p64(libc_csu_fini) + p64(main_addr)) - 这样程序会不断执行
main函数
- 修改
-
布置ROP链:
- 在栈上布置系统调用所需的参数:
write(esp, p64(rax)) write(esp + 8, p64(0x3b)) # execve系统调用号 write(esp + 16, p64(rdi)) # pop rdi; ret write(esp + 24, p64(bin_sh)) # "/bin/sh"字符串地址 write(esp + 32, p64(rsi)) # pop rsi; ret write(esp + 40, p64(0)) # argv = NULL write(esp + 48, p64(rdx)) # pop rdx; ret write(esp + 56, p64(0)) # envp = NULL write(esp + 64, p64(syscall)) # syscall指令地址
- 在栈上布置系统调用所需的参数:
-
触发ROP执行:
- 修改
.fini_array为栈转移指令:write(fini_array, p64(leave_ret) + p64(ret)) leave_ret会执行栈转移,然后执行布置好的ROP链
- 修改
4. 关键技术与技巧
-
整数溢出利用:
- 通过修改
int类型变量的最高位使其变为负数 - 示例:将
0x00000000改为0xffffffXX
- 通过修改
-
静态链接程序的特殊处理:
- 静态链接程序中
.fini_array的执行顺序是[1]→[0] - 需要构造循环执行后再触发最终利用
- 静态链接程序中
-
地址写入技巧:
- 当只能逐字节写入时,需要分解地址逐字节写入
- 注意小端序存储方式
-
栈转移技术:
leave指令相当于mov rsp, rbp; pop rbpleave; ret组合常用于栈转移攻击
5. 防御措施
-
编译时防护:
- 开启RELRO保护(特别是Full RELRO)可以防止
.fini_array被修改 - 开启PIE使地址随机化
- 开启NX防止执行shellcode
- 开启RELRO保护(特别是Full RELRO)可以防止
-
运行时检测:
- 监控关键节区(如
.fini_array)的修改 - 检查函数指针的合理性
- 监控关键节区(如
-
代码审计:
- 检查是否存在任意地址写漏洞
- 验证循环控制变量的边界条件
6. 扩展思考
-
其他可劫持的ELF结构:
.init_array:程序初始化函数数组GOT表:全局偏移表DTORS:旧版ELF的析构函数表
-
组合利用技术:
- 结合信息泄露绕过ASLR
- 结合ROP绕过NX
- 结合堆漏洞实现更复杂的利用
通过深入理解ELF结构和程序执行流程,.fini_array劫持技术可以成为二进制漏洞利用中的重要武器,同时也提醒我们在程序开发和安全防护中需要重视这些底层机制。