Linux Shellcode "Привет, мир!"

У меня есть следующий код NASM:

global _start

section .text

_start:
    mov eax, 0x4
    mov ebx, 0x1
    mov ecx, message
    mov edx, 0xF
    int 0x80

    mov eax, 0x1
    mov ebx, 0x0
    int 0x80

section .data
    message: db "Hello, World!", 0dh, 0ah

который выводит на экран "Hello, World!\n". У меня также есть следующая C-оболочка, которая содержит предыдущий объектный код NASM:

char code[] =
"\xb8\x04\x00\x00\x00"
"\xbb\x01\x00\x00\x00"
"\xb9\x00\x00\x00\x00"
"\xba\x0f\x00\x00\x00"
"\xcd\x80\xb8\x01\x00"
"\x00\x00\xbb\x00\x00"
"\x00\x00\xcd\x80";

int main(void)
{
    (*(void(*)())code)();
}

Однако, когда я запускаю код, похоже, что код ассемблера не выполняется, но программа выходит из строя. Есть идеи?

благодаря

Ответ 1

Когда вы вводите этот шеллкод, вы не знаете, что есть в message:

mov ecx, message

в инжектированном процессе это может быть что угодно, но это не будет "Hello world!\r\n" поскольку он находится в разделе данных, когда вы сбрасываете только текстовый раздел. Вы можете видеть, что ваш шеллкод не имеет "Hello world!\r\n":

"\xb8\x04\x00\x00\x00"
"\xbb\x01\x00\x00\x00"
"\xb9\x00\x00\x00\x00"
"\xba\x0f\x00\x00\x00"
"\xcd\x80\xb8\x01\x00"
"\x00\x00\xbb\x00\x00"
"\x00\x00\xcd\x80";

Это обычная проблема в разработке шеллкодов, способ обойти это так:

global _start

section .text

_start:
    jmp MESSAGE      ; 1) lets jump to MESSAGE

GOBACK:
    mov eax, 0x4
    mov ebx, 0x1
    pop ecx          ; 3) we are poping into 'ecx', now we have the
                     ; address of "Hello, World!\r\n" 
    mov edx, 0xF
    int 0x80

    mov eax, 0x1
    mov ebx, 0x0
    int 0x80

MESSAGE:
    call GOBACK       ; 2) we are going back, since we used 'call', that means
                      ; the return address, which is in this case the address 
                      ; of "Hello, World!\r\n", is pushed into the stack.
    db "Hello, World!", 0dh, 0ah

section .data

Теперь выгрузите текстовый раздел:

$ nasm -f elf shellcode.asm
$ ld shellcode.o -o shellcode
$ ./shellcode 
Hello, World!
$ objdump -d shellcode

shellcode:     file format elf32-i386


Disassembly of section .text:

08048060 <_start>:
 8048060:   e9 1e 00 00 00   jmp    8048083 <MESSAGE>

08048065 <GOBACK>:
 8048065:   b8 04 00 00 00   mov    $0x4,%eax
 804806a:   bb 01 00 00 00   mov    $0x1,%ebx
 804806f:   59               pop    %ecx
 8048070:   ba 0f 00 00 00   mov    $0xf,%edx
 8048075:   cd 80            int    $0x80
 8048077:   b8 01 00 00 00   mov    $0x1,%eax
 804807c:   bb 00 00 00 00   mov    $0x0,%ebx
 8048081:   cd 80            int    $0x80

08048083 <MESSAGE>:
 8048083:   e8 dd ff ff ff   call   8048065 <GOBACK>
 8048088:   48               dec    %eax                    <-+
 8048089:   65               gs                               |
 804808a:   6c               insb   (%dx),%es:(%edi)          |
 804808b:   6c               insb   (%dx),%es:(%edi)          |
 804808c:   6f               outsl  %ds:(%esi),(%dx)          |
 804808d:   2c 20            sub    $0x20,%al                 |
 804808f:   57               push   %edi                      |
 8048090:   6f               outsl  %ds:(%esi),(%dx)          |
 8048091:   72 6c            jb     80480ff <MESSAGE+0x7c>    |
 8048093:   64               fs                               |
 8048094:   21               .byte 0x21                       |
 8048095:   0d               .byte 0xd                        |
 8048096:   0a               .byte 0xa                      <-+

$

Строки, которые я обозначил, являются нашей строкой "Hello, World!\r\n":

$ printf "\x48\x65\x6c\x6c\x6f\x2c\x20\x57\x6f\x72\x6c\x64\x21\x0d\x0a"
Hello, World!

$ 

Итак, наша C-оболочка будет:

