We figured out that the H1N337 viruses has a virtual variant too, and a sample has been encapsulated in this sandbox. Can you write a code to pass through the sandbox without being infected?

Статический анализ

Перед нами классическая пвнка, в описании ничего интересного. Взглянем на бинарь в IDA:

Функция main в бинаре (уже причёсанная)

Функция main в бинаре (уже причёсанная)

read возвращает количество реально прочитанных байт, и если оно больше, чем 12, то нам кроме надписи “too big!” ничего не светит. Далее вызывается enable_seccomp, mprotect на наш шеллкод с правами rx (чтение + исполнение) и собственно сам шеллкод.

enable_seccomp запрещает все сисколы, кроме read, write, open, mprotect, exit, exit_group.

Вроде не так и страшно — по крайней мере мы можем читать файлы.

Вроде не так и страшно — по крайней мере мы можем читать файлы.

На этом статический анализ можно считать завершенным — больше в бинаре ничего интересного нет.

Что можно впихнуть в 12 байт?

Для начала можно отослать \\xeb\\xfe — jmp, который прыгает сам на себя. Результат предсказуемый — сервис зависает, что означает, что наш код действительно исполняется на сервере.

Следующим, более разумным шеллкодом мы должны решить одну из следующих задач:

  1. Прочитать какой-нибудь файл
  2. Загрузить и выполнить шеллкод размером больше 12 байт
  3. Загрузить и выполнить последовательно несколько шеллкодов размером до 12 байт

Очевидно, что впихнуть чтение файла в 12 байт сложно (если вообще возможно). На ум приходит сделать mprotect с rwx и ещё один read, чтобы прочитать в буфер шеллкод больше 12 байт. Чтобы не писать лишний код, попробуем переиспользовать то, что уже есть в регистрах и на стеке. Для этого остановим бинарь в gdb на начале шеллкода:

Да, мне было лень ставить брейкпоинт и я просто ввёл фигню, чтобы бинарь крашнулся прям на начале шеллкода. На скрине gdb с плагином peda, для удобства отладки.

Да, мне было лень ставить брейкпоинт и я просто ввёл фигню, чтобы бинарь крашнулся прям на начале шеллкода. На скрине gdb с плагином peda, для удобства отладки.

Для того чтобы сделать mprotect(buf, 0x1000, 7) нам достаточно записать в rdx 7 (третий аргумент) и в rax 10 (номер сискола), после чего вызвать syscall:

mov dl, 7   /* для экономии места пишем в однобайтовый регистр */
mov al, 10
syscall

Компилируем любым удобным способом (я предпочитаю pwntools), проверяем размер. 6 байт — ровно половина отведённого места. Осталось впихнуть read. Основная проблема заключается в том, что у mprotect и у read разный порядок аргументов:

int mprotect(void *addr, size_t len, int prot);
ssize_t read(int fd, void *buf, size_t count);