char code[] = 

    "\xe9\x1e\x00\x00\x00"  //          jmp    8048083 <MESSAGE>
    "\xb8\x04\x00\x00\x00"  //          mov    $0x4,%eax
    "\xbb\x01\x00\x00\x00"  //          mov    $0x1,%ebx
    "\x59"                  //          pop    %ecx
    "\xba\x0f\x00\x00\x00"  //          mov    $0xf,%edx
    "\xcd\x80"              //          int    $0x80
    "\xb8\x01\x00\x00\x00"  //          mov    $0x1,%eax
    "\xbb\x00\x00\x00\x00"  //          mov    $0x0,%ebx
    "\xcd\x80"              //          int    $0x80
    "\xe8\xdd\xff\xff\xff"  //          call   8048065 <GOBACK>
    "Hello wolrd!\r\n";     // OR       "\x48\x65\x6c\x6c\x6f\x2c\x20\x57"
                            //          "\x6f\x72\x6c\x64\x21\x0d\x0a"


int main(int argc, char **argv)
{
    (*(void(*)())code)();

    return 0;
}

Давайте проверим его:

$ gcc test.c -o test
$ ./test 
Hello wolrd!
$ 

оно работает.

Ответ 2

Как упоминалось в BSH, ваш шеллкод не содержит байтов сообщений. Переход к метке MESSAGE и вызов процедуры GOBACK непосредственно перед определением байта msg был хорошим шагом, так как адрес msg был бы в верхней части стека в качестве адреса возврата, который можно было бы выскочить в ecx, где хранится адрес msg,

Но и ваш, и код BSH имеют небольшое ограничение. Он содержит NULL bytes ( \x00 ) которые будут рассматриваться как конец строки при разыменовании указателем функции.

У этого есть разумный путь. Значения, которые вы храните в eax, ebx and edx, достаточно малы, чтобы напрямую записываться в нижние полубайты соответствующих регистров за один проход, обращаясь к al, bl and dl соответственно. Верхний полубайт может содержать значение нежелательной почты, чтобы его можно было хранить.

b8 04 00 00 00 ------ mov $0x4,%eax


становится

b0 04          ------ mov $0x4,%al
31 c0          ------ xor    %eax,%eax


В отличие от предыдущего набора инструкций, новый набор команд не содержит байт NULL.

Итак, финальная программа выглядит так:

global _start

section .text

_start:
jmp message

proc:
    xor eax, eax
    mov al, 0x04
    xor ebx, ebx
    mov bl, 0x01
    pop ecx
    xor edx, edx
    mov dl, 0x16
    int 0x80

    xor eax, eax
    mov al, 0x01
    xor ebx, ebx
    mov bl, 0x01   ; return 1
    int 0x80

message:
    call proc
    msg db " y0u sp34k 1337 ? "

section .data

Сборка и компоновка:

$ nasm -f elf hello.asm -o hello.o
$ ld -s -m elf_i386 hello.o -o hello
$ ./hello
 y0u sp34k 1337 ? $ 

Теперь извлеките шеллкод из двоичного файла hello:

$ for i in 'objdump -d hello | tr '\t' ' ' | tr ' ' '\n' | egrep '^[0-9a-f]{2}$' ' ; do echo -n "\\x$i" ; done

вывод:

\xeb\x19\x31\xc0\xb0\x04\x31\xdb\xb3\x01\x59\x31\xd2\xb2\x12\xcd\x80\x31\xc0\xb0\x01\x31\xdb\xb3\x01\xcd\x80\xe8\xe2\xff\xff\xff\x20\x79\x30\x75\x20\x73\x70\x33\x34\x6b\x20\x31\x33\x33\x37\x20\x3f\x20

Теперь у нас может быть наша программа для запуска shellcode.

#include <stdio.h>

char shellcode[] = "\xeb\x19\x31\xc0\xb0\x04\x31\xdb"
                   "\xb3\x01\x59\x31\xd2\xb2\x12\xcd"
                   "\x80\x31\xc0\xb0\x01\x31\xdb\xb3"
                   "\x01\xcd\x80\xe8\xe2\xff\xff\xff"
                   "\x20\x79\x30\x75\x20\x73\x70\x33"
                   "\x34\x6b\x20\x31\x33\x33\x37\x20"
                   "\x3f\x20";


int main(int argc, char **argv) {
    (*(void(*)())shellcode)();
    return 0;
}

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

$ gcc -g -Wall -fno-stack-protector -z execstack launcher.c -o launcher

Теперь можно запустить launcher, чтобы запустить шеллкод.

$ ./launcher
 y0u sp34k 1337 ? $ 

Для более сложных шеллкодов было бы еще одно препятствие. Современные ядра Linux имеют ASLR или Address Space Layout Randomization Возможно, вам придется отключить это, прежде чем вводить шелл-код, особенно когда он переполняется буфером.

[email protected]:~# echo 0 > /proc/sys/kernel/randomize_va_space